new EVM Upgrade

- Solidity Upgraded up to v0.8.0
-  Fixed and Added eth_chainId
- Fix error in TransactionRecipet
- Reward halving issue fixed
This commit is contained in:
olumuyiwadad 2021-09-21 16:53:46 +05:30
parent 8daf35ff1c
commit b5abbfed79
2118 changed files with 63833 additions and 717044 deletions

110
.travis.yml Normal file
View file

@ -0,0 +1,110 @@
sudo: required
language: go
go_import_path: github.com/XinFinOrg/XDPoSChain
env:
global:
- GOPROXY=https://proxy.golang.org
- GO111MODULE=on
jobs:
include:
- stage: Lint
sudo: false
go: '1.12.x'
git:
submodules: false
script:
- go run build/ci.go lint
- stage: Build and test
go: '1.12.x'
script:
- sudo modprobe fuse
- sudo chmod 666 /dev/fuse
- sudo chown root:$USER /etc/fuse.conf
- go run build/ci.go install
- while sleep 540; do echo "[ still running ]"; done &
- go run build/ci.go test -coverage
- kill %1
after_success:
- bash <(curl -s https://codecov.io/bash)
- go: '1.11.x'
script:
- sudo modprobe fuse
- sudo chmod 666 /dev/fuse
- sudo chown root:$USER /etc/fuse.conf
- go run build/ci.go install
- while sleep 540; do echo "[ still running ]"; done &
- go run build/ci.go test -coverage
- kill %1
- stage: Github release
go: '1.12.x'
script:
- GOARCH=amd64 GOOS=linux go build -o ./build/bin/XDC-linux-amd64 -v ./cmd/XDC
deploy:
provider: releases
api_key: $GITHUB_TOKEN
overwrite: true
file_glob: true
file: build/bin/XDC-*
skip_cleanup: true
on:
tags: true
- stage: Build and push image
services:
- docker
install: skip
before_script:
- docker build -t XDPoSChain/XDPoSChain .
- docker build -t XDPoSChain/node -f Dockerfile.node .
script:
- echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin
- docker tag XDPoSChain/XDPoSChain XDPoSChain/XDPoSChain:latest
- docker push XDPoSChain/XDPoSChain:latest
- docker tag XDPoSChain/XDPoSChain XDPoSChain/XDPoSChain:$TRAVIS_BUILD_ID
- docker push XDPoSChain/XDPoSChain:$TRAVIS_BUILD_ID
- docker tag XDPoSChain/node XDPoSChain/node:latest
- docker push XDPoSChain/node:latest
- docker tag XDPoSChain/node XDPoSChain/node:$TRAVIS_BUILD_ID
- docker push XDPoSChain/node:$TRAVIS_BUILD_ID
- stage: Build and push image (tagged)
services:
- docker
install: skip
before_script:
- docker build -t XDPoSChain/XDPoSChain .
- docker build -t XDPoSChain/XDPoSChain -f Dockerfile.node .
script:
- echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin
- docker tag XDPoSChain/XDPoSChain XDPoSChain/XDPoSChain:latest
- docker push XDPoSChain/XDPoSChain:latest
- docker tag XDPoSChain/XDPoSChain XDPoSChain/XDPoSChain:$TRAVIS_TAG
- docker push XDPoSChain/XDPoSChain:$TRAVIS_TAG
- docker tag XDPoSChain/XDPoSChain XDPoSChain/node:latest
- docker push XDPoSChain/node:latest
- docker tag XDPoSChain/node XDPoSChain/node:$TRAVIS_TAG
- docker push XDPoSChain/node:$TRAVIS_TAG
stages:
- name: Lint
- name: Build and test
- name: Github release
if: type != pull_request AND branch =~ ^v AND tag IS present AND repo = XDPoSChain/XDPoSChain
- name: Build and push image
if: type != pull_request AND branch = master AND tag IS blank AND repo = XDPoSChain/XDPoSChain
- name: Build and push image (tagged)
if: type != pull_request AND branch =~ ^v AND tag IS present AND repo = XDPoSChain/XDPoSChain
notifications:
slack:
rooms:
secure:
on_success: change
on_failure: always

View file

@ -1,17 +1,15 @@
FROM golang:1.10-alpine as builder
FROM golang:1.12-alpine as builder
RUN apk add --no-cache make gcc musl-dev linux-headers
RUN apk add --no-cache make gcc musl-dev linux-headers git
ADD . /XDCchain
RUN cd /XDCchain && make XDC
ADD . /XDPoSChain
RUN cd /XDPoSChain && make XDC
FROM alpine:latest
LABEL maintainer="anil@xinfin.org"
WORKDIR /XDPoSChain
WORKDIR /XDCchain
COPY --from=builder /XDCchain/build/bin/XDC /usr/local/bin/XDC
COPY --from=builder /XDPoSChain/build/bin/XDC /usr/local/bin/XDC
RUN chmod +x /usr/local/bin/XDC

View file

@ -1,19 +1,19 @@
FROM golang:1.10-alpine as builder
FROM golang:1.11-alpine as builder
RUN apk add --no-cache make gcc musl-dev linux-headers
ADD . /XDCchain
RUN cd /XDCchain && make bootnode
ADD . /XDPoSChain
RUN cd /XDPoSChain && make bootnode
RUN chmod +x /XDCchain/build/bin/bootnode
RUN chmod +x /XDPoSChain/build/bin/bootnode
FROM alpine:latest
LABEL maintainer="anil@xinfin.org"
LABEL maintainer="etienne@XDPoSChain.com"
WORKDIR /XDCchain
WORKDIR /XDPoSChain
COPY --from=builder /XDCchain/build/bin/bootnode /usr/local/bin/bootnode
COPY --from=builder /XDPoSChain/build/bin/bootnode /usr/local/bin/bootnode
COPY docker/bootnode ./

View file

@ -1,20 +1,18 @@
FROM golang:1.10-alpine as builder
FROM golang:1.12-alpine as builder
RUN apk add --no-cache make gcc musl-dev linux-headers
RUN apk add --no-cache make gcc musl-dev linux-headers git
ADD . /XDCchain
ADD . /XDPoSChain
RUN cd /XDCchain \
RUN cd /XDPoSChain \
&& make XDC \
&& chmod +x /XDCchain/build/bin/XDC
&& chmod +x /XDPoSChain/build/bin/XDC
FROM alpine:latest
LABEL maintainer="anil@xinfin.org"
WORKDIR /XDPoSChain
WORKDIR /XDCchain
COPY --from=builder /XDCchain/build/bin/XDC /usr/local/bin/XDC
COPY --from=builder /XDPoSChain/build/bin/XDC /usr/local/bin/XDC
ENV IDENTITY ''
ENV PASSWORD ''
@ -23,7 +21,7 @@ ENV BOOTNODES ''
ENV EXTIP ''
ENV VERBOSITY 3
ENV SYNC_MODE 'full'
ENV NETWORK_ID '89'
ENV NETWORK_ID '88'
ENV WS_SECRET ''
ENV NETSTATS_HOST 'netstats-server'
ENV NETSTATS_PORT '3000'
@ -31,7 +29,7 @@ ENV ANNOUNCE_TXS ''
RUN apk add --no-cache ca-certificates
COPY docker/XDCchain ./
COPY docker/XDPoSChain ./
COPY genesis/ ./
EXPOSE 8545 8546 30303 30303/udp

View file

@ -4,37 +4,37 @@
GOBIN = $(shell pwd)/build/bin
GOFMT = gofmt
GO ?= latest
GO ?= 1.13.1
GO_PACKAGES = .
GO_FILES := $(shell find $(shell go list -f '{{.Dir}}' $(GO_PACKAGES)) -name \*.go)
GIT = git
XDC:
build/env.sh go run build/ci.go install ./cmd/XDC
go run build/ci.go install ./cmd/XDC
@echo "Done building."
@echo "Run \"$(GOBIN)/XDC\" to launch XDC."
gc:
build/env.sh go run build/ci.go install ./cmd/gc
go run build/ci.go install ./cmd/gc
@echo "Done building."
@echo "Run \"$(GOBIN)/gc\" to launch gc."
bootnode:
build/env.sh go run build/ci.go install ./cmd/bootnode
go run build/ci.go install ./cmd/bootnode
@echo "Done building."
@echo "Run \"$(GOBIN)/bootnode\" to launch a bootnode."
puppeth:
build/env.sh go run build/ci.go install ./cmd/puppeth
go run build/ci.go install ./cmd/puppeth
@echo "Done building."
@echo "Run \"$(GOBIN)/puppeth\" to launch puppeth."
all:
build/env.sh go run build/ci.go install
go run build/ci.go install
test: all
build/env.sh go run build/ci.go test
go run build/ci.go test
clean:
rm -fr build/_workspace/pkg/ $(GOBIN)/*
@ -50,32 +50,32 @@ XDC-linux: XDC-linux-386 XDC-linux-amd64 XDC-linux-mips64 XDC-linux-mips64le
@ls -ld $(GOBIN)/XDC-linux-*
XDC-linux-386:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/386 -v ./cmd/XDC
go run build/ci.go xgo -- --go=$(GO) --targets=linux/386 -v ./cmd/XDC
@echo "Linux 386 cross compilation done:"
@ls -ld $(GOBIN)/XDC-linux-* | grep 386
XDC-linux-amd64:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/amd64 -v ./cmd/XDC
go run build/ci.go xgo -- --go=$(GO) --targets=linux/amd64 -v ./cmd/XDC
@echo "Linux amd64 cross compilation done:"
@ls -ld $(GOBIN)/XDC-linux-* | grep amd64
XDC-linux-mips:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/mips --ldflags '-extldflags "-static"' -v ./cmd/XDC
go run build/ci.go xgo -- --go=$(GO) --targets=linux/mips --ldflags '-extldflags "-static"' -v ./cmd/XDC
@echo "Linux MIPS cross compilation done:"
@ls -ld $(GOBIN)/XDC-linux-* | grep mips
XDC-linux-mipsle:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/mipsle --ldflags '-extldflags "-static"' -v ./cmd/XDC
go run build/ci.go xgo -- --go=$(GO) --targets=linux/mipsle --ldflags '-extldflags "-static"' -v ./cmd/XDC
@echo "Linux MIPSle cross compilation done:"
@ls -ld $(GOBIN)/XDC-linux-* | grep mipsle
XDC-linux-mips64:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/mips64 --ldflags '-extldflags "-static"' -v ./cmd/XDC
go run build/ci.go xgo -- --go=$(GO) --targets=linux/mips64 --ldflags '-extldflags "-static"' -v ./cmd/XDC
@echo "Linux MIPS64 cross compilation done:"
@ls -ld $(GOBIN)/XDC-linux-* | grep mips64
XDC-linux-mips64le:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=linux/mips64le --ldflags '-extldflags "-static"' -v ./cmd/XDC
go run build/ci.go xgo -- --go=$(GO) --targets=linux/mips64le --ldflags '-extldflags "-static"' -v ./cmd/XDC
@echo "Linux MIPS64le cross compilation done:"
@ls -ld $(GOBIN)/XDC-linux-* | grep mips64le
@ -84,18 +84,18 @@ XDC-darwin: XDC-darwin-386 XDC-darwin-amd64
@ls -ld $(GOBIN)/XDC-darwin-*
XDC-darwin-386:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=darwin/386 -v ./cmd/XDC
go run build/ci.go xgo -- --go=$(GO) --targets=darwin/386 -v ./cmd/XDC
@echo "Darwin 386 cross compilation done:"
@ls -ld $(GOBIN)/XDC-darwin-* | grep 386
XDC-darwin-amd64:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=darwin/amd64 -v ./cmd/XDC
go run build/ci.go xgo -- --go=$(GO) --targets=darwin/amd64 -v ./cmd/XDC
@echo "Darwin amd64 cross compilation done:"
@ls -ld $(GOBIN)/XDC-darwin-* | grep amd64
XDC-windows-amd64:
build/env.sh go run build/ci.go xgo -- --go=$(GO) --targets=windows/amd64 -v ./cmd/XDC
@echo "Darwin amd64 cross compilation done:"
go run build/ci.go xgo -- --go=$(GO) -buildmode=mode -x --targets=windows/amd64 -v ./cmd/XDC
@echo "Windows amd64 cross compilation done:"
@ls -ld $(GOBIN)/XDC-windows-* | grep amd64
gofmt:
$(GOFMT) -s -w $(GO_FILES)

View file

@ -1,9 +1 @@
## XinFin
Blockchain for decentralized applications, token issuance and integration
Website Resource : https://xinFin.org
XinFin Mainet URL: http://XinFin.network/
XinFin TestNet/Apothem URL: http://apothem.network/
# XDPoSChain

663
XDCx/XDCx.go Normal file
View file

@ -0,0 +1,663 @@
package XDCx
import (
"errors"
"fmt"
"math/big"
"strconv"
"time"
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
"github.com/XinFinOrg/XDPoSChain/XDCxDAO"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/p2p"
"gopkg.in/karalabe/cookiejar.v2/collections/prque"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/rpc"
lru "github.com/hashicorp/golang-lru"
"golang.org/x/sync/syncmap"
)
const (
ProtocolName = "XDCx"
ProtocolVersion = uint64(1)
ProtocolVersionStr = "1.0"
overflowIdx // Indicator of message queue overflow
defaultCacheLimit = 1024
MaximumTxMatchSize = 1000
)
var (
ErrNonceTooHigh = errors.New("nonce too high")
ErrNonceTooLow = errors.New("nonce too low")
)
type Config struct {
DataDir string `toml:",omitempty"`
DBEngine string `toml:",omitempty"`
DBName string `toml:",omitempty"`
ConnectionUrl string `toml:",omitempty"`
ReplicaSetName string `toml:",omitempty"`
}
// DefaultConfig represents (shocker!) the default configuration.
var DefaultConfig = Config{
DataDir: "",
}
type XDCX struct {
// Order related
db XDCxDAO.XDCXDAO
mongodb XDCxDAO.XDCXDAO
Triegc *prque.Prque // Priority queue mapping block numbers to tries to gc
StateCache tradingstate.Database // State database to reuse between imports (contains state cache) *XDCx_state.TradingStateDB
orderNonce map[common.Address]*big.Int
sdkNode bool
settings syncmap.Map // holds configuration settings that can be dynamically changed
tokenDecimalCache *lru.Cache
orderCache *lru.Cache
}
func (XDCx *XDCX) Protocols() []p2p.Protocol {
return []p2p.Protocol{}
}
func (XDCx *XDCX) Start(server *p2p.Server) error {
return nil
}
func (XDCx *XDCX) SaveData() {
}
func (XDCx *XDCX) Stop() error {
return nil
}
func NewLDBEngine(cfg *Config) *XDCxDAO.BatchDatabase {
datadir := cfg.DataDir
batchDB := XDCxDAO.NewBatchDatabaseWithEncode(datadir, 0)
return batchDB
}
func NewMongoDBEngine(cfg *Config) *XDCxDAO.MongoDatabase {
mongoDB, err := XDCxDAO.NewMongoDatabase(nil, cfg.DBName, cfg.ConnectionUrl, cfg.ReplicaSetName, 0)
if err != nil {
log.Crit("Failed to init mongodb engine", "err", err)
}
return mongoDB
}
func New(cfg *Config) *XDCX {
tokenDecimalCache, _ := lru.New(defaultCacheLimit)
orderCache, _ := lru.New(tradingstate.OrderCacheLimit)
XDCX := &XDCX{
orderNonce: make(map[common.Address]*big.Int),
Triegc: prque.New(),
tokenDecimalCache: tokenDecimalCache,
orderCache: orderCache,
}
// default DBEngine: levelDB
XDCX.db = NewLDBEngine(cfg)
XDCX.sdkNode = false
if cfg.DBEngine == "mongodb" { // this is an add-on DBEngine for SDK nodes
XDCX.mongodb = NewMongoDBEngine(cfg)
XDCX.sdkNode = true
}
XDCX.StateCache = tradingstate.NewDatabase(XDCX.db)
XDCX.settings.Store(overflowIdx, false)
return XDCX
}
// Overflow returns an indication if the message queue is full.
func (XDCx *XDCX) Overflow() bool {
val, _ := XDCx.settings.Load(overflowIdx)
return val.(bool)
}
func (XDCx *XDCX) IsSDKNode() bool {
return XDCx.sdkNode
}
func (XDCx *XDCX) GetLevelDB() XDCxDAO.XDCXDAO {
return XDCx.db
}
func (XDCx *XDCX) GetMongoDB() XDCxDAO.XDCXDAO {
return XDCx.mongodb
}
// APIs returns the RPC descriptors the XDCX implementation offers
func (XDCx *XDCX) APIs() []rpc.API {
return []rpc.API{
{
Namespace: ProtocolName,
Version: ProtocolVersionStr,
Service: NewPublicXDCXAPI(XDCx),
Public: true,
},
}
}
// Version returns the XDCX sub-protocols version number.
func (XDCx *XDCX) Version() uint64 {
return ProtocolVersion
}
func (XDCx *XDCX) ProcessOrderPending(header *types.Header, coinbase common.Address, chain consensus.ChainContext, pending map[common.Address]types.OrderTransactions, statedb *state.StateDB, XDCXstatedb *tradingstate.TradingStateDB) ([]tradingstate.TxDataMatch, map[common.Hash]tradingstate.MatchingResult) {
txMatches := []tradingstate.TxDataMatch{}
matchingResults := map[common.Hash]tradingstate.MatchingResult{}
txs := types.NewOrderTransactionByNonce(types.OrderTxSigner{}, pending)
numberTx := 0
for {
tx := txs.Peek()
if tx == nil {
break
}
if numberTx > MaximumTxMatchSize {
break
}
numberTx++
log.Debug("ProcessOrderPending start", "len", len(pending))
log.Debug("Get pending orders to process", "address", tx.UserAddress(), "nonce", tx.Nonce())
V, R, S := tx.Signature()
bigstr := V.String()
n, e := strconv.ParseInt(bigstr, 10, 8)
if e != nil {
continue
}
order := &tradingstate.OrderItem{
Nonce: big.NewInt(int64(tx.Nonce())),
Quantity: tx.Quantity(),
Price: tx.Price(),
ExchangeAddress: tx.ExchangeAddress(),
UserAddress: tx.UserAddress(),
BaseToken: tx.BaseToken(),
QuoteToken: tx.QuoteToken(),
Status: tx.Status(),
Side: tx.Side(),
Type: tx.Type(),
Hash: tx.OrderHash(),
OrderID: tx.OrderID(),
Signature: &tradingstate.Signature{
V: byte(n),
R: common.BigToHash(R),
S: common.BigToHash(S),
},
}
cancel := false
if order.Status == tradingstate.OrderStatusCancelled {
cancel = true
}
log.Info("Process order pending", "orderPending", order, "BaseToken", order.BaseToken.Hex(), "QuoteToken", order.QuoteToken)
originalOrder := &tradingstate.OrderItem{}
*originalOrder = *order
originalOrder.Quantity = tradingstate.CloneBigInt(order.Quantity)
if cancel {
order.Status = tradingstate.OrderStatusCancelled
}
newTrades, newRejectedOrders, err := XDCx.CommitOrder(header, coinbase, chain, statedb, XDCXstatedb, tradingstate.GetTradingOrderBookHash(order.BaseToken, order.QuoteToken), order)
for _, reject := range newRejectedOrders {
log.Debug("Reject order", "reject", *reject)
}
switch err {
case ErrNonceTooLow:
// New head notification data race between the transaction pool and miner, shift
log.Debug("Skipping order with low nonce", "sender", tx.UserAddress(), "nonce", tx.Nonce())
txs.Shift()
continue
case ErrNonceTooHigh:
// Reorg notification data race between the transaction pool and miner, skip account =
log.Debug("Skipping order account with high nonce", "sender", tx.UserAddress(), "nonce", tx.Nonce())
txs.Pop()
continue
case nil:
// everything ok
txs.Shift()
default:
// Strange error, discard the transaction and get the next in line (note, the
// nonce-too-high clause will prevent us from executing in vain).
log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
txs.Shift()
continue
}
// orderID has been updated
originalOrder.OrderID = order.OrderID
originalOrder.ExtraData = order.ExtraData
originalOrderValue, err := tradingstate.EncodeBytesItem(originalOrder)
if err != nil {
log.Error("Can't encode", "order", originalOrder, "err", err)
continue
}
txMatch := tradingstate.TxDataMatch{
Order: originalOrderValue,
}
txMatches = append(txMatches, txMatch)
matchingResults[tradingstate.GetMatchingResultCacheKey(order)] = tradingstate.MatchingResult{
Trades: newTrades,
Rejects: newRejectedOrders,
}
}
return txMatches, matchingResults
}
// return average price of the given pair in the last epoch
func (XDCx *XDCX) GetAveragePriceLastEpoch(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("GetAveragePriceLastEpoch", "baseToken", baseToken.Hex(), "quoteToken", quoteToken.Hex(), "price", price)
return price, nil
} else {
inversePrice := tradingStateDb.GetMediumPriceBeforeEpoch(tradingstate.GetTradingOrderBookHash(quoteToken, baseToken))
log.Debug("GetAveragePriceLastEpoch", "baseToken", baseToken.Hex(), "quoteToken", quoteToken.Hex(), "inversePrice", inversePrice)
if inversePrice != nil && inversePrice.Sign() > 0 {
quoteTokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, quoteToken)
if err != nil || quoteTokenDecimal.Sign() == 0 {
return nil, fmt.Errorf("fail to get tokenDecimal. Token: %v . Err: %v", quoteToken.String(), err)
}
baseTokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, baseToken)
if err != nil || baseTokenDecimal.Sign() == 0 {
return nil, fmt.Errorf("fail to get tokenDecimal. Token: %v . Err: %v", baseToken.String(), err)
}
price = new(big.Int).Mul(baseTokenDecimal, quoteTokenDecimal)
price = new(big.Int).Div(price, inversePrice)
log.Debug("GetAveragePriceLastEpoch", "baseToken", baseToken.Hex(), "quoteToken", quoteToken.Hex(), "baseTokenDecimal", baseTokenDecimal, "quoteTokenDecimal", quoteTokenDecimal, "inversePrice", inversePrice)
return price, nil
}
}
return nil, nil
}
// return tokenQuantity (after convert from XDC to token), tokenPriceInXDC, error
func (XDCx *XDCX) ConvertXDCToToken(chain consensus.ChainContext, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, token common.Address, quantity *big.Int) (*big.Int, *big.Int, error) {
if token.String() == common.XDCNativeAddress {
return quantity, common.BasePrice, nil
}
tokenPriceInXDC, err := XDCx.GetAveragePriceLastEpoch(chain, statedb, tradingStateDb, token, common.HexToAddress(common.XDCNativeAddress))
if err != nil || tokenPriceInXDC == nil || tokenPriceInXDC.Sign() <= 0 {
return common.Big0, common.Big0, err
}
tokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, token)
if err != nil || tokenDecimal.Sign() == 0 {
return common.Big0, common.Big0, fmt.Errorf("fail to get tokenDecimal. Token: %v . Err: %v", token.String(), err)
}
tokenQuantity := new(big.Int).Mul(quantity, tokenDecimal)
tokenQuantity = new(big.Int).Div(tokenQuantity, tokenPriceInXDC)
return tokenQuantity, tokenPriceInXDC, nil
}
// there are 3 tasks need to complete to update data in SDK nodes after matching
// 1. txMatchData.Order: order has been processed. This order should be put to `orders` collection with status sdktypes.OrderStatusOpen
// 2. txMatchData.Trades: includes information of matched orders.
// a. PutObject them to `trades` collection
// b. Update status of regrading orders to sdktypes.OrderStatusFilled
func (XDCx *XDCX) SyncDataToSDKNode(takerOrderInTx *tradingstate.OrderItem, txHash common.Hash, txMatchTime time.Time, statedb *state.StateDB, trades []map[string]string, rejectedOrders []*tradingstate.OrderItem, dirtyOrderCount *uint64) error {
var (
// originTakerOrder: order get from db, nil if it doesn't exist
// takerOrderInTx: order decoded from txdata
// updatedTakerOrder: order with new status, filledAmount, CreatedAt, UpdatedAt. This will be inserted to db
originTakerOrder, updatedTakerOrder *tradingstate.OrderItem
makerDirtyHashes []string
makerDirtyFilledAmount map[string]*big.Int
err error
)
db := XDCx.GetMongoDB()
db.InitBulk()
if takerOrderInTx.Status == tradingstate.OrderStatusCancelled && len(rejectedOrders) > 0 {
// cancel order is rejected -> nothing change
log.Debug("Cancel order is rejected", "order", tradingstate.ToJSON(takerOrderInTx))
return nil
}
// 1. put processed takerOrderInTx to db
lastState := tradingstate.OrderHistoryItem{}
val, err := db.GetObject(takerOrderInTx.Hash, &tradingstate.OrderItem{})
if err == nil && val != nil {
originTakerOrder = val.(*tradingstate.OrderItem)
lastState = tradingstate.OrderHistoryItem{
TxHash: originTakerOrder.TxHash,
FilledAmount: tradingstate.CloneBigInt(originTakerOrder.FilledAmount),
Status: originTakerOrder.Status,
UpdatedAt: originTakerOrder.UpdatedAt,
}
}
if originTakerOrder != nil {
updatedTakerOrder = originTakerOrder
} else {
updatedTakerOrder = takerOrderInTx
updatedTakerOrder.FilledAmount = new(big.Int)
}
if takerOrderInTx.Status != tradingstate.OrderStatusCancelled {
updatedTakerOrder.Status = tradingstate.OrderStatusOpen
} else {
updatedTakerOrder.Status = tradingstate.OrderStatusCancelled
updatedTakerOrder.ExtraData = takerOrderInTx.ExtraData
}
updatedTakerOrder.TxHash = txHash
if updatedTakerOrder.CreatedAt.IsZero() {
updatedTakerOrder.CreatedAt = txMatchTime
}
if txMatchTime.Before(updatedTakerOrder.UpdatedAt) || (txMatchTime.Equal(updatedTakerOrder.UpdatedAt) && *dirtyOrderCount == 0) {
log.Debug("Ignore old orders/trades taker", "txHash", txHash.Hex(), "txTime", txMatchTime.UnixNano(), "updatedAt", updatedTakerOrder.UpdatedAt.UnixNano())
return nil
}
*dirtyOrderCount++
XDCx.UpdateOrderCache(updatedTakerOrder.BaseToken, updatedTakerOrder.QuoteToken, updatedTakerOrder.Hash, txHash, lastState)
updatedTakerOrder.UpdatedAt = txMatchTime
// 2. put trades to db and update status to FILLED
log.Debug("Got trades", "number", len(trades), "txhash", txHash.Hex())
makerDirtyFilledAmount = make(map[string]*big.Int)
for _, trade := range trades {
// 2.a. put to trades
if trade == nil {
continue
}
tradeRecord := &tradingstate.Trade{}
quantity := tradingstate.ToBigInt(trade[tradingstate.TradeQuantity])
price := tradingstate.ToBigInt(trade[tradingstate.TradePrice])
if price.Cmp(big.NewInt(0)) <= 0 || quantity.Cmp(big.NewInt(0)) <= 0 {
return fmt.Errorf("trade misses important information. tradedPrice %v, tradedQuantity %v", price, quantity)
}
tradeRecord.Amount = quantity
tradeRecord.PricePoint = price
tradeRecord.BaseToken = updatedTakerOrder.BaseToken
tradeRecord.QuoteToken = updatedTakerOrder.QuoteToken
tradeRecord.Status = tradingstate.TradeStatusSuccess
tradeRecord.Taker = updatedTakerOrder.UserAddress
tradeRecord.Maker = common.HexToAddress(trade[tradingstate.TradeMaker])
tradeRecord.TakerOrderHash = updatedTakerOrder.Hash
tradeRecord.MakerOrderHash = common.HexToHash(trade[tradingstate.TradeMakerOrderHash])
tradeRecord.TxHash = txHash
tradeRecord.TakerOrderSide = updatedTakerOrder.Side
tradeRecord.TakerExchange = updatedTakerOrder.ExchangeAddress
tradeRecord.MakerExchange = common.HexToAddress(trade[tradingstate.TradeMakerExchange])
tradeRecord.MakeFee, _ = new(big.Int).SetString(trade[tradingstate.MakerFee], 10)
tradeRecord.TakeFee, _ = new(big.Int).SetString(trade[tradingstate.TakerFee], 10)
// set makerOrderType, takerOrderType
tradeRecord.MakerOrderType = trade[tradingstate.MakerOrderType]
tradeRecord.TakerOrderType = updatedTakerOrder.Type
if tradeRecord.CreatedAt.IsZero() {
tradeRecord.CreatedAt = txMatchTime
}
tradeRecord.UpdatedAt = txMatchTime
tradeRecord.Hash = tradeRecord.ComputeHash()
log.Debug("TRADE history", "amount", tradeRecord.Amount, "pricepoint", tradeRecord.PricePoint,
"taker", tradeRecord.Taker.Hex(), "maker", tradeRecord.Maker.Hex(), "takerOrder", tradeRecord.TakerOrderHash.Hex(), "makerOrder", tradeRecord.MakerOrderHash.Hex(),
"takerFee", tradeRecord.TakeFee, "makerFee", tradeRecord.MakeFee)
if err := db.PutObject(tradeRecord.Hash, tradeRecord); err != nil {
return fmt.Errorf("SDKNode: failed to store tradeRecord %s", err.Error())
}
// 2.b. update status and filledAmount
filledAmount := quantity
// maker dirty order
makerFilledAmount := big.NewInt(0)
if amount, ok := makerDirtyFilledAmount[trade[tradingstate.TradeMakerOrderHash]]; ok {
makerFilledAmount = tradingstate.CloneBigInt(amount)
}
makerFilledAmount = new(big.Int).Add(makerFilledAmount, filledAmount)
makerDirtyFilledAmount[trade[tradingstate.TradeMakerOrderHash]] = makerFilledAmount
makerDirtyHashes = append(makerDirtyHashes, trade[tradingstate.TradeMakerOrderHash])
//updatedTakerOrder = XDCx.updateMatchedOrder(updatedTakerOrder, filledAmount, txMatchTime, txHash)
// update filledAmount, status of takerOrder
updatedTakerOrder.FilledAmount = new(big.Int).Add(updatedTakerOrder.FilledAmount, filledAmount)
if updatedTakerOrder.FilledAmount.Cmp(updatedTakerOrder.Quantity) < 0 && updatedTakerOrder.Type == tradingstate.Limit {
updatedTakerOrder.Status = tradingstate.OrderStatusPartialFilled
} else {
updatedTakerOrder.Status = tradingstate.OrderStatusFilled
}
}
// for Market orders
// filledAmount > 0 : FILLED
// otherwise: REJECTED
if updatedTakerOrder.Type == tradingstate.Market {
if updatedTakerOrder.FilledAmount.Sign() > 0 {
updatedTakerOrder.Status = tradingstate.OrderStatusFilled
} else {
updatedTakerOrder.Status = tradingstate.OrderStatusRejected
}
}
log.Debug("PutObject processed takerOrder",
"userAddr", updatedTakerOrder.UserAddress.Hex(), "side", updatedTakerOrder.Side,
"price", updatedTakerOrder.Price, "quantity", updatedTakerOrder.Quantity, "filledAmount", updatedTakerOrder.FilledAmount, "status", updatedTakerOrder.Status,
"hash", updatedTakerOrder.Hash.Hex(), "txHash", updatedTakerOrder.TxHash.Hex())
if err := db.PutObject(updatedTakerOrder.Hash, updatedTakerOrder); err != nil {
return fmt.Errorf("SDKNode: failed to put processed takerOrder. Hash: %s Error: %s", updatedTakerOrder.Hash.Hex(), err.Error())
}
items := db.GetListItemByHashes(makerDirtyHashes, &tradingstate.OrderItem{})
if items != nil {
makerOrders := items.([]*tradingstate.OrderItem)
log.Debug("Maker dirty orders", "len", len(makerOrders), "txhash", txHash.Hex())
for _, o := range makerOrders {
if txMatchTime.Before(o.UpdatedAt) {
log.Debug("Ignore old orders/trades maker", "txHash", txHash.Hex(), "txTime", txMatchTime.UnixNano(), "updatedAt", updatedTakerOrder.UpdatedAt.UnixNano())
continue
}
lastState = tradingstate.OrderHistoryItem{
TxHash: o.TxHash,
FilledAmount: tradingstate.CloneBigInt(o.FilledAmount),
Status: o.Status,
UpdatedAt: o.UpdatedAt,
}
XDCx.UpdateOrderCache(o.BaseToken, o.QuoteToken, o.Hash, txHash, lastState)
o.TxHash = txHash
o.UpdatedAt = txMatchTime
o.FilledAmount = new(big.Int).Add(o.FilledAmount, makerDirtyFilledAmount[o.Hash.Hex()])
if o.FilledAmount.Cmp(o.Quantity) < 0 {
o.Status = tradingstate.OrderStatusPartialFilled
} else {
o.Status = tradingstate.OrderStatusFilled
}
log.Debug("PutObject processed makerOrder",
"userAddr", o.UserAddress.Hex(), "side", o.Side,
"price", o.Price, "quantity", o.Quantity, "filledAmount", o.FilledAmount, "status", o.Status,
"hash", o.Hash.Hex(), "txHash", o.TxHash.Hex())
if err := db.PutObject(o.Hash, o); err != nil {
return fmt.Errorf("SDKNode: failed to put processed makerOrder. Hash: %s Error: %s", o.Hash.Hex(), err.Error())
}
}
}
// 3. put rejected orders to db and update status REJECTED
log.Debug("Got rejected orders", "number", len(rejectedOrders), "rejectedOrders", rejectedOrders)
if len(rejectedOrders) > 0 {
var rejectedHashes []string
// updateRejectedOrders
for _, rejectedOrder := range rejectedOrders {
rejectedHashes = append(rejectedHashes, rejectedOrder.Hash.Hex())
if updatedTakerOrder.Hash == rejectedOrder.Hash && !txMatchTime.Before(updatedTakerOrder.UpdatedAt) {
// cache order history for handling reorg
orderHistoryRecord := tradingstate.OrderHistoryItem{
TxHash: updatedTakerOrder.TxHash,
FilledAmount: tradingstate.CloneBigInt(updatedTakerOrder.FilledAmount),
Status: updatedTakerOrder.Status,
UpdatedAt: updatedTakerOrder.UpdatedAt,
}
XDCx.UpdateOrderCache(updatedTakerOrder.BaseToken, updatedTakerOrder.QuoteToken, updatedTakerOrder.Hash, txHash, orderHistoryRecord)
// if whole order is rejected, status = REJECTED
// otherwise, status = FILLED
if updatedTakerOrder.FilledAmount.Sign() > 0 {
updatedTakerOrder.Status = tradingstate.OrderStatusFilled
} else {
updatedTakerOrder.Status = tradingstate.OrderStatusRejected
}
updatedTakerOrder.TxHash = txHash
updatedTakerOrder.UpdatedAt = txMatchTime
if err := db.PutObject(updatedTakerOrder.Hash, updatedTakerOrder); err != nil {
return fmt.Errorf("SDKNode: failed to reject takerOrder. Hash: %s Error: %s", updatedTakerOrder.Hash.Hex(), err.Error())
}
}
}
items := db.GetListItemByHashes(rejectedHashes, &tradingstate.OrderItem{})
if items != nil {
dirtyRejectedOrders := items.([]*tradingstate.OrderItem)
for _, order := range dirtyRejectedOrders {
if txMatchTime.Before(order.UpdatedAt) {
log.Debug("Ignore old orders/trades reject", "txHash", txHash.Hex(), "txTime", txMatchTime.UnixNano(), "updatedAt", updatedTakerOrder.UpdatedAt.UnixNano())
continue
}
// cache order history for handling reorg
orderHistoryRecord := tradingstate.OrderHistoryItem{
TxHash: order.TxHash,
FilledAmount: tradingstate.CloneBigInt(order.FilledAmount),
Status: order.Status,
UpdatedAt: order.UpdatedAt,
}
XDCx.UpdateOrderCache(order.BaseToken, order.QuoteToken, order.Hash, txHash, orderHistoryRecord)
dirtyFilledAmount, ok := makerDirtyFilledAmount[order.Hash.Hex()]
if ok && dirtyFilledAmount != nil {
order.FilledAmount = new(big.Int).Add(order.FilledAmount, dirtyFilledAmount)
}
// if whole order is rejected, status = REJECTED
// otherwise, status = FILLED
if order.FilledAmount.Sign() > 0 {
order.Status = tradingstate.OrderStatusFilled
} else {
order.Status = tradingstate.OrderStatusRejected
}
order.TxHash = txHash
order.UpdatedAt = txMatchTime
if err = db.PutObject(order.Hash, order); err != nil {
return fmt.Errorf("SDKNode: failed to update rejectedOder to sdkNode %s", err.Error())
}
}
}
}
if err := db.CommitBulk(); err != nil {
return fmt.Errorf("SDKNode fail to commit bulk update orders, trades at txhash %s . Error: %s", txHash.Hex(), err.Error())
}
return nil
}
func (XDCx *XDCX) GetTradingState(block *types.Block, author common.Address) (*tradingstate.TradingStateDB, error) {
root, err := XDCx.GetTradingStateRoot(block, author)
if err != nil {
return nil, err
}
if XDCx.StateCache == nil {
return nil, errors.New("Not initialized XDCx")
}
return tradingstate.New(root, XDCx.StateCache)
}
func (XDCx *XDCX) GetStateCache() tradingstate.Database {
return XDCx.StateCache
}
func (XDCx *XDCX) HasTradingState(block *types.Block, author common.Address) bool {
root, err := XDCx.GetTradingStateRoot(block, author)
if err != nil {
return false
}
_, err = XDCx.StateCache.OpenTrie(root)
if err != nil {
return false
}
return true
}
func (XDCx *XDCX) GetTriegc() *prque.Prque {
return XDCx.Triegc
}
func (XDCx *XDCX) GetTradingStateRoot(block *types.Block, author common.Address) (common.Hash, error) {
for _, tx := range block.Transactions() {
from := *(tx.From())
if tx.To() != nil && tx.To().Hex() == common.TradingStateAddr && from.String() == author.String() {
if len(tx.Data()) >= 32 {
return common.BytesToHash(tx.Data()[:32]), nil
}
}
}
return tradingstate.EmptyRoot, nil
}
func (XDCx *XDCX) UpdateOrderCache(baseToken, quoteToken common.Address, orderHash common.Hash, txhash common.Hash, lastState tradingstate.OrderHistoryItem) {
var orderCacheAtTxHash map[common.Hash]tradingstate.OrderHistoryItem
c, ok := XDCx.orderCache.Get(txhash)
if !ok || c == nil {
orderCacheAtTxHash = make(map[common.Hash]tradingstate.OrderHistoryItem)
} else {
orderCacheAtTxHash = c.(map[common.Hash]tradingstate.OrderHistoryItem)
}
orderKey := tradingstate.GetOrderHistoryKey(baseToken, quoteToken, orderHash)
_, ok = orderCacheAtTxHash[orderKey]
if !ok {
orderCacheAtTxHash[orderKey] = lastState
}
XDCx.orderCache.Add(txhash, orderCacheAtTxHash)
}
func (XDCx *XDCX) RollbackReorgTxMatch(txhash common.Hash) error {
db := XDCx.GetMongoDB()
db.InitBulk()
items := db.GetListItemByTxHash(txhash, &tradingstate.OrderItem{})
if items != nil {
for _, order := range items.([]*tradingstate.OrderItem) {
c, ok := XDCx.orderCache.Get(txhash)
log.Debug("XDCx reorg: rollback order", "txhash", txhash.Hex(), "order", tradingstate.ToJSON(order), "orderHistoryItem", c)
if !ok {
log.Debug("XDCx reorg: remove order due to no orderCache", "order", tradingstate.ToJSON(order))
if err := db.DeleteObject(order.Hash, &tradingstate.OrderItem{}); err != nil {
log.Crit("SDKNode: failed to remove reorg order", "err", err.Error(), "order", tradingstate.ToJSON(order))
}
continue
}
orderCacheAtTxHash := c.(map[common.Hash]tradingstate.OrderHistoryItem)
orderHistoryItem, _ := orderCacheAtTxHash[tradingstate.GetOrderHistoryKey(order.BaseToken, order.QuoteToken, order.Hash)]
if (orderHistoryItem == tradingstate.OrderHistoryItem{}) {
log.Debug("XDCx reorg: remove order due to empty orderHistory", "order", tradingstate.ToJSON(order))
if err := db.DeleteObject(order.Hash, &tradingstate.OrderItem{}); err != nil {
log.Crit("SDKNode: failed to remove reorg order", "err", err.Error(), "order", tradingstate.ToJSON(order))
}
continue
}
order.TxHash = orderHistoryItem.TxHash
order.Status = orderHistoryItem.Status
order.FilledAmount = tradingstate.CloneBigInt(orderHistoryItem.FilledAmount)
order.UpdatedAt = orderHistoryItem.UpdatedAt
log.Debug("XDCx reorg: update order to the last orderHistoryItem", "order", tradingstate.ToJSON(order), "orderHistoryItem", orderHistoryItem)
if err := db.PutObject(order.Hash, order); err != nil {
log.Crit("SDKNode: failed to update reorg order", "err", err.Error(), "order", tradingstate.ToJSON(order))
}
}
}
log.Debug("XDCx reorg: DeleteTradeByTxHash", "txhash", txhash.Hex())
db.DeleteItemByTxHash(txhash, &tradingstate.Trade{})
if err := db.CommitBulk(); err != nil {
return fmt.Errorf("failed to RollbackTradingData. %v", err)
}
return nil
}

42
XDCx/api.go Normal file
View file

@ -0,0 +1,42 @@
package XDCx
import (
"context"
"errors"
"sync"
"time"
)
const (
LimitThresholdOrderNonceInQueue = 100
)
// List of errors
var (
ErrNoTopics = errors.New("missing topic(s)")
ErrOrderNonceTooLow = errors.New("OrderNonce too low")
ErrOrderNonceTooHigh = errors.New("OrderNonce too high")
)
// PublicXDCXAPI provides the XDCX RPC service that can be
// use publicly without security implications.
type PublicXDCXAPI struct {
t *XDCX
mu sync.Mutex
lastUsed map[string]time.Time // keeps track when a filter was polled for the last time.
}
// NewPublicXDCXAPI create a new RPC XDCX service.
func NewPublicXDCXAPI(t *XDCX) *PublicXDCXAPI {
api := &PublicXDCXAPI{
t: t,
lastUsed: make(map[string]time.Time),
}
return api
}
// Version returns the XDCX sub-protocol version.
func (api *PublicXDCXAPI) Version(ctx context.Context) string {
return ProtocolVersionStr
}

783
XDCx/order_processor.go Normal file
View file

@ -0,0 +1,783 @@
package XDCx
import (
"encoding/json"
"github.com/XinFinOrg/XDPoSChain/core/types"
"math/big"
"strconv"
"time"
"github.com/XinFinOrg/XDPoSChain/consensus"
"fmt"
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/log"
)
func (XDCx *XDCX) CommitOrder(header *types.Header, coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, tradingStateDB *tradingstate.TradingStateDB, orderBook common.Hash, order *tradingstate.OrderItem) ([]map[string]string, []*tradingstate.OrderItem, error) {
XDCxSnap := tradingStateDB.Snapshot()
dbSnap := statedb.Snapshot()
trades, rejects, err := XDCx.ApplyOrder(header, coinbase, chain, statedb, tradingStateDB, orderBook, order)
if err != nil {
tradingStateDB.RevertToSnapshot(XDCxSnap)
statedb.RevertToSnapshot(dbSnap)
return nil, nil, err
}
return trades, rejects, err
}
func (XDCx *XDCX) ApplyOrder(header *types.Header, coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, tradingStateDB *tradingstate.TradingStateDB, orderBook common.Hash, order *tradingstate.OrderItem) ([]map[string]string, []*tradingstate.OrderItem, error) {
var (
rejects []*tradingstate.OrderItem
trades []map[string]string
err error
)
nonce := tradingStateDB.GetNonce(order.UserAddress.Hash())
log.Debug("ApplyOrder", "addr", order.UserAddress, "statenonce", nonce, "ordernonce", order.Nonce)
if big.NewInt(int64(nonce)).Cmp(order.Nonce) == -1 {
log.Debug("ApplyOrder ErrNonceTooHigh", "nonce", order.Nonce)
return nil, nil, ErrNonceTooHigh
} else if big.NewInt(int64(nonce)).Cmp(order.Nonce) == 1 {
log.Debug("ApplyOrder ErrNonceTooLow", "nonce", order.Nonce)
return nil, nil, ErrNonceTooLow
}
// increase nonce
log.Debug("ApplyOrder set nonce", "nonce", nonce+1, "addr", order.UserAddress.Hex(), "status", order.Status, "oldnonce", nonce)
tradingStateDB.SetNonce(order.UserAddress.Hash(), nonce+1)
XDCxSnap := tradingStateDB.Snapshot()
dbSnap := statedb.Snapshot()
defer func() {
if err != nil {
tradingStateDB.RevertToSnapshot(XDCxSnap)
statedb.RevertToSnapshot(dbSnap)
}
}()
if err := order.VerifyOrder(statedb); err != nil {
rejects = append(rejects, order)
return trades, rejects, nil
}
if order.Status == tradingstate.OrderStatusCancelled {
err, reject := XDCx.ProcessCancelOrder(header, tradingStateDB, statedb, chain, coinbase, orderBook, order)
if err != nil || reject {
log.Debug("Reject cancelled order", "err", err)
rejects = append(rejects, order)
}
return trades, rejects, nil
}
if order.Type != tradingstate.Market {
if order.Price.Sign() == 0 || common.BigToHash(order.Price).Big().Cmp(order.Price) != 0 {
log.Debug("Reject order price invalid", "price", order.Price)
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 price slot to avoid conflict
if orderType == tradingstate.Market {
log.Debug("Process maket order", "side", order.Side, "quantity", order.Quantity, "price", order.Price)
trades, rejects, err = XDCx.processMarketOrder(coinbase, chain, statedb, tradingStateDB, orderBook, order)
if err != nil {
log.Debug("Reject market order", "err", err, "order", tradingstate.ToJSON(order))
trades = []map[string]string{}
rejects = append(rejects, order)
}
} else {
log.Debug("Process limit order", "side", order.Side, "quantity", order.Quantity, "price", order.Price)
trades, rejects, err = XDCx.processLimitOrder(coinbase, chain, statedb, tradingStateDB, orderBook, order)
if err != nil {
log.Debug("Reject limit order", "err", err, "order", tradingstate.ToJSON(order))
trades = []map[string]string{}
rejects = append(rejects, order)
}
}
return trades, rejects, nil
}
// processMarketOrder : process the market order
func (XDCx *XDCX) processMarketOrder(coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, tradingStateDB *tradingstate.TradingStateDB, orderBook common.Hash, order *tradingstate.OrderItem) ([]map[string]string, []*tradingstate.OrderItem, error) {
var (
trades []map[string]string
newTrades []map[string]string
rejects []*tradingstate.OrderItem
newRejects []*tradingstate.OrderItem
err error
)
quantityToTrade := order.Quantity
side := order.Side
// speedup the comparison, do not assign because it is pointer
zero := tradingstate.Zero
if side == tradingstate.Bid {
bestPrice, volume := tradingStateDB.GetBestAskPrice(orderBook)
log.Debug("processMarketOrder ", "side", side, "bestPrice", bestPrice, "quantityToTrade", quantityToTrade, "volume", volume)
for quantityToTrade.Cmp(zero) > 0 && bestPrice.Cmp(zero) > 0 {
quantityToTrade, newTrades, newRejects, err = XDCx.processOrderList(coinbase, chain, statedb, tradingStateDB, tradingstate.Ask, orderBook, bestPrice, quantityToTrade, order)
if err != nil {
return nil, nil, err
}
trades = append(trades, newTrades...)
rejects = append(rejects, newRejects...)
bestPrice, volume = tradingStateDB.GetBestAskPrice(orderBook)
log.Debug("processMarketOrder ", "side", side, "bestPrice", bestPrice, "quantityToTrade", quantityToTrade, "volume", volume)
}
} else {
bestPrice, volume := tradingStateDB.GetBestBidPrice(orderBook)
log.Debug("processMarketOrder ", "side", side, "bestPrice", bestPrice, "quantityToTrade", quantityToTrade, "volume", volume)
for quantityToTrade.Cmp(zero) > 0 && bestPrice.Cmp(zero) > 0 {
quantityToTrade, newTrades, newRejects, err = XDCx.processOrderList(coinbase, chain, statedb, tradingStateDB, tradingstate.Bid, orderBook, bestPrice, quantityToTrade, order)
if err != nil {
return nil, nil, err
}
trades = append(trades, newTrades...)
rejects = append(rejects, newRejects...)
bestPrice, volume = tradingStateDB.GetBestBidPrice(orderBook)
log.Debug("processMarketOrder ", "side", side, "bestPrice", bestPrice, "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 (XDCx *XDCX) processLimitOrder(coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, tradingStateDB *tradingstate.TradingStateDB, orderBook common.Hash, order *tradingstate.OrderItem) ([]map[string]string, []*tradingstate.OrderItem, error) {
var (
trades []map[string]string
newTrades []map[string]string
rejects []*tradingstate.OrderItem
newRejects []*tradingstate.OrderItem
err error
)
quantityToTrade := order.Quantity
side := order.Side
price := order.Price
// speedup the comparison, do not assign because it is pointer
zero := tradingstate.Zero
if side == tradingstate.Bid {
minPrice, volume := tradingStateDB.GetBestAskPrice(orderBook)
log.Debug("processLimitOrder ", "side", side, "minPrice", minPrice, "orderPrice", price, "volume", volume)
for quantityToTrade.Cmp(zero) > 0 && price.Cmp(minPrice) >= 0 && minPrice.Cmp(zero) > 0 {
log.Debug("Min price in asks tree", "price", minPrice.String())
quantityToTrade, newTrades, newRejects, err = XDCx.processOrderList(coinbase, chain, statedb, tradingStateDB, tradingstate.Ask, orderBook, minPrice, 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)
minPrice, volume = tradingStateDB.GetBestAskPrice(orderBook)
log.Debug("processLimitOrder ", "side", side, "minPrice", minPrice, "orderPrice", price, "volume", volume)
}
} else {
maxPrice, volume := tradingStateDB.GetBestBidPrice(orderBook)
log.Debug("processLimitOrder ", "side", side, "maxPrice", maxPrice, "orderPrice", price, "volume", volume)
for quantityToTrade.Cmp(zero) > 0 && price.Cmp(maxPrice) <= 0 && maxPrice.Cmp(zero) > 0 {
log.Debug("Max price in bids tree", "price", maxPrice.String())
quantityToTrade, newTrades, newRejects, err = XDCx.processOrderList(coinbase, chain, statedb, tradingStateDB, tradingstate.Bid, orderBook, maxPrice, 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)
maxPrice, volume = tradingStateDB.GetBestBidPrice(orderBook)
log.Debug("processLimitOrder ", "side", side, "maxPrice", maxPrice, "orderPrice", price, "volume", volume)
}
}
if quantityToTrade.Cmp(zero) > 0 {
orderId := tradingStateDB.GetNonce(orderBook)
order.OrderID = orderId + 1
order.Quantity = quantityToTrade
tradingStateDB.SetNonce(orderBook, orderId+1)
orderIdHash := common.BigToHash(new(big.Int).SetUint64(order.OrderID))
tradingStateDB.InsertOrderItem(orderBook, orderIdHash, *order)
log.Debug("After matching, order (unmatched part) is now added to tree", "side", order.Side, "order", order)
}
return trades, rejects, nil
}
// processOrderList : process the order list
func (XDCx *XDCX) processOrderList(coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, tradingStateDB *tradingstate.TradingStateDB, side string, orderBook common.Hash, price *big.Int, quantityStillToTrade *big.Int, order *tradingstate.OrderItem) (*big.Int, []map[string]string, []*tradingstate.OrderItem, error) {
quantityToTrade := tradingstate.CloneBigInt(quantityStillToTrade)
log.Debug("Process matching between order and orderlist", "quantityToTrade", quantityToTrade)
var (
trades []map[string]string
rejects []*tradingstate.OrderItem
)
for quantityToTrade.Sign() > 0 {
orderId, amount, _ := tradingStateDB.GetBestOrderIdAndAmount(orderBook, price, side)
var oldestOrder tradingstate.OrderItem
if amount.Sign() > 0 {
oldestOrder = tradingStateDB.GetOrder(orderBook, orderId)
}
log.Debug("found order ", "orderId ", orderId, "side", oldestOrder.Side, "amount", amount)
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 = tradingstate.CloneBigInt(quantityToTrade)
} else {
maxTradedQuantity = tradingstate.CloneBigInt(amount)
}
var quotePrice *big.Int
if oldestOrder.QuoteToken.String() != common.XDCNativeAddress {
quotePrice = tradingStateDB.GetLastPrice(tradingstate.GetTradingOrderBookHash(oldestOrder.QuoteToken, common.HexToAddress(common.XDCNativeAddress)))
log.Debug("TryGet quotePrice QuoteToken/XDC", "quotePrice", quotePrice)
if quotePrice == nil || quotePrice.Sign() == 0 {
inversePrice := tradingStateDB.GetLastPrice(tradingstate.GetTradingOrderBookHash(common.HexToAddress(common.XDCNativeAddress), oldestOrder.QuoteToken))
quoteTokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, oldestOrder.QuoteToken)
if err != nil || quoteTokenDecimal.Sign() == 0 {
return nil, nil, nil, fmt.Errorf("Fail to get tokenDecimal. Token: %v . Err: %v", oldestOrder.QuoteToken.String(), err)
}
log.Debug("TryGet inversePrice XDC/QuoteToken", "inversePrice", inversePrice)
if inversePrice != nil && inversePrice.Sign() > 0 {
quotePrice = new(big.Int).Mul(common.BasePrice, quoteTokenDecimal)
quotePrice = new(big.Int).Div(quotePrice, inversePrice)
log.Debug("TryGet quotePrice after get inversePrice XDC/QuoteToken", "quotePrice", quotePrice, "quoteTokenDecimal", quoteTokenDecimal)
}
}
} else {
quotePrice = common.BasePrice
}
tradedQuantity, rejectMaker, settleBalanceResult, err := XDCx.getTradeQuantity(quotePrice, coinbase, chain, statedb, order, &oldestOrder, maxTradedQuantity)
if err != nil && err == tradingstate.ErrQuantityTradeTooSmall {
if tradedQuantity.Cmp(maxTradedQuantity) == 0 {
if quantityToTrade.Cmp(amount) == 0 { // reject Taker & maker
rejects = append(rejects, order)
quantityToTrade = tradingstate.Zero
rejects = append(rejects, &oldestOrder)
err = tradingStateDB.CancelOrder(orderBook, &oldestOrder)
if err != nil {
return nil, nil, nil, err
}
break
} else if quantityToTrade.Cmp(amount) < 0 { // reject Taker
rejects = append(rejects, order)
quantityToTrade = tradingstate.Zero
break
} else { // reject maker
rejects = append(rejects, &oldestOrder)
err = tradingStateDB.CancelOrder(orderBook, &oldestOrder)
if err != nil {
return nil, nil, nil, err
}
continue
}
} else {
if rejectMaker { // reject maker
rejects = append(rejects, &oldestOrder)
err = tradingStateDB.CancelOrder(orderBook, &oldestOrder)
if err != nil {
return nil, nil, nil, err
}
continue
} else { // reject Taker
rejects = append(rejects, order)
quantityToTrade = tradingstate.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 = tradingstate.Zero
break
}
if tradedQuantity.Sign() > 0 {
quantityToTrade = tradingstate.Sub(quantityToTrade, tradedQuantity)
tradingStateDB.SubAmountOrderItem(orderBook, orderId, price, tradedQuantity, side)
tradingStateDB.SetLastPrice(orderBook, price)
log.Debug("Update quantity for orderId", "orderId", orderId.Hex())
log.Debug("TRADE", "orderBook", orderBook, "Taker price", price, "maker price", order.Price, "Amount", tradedQuantity, "orderId", orderId, "side", side)
tradeRecord := make(map[string]string)
tradeRecord[tradingstate.TradeTakerOrderHash] = order.Hash.Hex()
tradeRecord[tradingstate.TradeMakerOrderHash] = oldestOrder.Hash.Hex()
tradeRecord[tradingstate.TradeTimestamp] = strconv.FormatInt(time.Now().Unix(), 10)
tradeRecord[tradingstate.TradeQuantity] = tradedQuantity.String()
tradeRecord[tradingstate.TradeMakerExchange] = oldestOrder.ExchangeAddress.String()
tradeRecord[tradingstate.TradeMaker] = oldestOrder.UserAddress.String()
tradeRecord[tradingstate.TradeBaseToken] = oldestOrder.BaseToken.String()
tradeRecord[tradingstate.TradeQuoteToken] = oldestOrder.QuoteToken.String()
if settleBalanceResult != nil {
tradeRecord[tradingstate.MakerFee] = settleBalanceResult.Maker.Fee.Text(10)
tradeRecord[tradingstate.TakerFee] = settleBalanceResult.Taker.Fee.Text(10)
}
// maker price is actual price
// Taker price is offer price
// tradedPrice is always actual price
tradeRecord[tradingstate.TradePrice] = oldestOrder.Price.String()
tradeRecord[tradingstate.MakerOrderType] = oldestOrder.Type
trades = append(trades, tradeRecord)
oldAveragePrice, oldTotalQuantity := tradingStateDB.GetMediumPriceAndTotalAmount(orderBook)
var newAveragePrice, newTotalQuantity *big.Int
if oldAveragePrice == nil || oldAveragePrice.Sign() <= 0 || oldTotalQuantity == nil || oldTotalQuantity.Sign() <= 0 {
newAveragePrice = price
newTotalQuantity = tradedQuantity
} else {
//volume = price * quantity
//=> price = volume /quantity
// averagePrice = totalVolume / totalQuantity
// averagePrice = (oldVolume + newTradeVolume) / (oldQuantity + newTradeQuantity)
// FIXME: average price formula
// https://user-images.githubusercontent.com/17243442/72722447-ecb83700-3bb0-11ea-9273-1c1028dbade0.jpg
oldVolume := new(big.Int).Mul(oldAveragePrice, oldTotalQuantity)
newTradeVolume := new(big.Int).Mul(price, tradedQuantity)
newTotalQuantity = new(big.Int).Add(oldTotalQuantity, tradedQuantity)
newAveragePrice = new(big.Int).Div(new(big.Int).Add(oldVolume, newTradeVolume), newTotalQuantity)
}
tradingStateDB.SetMediumPrice(orderBook, newAveragePrice, newTotalQuantity)
}
if rejectMaker {
rejects = append(rejects, &oldestOrder)
err := tradingStateDB.CancelOrder(orderBook, &oldestOrder)
if err != nil {
return nil, nil, nil, err
}
}
}
return quantityToTrade, trades, rejects, nil
}
func (XDCx *XDCX) getTradeQuantity(quotePrice *big.Int, coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, takerOrder *tradingstate.OrderItem, makerOrder *tradingstate.OrderItem, quantityToTrade *big.Int) (*big.Int, bool, *tradingstate.SettleBalance, error) {
baseTokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, makerOrder.BaseToken)
if err != nil || baseTokenDecimal.Sign() == 0 {
return tradingstate.Zero, false, nil, fmt.Errorf("Fail to get tokenDecimal. Token: %v . Err: %v", makerOrder.BaseToken.String(), err)
}
quoteTokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, makerOrder.QuoteToken)
if err != nil || quoteTokenDecimal.Sign() == 0 {
return tradingstate.Zero, false, nil, fmt.Errorf("Fail to get tokenDecimal. Token: %v . Err: %v", makerOrder.QuoteToken.String(), err)
}
if makerOrder.QuoteToken.String() == common.XDCNativeAddress {
quotePrice = quoteTokenDecimal
}
if takerOrder.ExchangeAddress.String() == makerOrder.ExchangeAddress.String() {
if err := tradingstate.CheckRelayerFee(takerOrder.ExchangeAddress, new(big.Int).Mul(common.RelayerFee, big.NewInt(2)), statedb); err != nil {
log.Debug("Reject order Taker Exchnage = Maker Exchange , relayer not enough fee ", "err", err)
return tradingstate.Zero, false, nil, nil
}
} else {
if err := tradingstate.CheckRelayerFee(takerOrder.ExchangeAddress, common.RelayerFee, statedb); err != nil {
log.Debug("Reject order Taker , relayer not enough fee ", "err", err)
return tradingstate.Zero, false, nil, nil
}
if err := tradingstate.CheckRelayerFee(makerOrder.ExchangeAddress, common.RelayerFee, statedb); err != nil {
log.Debug("Reject order maker , relayer not enough fee ", "err", err)
return tradingstate.Zero, true, nil, nil
}
}
takerFeeRate := tradingstate.GetExRelayerFee(takerOrder.ExchangeAddress, statedb)
makerFeeRate := tradingstate.GetExRelayerFee(makerOrder.ExchangeAddress, statedb)
var takerBalance, makerBalance *big.Int
switch takerOrder.Side {
case tradingstate.Bid:
takerBalance = tradingstate.GetTokenBalance(takerOrder.UserAddress, makerOrder.QuoteToken, statedb)
makerBalance = tradingstate.GetTokenBalance(makerOrder.UserAddress, makerOrder.BaseToken, statedb)
case tradingstate.Ask:
takerBalance = tradingstate.GetTokenBalance(takerOrder.UserAddress, makerOrder.BaseToken, statedb)
makerBalance = tradingstate.GetTokenBalance(makerOrder.UserAddress, makerOrder.QuoteToken, statedb)
default:
takerBalance = big.NewInt(0)
makerBalance = big.NewInt(0)
}
quantity, rejectMaker := GetTradeQuantity(takerOrder.Side, takerFeeRate, takerBalance, makerOrder.Price, makerFeeRate, makerBalance, baseTokenDecimal, quantityToTrade)
log.Debug("GetTradeQuantity", "side", takerOrder.Side, "takerBalance", takerBalance, "makerBalance", makerBalance, "BaseToken", makerOrder.BaseToken, "QuoteToken", makerOrder.QuoteToken, "quantity", quantity, "rejectMaker", rejectMaker, "quotePrice", quotePrice)
var settleBalanceResult *tradingstate.SettleBalance
if quantity.Sign() > 0 {
// Apply Match Order
settleBalanceResult, err = tradingstate.GetSettleBalance(quotePrice, takerOrder.Side, takerFeeRate, makerOrder.BaseToken, makerOrder.QuoteToken, makerOrder.Price, makerFeeRate, baseTokenDecimal, quoteTokenDecimal, quantity)
log.Debug("GetSettleBalance", "settleBalanceResult", settleBalanceResult, "err", err)
if err == nil {
err = DoSettleBalance(coinbase, takerOrder, makerOrder, settleBalanceResult, statedb)
}
return quantity, rejectMaker, settleBalanceResult, err
}
return quantity, rejectMaker, settleBalanceResult, nil
}
func GetTradeQuantity(takerSide string, takerFeeRate *big.Int, takerBalance *big.Int, makerPrice *big.Int, makerFeeRate *big.Int, makerBalance *big.Int, baseTokenDecimal *big.Int, quantityToTrade *big.Int) (*big.Int, bool) {
if takerSide == tradingstate.Bid {
// maker InQuantity quoteTokenQuantity=(quantityToTrade*maker.Price/baseTokenDecimal)
quoteTokenQuantity := new(big.Int).Mul(quantityToTrade, makerPrice)
quoteTokenQuantity = new(big.Int).Div(quoteTokenQuantity, baseTokenDecimal)
// Fee
// charge on the token he/she has before the trade, in this case: quoteToken
// charge on the token he/she has before the trade, in this case: baseToken
// takerFee = quoteTokenQuantity*takerFeeRate/baseFee=(quantityToTrade*maker.Price/baseTokenDecimal) * makerFeeRate/baseFee
takerFee := big.NewInt(0).Mul(quoteTokenQuantity, takerFeeRate)
takerFee = big.NewInt(0).Div(takerFee, common.XDCXBaseFee)
//takerOutTotal= quoteTokenQuantity + takerFee = quantityToTrade*maker.Price/baseTokenDecimal + quantityToTrade*maker.Price/baseTokenDecimal * takerFeeRate/baseFee
// = quantityToTrade * maker.Price/baseTokenDecimal ( 1 + takerFeeRate/baseFee)
// = quantityToTrade * maker.Price * (baseFee + takerFeeRate ) / ( baseTokenDecimal * baseFee)
takerOutTotal := new(big.Int).Add(quoteTokenQuantity, takerFee)
makerOutTotal := quantityToTrade
if takerBalance.Cmp(takerOutTotal) >= 0 && makerBalance.Cmp(makerOutTotal) >= 0 {
return quantityToTrade, false
} else if takerBalance.Cmp(takerOutTotal) < 0 && makerBalance.Cmp(makerOutTotal) >= 0 {
newQuantityTrade := new(big.Int).Mul(takerBalance, baseTokenDecimal)
newQuantityTrade = new(big.Int).Mul(newQuantityTrade, common.XDCXBaseFee)
newQuantityTrade = new(big.Int).Div(newQuantityTrade, new(big.Int).Add(common.XDCXBaseFee, takerFeeRate))
newQuantityTrade = new(big.Int).Div(newQuantityTrade, makerPrice)
if newQuantityTrade.Sign() == 0 {
log.Debug("Reject order Taker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "takerOutTotal", takerOutTotal)
}
return newQuantityTrade, false
} else if takerBalance.Cmp(takerOutTotal) >= 0 && makerBalance.Cmp(makerOutTotal) < 0 {
log.Debug("Reject order maker , not enough balance ", "makerBalance", makerBalance, " makerOutTotal", makerOutTotal)
return makerBalance, true
} else {
// takerBalance.Cmp(takerOutTotal) < 0 && makerBalance.Cmp(makerOutTotal) < 0
newQuantityTrade := new(big.Int).Mul(takerBalance, baseTokenDecimal)
newQuantityTrade = new(big.Int).Mul(newQuantityTrade, common.XDCXBaseFee)
newQuantityTrade = new(big.Int).Div(newQuantityTrade, new(big.Int).Add(common.XDCXBaseFee, takerFeeRate))
newQuantityTrade = new(big.Int).Div(newQuantityTrade, makerPrice)
if newQuantityTrade.Cmp(makerBalance) <= 0 {
if newQuantityTrade.Sign() == 0 {
log.Debug("Reject order Taker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "makerBalance", makerBalance, " newQuantityTrade ", newQuantityTrade)
}
return newQuantityTrade, false
}
log.Debug("Reject order maker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "makerBalance", makerBalance, " newQuantityTrade ", newQuantityTrade)
return makerBalance, true
}
} else {
// Taker InQuantity
// quoteTokenQuantity = quantityToTrade * makerPrice / baseTokenDecimal
quoteTokenQuantity := new(big.Int).Mul(quantityToTrade, makerPrice)
quoteTokenQuantity = new(big.Int).Div(quoteTokenQuantity, baseTokenDecimal)
// maker InQuantity
// Fee
// charge on the token he/she has before the trade, in this case: baseToken
// makerFee = quoteTokenQuantity * makerFeeRate / baseFee = quantityToTrade * makerPrice / baseTokenDecimal * makerFeeRate / baseFee
// charge on the token he/she has before the trade, in this case: quoteToken
makerFee := new(big.Int).Mul(quoteTokenQuantity, makerFeeRate)
makerFee = new(big.Int).Div(makerFee, common.XDCXBaseFee)
takerOutTotal := quantityToTrade
// makerOutTotal = quoteTokenQuantity + makerFee = quantityToTrade * makerPrice / baseTokenDecimal + quantityToTrade * makerPrice / baseTokenDecimal * makerFeeRate / baseFee
// = quantityToTrade * makerPrice / baseTokenDecimal * (1+makerFeeRate / baseFee)
// = quantityToTrade * makerPrice * (baseFee + makerFeeRate) / ( baseTokenDecimal * baseFee )
makerOutTotal := new(big.Int).Add(quoteTokenQuantity, makerFee)
if takerBalance.Cmp(takerOutTotal) >= 0 && makerBalance.Cmp(makerOutTotal) >= 0 {
return quantityToTrade, false
} else if takerBalance.Cmp(takerOutTotal) < 0 && makerBalance.Cmp(makerOutTotal) >= 0 {
if takerBalance.Sign() == 0 {
log.Debug("Reject order Taker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "takerOutTotal", takerOutTotal)
}
return takerBalance, false
} else if takerBalance.Cmp(takerOutTotal) >= 0 && makerBalance.Cmp(makerOutTotal) < 0 {
newQuantityTrade := new(big.Int).Mul(makerBalance, baseTokenDecimal)
newQuantityTrade = new(big.Int).Mul(newQuantityTrade, common.XDCXBaseFee)
newQuantityTrade = new(big.Int).Div(newQuantityTrade, new(big.Int).Add(common.XDCXBaseFee, makerFeeRate))
newQuantityTrade = new(big.Int).Div(newQuantityTrade, makerPrice)
log.Debug("Reject order maker , not enough balance ", "makerBalance", makerBalance, " makerOutTotal", makerOutTotal)
return newQuantityTrade, true
} else {
// takerBalance.Cmp(takerOutTotal) < 0 && makerBalance.Cmp(makerOutTotal) < 0
newQuantityTrade := new(big.Int).Mul(makerBalance, baseTokenDecimal)
newQuantityTrade = new(big.Int).Mul(newQuantityTrade, common.XDCXBaseFee)
newQuantityTrade = new(big.Int).Div(newQuantityTrade, new(big.Int).Add(common.XDCXBaseFee, makerFeeRate))
newQuantityTrade = new(big.Int).Div(newQuantityTrade, makerPrice)
if newQuantityTrade.Cmp(takerBalance) <= 0 {
log.Debug("Reject order maker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "makerBalance", makerBalance, " newQuantityTrade ", newQuantityTrade)
return newQuantityTrade, true
}
if takerBalance.Sign() == 0 {
log.Debug("Reject order Taker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "makerBalance", makerBalance, " newQuantityTrade ", newQuantityTrade)
}
return takerBalance, false
}
}
}
func DoSettleBalance(coinbase common.Address, takerOrder, makerOrder *tradingstate.OrderItem, settleBalance *tradingstate.SettleBalance, statedb *state.StateDB) error {
takerExOwner := tradingstate.GetRelayerOwner(takerOrder.ExchangeAddress, statedb)
makerExOwner := tradingstate.GetRelayerOwner(makerOrder.ExchangeAddress, statedb)
matchingFee := big.NewInt(0)
// masternodes charges fee of both 2 relayers. If maker and Taker are on same relayer, that relayer is charged fee twice
matchingFee = new(big.Int).Add(matchingFee, common.RelayerFee)
matchingFee = new(big.Int).Add(matchingFee, common.RelayerFee)
if common.EmptyHash(takerExOwner.Hash()) || common.EmptyHash(makerExOwner.Hash()) {
return fmt.Errorf("Echange owner empty , Taker: %v , maker : %v ", takerExOwner, makerExOwner)
}
mapBalances := map[common.Address]map[common.Address]*big.Int{}
//Checking balance
newTakerInTotal, err := tradingstate.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 := tradingstate.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 := tradingstate.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 := tradingstate.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 := tradingstate.CheckAddTokenBalance(takerExOwner, settleBalance.Taker.Fee, makerOrder.QuoteToken, statedb, mapBalances)
if err != nil {
return err
}
if mapBalances[makerOrder.QuoteToken] == nil {
mapBalances[makerOrder.QuoteToken] = map[common.Address]*big.Int{}
}
mapBalances[makerOrder.QuoteToken][takerExOwner] = newTakerFee
newMakerFee, err := tradingstate.CheckAddTokenBalance(makerExOwner, settleBalance.Maker.Fee, makerOrder.QuoteToken, statedb, mapBalances)
if err != nil {
return err
}
mapBalances[makerOrder.QuoteToken][makerExOwner] = newMakerFee
mapRelayerFee := map[common.Address]*big.Int{}
newRelayerTakerFee, err := tradingstate.CheckSubRelayerFee(takerOrder.ExchangeAddress, common.RelayerFee, statedb, mapRelayerFee)
if err != nil {
return err
}
mapRelayerFee[takerOrder.ExchangeAddress] = newRelayerTakerFee
newRelayerMakerFee, err := tradingstate.CheckSubRelayerFee(makerOrder.ExchangeAddress, common.RelayerFee, statedb, mapRelayerFee)
if err != nil {
return err
}
mapRelayerFee[makerOrder.ExchangeAddress] = newRelayerMakerFee
tradingstate.SetSubRelayerFee(takerOrder.ExchangeAddress, newRelayerTakerFee, common.RelayerFee, statedb)
tradingstate.SetSubRelayerFee(makerOrder.ExchangeAddress, newRelayerMakerFee, common.RelayerFee, statedb)
masternodeOwner := statedb.GetOwner(coinbase)
statedb.AddBalance(masternodeOwner, matchingFee)
tradingstate.SetTokenBalance(takerOrder.UserAddress, newTakerInTotal, settleBalance.Taker.InToken, statedb)
tradingstate.SetTokenBalance(takerOrder.UserAddress, newTakerOutTotal, settleBalance.Taker.OutToken, statedb)
tradingstate.SetTokenBalance(makerOrder.UserAddress, newMakerInTotal, settleBalance.Maker.InToken, statedb)
tradingstate.SetTokenBalance(makerOrder.UserAddress, newMakerOutTotal, settleBalance.Maker.OutToken, statedb)
// add balance for relayers
//log.Debug("ApplyXDCXMatchedTransaction settle fee for relayers",
// "takerRelayerOwner", takerExOwner,
// "takerFeeToken", quoteToken, "takerFee", settleBalanceResult[takerAddr][XDCx.Fee].(*big.Int),
// "makerRelayerOwner", makerExOwner,
// "makerFeeToken", quoteToken, "makerFee", settleBalanceResult[makerAddr][XDCx.Fee].(*big.Int))
// takerFee
tradingstate.SetTokenBalance(takerExOwner, newTakerFee, makerOrder.QuoteToken, statedb)
tradingstate.SetTokenBalance(makerExOwner, newMakerFee, makerOrder.QuoteToken, statedb)
return nil
}
func (XDCx *XDCX) ProcessCancelOrder(header *types.Header, tradingStateDB *tradingstate.TradingStateDB, statedb *state.StateDB, chain consensus.ChainContext, coinbase common.Address, orderBook common.Hash, order *tradingstate.OrderItem) (error, bool) {
if err := tradingstate.CheckRelayerFee(order.ExchangeAddress, common.RelayerCancelFee, statedb); err != nil {
log.Debug("Relayer not enough fee when cancel order", "err", err)
return nil, true
}
baseTokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, order.BaseToken)
if err != nil || baseTokenDecimal.Sign() == 0 {
log.Debug("Fail to get tokenDecimal ", "Token", order.BaseToken.String(), "err", err)
return err, false
}
// order: basic order information (includes orderId, orderHash, baseToken, quoteToken) which user send to XDCx to cancel order
// originOrder: full order information getting from order trie
originOrder := tradingStateDB.GetOrder(orderBook, common.BigToHash(new(big.Int).SetUint64(order.OrderID)))
if originOrder == tradingstate.EmptyOrder {
return fmt.Errorf("order not found. OrderId: %v. Base: %s. Quote: %s", order.OrderID, order.BaseToken.Hex(), order.QuoteToken.Hex()), false
}
var tokenBalance *big.Int
switch originOrder.Side {
case tradingstate.Ask:
tokenBalance = tradingstate.GetTokenBalance(originOrder.UserAddress, originOrder.BaseToken, statedb)
case tradingstate.Bid:
tokenBalance = tradingstate.GetTokenBalance(originOrder.UserAddress, originOrder.QuoteToken, statedb)
default:
log.Debug("Not found order side", "Side", originOrder.Side)
return nil, false
}
log.Debug("ProcessCancelOrder", "baseToken", originOrder.BaseToken, "quoteToken", originOrder.QuoteToken)
feeRate := tradingstate.GetExRelayerFee(originOrder.ExchangeAddress, statedb)
tokenCancelFee, tokenPriceInXDC := common.Big0, common.Big0
if !chain.Config().IsTIPXDCXCancellationFee(header.Number) {
tokenCancelFee = getCancelFeeV1(baseTokenDecimal, feeRate, &originOrder)
} else {
tokenCancelFee, tokenPriceInXDC = XDCx.getCancelFee(chain, statedb, tradingStateDB, &originOrder, feeRate)
}
if tokenBalance.Cmp(tokenCancelFee) < 0 {
log.Debug("User not enough balance when cancel order", "Side", originOrder.Side, "balance", tokenBalance, "fee", tokenCancelFee)
return nil, true
}
err = tradingStateDB.CancelOrder(orderBook, order)
if err != nil {
log.Debug("Error when cancel order", "order", order)
return err, false
}
// relayers pay XDC for masternode
tradingstate.SubRelayerFee(originOrder.ExchangeAddress, common.RelayerCancelFee, statedb)
masternodeOwner := statedb.GetOwner(coinbase)
// relayers pay XDC for masternode
statedb.AddBalance(masternodeOwner, common.RelayerCancelFee)
relayerOwner := tradingstate.GetRelayerOwner(originOrder.ExchangeAddress, statedb)
switch originOrder.Side {
case tradingstate.Ask:
// users pay token (which they have) for relayer
tradingstate.SubTokenBalance(originOrder.UserAddress, tokenCancelFee, originOrder.BaseToken, statedb)
tradingstate.AddTokenBalance(relayerOwner, tokenCancelFee, originOrder.BaseToken, statedb)
case tradingstate.Bid:
// users pay token (which they have) for relayer
tradingstate.SubTokenBalance(originOrder.UserAddress, tokenCancelFee, originOrder.QuoteToken, statedb)
tradingstate.AddTokenBalance(relayerOwner, tokenCancelFee, originOrder.QuoteToken, statedb)
default:
}
// update cancel fee
extraData, _ := json.Marshal(struct {
CancelFee string
TokenPriceInXDC string
}{
CancelFee: tokenCancelFee.Text(10),
TokenPriceInXDC: tokenPriceInXDC.Text(10),
})
order.ExtraData = string(extraData)
return nil, false
}
// cancellation fee = 1/10 trading fee
// deprecated after hardfork at TIPXDCXCancellationFee
func getCancelFeeV1(baseTokenDecimal *big.Int, feeRate *big.Int, order *tradingstate.OrderItem) *big.Int {
cancelFee := big.NewInt(0)
if order.Side == tradingstate.Ask {
// SELL 1 BTC => XDC ,,
// order.Quantity =1 && fee rate =2
// ==> cancel fee = 2/10000
// order.Quantity already included baseToken decimal
cancelFee = new(big.Int).Mul(order.Quantity, feeRate)
cancelFee = new(big.Int).Div(cancelFee, common.XDCXBaseCancelFee)
} else {
// BUY 1 BTC => XDC with Price : 10000
// quoteTokenQuantity = 10000 && fee rate =2
// => cancel fee =2
quoteTokenQuantity := new(big.Int).Mul(order.Quantity, order.Price)
quoteTokenQuantity = new(big.Int).Div(quoteTokenQuantity, baseTokenDecimal)
// Fee
// makerFee = quoteTokenQuantity * feeRate / baseFee = quantityToTrade * makerPrice / baseTokenDecimal * feeRate / baseFee
cancelFee = new(big.Int).Mul(quoteTokenQuantity, feeRate)
cancelFee = new(big.Int).Div(cancelFee, common.XDCXBaseCancelFee)
}
return cancelFee
}
// return tokenQuantity, tokenPriceInXDC
func (XDCx *XDCX) getCancelFee(chain consensus.ChainContext, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, order *tradingstate.OrderItem, feeRate *big.Int) (*big.Int, *big.Int) {
if feeRate == nil || feeRate.Sign() == 0 {
return common.Big0, common.Big0
}
cancelFee := big.NewInt(0)
tokenPriceInXDC := big.NewInt(0)
var err error
if order.Side == tradingstate.Ask {
cancelFee, tokenPriceInXDC, err = XDCx.ConvertXDCToToken(chain, statedb, tradingStateDb, order.BaseToken, common.RelayerCancelFee)
} else {
cancelFee, tokenPriceInXDC, err = XDCx.ConvertXDCToToken(chain, statedb, tradingStateDb, order.QuoteToken, common.RelayerCancelFee)
}
if err != nil {
return common.Big0, common.Big0
}
return cancelFee, tokenPriceInXDC
}
func (XDCx *XDCX) UpdateMediumPriceBeforeEpoch(epochNumber uint64, tradingStateDB *tradingstate.TradingStateDB, statedb *state.StateDB) error {
mapPairs, err := tradingstate.GetAllTradingPairs(statedb)
log.Debug("UpdateMediumPriceBeforeEpoch", "len(mapPairs)", len(mapPairs))
if err != nil {
return err
}
epochPriceResult := map[common.Hash]*big.Int{}
for orderbook := range mapPairs {
oldMediumPriceBeforeEpoch := tradingStateDB.GetMediumPriceBeforeEpoch(orderbook)
mediumPriceCurrent, _ := tradingStateDB.GetMediumPriceAndTotalAmount(orderbook)
// if there is no trade in this epoch, use average price of last epoch
epochPriceResult[orderbook] = oldMediumPriceBeforeEpoch
if mediumPriceCurrent.Sign() > 0 && mediumPriceCurrent.Cmp(oldMediumPriceBeforeEpoch) != 0 {
log.Debug("UpdateMediumPriceBeforeEpoch", "mediumPriceCurrent", mediumPriceCurrent)
tradingStateDB.SetMediumPriceBeforeEpoch(orderbook, mediumPriceCurrent)
epochPriceResult[orderbook] = mediumPriceCurrent
}
tradingStateDB.SetMediumPrice(orderbook, tradingstate.Zero, tradingstate.Zero)
}
if XDCx.IsSDKNode() {
if err := XDCx.LogEpochPrice(epochNumber, epochPriceResult); err != nil {
log.Error("failed to update epochPrice", "err", err)
}
}
return nil
}
// put average price of epoch to mongodb for tracking liquidation trades
// epochPriceResult: a map of epoch average price, key is orderbook hash , value is epoch average price
// orderbook hash genereted from baseToken, quoteToken at XDPoSChain/XDCx/tradingstate/common.go:214
func (XDCx *XDCX) LogEpochPrice(epochNumber uint64, epochPriceResult map[common.Hash]*big.Int) error {
db := XDCx.GetMongoDB()
db.InitBulk()
for orderbook, price := range epochPriceResult {
if price.Sign() <= 0 {
continue
}
epochPriceItem := &tradingstate.EpochPriceItem{
Epoch: epochNumber,
Orderbook: orderbook,
Price: price,
}
epochPriceItem.Hash = epochPriceItem.ComputeHash()
if err := db.PutObject(epochPriceItem.Hash, epochPriceItem); err != nil {
return err
}
}
if err := db.CommitBulk(); err != nil {
return err
}
return nil
}

View file

@ -0,0 +1,574 @@
package XDCx
import (
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"math/big"
"reflect"
"testing"
)
func Test_getCancelFeeV1(t *testing.T) {
type CancelFeeArg struct {
baseTokenDecimal *big.Int
feeRate *big.Int
order *tradingstate.OrderItem
}
tests := []struct {
name string
args CancelFeeArg
want *big.Int
}{
// zero fee test: SELL
{
"zero fee getCancelFeeV1: SELL",
CancelFeeArg{
baseTokenDecimal: common.Big1,
feeRate: common.Big0,
order: &tradingstate.OrderItem{
Quantity: new(big.Int).SetUint64(10000),
Side: tradingstate.Ask,
},
},
common.Big0,
},
// zero fee test: BUY
{
"zero fee getCancelFeeV1: BUY",
CancelFeeArg{
baseTokenDecimal: common.Big1,
feeRate: common.Big0,
order: &tradingstate.OrderItem{
Quantity: new(big.Int).SetUint64(10000),
Price: new(big.Int).SetUint64(1),
Side: tradingstate.Bid,
},
},
common.Big0,
},
// test getCancelFee: SELL
{
"test getCancelFeeV1:: SELL",
CancelFeeArg{
baseTokenDecimal: common.Big1,
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
order: &tradingstate.OrderItem{
Quantity: new(big.Int).SetUint64(10000),
Side: tradingstate.Ask,
},
},
common.Big1,
},
// test getCancelFee:: BUY
{
"test getCancelFeeV1:: BUY",
CancelFeeArg{
baseTokenDecimal: common.Big1,
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
order: &tradingstate.OrderItem{
Quantity: new(big.Int).SetUint64(10000),
Price: new(big.Int).SetUint64(1),
Side: tradingstate.Bid,
},
},
common.Big1,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := getCancelFeeV1(tt.args.baseTokenDecimal, tt.args.feeRate, tt.args.order); !reflect.DeepEqual(got, tt.want) {
t.Errorf("getCancelFeeV1() = %v, quantity %v", got, tt.want)
}
})
}
}
func Test_getCancelFee(t *testing.T) {
XDCx := New(&DefaultConfig)
db := rawdb.NewMemoryDatabase()
stateCache := tradingstate.NewDatabase(db)
tradingStateDb, _ := tradingstate.New(common.Hash{}, stateCache)
testTokenA := common.HexToAddress("0x1000000000000000000000000000000000000002")
testTokenB := common.HexToAddress("0x1100000000000000000000000000000000000003")
// set decimal
// tokenA has decimal 10^18
XDCx.SetTokenDecimal(testTokenA, common.BasePrice)
// tokenB has decimal 10^8
tokenBDecimal := new(big.Int).Exp(big.NewInt(10), big.NewInt(8), nil)
XDCx.SetTokenDecimal(testTokenB, tokenBDecimal)
// set tokenAPrice = 1 XDC
tradingStateDb.SetMediumPriceBeforeEpoch(tradingstate.GetTradingOrderBookHash(testTokenA, common.HexToAddress(common.XDCNativeAddress)), common.BasePrice)
// set tokenBPrice = 1 XDC
tradingStateDb.SetMediumPriceBeforeEpoch(tradingstate.GetTradingOrderBookHash(common.HexToAddress(common.XDCNativeAddress), testTokenB), tokenBDecimal)
type CancelFeeArg struct {
feeRate *big.Int
order *tradingstate.OrderItem
}
tests := []struct {
name string
args CancelFeeArg
want *big.Int
}{
// BASE: testTokenA,
// QUOTE: XDC
// zero fee test: SELL
{
"TokenA/XDC zero fee test: SELL",
CancelFeeArg{
feeRate: common.Big0,
order: &tradingstate.OrderItem{
BaseToken: testTokenA,
QuoteToken: common.HexToAddress(common.XDCNativeAddress),
Quantity: new(big.Int).SetUint64(10000),
Side: tradingstate.Ask,
},
},
common.Big0,
},
// zero fee test: BUY
{
"TokenA/XDC zero fee test: BUY",
CancelFeeArg{
feeRate: common.Big0,
order: &tradingstate.OrderItem{
BaseToken: testTokenA,
QuoteToken: common.HexToAddress(common.XDCNativeAddress),
Quantity: new(big.Int).SetUint64(10000),
Side: tradingstate.Bid,
},
},
common.Big0,
},
// test getCancelFee: SELL
{
"TokenA/XDC test getCancelFee:: SELL",
CancelFeeArg{
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
order: &tradingstate.OrderItem{
BaseToken: common.HexToAddress(common.XDCNativeAddress),
QuoteToken: testTokenA,
Quantity: new(big.Int).SetUint64(10000),
Side: tradingstate.Ask,
},
},
common.RelayerCancelFee,
},
// test getCancelFee:: BUY
{
"TokenA/XDC test getCancelFee:: BUY",
CancelFeeArg{
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
order: &tradingstate.OrderItem{
Quantity: new(big.Int).SetUint64(10000),
BaseToken: common.HexToAddress(common.XDCNativeAddress),
QuoteToken: testTokenA,
Side: tradingstate.Bid,
},
},
common.RelayerCancelFee,
},
// BASE: XDC
// QUOTE: testTokenA
// zero fee test: SELL
{
"XDC/TokenA zero fee test: SELL",
CancelFeeArg{
feeRate: common.Big0,
order: &tradingstate.OrderItem{
BaseToken: common.HexToAddress(common.XDCNativeAddress),
QuoteToken: testTokenA,
Quantity: new(big.Int).SetUint64(10000),
Side: tradingstate.Ask,
},
},
common.Big0,
},
// zero fee test: BUY
{
"XDC/TokenA zero fee test: BUY",
CancelFeeArg{
feeRate: common.Big0,
order: &tradingstate.OrderItem{
BaseToken: common.HexToAddress(common.XDCNativeAddress),
QuoteToken: testTokenA,
Quantity: new(big.Int).SetUint64(10000),
Side: tradingstate.Bid,
},
},
common.Big0,
},
// test getCancelFee: SELL
{
"XDC/TokenA test getCancelFee:: SELL",
CancelFeeArg{
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
order: &tradingstate.OrderItem{
BaseToken: common.HexToAddress(common.XDCNativeAddress),
QuoteToken: testTokenA,
Quantity: new(big.Int).SetUint64(10000),
Side: tradingstate.Ask,
},
},
common.RelayerCancelFee,
},
// test getCancelFee:: BUY
{
"XDC/TokenA test getCancelFee:: BUY",
CancelFeeArg{
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
order: &tradingstate.OrderItem{
Quantity: new(big.Int).SetUint64(10000),
BaseToken: common.HexToAddress(common.XDCNativeAddress),
QuoteToken: testTokenA,
Side: tradingstate.Bid,
},
},
common.RelayerCancelFee,
},
// BASE: testTokenB
// QUOTE: testTokenA
// zero fee test: SELL
{
"TokenB/TokenA zero fee test: SELL",
CancelFeeArg{
feeRate: common.Big0,
order: &tradingstate.OrderItem{
BaseToken: testTokenB,
QuoteToken: testTokenA,
Quantity: new(big.Int).SetUint64(10000),
Side: tradingstate.Ask,
},
},
common.Big0,
},
// zero fee test: BUY
{
"TokenB/TokenA zero fee test: BUY",
CancelFeeArg{
feeRate: common.Big0,
order: &tradingstate.OrderItem{
BaseToken: testTokenB,
QuoteToken: testTokenA,
Quantity: new(big.Int).SetUint64(10000),
Side: tradingstate.Bid,
},
},
common.Big0,
},
// test getCancelFee: SELL
{
"TokenB/TokenA test getCancelFee:: SELL",
CancelFeeArg{
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
order: &tradingstate.OrderItem{
BaseToken: testTokenB,
QuoteToken: testTokenA,
Quantity: new(big.Int).SetUint64(10000),
Side: tradingstate.Ask,
},
},
new(big.Int).Exp(big.NewInt(10), big.NewInt(4), nil),
},
// test getCancelFee:: BUY
{
"TokenB/TokenA test getCancelFee:: BUY",
CancelFeeArg{
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
order: &tradingstate.OrderItem{
Quantity: new(big.Int).SetUint64(10000),
BaseToken: testTokenB,
QuoteToken: testTokenA,
Side: tradingstate.Bid,
},
},
common.RelayerCancelFee,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got, _ := XDCx.getCancelFee(nil, nil, tradingStateDb, tt.args.order, tt.args.feeRate); !reflect.DeepEqual(got, tt.want) {
t.Errorf("getCancelFee() = %v, quantity %v", got, tt.want)
}
})
}
// testcase: can't get price of token in XDC
testTokenC := common.HexToAddress("0x1200000000000000000000000000000000000004")
XDCx.SetTokenDecimal(testTokenC, big.NewInt(1))
tokenCOrder := CancelFeeArg{
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
order: &tradingstate.OrderItem{
Quantity: new(big.Int).SetUint64(10000),
BaseToken: testTokenC,
QuoteToken: testTokenA,
Side: tradingstate.Ask,
},
}
if fee, _ := XDCx.getCancelFee(nil, nil, tradingStateDb, tokenCOrder.order, tokenCOrder.feeRate); fee != nil && fee.Sign() != 0 {
t.Errorf("getCancelFee() = %v, want %v", fee, common.Big0)
}
// testcase: invalid token decimal
testTokenD := common.HexToAddress("0x1300000000000000000000000000000000000005")
XDCx.SetTokenDecimal(testTokenD, big.NewInt(0))
tokenDOrder := CancelFeeArg{
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
order: &tradingstate.OrderItem{
Quantity: new(big.Int).SetUint64(10000),
BaseToken: testTokenD,
QuoteToken: testTokenA,
Side: tradingstate.Ask,
},
}
if fee, _ := XDCx.getCancelFee(nil, nil, tradingStateDb, tokenDOrder.order, tokenDOrder.feeRate); fee != nil && fee.Sign() != 0 {
t.Errorf("getCancelFee() = %v, want %v", fee, common.Big0)
}
}
func TestGetTradeQuantity(t *testing.T) {
type GetTradeQuantityArg struct {
takerSide string
takerFeeRate *big.Int
takerBalance *big.Int
makerPrice *big.Int
makerFeeRate *big.Int
makerBalance *big.Int
baseTokenDecimal *big.Int
quantityToTrade *big.Int
}
tests := []struct {
name string
args GetTradeQuantityArg
quantity *big.Int
rejectMaker bool
}{
{
"BUY: feeRate = 0, price 1, quantity 1000, taker balance 1000, maker balance 1000",
GetTradeQuantityArg{
takerSide: tradingstate.Bid,
takerFeeRate: common.Big0,
takerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
makerPrice: common.BasePrice,
makerFeeRate: common.Big0,
makerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
baseTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
},
new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
false,
},
{
"BUY: feeRate = 0, price 1, quantity 1000, taker balance 1000, maker balance 900 -> reject maker",
GetTradeQuantityArg{
takerSide: tradingstate.Bid,
takerFeeRate: common.Big0,
takerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
makerPrice: common.BasePrice,
makerFeeRate: common.Big0,
makerBalance: new(big.Int).Mul(big.NewInt(900), common.BasePrice),
baseTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
},
new(big.Int).Mul(big.NewInt(900), common.BasePrice),
true,
},
{
"BUY: feeRate = 0, price 1, quantity 1000, taker balance 900, maker balance 1000 -> reject taker",
GetTradeQuantityArg{
takerSide: tradingstate.Bid,
takerFeeRate: common.Big0,
takerBalance: new(big.Int).Mul(big.NewInt(900), common.BasePrice),
makerPrice: common.BasePrice,
makerFeeRate: common.Big0,
makerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
baseTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
},
new(big.Int).Mul(big.NewInt(900), common.BasePrice),
false,
},
{
"BUY: feeRate = 0, price 1, quantity 1000, taker balance 0, maker balance 1000 -> reject taker",
GetTradeQuantityArg{
takerSide: tradingstate.Bid,
takerFeeRate: common.Big0,
takerBalance: common.Big0,
makerPrice: common.BasePrice,
makerFeeRate: common.Big0,
makerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
baseTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
},
common.Big0,
false,
},
{
"BUY: feeRate = 0, price 1, quantity 1000, taker balance 0, maker balance 0 -> reject both taker",
GetTradeQuantityArg{
takerSide: tradingstate.Bid,
takerFeeRate: common.Big0,
takerBalance: common.Big0,
makerPrice: common.BasePrice,
makerFeeRate: common.Big0,
makerBalance: common.Big0,
baseTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
},
common.Big0,
false,
},
{
"BUY: feeRate = 0, price 1, quantity 1000, taker balance 500, maker balance 100 -> reject both taker, maker",
GetTradeQuantityArg{
takerSide: tradingstate.Bid,
takerFeeRate: common.Big0,
takerBalance: new(big.Int).Mul(big.NewInt(500), common.BasePrice),
makerPrice: common.BasePrice,
makerFeeRate: common.Big0,
makerBalance: new(big.Int).Mul(big.NewInt(100), common.BasePrice),
baseTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
},
new(big.Int).Mul(big.NewInt(100), common.BasePrice),
true,
},
{
"SELL: feeRate = 0, price 1, quantity 1000, taker balance 1000, maker balance 1000",
GetTradeQuantityArg{
takerSide: tradingstate.Ask,
takerFeeRate: common.Big0,
takerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
makerPrice: common.BasePrice,
makerFeeRate: common.Big0,
makerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
baseTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
},
new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
false,
},
{
"SELL: feeRate = 0, price 1, quantity 1000, taker balance 1000, maker balance 900 -> reject maker",
GetTradeQuantityArg{
takerSide: tradingstate.Ask,
takerFeeRate: common.Big0,
takerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
makerPrice: common.BasePrice,
makerFeeRate: common.Big0,
makerBalance: new(big.Int).Mul(big.NewInt(900), common.BasePrice),
baseTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
},
new(big.Int).Mul(big.NewInt(900), common.BasePrice),
true,
},
{
"SELL: feeRate = 0, price 1, quantity 1000, taker balance 900, maker balance 1000 -> reject taker",
GetTradeQuantityArg{
takerSide: tradingstate.Ask,
takerFeeRate: common.Big0,
takerBalance: new(big.Int).Mul(big.NewInt(900), common.BasePrice),
makerPrice: common.BasePrice,
makerFeeRate: common.Big0,
makerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
baseTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
},
new(big.Int).Mul(big.NewInt(900), common.BasePrice),
false,
},
{
"SELL: feeRate = 0, price 1, quantity 1000, taker balance 0, maker balance 1000 -> reject taker",
GetTradeQuantityArg{
takerSide: tradingstate.Ask,
takerFeeRate: common.Big0,
takerBalance: common.Big0,
makerPrice: common.BasePrice,
makerFeeRate: common.Big0,
makerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
baseTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
},
common.Big0,
false,
},
{
"SELL: feeRate = 0, price 1, quantity 1000, taker balance 0, maker balance 0 -> reject maker",
GetTradeQuantityArg{
takerSide: tradingstate.Ask,
takerFeeRate: common.Big0,
takerBalance: common.Big0,
makerPrice: common.BasePrice,
makerFeeRate: common.Big0,
makerBalance: common.Big0,
baseTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
},
common.Big0,
true,
},
{
"SELL: feeRate = 0, price 1, quantity 1000, taker balance 500, maker balance 100 -> reject both taker, maker",
GetTradeQuantityArg{
takerSide: tradingstate.Ask,
takerFeeRate: common.Big0,
takerBalance: new(big.Int).Mul(big.NewInt(500), common.BasePrice),
makerPrice: common.BasePrice,
makerFeeRate: common.Big0,
makerBalance: new(big.Int).Mul(big.NewInt(100), common.BasePrice),
baseTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
},
new(big.Int).Mul(big.NewInt(100), common.BasePrice),
true,
},
{
"SELL: feeRate = 0, price 1, quantity 1000, taker balance 0, maker balance 100 -> reject both taker, maker",
GetTradeQuantityArg{
takerSide: tradingstate.Ask,
takerFeeRate: common.Big0,
takerBalance: common.Big0,
makerPrice: common.BasePrice,
makerFeeRate: common.Big0,
makerBalance: new(big.Int).Mul(big.NewInt(100), common.BasePrice),
baseTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
},
common.Big0,
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1 := GetTradeQuantity(tt.args.takerSide, tt.args.takerFeeRate, tt.args.takerBalance, tt.args.makerPrice, tt.args.makerFeeRate, tt.args.makerBalance, tt.args.baseTokenDecimal, tt.args.quantityToTrade)
if !reflect.DeepEqual(got, tt.quantity) {
t.Errorf("GetTradeQuantity() got = %v, quantity %v", got, tt.quantity)
}
if got1 != tt.rejectMaker {
t.Errorf("GetTradeQuantity() got1 = %v, quantity %v", got1, tt.rejectMaker)
}
})
}
}

78
XDCx/token.go Normal file
View file

@ -0,0 +1,78 @@
package XDCx
import (
"github.com/XinFinOrg/XDPoSChain/contracts/XDCx/contract"
"github.com/XinFinOrg/XDPoSChain/log"
"math/big"
"strings"
"github.com/XinFinOrg/XDPoSChain"
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/core/state"
)
// GetTokenAbi return token abi
func GetTokenAbi() (*abi.ABI, error) {
contractABI, err := abi.JSON(strings.NewReader(contract.TRC21ABI))
if err != nil {
return nil, err
}
return &contractABI, nil
}
// RunContract run smart contract
func RunContract(chain consensus.ChainContext, statedb *state.StateDB, contractAddr common.Address, abi *abi.ABI, method string, args ...interface{}) (interface{}, error) {
input, err := abi.Pack(method)
if err != nil {
return nil, err
}
backend := (*backends.SimulatedBackend)(nil)
fakeCaller := common.HexToAddress("0x0000000000000000000000000000000000000001")
msg := XDPoSChain.CallMsg{To: &contractAddr, Data: input, From: fakeCaller}
result, err := backend.CallContractWithState(msg, chain, statedb)
if err != nil {
return nil, err
}
var unpackResult interface{}
err = abi.Unpack(&unpackResult, method, result)
if err != nil {
return nil, err
}
return unpackResult, nil
}
func (XDCx *XDCX) GetTokenDecimal(chain consensus.ChainContext, statedb *state.StateDB, tokenAddr common.Address) (*big.Int, error) {
if tokenDecimal, ok := XDCx.tokenDecimalCache.Get(tokenAddr); ok {
return tokenDecimal.(*big.Int), nil
}
if tokenAddr.String() == common.XDCNativeAddress {
XDCx.tokenDecimalCache.Add(tokenAddr, common.BasePrice)
return common.BasePrice, nil
}
var decimals uint8
defer func() {
log.Debug("GetTokenDecimal from ", "relayerSMC", common.RelayerRegistrationSMC, "tokenAddr", tokenAddr.Hex(), "decimals", decimals)
}()
contractABI, err := GetTokenAbi()
if err != nil {
return nil, err
}
stateCopy := statedb.Copy()
result, err := RunContract(chain, stateCopy, tokenAddr, contractABI, "decimals")
if err != nil {
return nil, err
}
decimals = result.(uint8)
tokenDecimal := new(big.Int).SetUint64(0).Exp(big.NewInt(10), big.NewInt(int64(decimals)), nil)
XDCx.tokenDecimalCache.Add(tokenAddr, tokenDecimal)
return tokenDecimal, nil
}
// FIXME: using in unit tests only
func (XDCx *XDCX) SetTokenDecimal(token common.Address, decimal *big.Int) {
XDCx.tokenDecimalCache.Add(token, decimal)
}

View file

@ -0,0 +1,221 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package tradingstate
import (
"fmt"
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/trie"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/log"
)
// XDCXTrie wraps a trie with key hashing. In a secure trie, all
// access operations hash the key using keccak256. This prevents
// calling code from creating long chains of nodes that
// increase the access time.
//
// Contrary to a regular trie, a XDCXTrie can only be created with
// New and must have an attached database. The database also stores
// the preimage of each key.
//
// XDCXTrie is not safe for concurrent use.
type XDCXTrie struct {
trie trie.Trie
hashKeyBuf [common.HashLength]byte
secKeyCache map[string][]byte
secKeyCacheOwner *XDCXTrie // Pointer to self, replace the key cache on mismatch
}
// NewXDCXTrie creates a trie with an existing root node from a backing database
// and optional intermediate in-memory node pool.
//
// If root is the zero hash or the sha3 hash of an empty string, the
// trie is initially empty. Otherwise, New will panic if db is nil
// and returns MissingNodeError if the root node cannot be found.
//
// Accessing the trie loads nodes from the database or node pool on demand.
// Loaded nodes are kept around until their 'cache generation' expires.
// A new cache generation is created by each call to Commit.
// cachelimit sets the number of past cache generations to keep.
func NewXDCXTrie(root common.Hash, db *trie.Database) (*XDCXTrie, error) {
if db == nil {
panic("trie.NewXDCXTrie called without a database")
}
trie, err := trie.New(root, db)
if err != nil {
return nil, err
}
return &XDCXTrie{trie: *trie}, nil
}
// Get returns the value for key stored in the trie.
// The value bytes must not be modified by the caller.
func (t *XDCXTrie) Get(key []byte) []byte {
res, err := t.TryGet(key)
if err != nil {
log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
}
return res
}
// TryGet returns the value for key stored in the trie.
// The value bytes must not be modified by the caller.
// If a node was not found in the database, a MissingNodeError is returned.
func (t *XDCXTrie) TryGet(key []byte) ([]byte, error) {
return t.trie.TryGet(key)
}
// TryGetBestLeftKey returns the value of max left leaf
// If a node was not found in the database, a MissingNodeError is returned.
func (t *XDCXTrie) TryGetBestLeftKeyAndValue() ([]byte, []byte, error) {
return t.trie.TryGetBestLeftKeyAndValue()
}
func (t *XDCXTrie) TryGetAllLeftKeyAndValue(limit []byte) ([][]byte, [][]byte, error) {
return t.trie.TryGetAllLeftKeyAndValue(limit)
}
// TryGetBestRightKey returns the value of max left leaf
// If a node was not found in the database, a MissingNodeError is returned.
func (t *XDCXTrie) TryGetBestRightKeyAndValue() ([]byte, []byte, error) {
return t.trie.TryGetBestRightKeyAndValue()
}
// Update associates key with value in the trie. Subsequent calls to
// Get will return value. If value has length zero, any existing value
// is deleted from the trie and calls to Get will return nil.
//
// The value bytes must not be modified by the caller while they are
// stored in the trie.
func (t *XDCXTrie) Update(key, value []byte) {
if err := t.TryUpdate(key, value); err != nil {
log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
}
}
// TryUpdate associates key with value in the trie. Subsequent calls to
// Get will return value. If value has length zero, any existing value
// is deleted from the trie and calls to Get will return nil.
//
// The value bytes must not be modified by the caller while they are
// stored in the trie.
//
// If a node was not found in the database, a MissingNodeError is returned.
func (t *XDCXTrie) TryUpdate(key, value []byte) error {
err := t.trie.TryUpdate(key, value)
if err != nil {
return err
}
t.getSecKeyCache()[string(key)] = common.CopyBytes(key)
return nil
}
// Delete removes any existing value for key from the trie.
func (t *XDCXTrie) Delete(key []byte) {
if err := t.TryDelete(key); err != nil {
log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
}
}
// TryDelete removes any existing value for key from the trie.
// If a node was not found in the database, a MissingNodeError is returned.
func (t *XDCXTrie) TryDelete(key []byte) error {
delete(t.getSecKeyCache(), string(key))
return t.trie.TryDelete(key)
}
// GetKey returns the sha3 preimage of a hashed key that was
// previously used to store a value.
func (t *XDCXTrie) GetKey(shaKey []byte) []byte {
if key, ok := t.getSecKeyCache()[string(shaKey)]; ok {
return key
}
key, _ := t.trie.Db.Preimage(common.BytesToHash(shaKey))
return key
}
// Commit writes all nodes and the secure hash pre-images to the trie's database.
// Nodes are stored with their sha3 hash as the key.
//
// Committing flushes nodes from memory. Subsequent Get calls will load nodes
// from the database.
func (t *XDCXTrie) Commit(onleaf trie.LeafCallback) (root common.Hash, err error) {
// Write all the pre-images to the actual disk database
if len(t.getSecKeyCache()) > 0 {
t.trie.Db.Lock.Lock()
for hk, key := range t.secKeyCache {
t.trie.Db.InsertPreimage(common.BytesToHash([]byte(hk)), key)
}
t.trie.Db.Lock.Unlock()
t.secKeyCache = make(map[string][]byte)
}
// Commit the trie to its intermediate node database
return t.trie.Commit(onleaf)
}
func (t *XDCXTrie) Hash() common.Hash {
return t.trie.Hash()
}
func (t *XDCXTrie) Copy() *XDCXTrie {
cpy := *t
return &cpy
}
// NodeIterator returns an iterator that returns nodes of the underlying trie. Iteration
// starts at the key after the given start key.
func (t *XDCXTrie) NodeIterator(start []byte) trie.NodeIterator {
return t.trie.NodeIterator(start)
}
// hashKey returns the hash of key as an ephemeral buffer.
// The caller must not hold onto the return value because it will become
// invalid on the next call to hashKey or secKey.
//func (t *XDCXTrie) hashKey(key []byte) []byte {
// h := newHasher(0, 0, nil)
// h.sha.Reset()
// h.sha.Write(key)
// buf := h.sha.Sum(t.hashKeyBuf[:0])
// returnHasherToPool(h)
// return buf
//}
// getSecKeyCache returns the current secure key cache, creating a new one if
// ownership changed (i.e. the current secure trie is a copy of another owning
// the actual cache).
func (t *XDCXTrie) getSecKeyCache() map[string][]byte {
if t != t.secKeyCacheOwner {
t.secKeyCacheOwner = t
t.secKeyCache = make(map[string][]byte)
}
return t.secKeyCache
}
// Prove constructs a merkle proof for key. The result contains all encoded nodes
// on the path to the value at key. The value itself is also included in the last
// node and can be retrieved by verifying the proof.
//
// If the trie does not contain a value for key, the returned proof contains all
// nodes of the longest existing prefix of the key (at least the root node), ending
// with the node that proves the absence of the key.
func (t *XDCXTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error {
return t.trie.Prove(key, fromLevel, proofDb)
}

View file

@ -0,0 +1,47 @@
package tradingstate
import (
"fmt"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"math/big"
"math/rand"
"testing"
"time"
)
func TestXDCxTrieTest(t *testing.T) {
t.SkipNow()
db := rawdb.NewMemoryDatabase()
stateCache := NewDatabase(db)
trie, _ := stateCache.OpenStorageTrie(EmptyHash, EmptyHash)
max := 1000000
for i := 1; i < max; i++ {
k := common.BigToHash(big.NewInt(int64(i))).Bytes()
trie.TryUpdate(k, k)
}
left, _, _ := trie.TryGetBestLeftKeyAndValue()
right, _, _ := trie.TryGetBestRightKeyAndValue()
fmt.Println(left, right)
for i := 0; i < 100; i++ {
limit := big.NewInt(rand.Int63n(int64(max / 10)))
fmt.Println(time.Now().Format("2006-01-02 15:04:05"), "test", i, limit)
allKeyLefts, allValues, err := trie.TryGetAllLeftKeyAndValue(common.BigToHash(limit).Bytes())
if err != nil {
t.Fatal("err", err)
}
if len(allKeyLefts) != int(limit.Int64())-1 {
t.Fatal("err when length", len(allKeyLefts), "limit", limit)
}
for j := 0; j < len(allKeyLefts); j++ {
key := new(big.Int).SetBytes(allKeyLefts[j])
value := new(big.Int).SetBytes(allValues[j])
if key.Cmp(value) != 0 {
t.Fatal("err when compare key", key, "value", value)
}
if key.Cmp(limit) >= 0 {
t.Fatal("err when compare key", key, "limit", limit)
}
}
}
}

220
XDCx/tradingstate/common.go Normal file
View file

@ -0,0 +1,220 @@
package tradingstate
import (
"encoding/json"
"errors"
"math/big"
"time"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/common"
)
const (
OrderCacheLimit = 10000
)
var (
EmptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
Ask = "SELL"
Bid = "BUY"
Market = "MO"
Limit = "LO"
Cancel = "CANCELLED"
OrderNew = "NEW"
)
var EmptyHash = common.Hash{}
var Zero = big.NewInt(0)
var One = big.NewInt(1)
var EmptyOrderList = orderList{
Volume: nil,
Root: EmptyHash,
}
var EmptyExchangeOnject = tradingExchangeObject{
Nonce: 0,
AskRoot: EmptyHash,
BidRoot: EmptyHash,
}
var EmptyOrder = OrderItem{
Quantity: Zero,
}
var (
ErrInvalidSignature = errors.New("verify order: invalid signature")
ErrInvalidPrice = errors.New("verify order: invalid price")
ErrInvalidQuantity = errors.New("verify order: invalid quantity")
ErrInvalidRelayer = errors.New("verify order: invalid relayer")
ErrInvalidOrderType = errors.New("verify order: unsupported order type")
ErrInvalidOrderSide = errors.New("verify order: invalid order side")
ErrInvalidStatus = errors.New("verify order: invalid status")
// supported order types
MatchingOrderType = map[string]bool{
Market: true,
Limit: true,
}
)
// tradingExchangeObject is the Ethereum consensus representation of exchanges.
// These objects are stored in the main orderId trie.
type orderList struct {
Volume *big.Int
Root common.Hash // merkle root of the storage trie
}
// tradingExchangeObject is the Ethereum consensus representation of exchanges.
// These objects are stored in the main orderId trie.
type tradingExchangeObject struct {
Nonce uint64
LastPrice *big.Int
MediumPriceBeforeEpoch *big.Int
MediumPrice *big.Int
TotalQuantity *big.Int
LendingCount *big.Int
AskRoot common.Hash // merkle root of the storage trie
BidRoot common.Hash // merkle root of the storage trie
OrderRoot common.Hash
LiquidationPriceRoot common.Hash
}
var (
TokenMappingSlot = map[string]uint64{
"balances": 0,
}
RelayerMappingSlot = map[string]uint64{
"CONTRACT_OWNER": 0,
"MaximumRelayers": 1,
"MaximumTokenList": 2,
"RELAYER_LIST": 3,
"RELAYER_COINBASES": 4,
"RESIGN_REQUESTS": 5,
"RELAYER_ON_SALE_LIST": 6,
"RelayerCount": 7,
"MinimumDeposit": 8,
}
RelayerStructMappingSlot = map[string]*big.Int{
"_deposit": big.NewInt(0),
"_fee": big.NewInt(1),
"_fromTokens": big.NewInt(2),
"_toTokens": big.NewInt(3),
"_index": big.NewInt(4),
"_owner": big.NewInt(5),
}
)
type TxDataMatch struct {
Order []byte // serialized data of order has been processed in this tx
}
type TxMatchBatch struct {
Data []TxDataMatch
Timestamp int64
TxHash common.Hash
}
type MatchingResult struct {
Trades []map[string]string
Rejects []*OrderItem
}
func EncodeTxMatchesBatch(txMatchBatch TxMatchBatch) ([]byte, error) {
data, err := json.Marshal(txMatchBatch)
if err != nil || data == nil {
return []byte{}, err
}
return data, nil
}
func DecodeTxMatchesBatch(data []byte) (TxMatchBatch, error) {
txMatchResult := TxMatchBatch{}
if err := json.Unmarshal(data, &txMatchResult); err != nil {
return TxMatchBatch{}, err
}
return txMatchResult, nil
}
// use orderHash instead of orderId
// because both takerOrders don't have orderId
func GetOrderHistoryKey(baseToken, quoteToken common.Address, orderHash common.Hash) common.Hash {
return crypto.Keccak256Hash(baseToken.Bytes(), quoteToken.Bytes(), orderHash.Bytes())
}
func (tx TxDataMatch) DecodeOrder() (*OrderItem, error) {
order := &OrderItem{}
if err := DecodeBytesItem(tx.Order, order); err != nil {
return order, err
}
return order, nil
}
type OrderHistoryItem struct {
TxHash common.Hash
FilledAmount *big.Int
Status string
UpdatedAt time.Time
}
// ToJSON : log json string
func ToJSON(object interface{}, args ...string) string {
var str []byte
if len(args) == 2 {
str, _ = json.MarshalIndent(object, args[0], args[1])
} else {
str, _ = json.Marshal(object)
}
return string(str)
}
func Mul(x, y *big.Int) *big.Int {
return big.NewInt(0).Mul(x, y)
}
func Div(x, y *big.Int) *big.Int {
return big.NewInt(0).Div(x, y)
}
func Add(x, y *big.Int) *big.Int {
return big.NewInt(0).Add(x, y)
}
func Sub(x, y *big.Int) *big.Int {
return big.NewInt(0).Sub(x, y)
}
func Neg(x *big.Int) *big.Int {
return big.NewInt(0).Neg(x)
}
func ToBigInt(s string) *big.Int {
res := big.NewInt(0)
res.SetString(s, 10)
return res
}
func CloneBigInt(bigInt *big.Int) *big.Int {
res := new(big.Int).SetBytes(bigInt.Bytes())
return res
}
func Exp(x, y *big.Int) *big.Int {
return big.NewInt(0).Exp(x, y, nil)
}
func Max(a, b *big.Int) *big.Int {
if a.Cmp(b) == 1 {
return a
} else {
return b
}
}
func GetTradingOrderBookHash(baseToken common.Address, quoteToken common.Address) common.Hash {
return common.BytesToHash(append(baseToken[:16], quoteToken[4:]...))
}
func GetMatchingResultCacheKey(order *OrderItem) common.Hash {
return crypto.Keccak256Hash(order.UserAddress.Bytes(), order.Nonce.Bytes())
}

View file

@ -0,0 +1,43 @@
package tradingstate
import (
"reflect"
"testing"
)
// Testing scenario:
// encode originalTxMatchesBatch -> byteData
// decode byteData -> txMatchesBatch
// compare originalTxMatchesBatch and txMatchesBatch
func TestTxMatchesBatch(t *testing.T) {
originalTxMatchesBatch := []TxDataMatch{
{
Order: []byte("order1"),
},
{
Order: []byte("order2"),
},
{
Order: []byte("order3"),
},
}
encodedData, err := EncodeTxMatchesBatch(TxMatchBatch{
Data: originalTxMatchesBatch,
})
if err != nil {
t.Error("Failed to encode", err.Error())
}
txMatchesBatch, err := DecodeTxMatchesBatch(encodedData)
if err != nil {
t.Error("Failed to decode", err.Error())
}
eq := reflect.DeepEqual(originalTxMatchesBatch, txMatchesBatch.Data)
if eq {
t.Log("Awesome, encode and decode txMatchesBatch are correct")
} else {
t.Error("txMatchesBatch is different from originalTxMatchesBatch", "txMatchesBatch", txMatchesBatch, "originalTxMatchesBatch", originalTxMatchesBatch)
}
}

View file

@ -0,0 +1,140 @@
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package tradingstate
import (
"fmt"
"sync"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/trie"
lru "github.com/hashicorp/golang-lru"
)
// Trie cache generation limit after which to evic trie nodes from memory.
var MaxTrieCacheGen = uint16(120)
const (
// Number of past tries to keep. This value is chosen such that
// reasonable chain reorg depths will hit an existing trie.
maxPastTries = 12
// Number of codehash->size associations to keep.
codeSizeCacheSize = 100000
)
// Database wraps access to tries and contract code.
type Database interface {
// OpenTrie opens the main account trie.
OpenTrie(root common.Hash) (Trie, error)
// OpenStorageTrie opens the storage trie of an account.
OpenStorageTrie(addrHash, root common.Hash) (Trie, error)
// CopyTrie returns an independent copy of the given trie.
CopyTrie(Trie) Trie
// ContractCode retrieves a particular contract's code.
ContractCode(addrHash, codeHash common.Hash) ([]byte, error)
// ContractCodeSize retrieves a particular contracts code's size.
ContractCodeSize(addrHash, codeHash common.Hash) (int, error)
// TrieDB retrieves the low level trie database used for data storage.
TrieDB() *trie.Database
}
// Trie is a Ethereum Merkle Trie.
type Trie interface {
TryGet(key []byte) ([]byte, error)
TryGetBestLeftKeyAndValue() ([]byte, []byte, error)
TryGetAllLeftKeyAndValue(limit []byte) ([][]byte, [][]byte, error)
TryGetBestRightKeyAndValue() ([]byte, []byte, error)
TryUpdate(key, value []byte) error
TryDelete(key []byte) error
Commit(onleaf trie.LeafCallback) (common.Hash, error)
Hash() common.Hash
NodeIterator(startKey []byte) trie.NodeIterator
GetKey([]byte) []byte // TODO(fjl): remove this when XDCXTrie is removed
Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error
}
// NewDatabase creates a backing store for state. The returned database is safe for
// concurrent use and retains cached trie nodes in memory. The pool is an optional
// intermediate trie-node memory pool between the low level storage layer and the
// high level trie abstraction.
func NewDatabase(db ethdb.Database) Database {
csc, _ := lru.New(codeSizeCacheSize)
return &cachingDB{
db: trie.NewDatabase(db),
codeSizeCache: csc,
}
}
type cachingDB struct {
db *trie.Database
mu sync.Mutex
pastTries []*XDCXTrie
codeSizeCache *lru.Cache
}
// OpenTrie opens the main account trie.
func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
return NewXDCXTrie(root, db.db)
}
func (db *cachingDB) pushTrie(t *XDCXTrie) {
db.mu.Lock()
defer db.mu.Unlock()
if len(db.pastTries) >= maxPastTries {
copy(db.pastTries, db.pastTries[1:])
db.pastTries[len(db.pastTries)-1] = t
} else {
db.pastTries = append(db.pastTries, t)
}
}
// OpenStorageTrie opens the storage trie of an account.
func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) {
return NewXDCXTrie(root, db.db)
}
// CopyTrie returns an independent copy of the given trie.
func (db *cachingDB) CopyTrie(t Trie) Trie {
switch t := t.(type) {
case *XDCXTrie:
return t.Copy()
default:
panic(fmt.Errorf("unknown trie type %T", t))
}
}
// ContractCode retrieves a particular contract's code.
func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) {
return nil, nil
}
// ContractCodeSize retrieves a particular contracts code's size.
func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, error) {
return 0, nil
}
// TrieDB retrieves any intermediate trie-node caching layer.
func (db *cachingDB) TrieDB() *trie.Database {
return db.db
}

384
XDCx/tradingstate/dump.go Normal file
View file

@ -0,0 +1,384 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package tradingstate
import (
"fmt"
"math/big"
"sort"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/rlp"
"github.com/XinFinOrg/XDPoSChain/trie"
)
type DumpOrderList struct {
Volume *big.Int
Orders map[*big.Int]*big.Int
}
type DumpLendingBook struct {
Volume *big.Int
LendingBooks map[common.Hash]DumpOrderList
}
type DumpOrderBookInfo struct {
LastPrice *big.Int
LendingCount *big.Int
MediumPrice *big.Int
MediumPriceBeforeEpoch *big.Int
Nonce uint64
TotalQuantity *big.Int
BestAsk *big.Int
BestBid *big.Int
LowestLiquidationPrice *big.Int
}
func (self *TradingStateDB) DumpAskTrie(orderBook common.Hash) (map[*big.Int]DumpOrderList, error) {
exhangeObject := self.getStateExchangeObject(orderBook)
if exhangeObject == nil {
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
}
mapResult := map[*big.Int]DumpOrderList{}
it := trie.NewIterator(exhangeObject.getAsksTrie(self.db).NodeIterator(nil))
for it.Next() {
priceHash := common.BytesToHash(it.Key)
if common.EmptyHash(priceHash) {
continue
}
price := new(big.Int).SetBytes(priceHash.Bytes())
if _, exist := exhangeObject.stateAskObjects[priceHash]; exist {
continue
} else {
var data orderList
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,price :%v ", orderBook.Hex(), price)
}
stateOrderList := newStateOrderList(self, Ask, orderBook, priceHash, data, nil)
mapResult[price] = stateOrderList.DumpOrderList(self.db)
}
}
for priceHash, stateOrderList := range exhangeObject.stateAskObjects {
if stateOrderList.Volume().Sign() > 0 {
mapResult[new(big.Int).SetBytes(priceHash.Bytes())] = stateOrderList.DumpOrderList(self.db)
}
}
listPrice := []*big.Int{}
for price := range mapResult {
listPrice = append(listPrice, price)
}
sort.Slice(listPrice, func(i, j int) bool {
return listPrice[i].Cmp(listPrice[j]) < 0
})
result := map[*big.Int]DumpOrderList{}
for _, price := range listPrice {
result[price] = mapResult[price]
}
return result, nil
}
func (self *TradingStateDB) DumpBidTrie(orderBook common.Hash) (map[*big.Int]DumpOrderList, error) {
exhangeObject := self.getStateExchangeObject(orderBook)
if exhangeObject == nil {
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
}
mapResult := map[*big.Int]DumpOrderList{}
it := trie.NewIterator(exhangeObject.getBidsTrie(self.db).NodeIterator(nil))
for it.Next() {
priceHash := common.BytesToHash(it.Key)
if common.EmptyHash(priceHash) {
continue
}
price := new(big.Int).SetBytes(priceHash.Bytes())
if _, exist := exhangeObject.stateBidObjects[priceHash]; exist {
continue
} else {
var data orderList
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,price :%v ", orderBook.Hex(), price)
}
stateOrderList := newStateOrderList(self, Bid, orderBook, priceHash, data, nil)
mapResult[price] = stateOrderList.DumpOrderList(self.db)
}
}
for priceHash, stateOrderList := range exhangeObject.stateBidObjects {
if stateOrderList.Volume().Sign() > 0 {
mapResult[new(big.Int).SetBytes(priceHash.Bytes())] = stateOrderList.DumpOrderList(self.db)
}
}
listPrice := []*big.Int{}
for price := range mapResult {
listPrice = append(listPrice, price)
}
sort.Slice(listPrice, func(i, j int) bool {
return listPrice[i].Cmp(listPrice[j]) < 0
})
result := map[*big.Int]DumpOrderList{}
for _, price := range listPrice {
result[price] = mapResult[price]
}
return mapResult, nil
}
func (self *TradingStateDB) GetBids(orderBook common.Hash) (map[*big.Int]*big.Int, error) {
exhangeObject := self.getStateExchangeObject(orderBook)
if exhangeObject == nil {
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
}
mapResult := map[*big.Int]*big.Int{}
it := trie.NewIterator(exhangeObject.getBidsTrie(self.db).NodeIterator(nil))
for it.Next() {
priceHash := common.BytesToHash(it.Key)
if common.EmptyHash(priceHash) {
continue
}
price := new(big.Int).SetBytes(priceHash.Bytes())
if _, exist := exhangeObject.stateBidObjects[priceHash]; exist {
continue
} else {
var data orderList
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,price :%v ", orderBook.Hex(), price)
}
stateOrderList := newStateOrderList(self, Bid, orderBook, priceHash, data, nil)
mapResult[price] = stateOrderList.data.Volume
}
}
for priceHash, stateOrderList := range exhangeObject.stateBidObjects {
if stateOrderList.Volume().Sign() > 0 {
mapResult[new(big.Int).SetBytes(priceHash.Bytes())] = stateOrderList.data.Volume
}
}
listPrice := []*big.Int{}
for price := range mapResult {
listPrice = append(listPrice, price)
}
sort.Slice(listPrice, func(i, j int) bool {
return listPrice[i].Cmp(listPrice[j]) < 0
})
result := map[*big.Int]*big.Int{}
for _, price := range listPrice {
result[price] = mapResult[price]
}
return mapResult, nil
}
func (self *TradingStateDB) GetAsks(orderBook common.Hash) (map[*big.Int]*big.Int, error) {
exhangeObject := self.getStateExchangeObject(orderBook)
if exhangeObject == nil {
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
}
mapResult := map[*big.Int]*big.Int{}
it := trie.NewIterator(exhangeObject.getAsksTrie(self.db).NodeIterator(nil))
for it.Next() {
priceHash := common.BytesToHash(it.Key)
if common.EmptyHash(priceHash) {
continue
}
price := new(big.Int).SetBytes(priceHash.Bytes())
if _, exist := exhangeObject.stateAskObjects[priceHash]; exist {
continue
} else {
var data orderList
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,price :%v ", orderBook.Hex(), price)
}
stateOrderList := newStateOrderList(self, Ask, orderBook, priceHash, data, nil)
mapResult[price] = stateOrderList.data.Volume
}
}
for priceHash, stateOrderList := range exhangeObject.stateAskObjects {
if stateOrderList.Volume().Sign() > 0 {
mapResult[new(big.Int).SetBytes(priceHash.Bytes())] = stateOrderList.data.Volume
}
}
listPrice := []*big.Int{}
for price := range mapResult {
listPrice = append(listPrice, price)
}
sort.Slice(listPrice, func(i, j int) bool {
return listPrice[i].Cmp(listPrice[j]) < 0
})
result := map[*big.Int]*big.Int{}
for _, price := range listPrice {
result[price] = mapResult[price]
}
return result, nil
}
func (self *stateOrderList) DumpOrderList(db Database) DumpOrderList {
mapResult := DumpOrderList{Volume: self.Volume(), Orders: map[*big.Int]*big.Int{}}
orderListIt := trie.NewIterator(self.getTrie(db).NodeIterator(nil))
for orderListIt.Next() {
keyHash := common.BytesToHash(orderListIt.Key)
if common.EmptyHash(keyHash) {
continue
}
if _, exist := self.cachedStorage[keyHash]; exist {
continue
} else {
_, content, _, _ := rlp.Split(orderListIt.Value)
mapResult.Orders[new(big.Int).SetBytes(keyHash.Bytes())] = new(big.Int).SetBytes(content)
}
}
for key, value := range self.cachedStorage {
if !common.EmptyHash(value) {
mapResult.Orders[new(big.Int).SetBytes(key.Bytes())] = new(big.Int).SetBytes(value.Bytes())
}
}
listIds := []*big.Int{}
for id := range mapResult.Orders {
listIds = append(listIds, id)
}
sort.Slice(listIds, func(i, j int) bool {
return listIds[i].Cmp(listIds[j]) < 0
})
result := DumpOrderList{Volume: self.Volume(), Orders: map[*big.Int]*big.Int{}}
for _, id := range listIds {
result.Orders[id] = mapResult.Orders[id]
}
return mapResult
}
func (self *TradingStateDB) DumpOrderBookInfo(orderBook common.Hash) (*DumpOrderBookInfo, error) {
exhangeObject := self.getStateExchangeObject(orderBook)
if exhangeObject == nil {
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
}
result := &DumpOrderBookInfo{}
result.LastPrice = exhangeObject.data.LastPrice
result.LendingCount = exhangeObject.data.LendingCount
result.MediumPrice = exhangeObject.data.MediumPrice
result.MediumPriceBeforeEpoch = exhangeObject.data.MediumPriceBeforeEpoch
result.Nonce = exhangeObject.data.Nonce
result.TotalQuantity = exhangeObject.data.TotalQuantity
result.BestAsk = new(big.Int).SetBytes(exhangeObject.getBestPriceAsksTrie(self.db).Bytes())
result.BestBid = new(big.Int).SetBytes(exhangeObject.getBestBidsTrie(self.db).Bytes())
lowestPrice, _ := exhangeObject.getLowestLiquidationPrice(self.db)
result.LowestLiquidationPrice = new(big.Int).SetBytes(lowestPrice.Bytes())
return result, nil
}
func (self *stateLendingBook) DumpOrderList(db Database) DumpOrderList {
mapResult := DumpOrderList{Volume: self.Volume(), Orders: map[*big.Int]*big.Int{}}
orderListIt := trie.NewIterator(self.getTrie(db).NodeIterator(nil))
for orderListIt.Next() {
keyHash := common.BytesToHash(orderListIt.Key)
if common.EmptyHash(keyHash) {
continue
}
if _, exist := self.cachedStorage[keyHash]; exist {
continue
} else {
_, content, _, _ := rlp.Split(orderListIt.Value)
mapResult.Orders[new(big.Int).SetBytes(keyHash.Bytes())] = new(big.Int).SetBytes(content)
}
}
for key, value := range self.cachedStorage {
if !common.EmptyHash(value) {
mapResult.Orders[new(big.Int).SetBytes(key.Bytes())] = new(big.Int).SetBytes(value.Bytes())
}
}
listIds := []*big.Int{}
for id := range mapResult.Orders {
listIds = append(listIds, id)
}
sort.Slice(listIds, func(i, j int) bool {
return listIds[i].Cmp(listIds[j]) < 0
})
result := DumpOrderList{Volume: self.Volume(), Orders: map[*big.Int]*big.Int{}}
for _, id := range listIds {
result.Orders[id] = mapResult.Orders[id]
}
return mapResult
}
func (self *liquidationPriceState) DumpLendingBook(db Database) (DumpLendingBook, error) {
result := DumpLendingBook{Volume: self.Volume(), LendingBooks: map[common.Hash]DumpOrderList{}}
it := trie.NewIterator(self.getTrie(db).NodeIterator(nil))
for it.Next() {
lendingBook := common.BytesToHash(it.Key)
if common.EmptyHash(lendingBook) {
continue
}
if _, exist := self.stateLendingBooks[lendingBook]; exist {
continue
} else {
var data orderList
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
return result, fmt.Errorf("Failed to decode state lending book orderbook : %s ,liquidation price :%s , lendingBook : %s ,err : %v", self.orderBook, self.liquidationPrice, lendingBook, err)
}
stateLendingBook := newStateLendingBook(self.orderBook, self.liquidationPrice, lendingBook, data, nil)
result.LendingBooks[lendingBook] = stateLendingBook.DumpOrderList(db)
}
}
for lendingBook, stateLendingBook := range self.stateLendingBooks {
if !common.EmptyHash(lendingBook) {
result.LendingBooks[lendingBook] = stateLendingBook.DumpOrderList(db)
}
}
return result, nil
}
func (self *TradingStateDB) DumpLiquidationPriceTrie(orderBook common.Hash) (map[*big.Int]DumpLendingBook, error) {
exhangeObject := self.getStateExchangeObject(orderBook)
if exhangeObject == nil {
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
}
mapResult := map[*big.Int]DumpLendingBook{}
it := trie.NewIterator(exhangeObject.getLiquidationPriceTrie(self.db).NodeIterator(nil))
for it.Next() {
priceHash := common.BytesToHash(it.Key)
if common.EmptyHash(priceHash) {
continue
}
price := new(big.Int).SetBytes(priceHash.Bytes())
if _, exist := exhangeObject.liquidationPriceStates[priceHash]; exist {
continue
} else {
var data orderList
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,price :%v ", orderBook.Hex(), price)
}
liquidationPriceState := newLiquidationPriceState(self, orderBook, priceHash, data, nil)
dumpLendingBook, err := liquidationPriceState.DumpLendingBook(self.db)
if err != nil {
return nil, err
}
mapResult[price] = dumpLendingBook
}
}
for priceHash, liquidationPriceState := range exhangeObject.liquidationPriceStates {
if liquidationPriceState.Volume().Sign() > 0 {
dumpLendingBook, err := liquidationPriceState.DumpLendingBook(self.db)
if err != nil {
return nil, err
}
mapResult[new(big.Int).SetBytes(priceHash.Bytes())] = dumpLendingBook
}
}
listPrice := []*big.Int{}
for price := range mapResult {
listPrice = append(listPrice, price)
}
sort.Slice(listPrice, func(i, j int) bool {
return listPrice[i].Cmp(listPrice[j]) < 0
})
result := map[*big.Int]DumpLendingBook{}
for _, price := range listPrice {
result[price] = mapResult[price]
}
return mapResult, nil
}

View file

@ -0,0 +1,14 @@
package tradingstate
import (
"github.com/XinFinOrg/XDPoSChain/rlp"
)
func EncodeBytesItem(val interface{}) ([]byte, error) {
return rlp.EncodeToBytes(val)
}
func DecodeBytesItem(bytes []byte, val interface{}) error {
return rlp.DecodeBytes(bytes, val)
}

View file

@ -0,0 +1,58 @@
package tradingstate
import (
"fmt"
"math/big"
"strconv"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/globalsign/mgo/bson"
)
type EpochPriceItem struct {
Epoch uint64 `bson:"epoch" json:"epoch"`
Orderbook common.Hash `bson:"orderbook" json:"orderbook"`
Hash common.Hash `bson:"hash" json:"hash"`
Price *big.Int `bson:"price" json:"price"`
}
type EpochPriceItemBSON struct {
Epoch string `bson:"epoch" json:"epoch"`
Orderbook string `bson:"orderbook" json:"orderbook"`
Hash string `bson:"hash" json:"hash"` // Keccak256Hash of Epoch and orderbook, used as an index of this collection
Price string `bson:"price" json:"price"`
}
func (item *EpochPriceItem) GetBSON() (interface{}, error) {
return EpochPriceItemBSON{
Epoch: strconv.FormatUint(item.Epoch, 10),
Orderbook: item.Orderbook.Hex(),
Price: item.Price.String(),
Hash: item.Hash.Hex(),
}, nil
}
func (item *EpochPriceItem) SetBSON(raw bson.Raw) error {
decoded := new(EpochPriceItemBSON)
err := raw.Unmarshal(decoded)
if err != nil {
return fmt.Errorf("failed to decode EpochPriceItem. Err: %v", err)
}
epochNumber, err := strconv.ParseUint(decoded.Epoch, 10, 64)
if err != nil {
return fmt.Errorf("failed to parse EpochPriceItem.Epoch. Err: %v", err)
}
item.Epoch = epochNumber
item.Orderbook = common.HexToHash(decoded.Orderbook)
item.Hash = common.HexToHash(decoded.Hash)
if decoded.Price != "" {
item.Price = ToBigInt(decoded.Price)
}
return nil
}
func (item *EpochPriceItem) ComputeHash() common.Hash {
return crypto.Keccak256Hash(new(big.Int).SetUint64(item.Epoch).Bytes(), item.Orderbook.Bytes())
}

View file

@ -0,0 +1,121 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package tradingstate
import (
"math/big"
"github.com/XinFinOrg/XDPoSChain/common"
)
type journalEntry interface {
undo(db *TradingStateDB)
}
type journal []journalEntry
type (
// Changes to the account trie.
insertOrder struct {
orderBook common.Hash
orderId common.Hash
order *OrderItem
}
cancelOrder struct {
orderBook common.Hash
orderId common.Hash
order OrderItem
}
subAmountOrder struct {
orderBook common.Hash
orderId common.Hash
order OrderItem
amount *big.Int
}
nonceChange struct {
hash common.Hash
prev uint64
}
lastPriceChange struct {
hash common.Hash
prev *big.Int
}
mediumPriceChange struct {
hash common.Hash
prevPrice *big.Int
prevQuantity *big.Int
}
mediumPriceBeforeEpochChange struct {
hash common.Hash
prevPrice *big.Int
}
insertLiquidationPrice struct {
orderBook common.Hash
price *big.Int
lendingBook common.Hash
tradeId uint64
}
removeLiquidationPrice struct {
orderBook common.Hash
price *big.Int
lendingBook common.Hash
tradeId uint64
}
)
func (ch insertOrder) undo(s *TradingStateDB) {
s.CancelOrder(ch.orderBook, ch.order)
}
func (ch cancelOrder) undo(s *TradingStateDB) {
s.InsertOrderItem(ch.orderBook, ch.orderId, ch.order)
}
func (ch insertLiquidationPrice) undo(s *TradingStateDB) {
s.RemoveLiquidationPrice(ch.orderBook, ch.price, ch.lendingBook, ch.tradeId)
}
func (ch removeLiquidationPrice) undo(s *TradingStateDB) {
s.InsertLiquidationPrice(ch.orderBook, ch.price, ch.lendingBook, ch.tradeId)
}
func (ch subAmountOrder) undo(s *TradingStateDB) {
priceHash := common.BigToHash(ch.order.Price)
stateOrderBook := s.getStateExchangeObject(ch.orderBook)
var stateOrderList *stateOrderList
switch ch.order.Side {
case Ask:
stateOrderList = stateOrderBook.getStateOrderListAskObject(s.db, priceHash)
case Bid:
stateOrderList = stateOrderBook.getStateBidOrderListObject(s.db, priceHash)
default:
return
}
stateOrderItem := stateOrderBook.getStateOrderObject(s.db, ch.orderId)
newAmount := new(big.Int).Add(stateOrderItem.Quantity(), ch.amount)
stateOrderItem.setVolume(newAmount)
stateOrderList.insertOrderItem(s.db, ch.orderId, common.BigToHash(newAmount))
stateOrderList.AddVolume(ch.amount)
}
func (ch nonceChange) undo(s *TradingStateDB) {
s.SetNonce(ch.hash, ch.prev)
}
func (ch lastPriceChange) undo(s *TradingStateDB) {
s.SetLastPrice(ch.hash, ch.prev)
}
func (ch mediumPriceChange) undo(s *TradingStateDB) {
s.SetMediumPrice(ch.hash, ch.prevPrice, ch.prevQuantity)
}
func (ch mediumPriceBeforeEpochChange) undo(s *TradingStateDB) {
s.SetMediumPriceBeforeEpoch(ch.hash, ch.prevPrice)
}

View file

@ -0,0 +1,140 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package tradingstate
import (
"sync"
"github.com/XinFinOrg/XDPoSChain/common"
)
type exchanges struct {
stateObject *tradingExchanges
nstart uint64
nonces []bool
}
type XDCXManagedState struct {
*TradingStateDB
mu sync.RWMutex
exchanges map[common.Hash]*exchanges
}
// XDCXManagedState returns a new managed state with the statedb as it's backing layer
func ManageState(statedb *TradingStateDB) *XDCXManagedState {
return &XDCXManagedState{
TradingStateDB: statedb.Copy(),
exchanges: make(map[common.Hash]*exchanges),
}
}
// SetState sets the backing layer of the managed state
func (ms *XDCXManagedState) SetState(statedb *TradingStateDB) {
ms.mu.Lock()
defer ms.mu.Unlock()
ms.TradingStateDB = statedb
}
// RemoveNonce removed the nonce from the managed state and all future pending nonces
func (ms *XDCXManagedState) RemoveNonce(addr common.Hash, n uint64) {
if ms.hasAccount(addr) {
ms.mu.Lock()
defer ms.mu.Unlock()
account := ms.getAccount(addr)
if n-account.nstart <= uint64(len(account.nonces)) {
reslice := make([]bool, n-account.nstart)
copy(reslice, account.nonces[:n-account.nstart])
account.nonces = reslice
}
}
}
// NewNonce returns the new canonical nonce for the managed orderId
func (ms *XDCXManagedState) NewNonce(addr common.Hash) uint64 {
ms.mu.Lock()
defer ms.mu.Unlock()
account := ms.getAccount(addr)
for i, nonce := range account.nonces {
if !nonce {
return account.nstart + uint64(i)
}
}
account.nonces = append(account.nonces, true)
return uint64(len(account.nonces)-1) + account.nstart
}
// GetNonce returns the canonical nonce for the managed or unmanaged orderId.
//
// Because GetNonce mutates the DB, we must take a write lock.
func (ms *XDCXManagedState) GetNonce(addr common.Hash) uint64 {
ms.mu.Lock()
defer ms.mu.Unlock()
if ms.hasAccount(addr) {
account := ms.getAccount(addr)
return uint64(len(account.nonces)) + account.nstart
} else {
return ms.TradingStateDB.GetNonce(addr)
}
}
// SetNonce sets the new canonical nonce for the managed state
func (ms *XDCXManagedState) SetNonce(addr common.Hash, nonce uint64) {
ms.mu.Lock()
defer ms.mu.Unlock()
so := ms.GetOrNewStateExchangeObject(addr)
so.SetNonce(nonce)
ms.exchanges[addr] = newAccount(so)
}
// HasAccount returns whether the given address is managed or not
func (ms *XDCXManagedState) HasAccount(addr common.Hash) bool {
ms.mu.RLock()
defer ms.mu.RUnlock()
return ms.hasAccount(addr)
}
func (ms *XDCXManagedState) hasAccount(addr common.Hash) bool {
_, ok := ms.exchanges[addr]
return ok
}
// populate the managed state
func (ms *XDCXManagedState) getAccount(addr common.Hash) *exchanges {
if account, ok := ms.exchanges[addr]; !ok {
so := ms.GetOrNewStateExchangeObject(addr)
ms.exchanges[addr] = newAccount(so)
} else {
// Always make sure the state orderId nonce isn't actually higher
// than the tracked one.
so := ms.TradingStateDB.getStateExchangeObject(addr)
if so != nil && uint64(len(account.nonces))+account.nstart < so.Nonce() {
ms.exchanges[addr] = newAccount(so)
}
}
return ms.exchanges[addr]
}
func newAccount(so *tradingExchanges) *exchanges {
return &exchanges{so, so.Nonce(), nil}
}

View file

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

View file

@ -0,0 +1,352 @@
package tradingstate
import (
"fmt"
"math/big"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/pkg/errors"
)
func GetLocMappingAtKey(key common.Hash, slot uint64) *big.Int {
slotHash := common.BigToHash(new(big.Int).SetUint64(slot))
retByte := crypto.Keccak256(key.Bytes(), slotHash.Bytes())
ret := new(big.Int)
ret.SetBytes(retByte)
return ret
}
func GetExRelayerFee(relayer common.Address, statedb *state.StateDB) *big.Int {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_fee"])
locHash := common.BigToHash(locBig)
return statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash).Big()
}
func GetRelayerOwner(relayer common.Address, statedb *state.StateDB) common.Address {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
log.Debug("GetRelayerOwner", "relayer", relayer.Hex(), "slot", slot, "locBig", locBig)
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_owner"])
locHash := common.BigToHash(locBig)
return common.BytesToAddress(statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash).Bytes())
}
// return true if relayer request to resign and have not withdraw locked fund
func IsResignedRelayer(relayer common.Address, statedb *state.StateDB) bool {
slot := RelayerMappingSlot["RESIGN_REQUESTS"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locHash := common.BigToHash(locBig)
if statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash) != (common.Hash{}) {
return true
}
return false
}
func GetBaseTokenLength(relayer common.Address, statedb *state.StateDB) uint64 {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_fromTokens"])
locHash := common.BigToHash(locBig)
return statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash).Big().Uint64()
}
func GetBaseTokenAtIndex(relayer common.Address, statedb *state.StateDB, index uint64) common.Address {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_fromTokens"])
locHash := common.BigToHash(locBig)
loc := state.GetLocDynamicArrAtElement(locHash, index, 1)
return common.BytesToAddress(statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), loc).Bytes())
}
func GetQuoteTokenLength(relayer common.Address, statedb *state.StateDB) uint64 {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_toTokens"])
locHash := common.BigToHash(locBig)
return statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash).Big().Uint64()
}
func GetQuoteTokenAtIndex(relayer common.Address, statedb *state.StateDB, index uint64) common.Address {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_toTokens"])
locHash := common.BigToHash(locBig)
loc := state.GetLocDynamicArrAtElement(locHash, index, 1)
return common.BytesToAddress(statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), loc).Bytes())
}
func GetRelayerCount(statedb *state.StateDB) uint64 {
slot := RelayerMappingSlot["RelayerCount"]
slotHash := common.BigToHash(new(big.Int).SetUint64(slot))
valueHash := statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), slotHash)
return new(big.Int).SetBytes(valueHash.Bytes()).Uint64()
}
func GetAllCoinbases(statedb *state.StateDB) []common.Address {
relayerCount := GetRelayerCount(statedb)
slot := RelayerMappingSlot["RELAYER_COINBASES"]
coinbases := []common.Address{}
for i := uint64(0); i < relayerCount; i++ {
valueHash := statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), common.BytesToHash(state.GetLocMappingAtKey(common.BigToHash(big.NewInt(int64(i))), slot).Bytes()))
coinbases = append(coinbases, common.BytesToAddress(valueHash.Bytes()))
}
return coinbases
}
func GetAllTradingPairs(statedb *state.StateDB) (map[common.Hash]bool, error) {
coinbases := GetAllCoinbases(statedb)
slot := RelayerMappingSlot["RELAYER_LIST"]
allPairs := map[common.Hash]bool{}
for _, coinbase := range coinbases {
locBig := GetLocMappingAtKey(coinbase.Hash(), slot)
fromTokenSlot := new(big.Int).Add(locBig, RelayerStructMappingSlot["_fromTokens"])
fromTokenLength := statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), common.BigToHash(fromTokenSlot)).Big().Uint64()
toTokenSlot := new(big.Int).Add(locBig, RelayerStructMappingSlot["_toTokens"])
toTokenLength := statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), common.BigToHash(toTokenSlot)).Big().Uint64()
if toTokenLength != fromTokenLength {
return map[common.Hash]bool{}, fmt.Errorf("Invalid length from token & to toke : from :%d , to :%d ", fromTokenLength, toTokenLength)
}
fromTokens := []common.Address{}
fromTokenSlotHash := common.BytesToHash(fromTokenSlot.Bytes())
for i := uint64(0); i < fromTokenLength; i++ {
fromToken := common.BytesToAddress(statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), state.GetLocDynamicArrAtElement(fromTokenSlotHash, i, uint64(1))).Bytes())
fromTokens = append(fromTokens, fromToken)
}
toTokenSlotHash := common.BytesToHash(toTokenSlot.Bytes())
for i := uint64(0); i < toTokenLength; i++ {
toToken := common.BytesToAddress(statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), state.GetLocDynamicArrAtElement(toTokenSlotHash, i, uint64(1))).Bytes())
log.Debug("GetAllTradingPairs all pair info", "from", fromTokens[i].Hex(), "toToken", toToken.Hex())
allPairs[GetTradingOrderBookHash(fromTokens[i], toToken)] = true
}
}
log.Debug("GetAllTradingPairs", "coinbase", len(coinbases), "allPairs", len(allPairs))
return allPairs, nil
}
func SubRelayerFee(relayer common.Address, fee *big.Int, statedb *state.StateDB) error {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"])
locHashDeposit := common.BigToHash(locBigDeposit)
balance := statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit).Big()
log.Debug("ApplyXDCXMatchedTransaction settle balance: SubRelayerFee BEFORE", "relayer", relayer.String(), "balance", balance)
if balance.Cmp(fee) < 0 {
return errors.Errorf("relayer %s isn't enough XDC fee", relayer.String())
} else {
balance = new(big.Int).Sub(balance, fee)
statedb.SetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit, common.BigToHash(balance))
statedb.SubBalance(common.HexToAddress(common.RelayerRegistrationSMC), fee)
log.Debug("ApplyXDCXMatchedTransaction settle balance: SubRelayerFee AFTER", "relayer", relayer.String(), "balance", balance)
return nil
}
}
func CheckRelayerFee(relayer common.Address, fee *big.Int, statedb *state.StateDB) error {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"])
locHashDeposit := common.BigToHash(locBigDeposit)
balance := statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit).Big()
if new(big.Int).Sub(balance, fee).Cmp(new(big.Int).Mul(common.BasePrice, common.RelayerLockedFund)) < 0 {
return errors.Errorf("relayer %s isn't enough XDC fee : balance %d , fee : %d ", relayer.Hex(), balance.Uint64(), fee.Uint64())
}
return nil
}
func AddTokenBalance(addr common.Address, value *big.Int, token common.Address, statedb *state.StateDB) error {
// XDC native
if token.String() == common.XDCNativeAddress {
balance := statedb.GetBalance(addr)
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD TOKEN XDC NATIVE BEFORE", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
statedb.AddBalance(addr, value)
balance = statedb.GetBalance(addr)
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD XDC NATIVE BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
return nil
}
// TRC tokens
if statedb.Exist(token) {
slot := TokenMappingSlot["balances"]
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
balance := statedb.GetState(token, locHash).Big()
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD TOKEN BALANCE BEFORE", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
balance = new(big.Int).Add(balance, value)
statedb.SetState(token, locHash, common.BigToHash(balance))
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD TOKEN BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
return nil
} else {
return errors.Errorf("token %s isn't exist", token.String())
}
}
func SubTokenBalance(addr common.Address, value *big.Int, token common.Address, statedb *state.StateDB) error {
// XDC native
if token.String() == common.XDCNativeAddress {
balance := statedb.GetBalance(addr)
log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB XDC NATIVE BALANCE BEFORE", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
if balance.Cmp(value) < 0 {
return errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), token.String(), balance, value)
}
statedb.SubBalance(addr, value)
balance = statedb.GetBalance(addr)
log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB XDC NATIVE BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
return nil
}
// TRC tokens
if statedb.Exist(token) {
slot := TokenMappingSlot["balances"]
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
balance := statedb.GetState(token, locHash).Big()
log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB TOKEN BALANCE BEFORE", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
if balance.Cmp(value) < 0 {
return errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), token.String(), balance, value)
}
balance = new(big.Int).Sub(balance, value)
statedb.SetState(token, locHash, common.BigToHash(balance))
log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB TOKEN BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
return nil
} else {
return errors.Errorf("token %s isn't exist", token.String())
}
}
func CheckSubTokenBalance(addr common.Address, value *big.Int, token common.Address, statedb *state.StateDB, mapBalances map[common.Address]map[common.Address]*big.Int) (*big.Int, error) {
// XDC native
if token.String() == common.XDCNativeAddress {
var balance *big.Int
if value := mapBalances[token][addr]; value != nil {
balance = value
} else {
balance = statedb.GetBalance(addr)
}
if balance.Cmp(value) < 0 {
return nil, errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), token.String(), balance, value)
}
newBalance := new(big.Int).Sub(balance, value)
log.Debug("CheckSubTokenBalance settle balance: SUB XDC NATIVE BALANCE ", "token", token.String(), "address", addr.String(), "balance", balance, "value", value, "newBalance", newBalance)
return newBalance, nil
}
// TRC tokens
if statedb.Exist(token) {
var balance *big.Int
if value := mapBalances[token][addr]; value != nil {
balance = value
} else {
slot := TokenMappingSlot["balances"]
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
balance = statedb.GetState(token, locHash).Big()
}
if balance.Cmp(value) < 0 {
return nil, errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), token.String(), balance, value)
}
newBalance := new(big.Int).Sub(balance, value)
log.Debug("CheckSubTokenBalance settle balance: SUB TOKEN BALANCE ", "token", token.String(), "address", addr.String(), "balance", balance, "value", value, "newBalance", newBalance)
return newBalance, nil
} else {
return nil, errors.Errorf("token %s isn't exist", token.String())
}
}
func CheckAddTokenBalance(addr common.Address, value *big.Int, token common.Address, statedb *state.StateDB, mapBalances map[common.Address]map[common.Address]*big.Int) (*big.Int, error) {
// XDC native
if token.String() == common.XDCNativeAddress {
var balance *big.Int
if value := mapBalances[token][addr]; value != nil {
balance = value
} else {
balance = statedb.GetBalance(addr)
}
newBalance := new(big.Int).Add(balance, value)
log.Debug("CheckAddTokenBalance settle balance: ADD XDC NATIVE BALANCE ", "token", token.String(), "address", addr.String(), "balance", balance, "value", value, "newBalance", newBalance)
return newBalance, nil
}
// TRC tokens
if statedb.Exist(token) {
var balance *big.Int
if value := mapBalances[token][addr]; value != nil {
balance = value
} else {
slot := TokenMappingSlot["balances"]
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
balance = statedb.GetState(token, locHash).Big()
}
newBalance := new(big.Int).Add(balance, value)
log.Debug("CheckAddTokenBalance settle balance: ADD TOKEN BALANCE ", "token", token.String(), "address", addr.String(), "balance", balance, "value", value, "newBalance", newBalance)
if common.BigToHash(newBalance).Big().Cmp(newBalance) != 0 {
return nil, fmt.Errorf("Overflow when try add token balance , max is 2^256 , balance : %v , value:%v ", balance, value)
} else {
return newBalance, nil
}
} else {
return nil, errors.Errorf("token %s isn't exist", token.String())
}
}
func CheckSubRelayerFee(relayer common.Address, fee *big.Int, statedb *state.StateDB, mapBalances map[common.Address]*big.Int) (*big.Int, error) {
balance := mapBalances[relayer]
if balance == nil {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"])
locHashDeposit := common.BigToHash(locBigDeposit)
balance = statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit).Big()
}
log.Debug("CheckSubRelayerFee settle balance: SubRelayerFee ", "relayer", relayer.String(), "balance", balance, "fee", fee)
if balance.Cmp(fee) < 0 {
return nil, errors.Errorf("relayer %s isn't enough XDC fee", relayer.String())
} else {
return new(big.Int).Sub(balance, fee), nil
}
}
func GetTokenBalance(addr common.Address, token common.Address, statedb *state.StateDB) *big.Int {
// XDC native
if token.String() == common.XDCNativeAddress {
return statedb.GetBalance(addr)
}
// TRC tokens
if statedb.Exist(token) {
slot := TokenMappingSlot["balances"]
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
return statedb.GetState(token, locHash).Big()
} else {
return common.Big0
}
}
func SetTokenBalance(addr common.Address, balance *big.Int, token common.Address, statedb *state.StateDB) error {
// XDC native
if token.String() == common.XDCNativeAddress {
statedb.SetBalance(addr, balance)
return nil
}
// TRC tokens
if statedb.Exist(token) {
slot := TokenMappingSlot["balances"]
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
statedb.SetState(token, locHash, common.BigToHash(balance))
return nil
} else {
return errors.Errorf("token %s isn't exist", token.String())
}
}
func SetSubRelayerFee(relayer common.Address, balance *big.Int, fee *big.Int, statedb *state.StateDB) {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"])
locHashDeposit := common.BigToHash(locBigDeposit)
statedb.SetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit, common.BigToHash(balance))
statedb.SubBalance(common.HexToAddress(common.RelayerRegistrationSMC), fee)
}

View file

@ -0,0 +1,165 @@
package tradingstate
import (
"encoding/json"
"errors"
"math/big"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/log"
)
const DefaultFeeRate = 10 // 10 / XDCXBaseFee = 10 / 10000 = 0.1%
var ErrQuantityTradeTooSmall = errors.New("quantity trade too small")
type TradeResult struct {
Fee *big.Int
InToken common.Address
InTotal *big.Int
OutToken common.Address
OutTotal *big.Int
}
type SettleBalance struct {
Taker TradeResult
Maker TradeResult
}
func (settleBalance *SettleBalance) String() string {
jsonData, _ := json.Marshal(settleBalance)
return string(jsonData)
}
func GetSettleBalance(quotePrice *big.Int, takerSide string, takerFeeRate *big.Int, baseToken, quoteToken common.Address, makerPrice *big.Int, makerFeeRate *big.Int, baseTokenDecimal *big.Int, quoteTokenDecimal *big.Int, quantityToTrade *big.Int) (*SettleBalance, error) {
log.Debug("GetSettleBalance", "takerSide", takerSide, "takerFeeRate", takerFeeRate, "baseToken", baseToken, "quoteToken", quoteToken, "makerPrice", makerPrice, "makerFeeRate", makerFeeRate, "baseTokenDecimal", baseTokenDecimal, "quantityToTrade", quantityToTrade, "quotePrice", quotePrice)
var result *SettleBalance
//result = map[common.Address]map[string]interface{}{}
// quoteTokenQuantity = quantityToTrade * makerPrice / baseTokenDecimal
quoteTokenQuantity := new(big.Int).Mul(quantityToTrade, makerPrice)
quoteTokenQuantity = new(big.Int).Div(quoteTokenQuantity, baseTokenDecimal)
makerFee := new(big.Int).Mul(quoteTokenQuantity, makerFeeRate)
makerFee = new(big.Int).Div(makerFee, common.XDCXBaseFee)
takerFee := new(big.Int).Mul(quoteTokenQuantity, takerFeeRate)
takerFee = new(big.Int).Div(takerFee, common.XDCXBaseFee)
// use the defaultFee to validate small orders
defaultFee := new(big.Int).Mul(quoteTokenQuantity, new(big.Int).SetUint64(DefaultFeeRate))
defaultFee = new(big.Int).Div(defaultFee, common.XDCXBaseFee)
if takerSide == Bid {
if quoteTokenQuantity.Cmp(makerFee) <= 0 || quoteTokenQuantity.Cmp(defaultFee) <= 0 {
log.Debug("quantity trade too small", "quoteTokenQuantity", quoteTokenQuantity, "makerFee", makerFee, "defaultFee", defaultFee)
return result, ErrQuantityTradeTooSmall
}
if quoteToken.String() != common.XDCNativeAddress && quotePrice != nil && quotePrice.Cmp(common.Big0) > 0 {
// defaultFeeInXDC
defaultFeeInXDC := new(big.Int).Mul(defaultFee, quotePrice)
defaultFeeInXDC = new(big.Int).Div(defaultFeeInXDC, quoteTokenDecimal)
exMakerReceivedFee := new(big.Int).Mul(makerFee, quotePrice)
exMakerReceivedFee = new(big.Int).Div(exMakerReceivedFee, quoteTokenDecimal)
if (exMakerReceivedFee.Cmp(common.RelayerFee) <= 0 && exMakerReceivedFee.Sign() > 0) || defaultFeeInXDC.Cmp(common.RelayerFee) <= 0 {
log.Debug("makerFee too small", "quoteTokenQuantity", quoteTokenQuantity, "makerFee", makerFee, "exMakerReceivedFee", exMakerReceivedFee, "quotePrice", quotePrice, "defaultFeeInXDC", defaultFeeInXDC)
return result, ErrQuantityTradeTooSmall
}
exTakerReceivedFee := new(big.Int).Mul(takerFee, quotePrice)
exTakerReceivedFee = new(big.Int).Div(exTakerReceivedFee, quoteTokenDecimal)
if (exTakerReceivedFee.Cmp(common.RelayerFee) <= 0 && exTakerReceivedFee.Sign() > 0) || defaultFeeInXDC.Cmp(common.RelayerFee) <= 0 {
log.Debug("takerFee too small", "quoteTokenQuantity", quoteTokenQuantity, "takerFee", takerFee, "exTakerReceivedFee", exTakerReceivedFee, "quotePrice", quotePrice, "defaultFeeInXDC", defaultFeeInXDC)
return result, ErrQuantityTradeTooSmall
}
} else if quoteToken.String() == common.XDCNativeAddress {
exMakerReceivedFee := makerFee
if (exMakerReceivedFee.Cmp(common.RelayerFee) <= 0 && exMakerReceivedFee.Sign() > 0) || defaultFee.Cmp(common.RelayerFee) <= 0 {
log.Debug("makerFee too small", "quantityToTrade", quantityToTrade, "makerFee", makerFee, "exMakerReceivedFee", exMakerReceivedFee, "makerFeeRate", makerFeeRate, "defaultFee", defaultFee)
return result, ErrQuantityTradeTooSmall
}
exTakerReceivedFee := takerFee
if (exTakerReceivedFee.Cmp(common.RelayerFee) <= 0 && exTakerReceivedFee.Sign() > 0) || defaultFee.Cmp(common.RelayerFee) <= 0 {
log.Debug("takerFee too small", "quantityToTrade", quantityToTrade, "takerFee", takerFee, "exTakerReceivedFee", exTakerReceivedFee, "takerFeeRate", takerFeeRate, "defaultFee", defaultFee)
return result, ErrQuantityTradeTooSmall
}
}
inTotal := new(big.Int).Sub(quoteTokenQuantity, makerFee)
//takerOutTotal= quoteTokenQuantity + takerFee = quantityToTrade*maker.Price/baseTokenDecimal + quantityToTrade*maker.Price/baseTokenDecimal * takerFeeRate/baseFee
// = quantityToTrade * maker.Price/baseTokenDecimal ( 1 + takerFeeRate/baseFee)
// = quantityToTrade * maker.Price * (baseFee + takerFeeRate ) / ( baseTokenDecimal * baseFee)
takerOutTotal := new(big.Int).Add(quoteTokenQuantity, takerFee)
result = &SettleBalance{
Taker: TradeResult{
Fee: takerFee,
InToken: baseToken,
InTotal: quantityToTrade,
OutToken: quoteToken,
OutTotal: takerOutTotal,
},
Maker: TradeResult{
Fee: makerFee,
InToken: quoteToken,
InTotal: inTotal,
OutToken: baseToken,
OutTotal: quantityToTrade,
},
}
} else {
if quoteTokenQuantity.Cmp(takerFee) <= 0 || quoteTokenQuantity.Cmp(defaultFee) <= 0 {
log.Debug("quantity trade too small", "quoteTokenQuantity", quoteTokenQuantity, "takerFee", takerFee)
return result, ErrQuantityTradeTooSmall
}
if quoteToken.String() != common.XDCNativeAddress && quotePrice != nil && quotePrice.Cmp(common.Big0) > 0 {
// defaultFeeInXDC
defaultFeeInXDC := new(big.Int).Mul(defaultFee, quotePrice)
defaultFeeInXDC = new(big.Int).Div(defaultFeeInXDC, quoteTokenDecimal)
exMakerReceivedFee := new(big.Int).Mul(makerFee, quotePrice)
exMakerReceivedFee = new(big.Int).Div(exMakerReceivedFee, quoteTokenDecimal)
log.Debug("exMakerReceivedFee", "quoteTokenQuantity", quoteTokenQuantity, "makerFee", makerFee, "exMakerReceivedFee", exMakerReceivedFee, "quotePrice", quotePrice)
if (exMakerReceivedFee.Cmp(common.RelayerFee) <= 0 && exMakerReceivedFee.Sign() > 0) || defaultFeeInXDC.Cmp(common.RelayerFee) <= 0 {
log.Debug("makerFee too small", "quoteTokenQuantity", quoteTokenQuantity, "makerFee", makerFee, "exMakerReceivedFee", exMakerReceivedFee, "quotePrice", quotePrice, "defaultMakerFeeInXDC", defaultFeeInXDC)
return result, ErrQuantityTradeTooSmall
}
exTakerReceivedFee := new(big.Int).Mul(takerFee, quotePrice)
exTakerReceivedFee = new(big.Int).Div(exTakerReceivedFee, quoteTokenDecimal)
if (exTakerReceivedFee.Cmp(common.RelayerFee) <= 0 && exTakerReceivedFee.Sign() > 0) || defaultFeeInXDC.Cmp(common.RelayerFee) <= 0 {
log.Debug("takerFee too small", "quoteTokenQuantity", quoteTokenQuantity, "takerFee", takerFee, "exTakerReceivedFee", exTakerReceivedFee, "quotePrice", quotePrice, "defaultFeeInXDC", defaultFeeInXDC)
return result, ErrQuantityTradeTooSmall
}
} else if quoteToken.String() == common.XDCNativeAddress {
exMakerReceivedFee := makerFee
if (exMakerReceivedFee.Cmp(common.RelayerFee) <= 0 && exMakerReceivedFee.Sign() > 0) || defaultFee.Cmp(common.RelayerFee) <= 0 {
log.Debug("makerFee too small", "quantityToTrade", quantityToTrade, "makerFee", makerFee, "exMakerReceivedFee", exMakerReceivedFee, "makerFeeRate", makerFeeRate, "defaultFee", defaultFee)
return result, ErrQuantityTradeTooSmall
}
exTakerReceivedFee := takerFee
if (exTakerReceivedFee.Cmp(common.RelayerFee) <= 0 && exTakerReceivedFee.Sign() > 0) || defaultFee.Cmp(common.RelayerFee) <= 0 {
log.Debug("takerFee too small", "quantityToTrade", quantityToTrade, "takerFee", takerFee, "exTakerReceivedFee", exTakerReceivedFee, "takerFeeRate", takerFeeRate, "defaultFee", defaultFee)
return result, ErrQuantityTradeTooSmall
}
}
inTotal := new(big.Int).Sub(quoteTokenQuantity, takerFee)
// makerOutTotal = quoteTokenQuantity + makerFee = quantityToTrade * makerPrice / baseTokenDecimal + quantityToTrade * makerPrice / baseTokenDecimal * makerFeeRate / baseFee
// = quantityToTrade * makerPrice / baseTokenDecimal * (1+makerFeeRate / baseFee)
// = quantityToTrade * makerPrice * (baseFee + makerFeeRate) / ( baseTokenDecimal * baseFee )
makerOutTotal := new(big.Int).Add(quoteTokenQuantity, makerFee)
// Fee
result = &SettleBalance{
Taker: TradeResult{
Fee: takerFee,
InToken: quoteToken,
InTotal: inTotal,
OutToken: baseToken,
OutTotal: quantityToTrade,
},
Maker: TradeResult{
Fee: makerFee,
InToken: baseToken,
InTotal: quantityToTrade,
OutToken: quoteToken,
OutTotal: makerOutTotal,
},
}
}
return result, nil
}

View file

@ -0,0 +1,263 @@
package tradingstate
import (
"github.com/XinFinOrg/XDPoSChain/common"
"math/big"
"reflect"
"testing"
)
func TestGetSettleBalance(t *testing.T) {
testToken := common.HexToAddress("0x0000000000000000000000000000000000000022")
testFee, _ := new(big.Int).SetString("1000000000000000000", 10)
tradeQuantity, _ := new(big.Int).SetString("1000000000000000000000", 10)
tradeQuantityIncludedFee, _ := new(big.Int).SetString("1001000000000000000000", 10)
tradeQuantityExcludedFee, _ := new(big.Int).SetString("999000000000000000000", 10)
type GetSettleBalanceArg struct {
quotePrice *big.Int
takerSide string
takerFeeRate *big.Int
baseToken common.Address
quoteToken common.Address
makerPrice *big.Int
makerFeeRate *big.Int
baseTokenDecimal *big.Int
quoteTokenDecimal *big.Int
quantityToTrade *big.Int
}
tests := []struct {
name string
args GetSettleBalanceArg
want *SettleBalance
wantErr bool
}{
{
"BUY tradeQuantity == fee",
GetSettleBalanceArg{
quotePrice: common.BasePrice,
takerSide: Bid,
takerFeeRate: big.NewInt(10000), // feeRate 100%
baseToken: common.Address{},
quoteToken: common.Address{},
makerPrice: common.BasePrice,
makerFeeRate: big.NewInt(10000), // feeRate 100%
baseTokenDecimal: common.BasePrice,
quoteTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
},
nil,
true,
},
{
"BUY, quote is not XDC, makerFee <= 0.001 XDC",
GetSettleBalanceArg{
quotePrice: common.BasePrice,
takerSide: Bid,
takerFeeRate: big.NewInt(10), // feeRate 0.1%
baseToken: testToken,
quoteToken: common.HexToAddress("0x0000000000000000000000000000000000000002"),
makerPrice: common.BasePrice,
makerFeeRate: big.NewInt(10), // feeRate 0.1%
baseTokenDecimal: common.BasePrice,
quoteTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1), common.BasePrice),
},
nil,
true,
},
{
"BUY, quote is not XDC, takerFee <= 0.001 XDC",
GetSettleBalanceArg{
quotePrice: common.BasePrice,
takerSide: Bid,
takerFeeRate: big.NewInt(5), // feeRate 0.05%
baseToken: testToken,
quoteToken: common.HexToAddress("0x0000000000000000000000000000000000000002"),
makerPrice: common.BasePrice,
makerFeeRate: big.NewInt(10), // feeRate 0.1%
baseTokenDecimal: common.BasePrice,
quoteTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(2), common.BasePrice),
},
nil,
true,
},
{
"BUY, quote is XDC, makerFee <= 0.001 XDC",
GetSettleBalanceArg{
quotePrice: common.BasePrice,
takerSide: Bid,
takerFeeRate: big.NewInt(10), // feeRate 0.1%
baseToken: testToken,
quoteToken: common.HexToAddress(common.XDCNativeAddress),
makerPrice: common.BasePrice,
makerFeeRate: big.NewInt(10), // feeRate 0.1%
baseTokenDecimal: common.BasePrice,
quoteTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1), common.BasePrice),
},
nil,
true,
},
{
"BUY, quote is XDC, takerFee <= 0.001 XDC",
GetSettleBalanceArg{
quotePrice: common.BasePrice,
takerSide: Bid,
takerFeeRate: big.NewInt(5), // feeRate 0.05%
baseToken: testToken,
quoteToken: common.HexToAddress(common.XDCNativeAddress),
makerPrice: common.BasePrice,
makerFeeRate: big.NewInt(10), // feeRate 0.1%
baseTokenDecimal: common.BasePrice,
quoteTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(2), common.BasePrice),
},
nil,
true,
},
{
"BUY, no error",
GetSettleBalanceArg{
quotePrice: common.BasePrice,
takerSide: Bid,
takerFeeRate: big.NewInt(10), // feeRate 0.1%
baseToken: testToken,
quoteToken: common.HexToAddress(common.XDCNativeAddress),
makerPrice: common.BasePrice,
makerFeeRate: big.NewInt(10), // feeRate 0.1%
baseTokenDecimal: common.BasePrice,
quoteTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
},
&SettleBalance{
Taker: TradeResult{Fee: testFee, InToken: testToken, InTotal: tradeQuantity, OutToken: common.HexToAddress(common.XDCNativeAddress), OutTotal: tradeQuantityIncludedFee},
Maker: TradeResult{Fee: testFee, InToken: common.HexToAddress(common.XDCNativeAddress), InTotal: tradeQuantityExcludedFee, OutToken: testToken, OutTotal: tradeQuantity},
},
false,
},
{
"SELL tradeQuantity == fee",
GetSettleBalanceArg{
quotePrice: common.BasePrice,
takerSide: Ask,
takerFeeRate: big.NewInt(10000), // feeRate 100%
baseToken: testToken,
quoteToken: common.Address{},
makerPrice: common.BasePrice,
makerFeeRate: big.NewInt(10000), // feeRate 100%
baseTokenDecimal: common.BasePrice,
quoteTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
},
nil,
true,
},
{
"SELL, quote is not XDC, makerFee <= 0.001 XDC",
GetSettleBalanceArg{
quotePrice: common.BasePrice,
takerSide: Ask,
takerFeeRate: big.NewInt(10), // feeRate 0.1%
baseToken: testToken,
quoteToken: common.HexToAddress("0x0000000000000000000000000000000000000002"),
makerPrice: common.BasePrice,
makerFeeRate: big.NewInt(10), // feeRate 0.1%
baseTokenDecimal: common.BasePrice,
quoteTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1), common.BasePrice),
},
nil,
true,
},
{
"SELL, quote is not XDC, takerFee <= 0.001 XDC",
GetSettleBalanceArg{
quotePrice: common.BasePrice,
takerSide: Ask,
takerFeeRate: big.NewInt(5), // feeRate 0.05%
baseToken: testToken,
quoteToken: common.HexToAddress("0x0000000000000000000000000000000000000002"),
makerPrice: common.BasePrice,
makerFeeRate: big.NewInt(10), // feeRate 0.1%
baseTokenDecimal: common.BasePrice,
quoteTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(2), common.BasePrice),
},
nil,
true,
},
{
"SELL, quote is XDC, makerFee <= 0.001 XDC",
GetSettleBalanceArg{
quotePrice: common.BasePrice,
takerSide: Ask,
takerFeeRate: big.NewInt(10), // feeRate 0.1%
baseToken: testToken,
quoteToken: common.HexToAddress(common.XDCNativeAddress),
makerPrice: common.BasePrice,
makerFeeRate: big.NewInt(10), // feeRate 0.1%
baseTokenDecimal: common.BasePrice,
quoteTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1), common.BasePrice),
},
nil,
true,
},
{
"SELL, quote is XDC, takerFee <= 0.001 XDC",
GetSettleBalanceArg{
quotePrice: common.BasePrice,
takerSide: Ask,
takerFeeRate: big.NewInt(5), // feeRate 0.05%
baseToken: testToken,
quoteToken: common.HexToAddress(common.XDCNativeAddress),
makerPrice: common.BasePrice,
makerFeeRate: big.NewInt(10), // feeRate 0.1%
baseTokenDecimal: common.BasePrice,
quoteTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(2), common.BasePrice),
},
nil,
true,
},
{
"SELL, no error",
GetSettleBalanceArg{
quotePrice: common.BasePrice,
takerSide: Ask,
takerFeeRate: big.NewInt(10), // feeRate 15%
baseToken: testToken,
quoteToken: common.HexToAddress(common.XDCNativeAddress),
makerPrice: common.BasePrice,
makerFeeRate: big.NewInt(10), // feeRate 0.1%
baseTokenDecimal: common.BasePrice,
quoteTokenDecimal: common.BasePrice,
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
},
&SettleBalance{
Maker: TradeResult{Fee: testFee, InToken: testToken, InTotal: tradeQuantity, OutToken: common.HexToAddress(common.XDCNativeAddress), OutTotal: tradeQuantityIncludedFee},
Taker: TradeResult{Fee: testFee, InToken: common.HexToAddress(common.XDCNativeAddress), InTotal: tradeQuantityExcludedFee, OutToken: testToken, OutTotal: tradeQuantity},
},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetSettleBalance(tt.args.quotePrice, tt.args.takerSide, tt.args.takerFeeRate, tt.args.baseToken, tt.args.quoteToken, tt.args.makerPrice, tt.args.makerFeeRate, tt.args.baseTokenDecimal, tt.args.quoteTokenDecimal, tt.args.quantityToTrade)
if (err != nil) != tt.wantErr {
t.Errorf("GetSettleBalance() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.want != nil {
t.Log(tt.want.String())
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetSettleBalance() got = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -0,0 +1,215 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package tradingstate
import (
"bytes"
"fmt"
"io"
"math/big"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/rlp"
"github.com/XinFinOrg/XDPoSChain/trie"
)
type stateLendingBook struct {
price common.Hash
orderBook common.Hash
lendingBook common.Hash
data orderList
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by TradingStateDB.Commit.
dbErr error
// Write caches.
trie Trie // storage trie, which becomes non-nil on first access
cachedStorage map[common.Hash]common.Hash
dirtyStorage map[common.Hash]common.Hash
onDirty func(price common.Hash) // Callback method to mark a state object newly dirty
}
func (s *stateLendingBook) empty() bool {
return s.data.Volume == nil || s.data.Volume.Sign() == 0
}
func newStateLendingBook(orderBook common.Hash, price common.Hash, lendingBook common.Hash, data orderList, onDirty func(price common.Hash)) *stateLendingBook {
return &stateLendingBook{
lendingBook: lendingBook,
orderBook: orderBook,
price: price,
data: data,
cachedStorage: make(map[common.Hash]common.Hash),
dirtyStorage: make(map[common.Hash]common.Hash),
onDirty: onDirty,
}
}
func (self *stateLendingBook) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, self.data)
}
func (self *stateLendingBook) setError(err error) {
if self.dbErr == nil {
self.dbErr = err
}
}
func (self *stateLendingBook) getTrie(db Database) Trie {
if self.trie == nil {
var err error
self.trie, err = db.OpenStorageTrie(self.lendingBook, self.data.Root)
if err != nil {
self.trie, _ = db.OpenStorageTrie(self.price, EmptyHash)
self.setError(fmt.Errorf("can't create storage trie: %v", err))
}
}
return self.trie
}
func (self *stateLendingBook) Exist(db Database, lendingId common.Hash) bool {
amount, exists := self.cachedStorage[lendingId]
if exists {
return true
}
// Load from DB in case it is missing.
enc, err := self.getTrie(db).TryGet(lendingId[:])
if err != nil {
self.setError(err)
return false
}
if len(enc) > 0 {
_, content, _, err := rlp.Split(enc)
if err != nil {
self.setError(err)
}
amount.SetBytes(content)
}
if (amount != common.Hash{}) {
self.cachedStorage[lendingId] = amount
}
return true
}
func (self *stateLendingBook) getAllTradeIds(db Database) []common.Hash {
tradeIds := []common.Hash{}
lendingBookTrie := self.getTrie(db)
if lendingBookTrie == nil {
return tradeIds
}
for id, value := range self.cachedStorage {
if !common.EmptyHash(value) {
tradeIds = append(tradeIds, id)
}
}
orderListIt := trie.NewIterator(lendingBookTrie.NodeIterator(nil))
for orderListIt.Next() {
id := common.BytesToHash(orderListIt.Key)
if _, exist := self.cachedStorage[id]; exist {
continue
}
tradeIds = append(tradeIds, id)
}
return tradeIds
}
func (self *stateLendingBook) insertTradingId(db Database, tradeId common.Hash) {
self.setTradingId(tradeId, tradeId)
self.setError(self.getTrie(db).TryUpdate(tradeId[:], tradeId[:]))
}
func (self *stateLendingBook) removeTradingId(db Database, tradeId common.Hash) {
tr := self.getTrie(db)
self.setError(tr.TryDelete(tradeId[:]))
self.setTradingId(tradeId, EmptyHash)
}
func (self *stateLendingBook) setTradingId(tradeId common.Hash, value common.Hash) {
self.cachedStorage[tradeId] = value
self.dirtyStorage[tradeId] = value
if self.onDirty != nil {
self.onDirty(self.lendingBook)
self.onDirty = nil
}
}
func (self *stateLendingBook) updateTrie(db Database) Trie {
tr := self.getTrie(db)
for key, value := range self.dirtyStorage {
delete(self.dirtyStorage, key)
if value == EmptyHash {
self.setError(tr.TryDelete(key[:]))
continue
}
v, _ := rlp.EncodeToBytes(bytes.TrimLeft(value[:], "\x00"))
self.setError(tr.TryUpdate(key[:], v))
}
return tr
}
func (self *stateLendingBook) updateRoot(db Database) error {
self.updateTrie(db)
if self.dbErr != nil {
return self.dbErr
}
root, err := self.trie.Commit(nil)
if err == nil {
self.data.Root = root
}
return err
}
func (self *stateLendingBook) deepCopy(db *TradingStateDB, onDirty func(price common.Hash)) *stateLendingBook {
stateLendingBook := newStateLendingBook(self.lendingBook, self.orderBook, self.price, self.data, onDirty)
if self.trie != nil {
stateLendingBook.trie = db.db.CopyTrie(self.trie)
}
for key, value := range self.dirtyStorage {
stateLendingBook.dirtyStorage[key] = value
}
for key, value := range self.cachedStorage {
stateLendingBook.cachedStorage[key] = value
}
return stateLendingBook
}
func (c *stateLendingBook) AddVolume(amount *big.Int) {
c.setVolume(new(big.Int).Add(c.data.Volume, amount))
}
func (c *stateLendingBook) subVolume(amount *big.Int) {
c.setVolume(new(big.Int).Sub(c.data.Volume, amount))
}
func (self *stateLendingBook) setVolume(volume *big.Int) {
self.data.Volume = volume
if self.onDirty != nil {
self.onDirty(self.lendingBook)
self.onDirty = nil
}
}
func (self *stateLendingBook) Volume() *big.Int {
return self.data.Volume
}

View file

@ -0,0 +1,235 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package tradingstate
import (
"fmt"
"io"
"math/big"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/rlp"
"github.com/XinFinOrg/XDPoSChain/trie"
)
type liquidationPriceState struct {
liquidationPrice common.Hash
orderBook common.Hash
data orderList
db *TradingStateDB
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by TradingStateDB.Commit.
dbErr error
// Write caches.
trie Trie // storage trie, which becomes non-nil on first access
stateLendingBooks map[common.Hash]*stateLendingBook
stateLendingBooksDirty map[common.Hash]struct{}
onDirty func(price common.Hash) // Callback method to mark a state object newly dirty
}
// empty returns whether the orderId is considered empty.
func (s *liquidationPriceState) empty() bool {
return s.data.Volume == nil || s.data.Volume.Sign() == 0
}
// newObject creates a state object.
func newLiquidationPriceState(db *TradingStateDB, orderBook common.Hash, price common.Hash, data orderList, onDirty func(price common.Hash)) *liquidationPriceState {
return &liquidationPriceState{
db: db,
orderBook: orderBook,
liquidationPrice: price,
data: data,
stateLendingBooks: make(map[common.Hash]*stateLendingBook),
stateLendingBooksDirty: make(map[common.Hash]struct{}),
onDirty: onDirty,
}
}
// EncodeRLP implements rlp.Encoder.
func (c *liquidationPriceState) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, c.data)
}
// setError remembers the first non-nil error it is called with.
func (self *liquidationPriceState) setError(err error) {
if self.dbErr == nil {
self.dbErr = err
}
}
func (self *liquidationPriceState) MarkStateLendingBookDirty(price common.Hash) {
self.stateLendingBooksDirty[price] = struct{}{}
if self.onDirty != nil {
self.onDirty(self.liquidationPrice)
self.onDirty = nil
}
}
func (self *liquidationPriceState) createLendingBook(db Database, lendingBook common.Hash) (newobj *stateLendingBook) {
newobj = newStateLendingBook(self.orderBook, self.liquidationPrice, lendingBook, orderList{Volume: Zero}, self.MarkStateLendingBookDirty)
self.stateLendingBooks[lendingBook] = newobj
self.stateLendingBooksDirty[lendingBook] = struct{}{}
if self.onDirty != nil {
self.onDirty(self.liquidationPrice)
self.onDirty = nil
}
return newobj
}
func (self *liquidationPriceState) getTrie(db Database) Trie {
if self.trie == nil {
var err error
self.trie, err = db.OpenStorageTrie(self.liquidationPrice, self.data.Root)
if err != nil {
self.trie, _ = db.OpenStorageTrie(self.liquidationPrice, EmptyHash)
self.setError(fmt.Errorf("can't create storage trie: %v", err))
}
}
return self.trie
}
func (self *liquidationPriceState) updateTrie(db Database) Trie {
tr := self.getTrie(db)
for lendingId, stateObject := range self.stateLendingBooks {
delete(self.stateLendingBooksDirty, lendingId)
if stateObject.empty() {
self.setError(tr.TryDelete(lendingId[:]))
continue
}
stateObject.updateRoot(db)
// Encoding []byte cannot fail, ok to ignore the error.
v, _ := rlp.EncodeToBytes(stateObject)
self.setError(tr.TryUpdate(lendingId[:], v))
}
return tr
}
func (self *liquidationPriceState) updateRoot(db Database) error {
self.updateTrie(db)
if self.dbErr != nil {
return self.dbErr
}
root, err := self.trie.Commit(func(leaf []byte, parent common.Hash) error {
var orderList orderList
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
return nil
}
if orderList.Root != EmptyRoot {
db.TrieDB().Reference(orderList.Root, parent)
}
return nil
})
if err == nil {
self.data.Root = root
}
return err
}
func (self *liquidationPriceState) deepCopy(db *TradingStateDB, onDirty func(liquidationPrice common.Hash)) *liquidationPriceState {
stateOrderList := newLiquidationPriceState(db, self.orderBook, self.liquidationPrice, self.data, onDirty)
if self.trie != nil {
stateOrderList.trie = db.db.CopyTrie(self.trie)
}
for key, value := range self.stateLendingBooks {
stateOrderList.stateLendingBooks[key] = value.deepCopy(db, self.MarkStateLendingBookDirty)
}
for key, value := range self.stateLendingBooksDirty {
stateOrderList.stateLendingBooksDirty[key] = value
}
return stateOrderList
}
// Retrieve a state object given my the address. Returns nil if not found.
func (self *liquidationPriceState) getStateLendingBook(db Database, lendingBook common.Hash) (stateObject *stateLendingBook) {
// Prefer 'live' objects.
if obj := self.stateLendingBooks[lendingBook]; obj != nil {
return obj
}
// Load the object from the database.
enc, err := self.getTrie(db).TryGet(lendingBook[:])
if len(enc) == 0 {
self.setError(err)
return nil
}
var data orderList
if err := rlp.DecodeBytes(enc, &data); err != nil {
log.Error("Failed to decode state lending book ", "orderbook", self.orderBook, "liquidation price", self.liquidationPrice, "lendingBook", lendingBook, "err", err)
return nil
}
// Insert into the live set.
obj := newStateLendingBook(self.orderBook, self.liquidationPrice, lendingBook, data, self.MarkStateLendingBookDirty)
self.stateLendingBooks[lendingBook] = obj
return obj
}
func (self *liquidationPriceState) getAllLiquidationData(db Database) map[common.Hash][]common.Hash {
liquidationData := map[common.Hash][]common.Hash{}
lendingBookTrie := self.getTrie(db)
if lendingBookTrie == nil {
return liquidationData
}
lendingBooks := []common.Hash{}
for id, stateLendingBook := range self.stateLendingBooks {
if !stateLendingBook.empty() {
lendingBooks = append(lendingBooks, id)
}
}
lendingBookListIt := trie.NewIterator(lendingBookTrie.NodeIterator(nil))
for lendingBookListIt.Next() {
id := common.BytesToHash(lendingBookListIt.Key)
if _, exist := self.stateLendingBooks[id]; exist {
continue
}
lendingBooks = append(lendingBooks, id)
}
for _, lendingBook := range lendingBooks {
stateLendingBook := self.getStateLendingBook(db, lendingBook)
if stateLendingBook != nil {
liquidationData[lendingBook] = stateLendingBook.getAllTradeIds(db)
}
}
return liquidationData
}
func (c *liquidationPriceState) AddVolume(amount *big.Int) {
c.setVolume(new(big.Int).Add(c.data.Volume, amount))
}
func (c *liquidationPriceState) subVolume(amount *big.Int) {
c.setVolume(new(big.Int).Sub(c.data.Volume, amount))
}
func (self *liquidationPriceState) setVolume(volume *big.Int) {
self.data.Volume = volume
if self.onDirty != nil {
self.onDirty(self.liquidationPrice)
self.onDirty = nil
}
}
func (self *liquidationPriceState) Volume() *big.Int {
return self.data.Volume
}

View file

@ -0,0 +1,75 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package tradingstate
import (
"io"
"math/big"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/rlp"
)
// stateObject represents an Ethereum orderId which is being modified.
//
// The usage pattern is as follows:
// First you need to obtain a state object.
// tradingExchangeObject values can be accessed and modified through the object.
// Finally, call CommitAskTrie to write the modified storage trie into a database.
type stateOrderItem struct {
orderBook common.Hash
orderId common.Hash
data OrderItem
onDirty func(orderId common.Hash) // Callback method to mark a state object newly dirty
}
// empty returns whether the orderId is considered empty.
func (s *stateOrderItem) empty() bool {
return s.data.Quantity == nil || s.data.Quantity.Cmp(Zero) == 0
}
// newObject creates a state object.
func newStateOrderItem(orderBook common.Hash, orderId common.Hash, data OrderItem, onDirty func(orderId common.Hash)) *stateOrderItem {
return &stateOrderItem{
orderBook: orderBook,
orderId: orderId,
data: data,
onDirty: onDirty,
}
}
// EncodeRLP implements rlp.Encoder.
func (c *stateOrderItem) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, c.data)
}
func (self *stateOrderItem) deepCopy(onDirty func(orderId common.Hash)) *stateOrderItem {
stateOrderList := newStateOrderItem(self.orderBook, self.orderId, self.data, onDirty)
return stateOrderList
}
func (self *stateOrderItem) setVolume(volume *big.Int) {
self.data.Quantity = volume
if self.onDirty != nil {
self.onDirty(self.orderId)
self.onDirty = nil
}
}
func (self *stateOrderItem) Quantity() *big.Int {
return self.data.Quantity
}

View file

@ -0,0 +1,218 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package tradingstate
import (
"bytes"
"fmt"
"io"
"math/big"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/rlp"
)
// stateObject represents an Ethereum orderId which is being modified.
//
// The usage pattern is as follows:
// First you need to obtain a state object.
// tradingExchangeObject values can be accessed and modified through the object.
// Finally, call CommitAskTrie to write the modified storage trie into a database.
type stateOrderList struct {
price common.Hash
orderBook common.Hash
orderType string
data orderList
db *TradingStateDB
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by TradingStateDB.Commit.
dbErr error
// Write caches.
trie Trie // storage trie, which becomes non-nil on first access
cachedStorage map[common.Hash]common.Hash // Storage entry cache to avoid duplicate reads
dirtyStorage map[common.Hash]common.Hash // Storage entries that need to be flushed to disk
onDirty func(price common.Hash) // Callback method to mark a state object newly dirty
}
// empty returns whether the orderId is considered empty.
func (s *stateOrderList) empty() bool {
return s.data.Volume == nil || s.data.Volume.Cmp(Zero) == 0
}
// newObject creates a state object.
func newStateOrderList(db *TradingStateDB, orderType string, orderBook common.Hash, price common.Hash, data orderList, onDirty func(price common.Hash)) *stateOrderList {
return &stateOrderList{
db: db,
orderType: orderType,
orderBook: orderBook,
price: price,
data: data,
cachedStorage: make(map[common.Hash]common.Hash),
dirtyStorage: make(map[common.Hash]common.Hash),
onDirty: onDirty,
}
}
// EncodeRLP implements rlp.Encoder.
func (c *stateOrderList) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, c.data)
}
// setError remembers the first non-nil error it is called with.
func (self *stateOrderList) setError(err error) {
if self.dbErr == nil {
self.dbErr = err
}
}
func (c *stateOrderList) getTrie(db Database) Trie {
if c.trie == nil {
var err error
c.trie, err = db.OpenStorageTrie(c.price, c.data.Root)
if err != nil {
c.trie, _ = db.OpenStorageTrie(c.price, EmptyHash)
c.setError(fmt.Errorf("can't create storage trie: %v", err))
}
}
return c.trie
}
// GetState returns a value in orderId storage.
func (self *stateOrderList) GetOrderAmount(db Database, orderId common.Hash) common.Hash {
amount, exists := self.cachedStorage[orderId]
if exists {
return amount
}
// Load from DB in case it is missing.
enc, err := self.getTrie(db).TryGet(orderId[:])
if err != nil {
self.setError(err)
return EmptyHash
}
if len(enc) > 0 {
_, content, _, err := rlp.Split(enc)
if err != nil {
self.setError(err)
}
amount.SetBytes(content)
}
if (amount != common.Hash{}) {
self.cachedStorage[orderId] = amount
}
return amount
}
// SetState updates a value in orderId storage.
func (self *stateOrderList) insertOrderItem(db Database, orderId common.Hash, amount common.Hash) {
self.setOrderItem(orderId, amount)
self.setError(self.getTrie(db).TryUpdate(orderId[:], amount[:]))
}
// SetState updates a value in orderId storage.
func (self *stateOrderList) removeOrderItem(db Database, orderId common.Hash) {
tr := self.getTrie(db)
self.setError(tr.TryDelete(orderId[:]))
self.setOrderItem(orderId, EmptyHash)
}
func (self *stateOrderList) setOrderItem(orderId common.Hash, amount common.Hash) {
self.cachedStorage[orderId] = amount
self.dirtyStorage[orderId] = amount
if self.onDirty != nil {
self.onDirty(self.Price())
self.onDirty = nil
}
}
// updateAskTrie writes cached storage modifications into the object's storage trie.
func (self *stateOrderList) updateTrie(db Database) Trie {
tr := self.getTrie(db)
for orderId, amount := range self.dirtyStorage {
delete(self.dirtyStorage, orderId)
if amount == EmptyHash {
self.setError(tr.TryDelete(orderId[:]))
continue
}
v, _ := rlp.EncodeToBytes(bytes.TrimLeft(amount[:], "\x00"))
self.setError(tr.TryUpdate(orderId[:], v))
}
return tr
}
// UpdateRoot sets the trie root to the current root orderId of
func (self *stateOrderList) updateRoot(db Database) error {
self.updateTrie(db)
if self.dbErr != nil {
return self.dbErr
}
root, err := self.trie.Commit(nil)
if err == nil {
self.data.Root = root
}
return err
}
func (self *stateOrderList) deepCopy(db *TradingStateDB, onDirty func(price common.Hash)) *stateOrderList {
stateOrderList := newStateOrderList(db, self.orderType, self.orderBook, self.price, self.data, onDirty)
if self.trie != nil {
stateOrderList.trie = db.db.CopyTrie(self.trie)
}
for orderId, amount := range self.dirtyStorage {
stateOrderList.dirtyStorage[orderId] = amount
}
for orderId, amount := range self.cachedStorage {
stateOrderList.cachedStorage[orderId] = amount
}
return stateOrderList
}
// AddVolume removes amount from c's balance.
// It is used to add funds to the destination exchanges of a transfer.
func (c *stateOrderList) AddVolume(amount *big.Int) {
c.setVolume(new(big.Int).Add(c.data.Volume, amount))
}
// AddVolume removes amount from c's balance.
// It is used to add funds to the destination exchanges of a transfer.
func (c *stateOrderList) subVolume(amount *big.Int) {
c.setVolume(new(big.Int).Sub(c.data.Volume, amount))
}
func (self *stateOrderList) setVolume(volume *big.Int) {
self.data.Volume = volume
if self.onDirty != nil {
self.onDirty(self.price)
self.onDirty = nil
}
}
// Returns the address of the contract/orderId
func (c *stateOrderList) Price() common.Hash {
return c.price
}
func (self *stateOrderList) Volume() *big.Int {
return self.data.Volume
}

View file

@ -0,0 +1,805 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package tradingstate
import (
"fmt"
"io"
"math/big"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/rlp"
)
// stateObject represents an Ethereum orderId which is being modified.
//
// The usage pattern is as follows:
// First you need to obtain a state object.
// tradingExchangeObject values can be accessed and modified through the object.
// Finally, call CommitAskTrie to write the modified storage trie into a database.
type tradingExchanges struct {
orderBookHash common.Hash
data tradingExchangeObject
db *TradingStateDB
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by TradingStateDB.Commit.
dbErr error
// Write caches.
asksTrie Trie // storage trie, which becomes non-nil on first access
bidsTrie Trie // storage trie, which becomes non-nil on first access
ordersTrie Trie // storage trie, which becomes non-nil on first access
liquidationPriceTrie Trie
stateAskObjects map[common.Hash]*stateOrderList
stateAskObjectsDirty map[common.Hash]struct{}
stateBidObjects map[common.Hash]*stateOrderList
stateBidObjectsDirty map[common.Hash]struct{}
stateOrderObjects map[common.Hash]*stateOrderItem
stateOrderObjectsDirty map[common.Hash]struct{}
liquidationPriceStates map[common.Hash]*liquidationPriceState
liquidationPriceStatesDirty map[common.Hash]struct{}
onDirty func(hash common.Hash) // Callback method to mark a state object newly dirty
}
// empty returns whether the orderId is considered empty.
func (s *tradingExchanges) empty() bool {
if s.data.Nonce != 0 {
return false
}
if s.data.LendingCount != nil && s.data.LendingCount.Sign() > 0 {
return false
}
if s.data.LastPrice != nil && s.data.LastPrice.Sign() > 0 {
return false
}
if s.data.MediumPrice != nil && s.data.MediumPrice.Sign() > 0 {
return false
}
if s.data.MediumPriceBeforeEpoch != nil && s.data.MediumPriceBeforeEpoch.Sign() > 0 {
return false
}
if s.data.TotalQuantity != nil && s.data.TotalQuantity.Sign() > 0 {
return false
}
if !common.EmptyHash(s.data.AskRoot) {
return false
}
if !common.EmptyHash(s.data.BidRoot) {
return false
}
if !common.EmptyHash(s.data.OrderRoot) {
return false
}
if !common.EmptyHash(s.data.LiquidationPriceRoot) {
return false
}
return true
}
// newObject creates a state object.
func newStateExchanges(db *TradingStateDB, hash common.Hash, data tradingExchangeObject, onDirty func(addr common.Hash)) *tradingExchanges {
return &tradingExchanges{
db: db,
orderBookHash: hash,
data: data,
stateAskObjects: make(map[common.Hash]*stateOrderList),
stateBidObjects: make(map[common.Hash]*stateOrderList),
stateOrderObjects: make(map[common.Hash]*stateOrderItem),
liquidationPriceStates: make(map[common.Hash]*liquidationPriceState),
stateAskObjectsDirty: make(map[common.Hash]struct{}),
stateBidObjectsDirty: make(map[common.Hash]struct{}),
stateOrderObjectsDirty: make(map[common.Hash]struct{}),
liquidationPriceStatesDirty: make(map[common.Hash]struct{}),
onDirty: onDirty,
}
}
// EncodeRLP implements rlp.Encoder.
func (c *tradingExchanges) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, c.data)
}
// setError remembers the first non-nil error it is called with.
func (self *tradingExchanges) setError(err error) {
if self.dbErr == nil {
self.dbErr = err
}
}
func (c *tradingExchanges) getAsksTrie(db Database) Trie {
if c.asksTrie == nil {
var err error
c.asksTrie, err = db.OpenStorageTrie(c.orderBookHash, c.data.AskRoot)
if err != nil {
c.asksTrie, _ = db.OpenStorageTrie(c.orderBookHash, EmptyHash)
c.setError(fmt.Errorf("can't create asks trie: %v", err))
}
}
return c.asksTrie
}
func (c *tradingExchanges) getOrdersTrie(db Database) Trie {
if c.ordersTrie == nil {
var err error
c.ordersTrie, err = db.OpenStorageTrie(c.orderBookHash, c.data.OrderRoot)
if err != nil {
c.ordersTrie, _ = db.OpenStorageTrie(c.orderBookHash, EmptyHash)
c.setError(fmt.Errorf("can't create asks trie: %v", err))
}
}
return c.ordersTrie
}
func (c *tradingExchanges) getBestPriceAsksTrie(db Database) common.Hash {
trie := c.getAsksTrie(db)
encKey, encValue, err := trie.TryGetBestLeftKeyAndValue()
if err != nil {
log.Error("Failed find best price ask trie ", "orderbook", c.orderBookHash.Hex())
return EmptyHash
}
if len(encKey) == 0 || len(encValue) == 0 {
log.Debug("Not found get best ask trie", "encKey", encKey, "encValue", encValue)
return EmptyHash
}
price := common.BytesToHash(encKey)
if _, exit := c.stateAskObjects[price]; !exit {
var data orderList
if err := rlp.DecodeBytes(encValue, &data); err != nil {
log.Error("Failed to decode state get best ask trie", "err", err)
return EmptyHash
}
obj := newStateOrderList(c.db, Bid, c.orderBookHash, price, data, c.MarkStateAskObjectDirty)
c.stateAskObjects[price] = obj
}
return common.BytesToHash(encKey)
}
func (c *tradingExchanges) getBestBidsTrie(db Database) common.Hash {
trie := c.getBidsTrie(db)
encKey, encValue, err := trie.TryGetBestRightKeyAndValue()
if err != nil {
log.Error("Failed find best price bid trie ", "orderbook", c.orderBookHash.Hex())
return EmptyHash
}
if len(encKey) == 0 || len(encValue) == 0 {
log.Debug("Not found get best bid trie", "encKey", encKey, "encValue", encValue)
return EmptyHash
}
price := common.BytesToHash(encKey)
if _, exit := c.stateBidObjects[price]; !exit {
var data orderList
if err := rlp.DecodeBytes(encValue, &data); err != nil {
log.Error("Failed to decode state get best bid trie", "err", err)
return EmptyHash
}
// Insert into the live set.
obj := newStateOrderList(c.db, Bid, c.orderBookHash, price, data, c.MarkStateBidObjectDirty)
c.stateBidObjects[price] = obj
}
return common.BytesToHash(encKey)
}
// updateAskTrie writes cached storage modifications into the object's storage trie.
func (self *tradingExchanges) updateAsksTrie(db Database) Trie {
tr := self.getAsksTrie(db)
for price, orderList := range self.stateAskObjects {
if _, isDirty := self.stateAskObjectsDirty[price]; isDirty {
delete(self.stateAskObjectsDirty, price)
if orderList.empty() {
self.setError(tr.TryDelete(price[:]))
continue
}
orderList.updateRoot(db)
// Encoding []byte cannot fail, ok to ignore the error.
v, _ := rlp.EncodeToBytes(orderList)
self.setError(tr.TryUpdate(price[:], v))
}
}
return tr
}
// CommitAskTrie the storage trie of the object to dwb.
// This updates the trie root.
func (self *tradingExchanges) updateAsksRoot(db Database) error {
self.updateAsksTrie(db)
if self.dbErr != nil {
return self.dbErr
}
self.data.AskRoot = self.asksTrie.Hash()
return nil
}
// CommitAskTrie the storage trie of the object to dwb.
// This updates the trie root.
func (self *tradingExchanges) CommitAsksTrie(db Database) error {
self.updateAsksTrie(db)
if self.dbErr != nil {
return self.dbErr
}
root, err := self.asksTrie.Commit(func(leaf []byte, parent common.Hash) error {
var orderList orderList
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
return nil
}
if orderList.Root != EmptyRoot {
db.TrieDB().Reference(orderList.Root, parent)
}
return nil
})
if err == nil {
self.data.AskRoot = root
}
return err
}
func (c *tradingExchanges) getBidsTrie(db Database) Trie {
if c.bidsTrie == nil {
var err error
c.bidsTrie, err = db.OpenStorageTrie(c.orderBookHash, c.data.BidRoot)
if err != nil {
c.bidsTrie, _ = db.OpenStorageTrie(c.orderBookHash, EmptyHash)
c.setError(fmt.Errorf("can't create bids trie: %v", err))
}
}
return c.bidsTrie
}
// updateAskTrie writes cached storage modifications into the object's storage trie.
func (self *tradingExchanges) updateBidsTrie(db Database) Trie {
tr := self.getBidsTrie(db)
for price, orderList := range self.stateBidObjects {
if _, isDirty := self.stateBidObjectsDirty[price]; isDirty {
delete(self.stateBidObjectsDirty, price)
if orderList.empty() {
self.setError(tr.TryDelete(price[:]))
continue
}
orderList.updateRoot(db)
// Encoding []byte cannot fail, ok to ignore the error.
v, _ := rlp.EncodeToBytes(orderList)
self.setError(tr.TryUpdate(price[:], v))
}
}
return tr
}
func (self *tradingExchanges) updateBidsRoot(db Database) {
self.updateBidsTrie(db)
self.data.BidRoot = self.bidsTrie.Hash()
}
// CommitAskTrie the storage trie of the object to dwb.
// This updates the trie root.
func (self *tradingExchanges) CommitBidsTrie(db Database) error {
self.updateBidsTrie(db)
if self.dbErr != nil {
return self.dbErr
}
root, err := self.bidsTrie.Commit(func(leaf []byte, parent common.Hash) error {
var orderList orderList
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
return nil
}
if orderList.Root != EmptyRoot {
db.TrieDB().Reference(orderList.Root, parent)
}
return nil
})
if err == nil {
self.data.BidRoot = root
}
return err
}
func (self *tradingExchanges) deepCopy(db *TradingStateDB, onDirty func(hash common.Hash)) *tradingExchanges {
stateExchanges := newStateExchanges(db, self.orderBookHash, self.data, onDirty)
if self.asksTrie != nil {
stateExchanges.asksTrie = db.db.CopyTrie(self.asksTrie)
}
if self.bidsTrie != nil {
stateExchanges.bidsTrie = db.db.CopyTrie(self.bidsTrie)
}
if self.ordersTrie != nil {
stateExchanges.ordersTrie = db.db.CopyTrie(self.ordersTrie)
}
for price, bidObject := range self.stateBidObjects {
stateExchanges.stateBidObjects[price] = bidObject.deepCopy(db, self.MarkStateBidObjectDirty)
}
for price := range self.stateBidObjectsDirty {
stateExchanges.stateBidObjectsDirty[price] = struct{}{}
}
for price, askObject := range self.stateAskObjects {
stateExchanges.stateAskObjects[price] = askObject.deepCopy(db, self.MarkStateAskObjectDirty)
}
for price := range self.stateAskObjectsDirty {
stateExchanges.stateAskObjectsDirty[price] = struct{}{}
}
for orderId, orderItem := range self.stateOrderObjects {
stateExchanges.stateOrderObjects[orderId] = orderItem.deepCopy(self.MarkStateOrderObjectDirty)
}
for orderId := range self.stateOrderObjectsDirty {
stateExchanges.stateOrderObjectsDirty[orderId] = struct{}{}
}
for price, liquidationPrice := range self.liquidationPriceStates {
stateExchanges.liquidationPriceStates[price] = liquidationPrice.deepCopy(db, self.MarkStateLiquidationPriceDirty)
}
for price := range self.liquidationPriceStatesDirty {
stateExchanges.liquidationPriceStatesDirty[price] = struct{}{}
}
return stateExchanges
}
// Returns the address of the contract/orderId
func (c *tradingExchanges) Hash() common.Hash {
return c.orderBookHash
}
func (self *tradingExchanges) SetNonce(nonce uint64) {
self.setNonce(nonce)
}
func (self *tradingExchanges) setNonce(nonce uint64) {
self.data.Nonce = nonce
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
}
func (self *tradingExchanges) Nonce() uint64 {
return self.data.Nonce
}
func (self *tradingExchanges) setLastPrice(price *big.Int) {
self.data.LastPrice = price
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
}
func (self *tradingExchanges) setMediumPriceBeforeEpoch(price *big.Int) {
self.data.MediumPriceBeforeEpoch = price
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
}
func (self *tradingExchanges) setMediumPrice(price *big.Int, quantity *big.Int) {
self.data.MediumPrice = price
self.data.TotalQuantity = quantity
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
}
// updateStateExchangeObject writes the given object to the trie.
func (self *tradingExchanges) removeStateOrderListAskObject(db Database, stateOrderList *stateOrderList) {
self.setError(self.asksTrie.TryDelete(stateOrderList.price[:]))
}
// updateStateExchangeObject writes the given object to the trie.
func (self *tradingExchanges) removeStateOrderListBidObject(db Database, stateOrderList *stateOrderList) {
self.setError(self.bidsTrie.TryDelete(stateOrderList.price[:]))
}
// Retrieve a state object given my the address. Returns nil if not found.
func (self *tradingExchanges) getStateOrderListAskObject(db Database, price common.Hash) (stateOrderList *stateOrderList) {
// Prefer 'live' objects.
if obj := self.stateAskObjects[price]; obj != nil {
return obj
}
// Load the object from the database.
enc, err := self.getAsksTrie(db).TryGet(price[:])
if len(enc) == 0 {
self.setError(err)
return nil
}
var data orderList
if err := rlp.DecodeBytes(enc, &data); err != nil {
log.Error("Failed to decode state order list object", "orderId", price, "err", err)
return nil
}
// Insert into the live set.
obj := newStateOrderList(self.db, Bid, self.orderBookHash, price, data, self.MarkStateAskObjectDirty)
self.stateAskObjects[price] = obj
return obj
}
// MarkStateAskObjectDirty adds the specified object to the dirty map to avoid costly
// state object cache iteration to find a handful of modified ones.
func (self *tradingExchanges) MarkStateAskObjectDirty(price common.Hash) {
self.stateAskObjectsDirty[price] = struct{}{}
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
}
// createStateOrderListObject creates a new state object. If there is an existing orderId with
// the given address, it is overwritten and returned as the second return value.
func (self *tradingExchanges) createStateOrderListAskObject(db Database, price common.Hash) (newobj *stateOrderList) {
newobj = newStateOrderList(self.db, Ask, self.orderBookHash, price, orderList{Volume: Zero}, self.MarkStateAskObjectDirty)
self.stateAskObjects[price] = newobj
self.stateAskObjectsDirty[price] = struct{}{}
data, err := rlp.EncodeToBytes(newobj)
if err != nil {
panic(fmt.Errorf("can't encode order list object at %x: %v", price[:], err))
}
self.setError(self.asksTrie.TryUpdate(price[:], data))
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
return newobj
}
// Retrieve a state object given my the address. Returns nil if not found.
func (self *tradingExchanges) getStateBidOrderListObject(db Database, price common.Hash) (stateOrderList *stateOrderList) {
// Prefer 'live' objects.
if obj := self.stateBidObjects[price]; obj != nil {
return obj
}
// Load the object from the database.
enc, err := self.getBidsTrie(db).TryGet(price[:])
if len(enc) == 0 {
self.setError(err)
return nil
}
var data orderList
if err := rlp.DecodeBytes(enc, &data); err != nil {
log.Error("Failed to decode state order list object", "orderId", price, "err", err)
return nil
}
// Insert into the live set.
obj := newStateOrderList(self.db, Bid, self.orderBookHash, price, data, self.MarkStateBidObjectDirty)
self.stateBidObjects[price] = obj
return obj
}
// MarkStateAskObjectDirty adds the specified object to the dirty map to avoid costly
// state object cache iteration to find a handful of modified ones.
func (self *tradingExchanges) MarkStateBidObjectDirty(price common.Hash) {
self.stateBidObjectsDirty[price] = struct{}{}
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
}
// createStateOrderListObject creates a new state object. If there is an existing orderId with
// the given address, it is overwritten and returned as the second return value.
func (self *tradingExchanges) createStateBidOrderListObject(db Database, price common.Hash) (newobj *stateOrderList) {
newobj = newStateOrderList(self.db, Bid, self.orderBookHash, price, orderList{Volume: Zero}, self.MarkStateBidObjectDirty)
self.stateBidObjects[price] = newobj
self.stateBidObjectsDirty[price] = struct{}{}
data, err := rlp.EncodeToBytes(newobj)
if err != nil {
panic(fmt.Errorf("can't encode order list object at %x: %v", price[:], err))
}
self.setError(self.bidsTrie.TryUpdate(price[:], data))
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
return newobj
}
// Retrieve a state object given my the address. Returns nil if not found.
func (self *tradingExchanges) getStateOrderObject(db Database, orderId common.Hash) (stateOrderItem *stateOrderItem) {
// Prefer 'live' objects.
if obj := self.stateOrderObjects[orderId]; obj != nil {
return obj
}
// Load the object from the database.
enc, err := self.getOrdersTrie(db).TryGet(orderId[:])
if len(enc) == 0 {
self.setError(err)
return nil
}
var data OrderItem
if err := rlp.DecodeBytes(enc, &data); err != nil {
log.Error("Failed to decode state order object", "orderId", orderId, "err", err)
return nil
}
// Insert into the live set.
obj := newStateOrderItem(self.orderBookHash, orderId, data, self.MarkStateOrderObjectDirty)
self.stateOrderObjects[orderId] = obj
return obj
}
// MarkStateAskObjectDirty adds the specified object to the dirty map to avoid costly
// state object cache iteration to find a handful of modified ones.
func (self *tradingExchanges) MarkStateOrderObjectDirty(orderId common.Hash) {
self.stateOrderObjectsDirty[orderId] = struct{}{}
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
}
// createStateOrderListObject creates a new state object. If there is an existing orderId with
// the given address, it is overwritten and returned as the second return value.
func (self *tradingExchanges) createStateOrderObject(db Database, orderId common.Hash, order OrderItem) (newobj *stateOrderItem) {
newobj = newStateOrderItem(self.orderBookHash, orderId, order, self.MarkStateOrderObjectDirty)
orderIdHash := common.BigToHash(new(big.Int).SetUint64(order.OrderID))
self.stateOrderObjects[orderIdHash] = newobj
self.stateOrderObjectsDirty[orderIdHash] = struct{}{}
if self.onDirty != nil {
self.onDirty(self.orderBookHash)
self.onDirty = nil
}
return newobj
}
// updateAskTrie writes cached storage modifications into the object's storage trie.
func (self *tradingExchanges) updateOrdersTrie(db Database) Trie {
tr := self.getOrdersTrie(db)
for orderId, orderItem := range self.stateOrderObjects {
if _, isDirty := self.stateOrderObjectsDirty[orderId]; isDirty {
delete(self.stateOrderObjectsDirty, orderId)
if orderItem.empty() {
self.setError(tr.TryDelete(orderId[:]))
continue
}
// Encoding []byte cannot fail, ok to ignore the error.
v, _ := rlp.EncodeToBytes(orderItem)
self.setError(tr.TryUpdate(orderId[:], v))
}
}
return tr
}
// CommitAskTrie the storage trie of the object to dwb.
// This updates the trie root.
func (self *tradingExchanges) updateOrdersRoot(db Database) {
self.updateOrdersTrie(db)
self.data.OrderRoot = self.ordersTrie.Hash()
}
// CommitAskTrie the storage trie of the object to dwb.
// This updates the trie root.
func (self *tradingExchanges) CommitOrdersTrie(db Database) error {
self.updateOrdersTrie(db)
if self.dbErr != nil {
return self.dbErr
}
root, err := self.ordersTrie.Commit(nil)
if err == nil {
self.data.OrderRoot = root
}
return err
}
func (self *tradingExchanges) MarkStateLiquidationPriceDirty(price common.Hash) {
self.liquidationPriceStatesDirty[price] = struct{}{}
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
}
func (self *tradingExchanges) createStateLiquidationPrice(db Database, liquidationPrice common.Hash) (newobj *liquidationPriceState) {
newobj = newLiquidationPriceState(self.db, self.orderBookHash, liquidationPrice, orderList{Volume: Zero}, self.MarkStateLiquidationPriceDirty)
self.liquidationPriceStates[liquidationPrice] = newobj
self.liquidationPriceStatesDirty[liquidationPrice] = struct{}{}
data, err := rlp.EncodeToBytes(newobj)
if err != nil {
panic(fmt.Errorf("can't encode liquidation price object at %x: %v", liquidationPrice[:], err))
}
self.setError(self.getLiquidationPriceTrie(db).TryUpdate(liquidationPrice[:], data))
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
return newobj
}
func (self *tradingExchanges) getLiquidationPriceTrie(db Database) Trie {
if self.liquidationPriceTrie == nil {
var err error
self.liquidationPriceTrie, err = db.OpenStorageTrie(self.orderBookHash, self.data.LiquidationPriceRoot)
if err != nil {
self.liquidationPriceTrie, _ = db.OpenStorageTrie(self.orderBookHash, EmptyHash)
self.setError(fmt.Errorf("can't create liquidation liquidationPrice trie: %v", err))
}
}
return self.liquidationPriceTrie
}
func (self *tradingExchanges) getStateLiquidationPrice(db Database, price common.Hash) (stateObject *liquidationPriceState) {
// Prefer 'live' objects.
if obj := self.liquidationPriceStates[price]; obj != nil {
return obj
}
// Load the object from the database.
enc, err := self.getLiquidationPriceTrie(db).TryGet(price[:])
if len(enc) == 0 {
self.setError(err)
return nil
}
var data orderList
if err := rlp.DecodeBytes(enc, &data); err != nil {
log.Error("Failed to decode state liquidation liquidationPrice", "liquidationPrice", price, "err", err)
return nil
}
// Insert into the live set.
obj := newLiquidationPriceState(self.db, self.orderBookHash, price, data, self.MarkStateLiquidationPriceDirty)
self.liquidationPriceStates[price] = obj
return obj
}
func (self *tradingExchanges) getLowestLiquidationPrice(db Database) (common.Hash, *liquidationPriceState) {
trie := self.getLiquidationPriceTrie(db)
encKey, encValue, err := trie.TryGetBestLeftKeyAndValue()
if err != nil {
log.Error("Failed find best liquidationPrice ask trie ", "orderbook", self.orderBookHash.Hex())
return EmptyHash, nil
}
if len(encKey) == 0 || len(encValue) == 0 {
log.Debug("Not found get best ask trie", "encKey", encKey, "encValue", encValue)
return EmptyHash, nil
}
price := common.BytesToHash(encKey)
obj := self.liquidationPriceStates[price]
if obj == nil {
var data orderList
if err := rlp.DecodeBytes(encValue, &data); err != nil {
log.Error("Failed to decode state get best ask trie", "err", err)
return EmptyHash, nil
}
obj = newLiquidationPriceState(self.db, self.orderBookHash, price, data, self.MarkStateLiquidationPriceDirty)
self.liquidationPriceStates[price] = obj
}
return price, obj
}
func (self *tradingExchanges) getAllLowerLiquidationPrice(db Database, limit common.Hash) map[common.Hash]*liquidationPriceState {
trie := self.getLiquidationPriceTrie(db)
encKeys, encValues, err := trie.TryGetAllLeftKeyAndValue(limit.Bytes())
result := map[common.Hash]*liquidationPriceState{}
if err != nil || len(encKeys) != len(encValues) {
log.Error("Failed get lower liquidation price trie ", "orderbook", self.orderBookHash.Hex(), "encKeys", len(encKeys), "encValues", len(encValues))
return result
}
if len(encKeys) == 0 || len(encValues) == 0 {
log.Debug("Not found get lower liquidation price trie ", "limit", limit)
return result
}
for i := range encKeys {
price := common.BytesToHash(encKeys[i])
obj := self.liquidationPriceStates[price]
if obj == nil {
var data orderList
if err := rlp.DecodeBytes(encValues[i], &data); err != nil {
log.Error("Failed to decode state get all lower liquidation price trie", "price", price, "encValues", encValues[i], "err", err)
return result
}
obj = newLiquidationPriceState(self.db, self.orderBookHash, price, data, self.MarkStateLiquidationPriceDirty)
self.liquidationPriceStates[price] = obj
}
if obj.empty() {
continue
}
result[price] = obj
}
return result
}
func (self *tradingExchanges) getHighestLiquidationPrice(db Database) (common.Hash, *liquidationPriceState) {
trie := self.getLiquidationPriceTrie(db)
encKey, encValue, err := trie.TryGetBestRightKeyAndValue()
if err != nil {
log.Error("Failed find best liquidationPrice ask trie ", "orderbook", self.orderBookHash.Hex())
return EmptyHash, nil
}
if len(encKey) == 0 || len(encValue) == 0 {
log.Debug("Not found get best ask trie", "encKey", encKey, "encValue", encValue)
return EmptyHash, nil
}
price := common.BytesToHash(encKey)
obj := self.liquidationPriceStates[price]
if obj == nil {
var data orderList
if err := rlp.DecodeBytes(encValue, &data); err != nil {
log.Error("Failed to decode state get best ask trie", "err", err)
return EmptyHash, nil
}
obj = newLiquidationPriceState(self.db, self.orderBookHash, price, data, self.MarkStateLiquidationPriceDirty)
self.liquidationPriceStates[price] = obj
}
if obj.empty() {
return EmptyHash, nil
}
return price, obj
}
func (self *tradingExchanges) updateLiquidationPriceTrie(db Database) Trie {
tr := self.getLiquidationPriceTrie(db)
for price, stateObject := range self.liquidationPriceStates {
if _, isDirty := self.liquidationPriceStatesDirty[price]; isDirty {
delete(self.liquidationPriceStatesDirty, price)
if stateObject.empty() {
self.setError(tr.TryDelete(price[:]))
continue
}
stateObject.updateRoot(db)
// Encoding []byte cannot fail, ok to ignore the error.
v, _ := rlp.EncodeToBytes(stateObject)
self.setError(tr.TryUpdate(price[:], v))
}
}
return tr
}
func (self *tradingExchanges) updateLiquidationPriceRoot(db Database) {
self.updateLiquidationPriceTrie(db)
self.data.LiquidationPriceRoot = self.liquidationPriceTrie.Hash()
}
func (self *tradingExchanges) CommitLiquidationPriceTrie(db Database) error {
self.updateLiquidationPriceTrie(db)
if self.dbErr != nil {
return self.dbErr
}
root, err := self.liquidationPriceTrie.Commit(func(leaf []byte, parent common.Hash) error {
var orderList orderList
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
return nil
}
if orderList.Root != EmptyRoot {
db.TrieDB().Reference(orderList.Root, parent)
}
return nil
})
if err == nil {
self.data.LiquidationPriceRoot = root
}
return err
}
func (c *tradingExchanges) addLendingCount(amount *big.Int) {
c.setLendingCount(new(big.Int).Add(c.data.LendingCount, amount))
}
func (c *tradingExchanges) subLendingCount(amount *big.Int) {
c.setLendingCount(new(big.Int).Sub(c.data.LendingCount, amount))
}
func (self *tradingExchanges) setLendingCount(volume *big.Int) {
self.data.LendingCount = volume
if self.onDirty != nil {
self.onDirty(self.orderBookHash)
self.onDirty = nil
}
}

View file

@ -0,0 +1,726 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package state provides a caching layer atop the Ethereum state trie.
package tradingstate
import (
"fmt"
"math/big"
"sort"
"sync"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/rlp"
)
type revision struct {
id int
journalIndex int
}
// StateDBs within the ethereum protocol are used to store anything
// within the merkle trie. StateDBs take care of caching and storing
// nested states. It's the general query interface to retrieve:
// * Contracts
// * Accounts
type TradingStateDB struct {
db Database
trie Trie
// This map holds 'live' objects, which will get modified while processing a state transition.
stateExhangeObjects map[common.Hash]*tradingExchanges
stateExhangeObjectsDirty map[common.Hash]struct{}
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by TradingStateDB.Commit.
dbErr error
// Journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot.
journal journal
validRevisions []revision
nextRevisionId int
lock sync.Mutex
}
// Create a new state from a given trie.
func New(root common.Hash, db Database) (*TradingStateDB, error) {
tr, err := db.OpenTrie(root)
if err != nil {
log.Error("Error when init new trading state trie ", "root", root.Hex(), "err", err)
return nil, err
}
return &TradingStateDB{
db: db,
trie: tr,
stateExhangeObjects: make(map[common.Hash]*tradingExchanges),
stateExhangeObjectsDirty: make(map[common.Hash]struct{}),
}, nil
}
// setError remembers the first non-nil error it is called with.
func (self *TradingStateDB) setError(err error) {
if self.dbErr == nil {
self.dbErr = err
}
}
func (self *TradingStateDB) Error() error {
return self.dbErr
}
// Exist reports whether the given orderId address exists in the state.
// Notably this also returns true for suicided exchanges.
func (self *TradingStateDB) Exist(addr common.Hash) bool {
return self.getStateExchangeObject(addr) != nil
}
// Empty returns whether the state object is either non-existent
// or empty according to the EIP161 specification (balance = nonce = code = 0)
func (self *TradingStateDB) Empty(addr common.Hash) bool {
so := self.getStateExchangeObject(addr)
return so == nil || so.empty()
}
func (self *TradingStateDB) GetNonce(addr common.Hash) uint64 {
stateObject := self.getStateExchangeObject(addr)
if stateObject != nil {
return stateObject.Nonce()
}
return 0
}
func (self *TradingStateDB) GetLastPrice(addr common.Hash) *big.Int {
stateObject := self.getStateExchangeObject(addr)
if stateObject != nil {
return stateObject.data.LastPrice
}
return nil
}
func (self *TradingStateDB) GetMediumPriceBeforeEpoch(addr common.Hash) *big.Int {
stateObject := self.getStateExchangeObject(addr)
if stateObject != nil {
return stateObject.data.MediumPriceBeforeEpoch
}
return Zero
}
func (self *TradingStateDB) GetMediumPriceAndTotalAmount(addr common.Hash) (*big.Int, *big.Int) {
stateObject := self.getStateExchangeObject(addr)
if stateObject != nil {
return stateObject.data.MediumPrice, stateObject.data.TotalQuantity
}
return Zero, Zero
}
// Database retrieves the low level database supporting the lower level trie ops.
func (self *TradingStateDB) Database() Database {
return self.db
}
func (self *TradingStateDB) SetNonce(addr common.Hash, nonce uint64) {
stateObject := self.GetOrNewStateExchangeObject(addr)
if stateObject != nil {
self.journal = append(self.journal, nonceChange{
hash: addr,
prev: self.GetNonce(addr),
})
stateObject.SetNonce(nonce)
}
}
func (self *TradingStateDB) SetLastPrice(addr common.Hash, price *big.Int) {
stateObject := self.GetOrNewStateExchangeObject(addr)
if stateObject != nil {
self.journal = append(self.journal, lastPriceChange{
hash: addr,
prev: stateObject.data.LastPrice,
})
stateObject.setLastPrice(price)
}
}
func (self *TradingStateDB) SetMediumPrice(addr common.Hash, price *big.Int, quantity *big.Int) {
stateObject := self.GetOrNewStateExchangeObject(addr)
if stateObject != nil {
self.journal = append(self.journal, mediumPriceChange{
hash: addr,
prevPrice: stateObject.data.MediumPrice,
prevQuantity: stateObject.data.TotalQuantity,
})
stateObject.setMediumPrice(price, quantity)
}
}
func (self *TradingStateDB) SetMediumPriceBeforeEpoch(addr common.Hash, price *big.Int) {
stateObject := self.GetOrNewStateExchangeObject(addr)
if stateObject != nil {
self.journal = append(self.journal, mediumPriceBeforeEpochChange{
hash: addr,
prevPrice: stateObject.data.MediumPriceBeforeEpoch,
})
stateObject.setMediumPriceBeforeEpoch(price)
}
}
func (self *TradingStateDB) InsertOrderItem(orderBook common.Hash, orderId common.Hash, order OrderItem) {
priceHash := common.BigToHash(order.Price)
stateExchange := self.getStateExchangeObject(orderBook)
if stateExchange == nil {
stateExchange = self.createExchangeObject(orderBook)
}
var stateOrderList *stateOrderList
switch order.Side {
case Ask:
stateOrderList = stateExchange.getStateOrderListAskObject(self.db, priceHash)
if stateOrderList == nil {
stateOrderList = stateExchange.createStateOrderListAskObject(self.db, priceHash)
}
case Bid:
stateOrderList = stateExchange.getStateBidOrderListObject(self.db, priceHash)
if stateOrderList == nil {
stateOrderList = stateExchange.createStateBidOrderListObject(self.db, priceHash)
}
default:
return
}
self.journal = append(self.journal, insertOrder{
orderBook: orderBook,
orderId: orderId,
order: &order,
})
stateExchange.createStateOrderObject(self.db, orderId, order)
stateOrderList.insertOrderItem(self.db, orderId, common.BigToHash(order.Quantity))
stateOrderList.AddVolume(order.Quantity)
}
func (self *TradingStateDB) GetOrder(orderBook common.Hash, orderId common.Hash) OrderItem {
stateObject := self.GetOrNewStateExchangeObject(orderBook)
if stateObject == nil {
return EmptyOrder
}
stateOrderItem := stateObject.getStateOrderObject(self.db, orderId)
if stateOrderItem == nil {
return EmptyOrder
}
return stateOrderItem.data
}
func (self *TradingStateDB) SubAmountOrderItem(orderBook common.Hash, orderId common.Hash, price *big.Int, amount *big.Int, side string) error {
priceHash := common.BigToHash(price)
stateObject := self.GetOrNewStateExchangeObject(orderBook)
if stateObject == nil {
return fmt.Errorf("Order book not found : %s ", orderBook.Hex())
}
var stateOrderList *stateOrderList
switch side {
case Ask:
stateOrderList = stateObject.getStateOrderListAskObject(self.db, priceHash)
case Bid:
stateOrderList = stateObject.getStateBidOrderListObject(self.db, priceHash)
default:
return fmt.Errorf("Order type not found : %s ", side)
}
if stateOrderList == nil || stateOrderList.empty() {
return fmt.Errorf("Order list empty order book : %s , order id : %s , price : %s ", orderBook, orderId.Hex(), priceHash.Hex())
}
stateOrderItem := stateObject.getStateOrderObject(self.db, orderId)
if stateOrderItem == nil || stateOrderItem.empty() {
return fmt.Errorf("Order item empty order book : %s , order id : %s , price : %s ", orderBook, orderId.Hex(), priceHash.Hex())
}
currentAmount := new(big.Int).SetBytes(stateOrderList.GetOrderAmount(self.db, orderId).Bytes()[:])
if currentAmount.Cmp(amount) < 0 {
return fmt.Errorf("Order amount not enough : %s , have : %d , want : %d ", orderId.Hex(), currentAmount, amount)
}
self.journal = append(self.journal, subAmountOrder{
orderBook: orderBook,
orderId: orderId,
order: self.GetOrder(orderBook, orderId),
amount: amount,
})
newAmount := new(big.Int).Sub(currentAmount, amount)
log.Debug("SubAmountOrderItem", "orderId", orderId.Hex(), "side", side, "price", price.Uint64(), "amount", amount.Uint64(), "new amount", newAmount.Uint64())
stateOrderList.subVolume(amount)
stateOrderItem.setVolume(newAmount)
if newAmount.Sign() == 0 {
stateOrderList.removeOrderItem(self.db, orderId)
} else {
stateOrderList.setOrderItem(orderId, common.BigToHash(newAmount))
}
if stateOrderList.empty() {
switch side {
case Ask:
stateObject.removeStateOrderListAskObject(self.db, stateOrderList)
case Bid:
stateObject.removeStateOrderListBidObject(self.db, stateOrderList)
default:
}
}
return nil
}
func (self *TradingStateDB) CancelOrder(orderBook common.Hash, order *OrderItem) error {
orderIdHash := common.BigToHash(new(big.Int).SetUint64(order.OrderID))
stateObject := self.GetOrNewStateExchangeObject(orderBook)
if stateObject == nil {
return fmt.Errorf("Order book not found : %s ", orderBook.Hex())
}
stateOrderItem := stateObject.getStateOrderObject(self.db, orderIdHash)
if stateOrderItem == nil || stateOrderItem.empty() {
return fmt.Errorf("Order item empty order book : %s , order id : %s ", orderBook, orderIdHash.Hex())
}
priceHash := common.BigToHash(stateOrderItem.data.Price)
var stateOrderList *stateOrderList
switch stateOrderItem.data.Side {
case Ask:
stateOrderList = stateObject.getStateOrderListAskObject(self.db, priceHash)
case Bid:
stateOrderList = stateObject.getStateBidOrderListObject(self.db, priceHash)
default:
return fmt.Errorf("Order side not found : %s ", order.Side)
}
if stateOrderList == nil || stateOrderList.empty() {
return fmt.Errorf("Order list empty order book : %s , order id : %s , price : %s ", orderBook, orderIdHash.Hex(), priceHash.Hex())
}
if stateOrderItem.data.UserAddress != order.UserAddress {
return fmt.Errorf("Error Order User Address mismatch when cancel order book : %s , order id : %s , got : %s , expect : %s ", orderBook, orderIdHash.Hex(), stateOrderItem.data.UserAddress.Hex(), order.UserAddress.Hex())
}
if stateOrderItem.data.Hash != order.Hash {
return fmt.Errorf("Invalid order hash : got : %s , expect : %s ", order.Hash.Hex(), stateOrderItem.data.Hash.Hex())
}
if stateOrderItem.data.ExchangeAddress != order.ExchangeAddress {
return fmt.Errorf("Exchange Address mismatch when cancel. order book : %s , order id : %s , got : %s , expect : %s ", orderBook, orderIdHash.Hex(), order.ExchangeAddress.Hex(), stateOrderItem.data.ExchangeAddress.Hex())
}
self.journal = append(self.journal, cancelOrder{
orderBook: orderBook,
orderId: orderIdHash,
order: stateOrderItem.data,
})
currentAmount := new(big.Int).SetBytes(stateOrderList.GetOrderAmount(self.db, orderIdHash).Bytes()[:])
stateOrderItem.setVolume(big.NewInt(0))
stateOrderList.subVolume(currentAmount)
stateOrderList.removeOrderItem(self.db, orderIdHash)
if stateOrderList.empty() {
switch stateOrderItem.data.Side {
case Ask:
stateObject.removeStateOrderListAskObject(self.db, stateOrderList)
case Bid:
stateObject.removeStateOrderListBidObject(self.db, stateOrderList)
default:
}
}
return nil
}
func (self *TradingStateDB) GetVolume(orderBook common.Hash, price *big.Int, orderType string) *big.Int {
stateObject := self.GetOrNewStateExchangeObject(orderBook)
var volume *big.Int = nil
if stateObject != nil {
var stateOrderList *stateOrderList
switch orderType {
case Ask:
stateOrderList = stateObject.getStateOrderListAskObject(self.db, common.BigToHash(price))
case Bid:
stateOrderList = stateObject.getStateBidOrderListObject(self.db, common.BigToHash(price))
default:
return Zero
}
if stateOrderList == nil || stateOrderList.empty() {
return Zero
}
volume = stateOrderList.Volume()
}
return volume
}
func (self *TradingStateDB) GetBestAskPrice(orderBook common.Hash) (*big.Int, *big.Int) {
stateObject := self.getStateExchangeObject(orderBook)
if stateObject != nil {
priceHash := stateObject.getBestPriceAsksTrie(self.db)
if common.EmptyHash(priceHash) {
return Zero, Zero
}
orderList := stateObject.getStateOrderListAskObject(self.db, priceHash)
if orderList == nil {
log.Error("order list ask not found", "price", priceHash.Hex())
return Zero, Zero
}
return new(big.Int).SetBytes(priceHash.Bytes()), orderList.Volume()
}
return Zero, Zero
}
func (self *TradingStateDB) GetBestBidPrice(orderBook common.Hash) (*big.Int, *big.Int) {
stateObject := self.getStateExchangeObject(orderBook)
if stateObject != nil {
priceHash := stateObject.getBestBidsTrie(self.db)
if common.EmptyHash(priceHash) {
return Zero, Zero
}
orderList := stateObject.getStateBidOrderListObject(self.db, priceHash)
if orderList == nil {
log.Error("order list bid not found", "price", priceHash.Hex())
return Zero, Zero
}
return new(big.Int).SetBytes(priceHash.Bytes()), orderList.Volume()
}
return Zero, Zero
}
func (self *TradingStateDB) GetBestOrderIdAndAmount(orderBook common.Hash, price *big.Int, side string) (common.Hash, *big.Int, error) {
stateObject := self.GetOrNewStateExchangeObject(orderBook)
if stateObject != nil {
var stateOrderList *stateOrderList
switch side {
case Ask:
stateOrderList = stateObject.getStateOrderListAskObject(self.db, common.BigToHash(price))
case Bid:
stateOrderList = stateObject.getStateBidOrderListObject(self.db, common.BigToHash(price))
default:
return EmptyHash, Zero, fmt.Errorf("not found side :%s ", side)
}
if stateOrderList != nil {
key, _, err := stateOrderList.getTrie(self.db).TryGetBestLeftKeyAndValue()
if err != nil {
return EmptyHash, Zero, err
}
orderId := common.BytesToHash(key)
amount := stateOrderList.GetOrderAmount(self.db, orderId)
return orderId, new(big.Int).SetBytes(amount.Bytes()), nil
}
return EmptyHash, Zero, fmt.Errorf("not found order list with orderBook : %s , price : %d , side :%s ", orderBook.Hex(), price, side)
}
return EmptyHash, Zero, fmt.Errorf("not found orderBook : %s ", orderBook.Hex())
}
// updateStateExchangeObject writes the given object to the trie.
func (self *TradingStateDB) updateStateExchangeObject(stateObject *tradingExchanges) {
addr := stateObject.Hash()
data, err := rlp.EncodeToBytes(stateObject)
if err != nil {
panic(fmt.Errorf("can't encode object at %x: %v", addr[:], err))
}
self.setError(self.trie.TryUpdate(addr[:], data))
}
// Retrieve a state object given my the address. Returns nil if not found.
func (self *TradingStateDB) getStateExchangeObject(addr common.Hash) (stateObject *tradingExchanges) {
// Prefer 'live' objects.
if obj := self.stateExhangeObjects[addr]; obj != nil {
return obj
}
// Load the object from the database.
enc, err := self.trie.TryGet(addr[:])
if len(enc) == 0 {
self.setError(err)
return nil
}
var data tradingExchangeObject
if err := rlp.DecodeBytes(enc, &data); err != nil {
log.Error("Failed to decode state object", "addr", addr, "err", err)
return nil
}
// Insert into the live set.
obj := newStateExchanges(self, addr, data, self.MarkStateExchangeObjectDirty)
self.stateExhangeObjects[addr] = obj
return obj
}
func (self *TradingStateDB) setStateExchangeObject(object *tradingExchanges) {
self.stateExhangeObjects[object.Hash()] = object
self.stateExhangeObjectsDirty[object.Hash()] = struct{}{}
}
// Retrieve a state object or create a new state object if nil.
func (self *TradingStateDB) GetOrNewStateExchangeObject(addr common.Hash) *tradingExchanges {
stateExchangeObject := self.getStateExchangeObject(addr)
if stateExchangeObject == nil {
stateExchangeObject = self.createExchangeObject(addr)
}
return stateExchangeObject
}
// MarkStateAskObjectDirty adds the specified object to the dirty map to avoid costly
// state object cache iteration to find a handful of modified ones.
func (self *TradingStateDB) MarkStateExchangeObjectDirty(addr common.Hash) {
self.stateExhangeObjectsDirty[addr] = struct{}{}
}
// createStateOrderListObject creates a new state object. If there is an existing orderId with
// the given address, it is overwritten and returned as the second return value.
func (self *TradingStateDB) createExchangeObject(hash common.Hash) (newobj *tradingExchanges) {
newobj = newStateExchanges(self, hash, tradingExchangeObject{LendingCount: Zero, MediumPrice: Zero, MediumPriceBeforeEpoch: Zero, TotalQuantity: Zero}, self.MarkStateExchangeObjectDirty)
newobj.setNonce(0) // sets the object to dirty
self.setStateExchangeObject(newobj)
return newobj
}
// Copy creates a deep, independent copy of the state.
// Snapshots of the copied state cannot be applied to the copy.
func (self *TradingStateDB) Copy() *TradingStateDB {
self.lock.Lock()
defer self.lock.Unlock()
// Copy all the basic fields, initialize the memory ones
state := &TradingStateDB{
db: self.db,
trie: self.db.CopyTrie(self.trie),
stateExhangeObjects: make(map[common.Hash]*tradingExchanges, len(self.stateExhangeObjectsDirty)),
stateExhangeObjectsDirty: make(map[common.Hash]struct{}, len(self.stateExhangeObjectsDirty)),
}
// Copy the dirty states, logs, and preimages
for addr := range self.stateExhangeObjectsDirty {
state.stateExhangeObjectsDirty[addr] = struct{}{}
}
for addr, exchangeObject := range self.stateExhangeObjects {
state.stateExhangeObjects[addr] = exchangeObject.deepCopy(state, state.MarkStateExchangeObjectDirty)
}
return state
}
func (s *TradingStateDB) clearJournalAndRefund() {
s.journal = nil
s.validRevisions = s.validRevisions[:0]
}
// Snapshot returns an identifier for the current revision of the state.
func (self *TradingStateDB) Snapshot() int {
id := self.nextRevisionId
self.nextRevisionId++
self.validRevisions = append(self.validRevisions, revision{id, len(self.journal)})
return id
}
// RevertToSnapshot reverts all state changes made since the given revision.
func (self *TradingStateDB) RevertToSnapshot(revid int) {
// Find the snapshot in the stack of valid snapshots.
idx := sort.Search(len(self.validRevisions), func(i int) bool {
return self.validRevisions[i].id >= revid
})
if idx == len(self.validRevisions) || self.validRevisions[idx].id != revid {
panic(fmt.Errorf("revision id %v cannot be reverted", revid))
}
snapshot := self.validRevisions[idx].journalIndex
// Replay the journal to undo changes.
for i := len(self.journal) - 1; i >= snapshot; i-- {
self.journal[i].undo(self)
}
self.journal = self.journal[:snapshot]
// Remove invalidated snapshots from the stack.
self.validRevisions = self.validRevisions[:idx]
}
// Finalise finalises the state by removing the self destructed objects
// and clears the journal as well as the refunds.
func (s *TradingStateDB) Finalise() {
// Commit objects to the trie.
for addr, stateObject := range s.stateExhangeObjects {
if _, isDirty := s.stateExhangeObjectsDirty[addr]; isDirty {
// Write any storage changes in the state object to its storage trie.
stateObject.updateAsksRoot(s.db)
stateObject.updateBidsRoot(s.db)
stateObject.updateOrdersRoot(s.db)
stateObject.updateLiquidationPriceRoot(s.db)
// Update the object in the main orderId trie.
s.updateStateExchangeObject(stateObject)
//delete(s.stateExhangeObjectsDirty, addr)
}
}
s.clearJournalAndRefund()
}
// IntermediateRoot computes the current root orderBookHash of the state trie.
// It is called in between transactions to get the root orderBookHash that
// goes into transaction receipts.
func (s *TradingStateDB) IntermediateRoot() common.Hash {
s.Finalise()
return s.trie.Hash()
}
// Commit writes the state to the underlying in-memory trie database.
func (s *TradingStateDB) Commit() (root common.Hash, err error) {
defer s.clearJournalAndRefund()
// Commit objects to the trie.
for addr, stateObject := range s.stateExhangeObjects {
if _, isDirty := s.stateExhangeObjectsDirty[addr]; isDirty {
// Write any storage changes in the state object to its storage trie.
if err := stateObject.CommitAsksTrie(s.db); err != nil {
return EmptyHash, err
}
if err := stateObject.CommitBidsTrie(s.db); err != nil {
return EmptyHash, err
}
if err := stateObject.CommitOrdersTrie(s.db); err != nil {
return EmptyHash, err
}
if err := stateObject.CommitLiquidationPriceTrie(s.db); err != nil {
return EmptyHash, err
}
// Update the object in the main orderId trie.
s.updateStateExchangeObject(stateObject)
delete(s.stateExhangeObjectsDirty, addr)
}
}
// Write trie changes.
root, err = s.trie.Commit(func(leaf []byte, parent common.Hash) error {
var exchange tradingExchangeObject
if err := rlp.DecodeBytes(leaf, &exchange); err != nil {
return nil
}
if exchange.AskRoot != EmptyRoot {
s.db.TrieDB().Reference(exchange.AskRoot, parent)
}
if exchange.BidRoot != EmptyRoot {
s.db.TrieDB().Reference(exchange.BidRoot, parent)
}
if exchange.OrderRoot != EmptyRoot {
s.db.TrieDB().Reference(exchange.OrderRoot, parent)
}
if exchange.LiquidationPriceRoot != EmptyRoot {
s.db.TrieDB().Reference(exchange.LiquidationPriceRoot, parent)
}
return nil
})
log.Debug("Trading State Trie cache stats after commit", "root", root.Hex())
return root, err
}
func (self *TradingStateDB) GetAllLowerLiquidationPriceData(orderBook common.Hash, limit *big.Int) map[*big.Int]map[common.Hash][]common.Hash {
result := map[*big.Int]map[common.Hash][]common.Hash{}
orderbookState := self.getStateExchangeObject(orderBook)
if orderbookState == nil {
return result
}
mapPrices := orderbookState.getAllLowerLiquidationPrice(self.db, common.BigToHash(limit))
for priceHash, liquidationState := range mapPrices {
price := new(big.Int).SetBytes(priceHash[:])
log.Debug("GetAllLowerLiquidationPriceData", "price", price, "limit", limit)
if liquidationState != nil && price.Sign() > 0 && price.Cmp(limit) < 0 {
liquidationData := map[common.Hash][]common.Hash{}
priceLiquidationData := liquidationState.getAllLiquidationData(self.db)
for lendingBook, data := range priceLiquidationData {
if len(data) == 0 {
continue
}
oldData := liquidationData[lendingBook]
if len(oldData) == 0 {
oldData = data
} else {
oldData = append(oldData, data...)
}
liquidationData[lendingBook] = oldData
}
result[price] = liquidationData
}
}
return result
}
func (self *TradingStateDB) GetHighestLiquidationPriceData(orderBook common.Hash, price *big.Int) (*big.Int, map[common.Hash][]common.Hash) {
liquidationData := map[common.Hash][]common.Hash{}
orderbookState := self.getStateExchangeObject(orderBook)
if orderbookState == nil {
return common.Big0, liquidationData
}
highestPriceHash, liquidationState := orderbookState.getHighestLiquidationPrice(self.db)
highestPrice := new(big.Int).SetBytes(highestPriceHash[:])
if liquidationState != nil && highestPrice.Sign() > 0 && price.Cmp(highestPrice) < 0 {
priceLiquidationData := liquidationState.getAllLiquidationData(self.db)
for lendingBook, data := range priceLiquidationData {
if len(data) == 0 {
continue
}
oldData := liquidationData[lendingBook]
if len(oldData) == 0 {
oldData = data
} else {
oldData = append(oldData, data...)
}
liquidationData[lendingBook] = oldData
}
}
return highestPrice, liquidationData
}
func (self *TradingStateDB) InsertLiquidationPrice(orderBook common.Hash, price *big.Int, lendingBook common.Hash, tradeId uint64) {
tradIdHash := common.Uint64ToHash(tradeId)
priceHash := common.BigToHash(price)
orderBookState := self.getStateExchangeObject(orderBook)
if orderBookState == nil {
orderBookState = self.createExchangeObject(orderBook)
}
liquidationPriceState := orderBookState.getStateLiquidationPrice(self.db, priceHash)
if liquidationPriceState == nil {
liquidationPriceState = orderBookState.createStateLiquidationPrice(self.db, priceHash)
}
lendingBookState := liquidationPriceState.getStateLendingBook(self.db, lendingBook)
if lendingBookState == nil {
lendingBookState = liquidationPriceState.createLendingBook(self.db, lendingBook)
}
lendingBookState.insertTradingId(self.db, tradIdHash)
lendingBookState.AddVolume(One)
liquidationPriceState.AddVolume(One)
orderBookState.addLendingCount(One)
self.journal = append(self.journal, insertLiquidationPrice{
orderBook: orderBook,
price: price,
lendingBook: lendingBook,
tradeId: tradeId,
})
}
func (self *TradingStateDB) RemoveLiquidationPrice(orderBook common.Hash, price *big.Int, lendingBook common.Hash, tradeId uint64) error {
tradeIdHash := common.Uint64ToHash(tradeId)
priceHash := common.BigToHash(price)
orderbookState := self.getStateExchangeObject(orderBook)
if orderbookState == nil {
return fmt.Errorf("order book not found : %s ", orderBook.Hex())
}
liquidationPriceState := orderbookState.getStateLiquidationPrice(self.db, priceHash)
if liquidationPriceState == nil {
return fmt.Errorf("liquidation price not found : %s , %s ", orderBook.Hex(), priceHash.Hex())
}
lendingBookState := liquidationPriceState.getStateLendingBook(self.db, lendingBook)
if lendingBookState == nil {
return fmt.Errorf("lending book not found : %s , %s ,%s ", orderBook.Hex(), priceHash.Hex(), lendingBook.Hex())
}
if !lendingBookState.Exist(self.db, tradeIdHash) {
return fmt.Errorf("trade id not found : %s , %s ,%s , %d ", orderBook.Hex(), priceHash.Hex(), lendingBook.Hex(), tradeId)
}
lendingBookState.removeTradingId(self.db, tradeIdHash)
lendingBookState.subVolume(One)
liquidationPriceState.subVolume(One)
if liquidationPriceState.Volume().Sign() == 0 {
orderbookState.getLiquidationPriceTrie(self.db).TryDelete(priceHash[:])
}
orderbookState.subLendingCount(One)
self.journal = append(self.journal, removeLiquidationPrice{
orderBook: orderBook,
price: price,
lendingBook: lendingBook,
tradeId: tradeId,
})
return nil
}

View file

@ -0,0 +1,302 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package tradingstate
import (
"fmt"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/math"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"math/big"
"testing"
)
func TestEchangeStates(t *testing.T) {
t.SkipNow()
orderBook := common.StringToHash("BTC/XDC")
price := big.NewInt(10000)
numberOrder := 20
orderItems := []OrderItem{}
relayers := []common.Hash{}
for i := 0; i < numberOrder; i++ {
relayers = append(relayers, common.BigToHash(big.NewInt(int64(i))))
id := new(big.Int).SetUint64(uint64(i) + 1)
orderItems = append(orderItems, OrderItem{OrderID: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Price: big.NewInt(int64(2*i + 1)), Side: Ask, Signature: &Signature{V: 1, R: common.HexToHash("111111"), S: common.HexToHash("222222222222")}})
orderItems = append(orderItems, OrderItem{OrderID: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Price: big.NewInt(int64(2*i + 1)), Side: Bid, Signature: &Signature{V: 1, R: common.HexToHash("3333333333"), S: common.HexToHash("22222222222222222")}})
}
// Create an empty statedb database
db := rawdb.NewMemoryDatabase()
stateCache := NewDatabase(db)
statedb, _ := New(common.Hash{}, stateCache)
// Update it with some exchanges
for i := 0; i < numberOrder; i++ {
statedb.SetNonce(relayers[i], uint64(1))
}
mapPriceSell := map[uint64]uint64{}
mapPriceBuy := map[uint64]uint64{}
for i := 0; i < len(orderItems); i++ {
amount := orderItems[i].Quantity.Uint64()
orderIdHash := common.BigToHash(new(big.Int).SetUint64(orderItems[i].OrderID))
statedb.InsertOrderItem(orderBook, orderIdHash, orderItems[i])
switch orderItems[i].Side {
case Ask:
old := mapPriceSell[amount]
mapPriceSell[amount] = old + amount
case Bid:
old := mapPriceBuy[amount]
mapPriceBuy[amount] = old + amount
default:
}
}
statedb.SetLastPrice(orderBook, price)
statedb.InsertLiquidationPrice(orderBook, big.NewInt(1), orderBook, 2)
statedb.InsertLiquidationPrice(orderBook, big.NewInt(2), orderBook, 3)
statedb.InsertLiquidationPrice(orderBook, big.NewInt(3), orderBook, 1)
root := statedb.IntermediateRoot()
statedb.Commit()
err := stateCache.TrieDB().Commit(root, false)
if err != nil {
t.Errorf("Error when commit into database: %v", err)
}
stateCache.TrieDB().Reference(root, common.Hash{})
statedb, err = New(root, stateCache)
if err != nil {
t.Fatalf("Error when get trie in database: %s , err: %v", root.Hex(), err)
}
liquidationPrice, liquidationData := statedb.GetHighestLiquidationPriceData(orderBook, big.NewInt(1))
if len(liquidationData) == 0 {
t.Fatalf("Error when get liquidation data save in database: got : %d , %s ", liquidationPrice, liquidationData)
}
fmt.Println("liquidationPrice", liquidationPrice)
for lendingBook, tradingIds := range liquidationData {
for _, tradingIdHash := range tradingIds {
fmt.Println("lendingBook", lendingBook.Hex(), "tradingIdHash", tradingIdHash.Hex())
err := statedb.RemoveLiquidationPrice(orderBook, liquidationPrice, lendingBook, new(big.Int).SetBytes(tradingIdHash.Bytes()).Uint64())
if err != nil {
t.Fatalf("Error when remove liquidation price in database: %s , err: %v", root.Hex(), err)
}
}
}
statedb.RemoveLiquidationPrice(orderBook, big.NewInt(2), orderBook, 2)
mapData := statedb.GetAllLowerLiquidationPriceData(orderBook, big.NewInt(2))
for price, lendingBooks := range mapData {
for lendingBook, tradeIds := range lendingBooks {
for _, tradeId := range tradeIds {
fmt.Println("price", price, "lendingBook", lendingBook.Hex(), "tradeId", tradeId.Hex())
}
}
}
fmt.Println("mapData", mapData)
liquidationPrice, liquidationData = statedb.GetHighestLiquidationPriceData(orderBook, big.NewInt(2))
for lendingBook, tradingIds := range liquidationData {
for _, tradingIdHash := range tradingIds {
fmt.Println("liquidationPrice", liquidationPrice, "lendingBook", lendingBook.Hex(), "tradingIdHash", tradingIdHash.Hex())
}
}
gotPrice := statedb.GetLastPrice(orderBook)
if gotPrice.Cmp(price) != 0 {
t.Fatalf("Error when get price save in database: got : %d , wanted : %d ", gotPrice, price)
}
fmt.Println(gotPrice)
for i := 0; i < numberOrder; i++ {
nonce := statedb.GetNonce(relayers[i])
if nonce != uint64(1) {
t.Fatalf("Error when get nonce save in database: got : %d , wanted : %d ", nonce, i)
}
}
minSell := uint64(math.MaxUint64)
for price, amount := range mapPriceSell {
data := statedb.GetVolume(orderBook, new(big.Int).SetUint64(price), Ask)
if data.Uint64() != amount {
t.Fatalf("Error when get volume save in database: price %d ,got : %d , wanted : %d ", price, data.Uint64(), amount)
}
if price < minSell {
minSell = price
}
}
maxBuy := uint64(0)
for price, amount := range mapPriceBuy {
data := statedb.GetVolume(orderBook, new(big.Int).SetUint64(price), Bid)
if data.Uint64() != amount {
t.Fatalf("Error when get volume save in database: price %d ,got : %d , wanted : %d ", price, data.Uint64(), amount)
}
if price > maxBuy {
maxBuy = price
}
}
for i := 0; i < len(orderItems); i++ {
amount := statedb.GetOrder(orderBook, common.BigToHash(new(big.Int).SetUint64(orderItems[i].OrderID))).Quantity
if orderItems[i].Quantity.Cmp(amount) != 0 {
t.Fatalf("Error when get amount save in database: orderId %d , orderType %s,got : %d , wanted : %d ", orderItems[i].OrderID, orderItems[i].Side, amount.Uint64(), orderItems[i].Quantity.Uint64())
}
}
maxPrice, volumeMax := statedb.GetBestBidPrice(orderBook)
minPrice, volumeMin := statedb.GetBestAskPrice(orderBook)
fmt.Println("price", minPrice, volumeMin, maxPrice, volumeMax)
db.Close()
}
func TestRevertStates(t *testing.T) {
orderBook := common.StringToHash("BTC/XDC")
numberOrder := 20
orderItems := []OrderItem{}
relayers := []common.Hash{}
for i := 0; i < numberOrder; i++ {
relayers = append(relayers, common.BigToHash(big.NewInt(int64(i))))
id := new(big.Int).SetUint64(uint64(i) + 1)
orderItems = append(orderItems, OrderItem{OrderID: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Price: big.NewInt(int64(2*i + 1)), Side: Ask, Signature: &Signature{V: 1, R: common.HexToHash("111111"), S: common.HexToHash("222222222222")}})
orderItems = append(orderItems, OrderItem{OrderID: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Price: big.NewInt(int64(2*i + 1)), Side: Bid, Signature: &Signature{V: 1, R: common.HexToHash("3333333333"), S: common.HexToHash("22222222222222222")}})
}
// Create an empty statedb database
db := rawdb.NewMemoryDatabase()
stateCache := NewDatabase(db)
statedb, _ := New(common.Hash{}, stateCache)
// Update it with some exchanges
for i := 0; i < numberOrder; i++ {
statedb.SetNonce(relayers[i], uint64(1))
}
mapPriceSell := map[uint64]uint64{}
mapPriceBuy := map[uint64]uint64{}
for i := 0; i < len(orderItems); i++ {
amount := orderItems[i].Quantity.Uint64()
orderIdHash := common.BigToHash(new(big.Int).SetUint64(orderItems[i].OrderID))
statedb.InsertOrderItem(orderBook, orderIdHash, orderItems[i])
switch orderItems[i].Side {
case Ask:
old := mapPriceSell[amount]
mapPriceSell[amount] = old + amount
case Bid:
old := mapPriceBuy[amount]
mapPriceBuy[amount] = old + amount
default:
}
}
root := statedb.IntermediateRoot()
statedb.Commit()
//err := stateCache.TrieDB().Commit(root, false)
//if err != nil {
// t.Errorf("Error when commit into database: %v", err)
//}
stateCache.TrieDB().Reference(root, common.Hash{})
statedb, err := New(root, stateCache)
if err != nil {
t.Fatalf("Error when get trie in database: %s , err: %v", root.Hex(), err)
}
orderIdHash := common.BigToHash(new(big.Int).SetUint64(orderItems[0].OrderID))
order := statedb.GetOrder(orderBook, orderIdHash)
// sub amount order
wanted := statedb.GetVolume(orderBook, order.Price, order.Side)
snap := statedb.Snapshot()
statedb.SubAmountOrderItem(orderBook, orderIdHash, order.Price, order.Quantity, order.Side)
statedb.RevertToSnapshot(snap)
got := statedb.GetVolume(orderBook, order.Price, order.Side)
if got.Cmp(wanted) != 0 {
t.Fatalf(" err get volume price : %d after try revert snap shot , got : %d ,want : %d", order.Price, got, wanted)
}
// set nonce
wantedNonce := statedb.GetNonce(relayers[1])
snap = statedb.Snapshot()
statedb.SetNonce(relayers[1], 0)
statedb.RevertToSnapshot(snap)
gotNonce := statedb.GetNonce(relayers[1])
if wantedNonce != gotNonce {
t.Fatalf(" err get nonce addr: %v after try revert snap shot , got : %d ,want : %d", relayers[1].Hex(), gotNonce, wantedNonce)
}
// cancel order
wantedOrder := statedb.GetOrder(orderBook, orderIdHash)
snap = statedb.Snapshot()
statedb.CancelOrder(orderBook, &wantedOrder)
statedb.RevertToSnapshot(snap)
gotOrder := statedb.GetOrder(orderBook, orderIdHash)
if gotOrder.Quantity.Cmp(wantedOrder.Quantity) != 0 {
t.Fatalf(" err cancel order info : %v after try revert snap shot , got : %v ,want : %v", orderIdHash.Hex(), gotOrder, wantedOrder)
}
// insert order
i := 2*numberOrder + 1
id := new(big.Int).SetUint64(uint64(i) + 1)
testOrder := OrderItem{OrderID: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Price: big.NewInt(int64(2*i + 1)), Side: Ask, Signature: &Signature{V: 1, R: common.HexToHash("111111"), S: common.HexToHash("222222222222")}}
orderIdHash = common.BigToHash(new(big.Int).SetUint64(testOrder.OrderID))
snap = statedb.Snapshot()
statedb.InsertOrderItem(orderBook, orderIdHash, testOrder)
statedb.RevertToSnapshot(snap)
gotOrder = statedb.GetOrder(orderBook, orderIdHash)
if gotOrder.Quantity.Cmp(EmptyOrder.Quantity) != 0 {
t.Fatalf(" err insert order info : %v after try revert snap shot , got : %v ,want Empty Order", orderIdHash.Hex(), gotOrder)
}
// change price
price := big.NewInt(10000)
statedb.SetLastPrice(orderBook, price)
snap = statedb.Snapshot()
statedb.SetLastPrice(orderBook, big.NewInt(0))
statedb.RevertToSnapshot(snap)
gotPrice := statedb.GetLastPrice(orderBook)
if gotPrice.Cmp(price) != 0 {
t.Fatalf("Error when get price save in database: got : %d , wanted : %d ", gotPrice, price)
}
db.Close()
}
func TestDumpState(t *testing.T) {
orderBook := common.StringToHash("BTC/XDC")
numberOrder := 5
orderItems := []OrderItem{}
for i := 0; i < numberOrder; i++ {
id := new(big.Int).SetUint64(uint64(i) + 1)
orderItems = append(orderItems, OrderItem{OrderID: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Price: big.NewInt(int64(2*i + 1)), Side: Ask, Signature: &Signature{V: 1, R: common.HexToHash("111111"), S: common.HexToHash("222222222222")}})
orderItems = append(orderItems, OrderItem{OrderID: id.Uint64(), Quantity: big.NewInt(int64(2*i + 2)), Price: big.NewInt(int64(2*i + 2)), Side: Bid, Signature: &Signature{V: 1, R: common.HexToHash("3333333333"), S: common.HexToHash("22222222222222222")}})
}
// Create an empty statedb database
db := rawdb.NewMemoryDatabase()
stateCache := NewDatabase(db)
statedb, _ := New(common.Hash{}, stateCache)
for i := 0; i < len(orderItems); i++ {
orderIdHash := common.BigToHash(new(big.Int).SetUint64(orderItems[i].OrderID))
statedb.InsertOrderItem(orderBook, orderIdHash, orderItems[i])
}
bidTrie, _ := statedb.DumpAskTrie(orderBook)
fmt.Println("bidTrie", bidTrie)
root := statedb.IntermediateRoot()
statedb.Commit()
stateCache.TrieDB().Reference(root, common.Hash{})
statedb, err := New(root, stateCache)
if err != nil {
t.Fatalf("Error when get trie in database: %s , err: %v", root.Hex(), err)
}
maxPrice, volumeMax := statedb.GetBestBidPrice(orderBook)
minPrice, volumeMin := statedb.GetBestAskPrice(orderBook)
fmt.Println("price", minPrice, volumeMin, maxPrice, volumeMax)
bidTrie, _ = statedb.DumpBidTrie(orderBook)
fmt.Println("bidTrie", bidTrie)
db.Close()
}

143
XDCx/tradingstate/trade.go Normal file
View file

@ -0,0 +1,143 @@
package tradingstate
import (
"math/big"
"time"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
"github.com/globalsign/mgo/bson"
)
const (
TradeStatusSuccess = "SUCCESS"
TradeTakerOrderHash = "takerOrderHash"
TradeMakerOrderHash = "makerOrderHash"
TradeTimestamp = "timestamp"
TradeQuantity = "quantity"
TradeMakerExchange = "makerExAddr"
TradeMaker = "uAddr"
TradeBaseToken = "bToken"
TradeQuoteToken = "qToken"
TradePrice = "tradedPrice"
MakerOrderType = "makerOrderType"
MakerFee = "makerFee"
TakerFee = "takerFee"
)
type Trade struct {
Taker common.Address `json:"taker" bson:"taker"`
Maker common.Address `json:"maker" bson:"maker"`
BaseToken common.Address `json:"baseToken" bson:"baseToken"`
QuoteToken common.Address `json:"quoteToken" bson:"quoteToken"`
MakerOrderHash common.Hash `json:"makerOrderHash" bson:"makerOrderHash"`
TakerOrderHash common.Hash `json:"takerOrderHash" bson:"takerOrderHash"`
MakerExchange common.Address `json:"makerExchange" bson:"makerExchange"`
TakerExchange common.Address `json:"takerExchange" bson:"takerExchange"`
Hash common.Hash `json:"hash" bson:"hash"`
TxHash common.Hash `json:"txHash" bson:"txHash"`
PricePoint *big.Int `json:"pricepoint" bson:"pricepoint"`
Amount *big.Int `json:"amount" bson:"amount"`
MakeFee *big.Int `json:"makeFee" bson:"makeFee"`
TakeFee *big.Int `json:"takeFee" bson:"takeFee"`
Status string `json:"status" bson:"status"`
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
TakerOrderSide string `json:"takerOrderSide" bson:"takerOrderSide"`
TakerOrderType string `json:"takerOrderType" bson:"takerOrderType"`
MakerOrderType string `json:"makerOrderType" bson:"makerOrderType"`
}
type TradeBSON struct {
Taker string `json:"taker" bson:"taker"`
Maker string `json:"maker" bson:"maker"`
BaseToken string `json:"baseToken" bson:"baseToken"`
QuoteToken string `json:"quoteToken" bson:"quoteToken"`
MakerOrderHash string `json:"makerOrderHash" bson:"makerOrderHash"`
TakerOrderHash string `json:"takerOrderHash" bson:"takerOrderHash"`
MakerExchange string `json:"makerExchange" bson:"makerExchange"`
TakerExchange string `json:"takerExchange" bson:"takerExchange"`
Hash string `json:"hash" bson:"hash"`
TxHash string `json:"txHash" bson:"txHash"`
Amount string `json:"amount" bson:"amount"`
MakeFee string `json:"makeFee" bson:"makeFee"`
TakeFee string `json:"takeFee" bson:"takeFee"`
PricePoint string `json:"pricepoint" bson:"pricepoint"`
Status string `json:"status" bson:"status"`
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
TakerOrderSide string `json:"takerOrderSide" bson:"takerOrderSide"`
TakerOrderType string `json:"takerOrderType" bson:"takerOrderType"`
MakerOrderType string `json:"makerOrderType" bson:"makerOrderType"`
}
func (t *Trade) GetBSON() (interface{}, error) {
tr := TradeBSON{
Maker: t.Maker.Hex(),
Taker: t.Taker.Hex(),
BaseToken: t.BaseToken.Hex(),
QuoteToken: t.QuoteToken.Hex(),
MakerOrderHash: t.MakerOrderHash.Hex(),
TakerOrderHash: t.TakerOrderHash.Hex(),
MakerExchange: t.MakerExchange.Hex(),
TakerExchange: t.TakerExchange.Hex(),
Hash: t.Hash.Hex(),
TxHash: t.TxHash.Hex(),
CreatedAt: t.CreatedAt,
UpdatedAt: t.UpdatedAt,
PricePoint: t.PricePoint.String(),
Status: t.Status,
Amount: t.Amount.String(),
MakeFee: t.MakeFee.String(),
TakeFee: t.TakeFee.String(),
TakerOrderSide: t.TakerOrderSide,
TakerOrderType: t.TakerOrderType,
MakerOrderType: t.MakerOrderType,
}
return tr, nil
}
func (t *Trade) SetBSON(raw bson.Raw) error {
decoded := &TradeBSON{}
err := raw.Unmarshal(decoded)
if err != nil {
return err
}
t.Taker = common.HexToAddress(decoded.Taker)
t.Maker = common.HexToAddress(decoded.Maker)
t.BaseToken = common.HexToAddress(decoded.BaseToken)
t.QuoteToken = common.HexToAddress(decoded.QuoteToken)
t.MakerOrderHash = common.HexToHash(decoded.MakerOrderHash)
t.TakerOrderHash = common.HexToHash(decoded.TakerOrderHash)
t.MakerExchange = common.HexToAddress(decoded.MakerExchange)
t.TakerExchange = common.HexToAddress(decoded.TakerExchange)
t.Hash = common.HexToHash(decoded.Hash)
t.TxHash = common.HexToHash(decoded.TxHash)
t.Status = decoded.Status
t.Amount = ToBigInt(decoded.Amount)
t.PricePoint = ToBigInt(decoded.PricePoint)
t.MakeFee = ToBigInt(decoded.MakeFee)
t.TakeFee = ToBigInt(decoded.TakeFee)
t.CreatedAt = decoded.CreatedAt
t.UpdatedAt = decoded.UpdatedAt
t.TakerOrderSide = decoded.TakerOrderSide
t.TakerOrderType = decoded.TakerOrderType
t.MakerOrderType = decoded.MakerOrderType
return nil
}
// ComputeHash returns hashes the trade
// The OrderHash, Amount, Taker and TradeNonce attributes must be
// set before attempting to compute the trade orderBookHash
func (t *Trade) ComputeHash() common.Hash {
sha := sha3.NewKeccak256()
sha.Write(t.MakerOrderHash.Bytes())
sha.Write(t.TakerOrderHash.Bytes())
return common.BytesToHash(sha.Sum(nil))
}

59
XDCxDAO/interfaces.go Normal file
View file

@ -0,0 +1,59 @@
// Copyright 2019 The XDPoSChain Authors
// This file is part of the Core XDPoSChain infrastructure
// https://XDPoSChain.com
// Package XDCxDAO provides an interface to work with XDCx database, including leveldb for masternode and mongodb for SDK node
package XDCxDAO
import (
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/ethdb"
)
const defaultCacheLimit = 1024
type XDCXDAO interface {
// for both leveldb and mongodb
IsEmptyKey(key []byte) bool
Close() error
// mongodb methods
HasObject(hash common.Hash, val interface{}) (bool, error)
GetObject(hash common.Hash, val interface{}) (interface{}, error)
PutObject(hash common.Hash, val interface{}) error
DeleteObject(hash common.Hash, val interface{}) error // won't return error if key not found
GetListItemByTxHash(txhash common.Hash, val interface{}) interface{}
GetListItemByHashes(hashes []string, val interface{}) interface{}
DeleteItemByTxHash(txhash common.Hash, val interface{})
// basic XDCx
InitBulk()
CommitBulk() error
// XDCx lending
InitLendingBulk()
CommitLendingBulk() error
// leveldb methods
Put(key []byte, value []byte) error
Get(key []byte) ([]byte, error)
Has(key []byte) (bool, error)
Delete(key []byte) error
NewBatch() ethdb.Batch
HasAncient(kind string, number uint64) (bool, error)
Ancient(kind string, number uint64) ([]byte, error)
Ancients() (uint64, error)
AncientSize(kind string) (uint64, error)
AppendAncient(number uint64, hash, header, body, receipt, td []byte) error
TruncateAncients(n uint64) error
Sync() error
NewIterator(prefix []byte, start []byte) ethdb.Iterator
Stat(property string) (string, error)
Compact(start []byte, limit []byte) error
}
// use alloc to prevent reference manipulation
func EmptyKey() []byte {
key := make([]byte, common.HashLength)
return key
}

183
XDCxDAO/leveldb.go Normal file
View file

@ -0,0 +1,183 @@
package XDCxDAO
import (
"bytes"
"encoding/hex"
"errors"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"sync"
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/log"
lru "github.com/hashicorp/golang-lru"
)
type BatchItem struct {
Value interface{}
}
type BatchDatabase struct {
db ethdb.Database
emptyKey []byte
cacheItems *lru.Cache // Cache for reading
lock sync.RWMutex
cacheLimit int
Debug bool
}
// NewBatchDatabase use rlp as encoding
func NewBatchDatabase(datadir string, cacheLimit int) *BatchDatabase {
return NewBatchDatabaseWithEncode(datadir, cacheLimit)
}
// batchdatabase is a fast cache db to retrieve in-mem object
func NewBatchDatabaseWithEncode(datadir string, cacheLimit int) *BatchDatabase {
db, err := rawdb.NewLevelDBDatabase(datadir, 128, 1024, "")
if err != nil {
log.Error("Can't create new DB", "error", err)
return nil
}
itemCacheLimit := defaultCacheLimit
if cacheLimit > 0 {
itemCacheLimit = cacheLimit
}
cacheItems, _ := lru.New(itemCacheLimit)
batchDB := &BatchDatabase{
db: db,
cacheItems: cacheItems,
emptyKey: EmptyKey(), // pre alloc for comparison
cacheLimit: itemCacheLimit,
}
return batchDB
}
func (db *BatchDatabase) IsEmptyKey(key []byte) bool {
return key == nil || len(key) == 0 || bytes.Equal(key, db.emptyKey)
}
func (db *BatchDatabase) getCacheKey(key []byte) string {
return hex.EncodeToString(key)
}
func (db *BatchDatabase) HasObject(hash common.Hash, val interface{}) (bool, error) {
// for mongodb only
return false, nil
}
func (db *BatchDatabase) GetObject(hash common.Hash, val interface{}) (interface{}, error) {
// for mongodb only
return nil, nil
}
func (db *BatchDatabase) PutObject(hash common.Hash, val interface{}) error {
// for mongodb only
return nil
}
func (db *BatchDatabase) DeleteObject(hash common.Hash, val interface{}) error {
// for mongodb only
return nil
}
func (db *BatchDatabase) Put(key []byte, val []byte) error {
return db.db.Put(key, val)
}
func (db *BatchDatabase) Delete(key []byte) error {
return db.db.Delete(key)
}
func (db *BatchDatabase) Has(key []byte) (bool, error) {
return db.db.Has(key)
}
func (db *BatchDatabase) Get(key []byte) ([]byte, error) {
return db.db.Get(key)
}
func (db *BatchDatabase) Close() error {
return db.db.Close()
}
func (db *BatchDatabase) NewBatch() ethdb.Batch {
return db.db.NewBatch()
}
func (db *BatchDatabase) DeleteItemByTxHash(txhash common.Hash, val interface{}) {
}
func (db *BatchDatabase) GetListItemByTxHash(txhash common.Hash, val interface{}) interface{} {
return []interface{}{}
}
func (db *BatchDatabase) GetListItemByHashes(hashes []string, val interface{}) interface{} {
return []interface{}{}
}
func (db *BatchDatabase) InitBulk() {
}
func (db *BatchDatabase) CommitBulk() error {
return nil
}
func (db *BatchDatabase) InitLendingBulk() {
}
func (db *BatchDatabase) CommitLendingBulk() error {
return nil
}
var errNotSupported = errors.New("this operation is not supported")
// HasAncient returns an error as we don't have a backing chain freezer.
func (db *BatchDatabase) HasAncient(kind string, number uint64) (bool, error) {
return false, errNotSupported
}
// Ancient returns an error as we don't have a backing chain freezer.
func (db *BatchDatabase) Ancient(kind string, number uint64) ([]byte, error) {
return nil, errNotSupported
}
// Ancients returns an error as we don't have a backing chain freezer.
func (db *BatchDatabase) Ancients() (uint64, error) {
return 0, errNotSupported
}
// AncientSize returns an error as we don't have a backing chain freezer.
func (db *BatchDatabase) AncientSize(kind string) (uint64, error) {
return 0, errNotSupported
}
// AppendAncient returns an error as we don't have a backing chain freezer.
func (db *BatchDatabase) AppendAncient(number uint64, hash, header, body, receipts, td []byte) error {
return errNotSupported
}
// TruncateAncients returns an error as we don't have a backing chain freezer.
func (db *BatchDatabase) TruncateAncients(items uint64) error {
return errNotSupported
}
// Sync returns an error as we don't have a backing chain freezer.
func (db *BatchDatabase) Sync() error {
return errNotSupported
}
func (db *BatchDatabase) NewIterator(prefix []byte, start []byte) ethdb.Iterator {
return db.NewIterator(prefix, start)
}
func (db *BatchDatabase) Stat(property string) (string, error) {
return db.Stat(property)
}
func (db *BatchDatabase) Compact(start []byte, limit []byte) error {
return db.Compact(start, limit)
}

935
XDCxDAO/mongodb.go Normal file
View file

@ -0,0 +1,935 @@
package XDCxDAO
import (
"bytes"
"encoding/hex"
"fmt"
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
"github.com/XinFinOrg/XDPoSChain/XDCxlending/lendingstate"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/globalsign/mgo"
"github.com/globalsign/mgo/bson"
lru "github.com/hashicorp/golang-lru"
"strings"
"time"
)
const (
ordersCollection = "orders"
tradesCollection = "trades"
lendingItemsCollection = "lending_items"
lendingTradesCollection = "lending_trades"
lendingTopUpCollection = "lending_topups"
lendingRepayCollection = "lending_repays"
lendingRecallCollection = "lending_recalls"
epochPriceCollection = "epoch_prices"
)
type MongoDatabase struct {
Session *mgo.Session
dbName string
emptyKey []byte
cacheItems *lru.Cache // Cache for reading
orderBulk *mgo.Bulk
tradeBulk *mgo.Bulk
epochPriceBulk *mgo.Bulk
lendingItemBulk *mgo.Bulk
topUpBulk *mgo.Bulk
recallBulk *mgo.Bulk
repayBulk *mgo.Bulk
lendingTradeBulk *mgo.Bulk
}
// InitSession initializes a new session with mongodb
func NewMongoDatabase(session *mgo.Session, dbName string, mongoURL string, replicaSetName string, cacheLimit int) (*MongoDatabase, error) {
if session == nil {
// in case of multiple database instances
hosts := strings.Split(mongoURL, ",")
dbInfo := &mgo.DialInfo{
Addrs: hosts,
Database: dbName,
ReplicaSetName: replicaSetName,
Timeout: 30 * time.Second,
}
ns, err := mgo.DialWithInfo(dbInfo)
if err != nil {
return nil, err
}
session = ns
}
itemCacheLimit := defaultCacheLimit
if cacheLimit > 0 {
itemCacheLimit = cacheLimit
}
cacheItems, _ := lru.New(itemCacheLimit)
db := &MongoDatabase{
Session: session,
dbName: dbName,
cacheItems: cacheItems,
}
if err := db.EnsureIndexes(); err != nil {
return nil, err
}
return db, nil
}
func (db *MongoDatabase) IsEmptyKey(key []byte) bool {
return key == nil || len(key) == 0 || bytes.Equal(key, db.emptyKey)
}
func (db *MongoDatabase) getCacheKey(key []byte) string {
return hex.EncodeToString(key)
}
func (db *MongoDatabase) HasObject(hash common.Hash, val interface{}) (bool, error) {
if db.IsEmptyKey(hash.Bytes()) {
return false, nil
}
cacheKey := db.getCacheKey(hash.Bytes())
if db.cacheItems.Contains(cacheKey) {
return true, nil
}
sc := db.Session.Copy()
defer sc.Close()
var (
count int
err error
)
query := bson.M{"hash": hash.Hex()}
switch val.(type) {
case *tradingstate.OrderItem:
// Find key in ordersCollection collection
count, err = sc.DB(db.dbName).C(ordersCollection).Find(query).Limit(1).Count()
if err != nil {
return false, err
}
if count == 1 {
return true, nil
}
case *tradingstate.Trade:
// Find key in tradesCollection collection
count, err = sc.DB(db.dbName).C(tradesCollection).Find(query).Limit(1).Count()
if err != nil {
return false, err
}
if count == 1 {
return true, nil
}
case *lendingstate.LendingItem:
// Find key in lendingItemsCollection collection
item := val.(*lendingstate.LendingItem)
switch item.Type {
case lendingstate.Repay:
count, err = sc.DB(db.dbName).C(lendingRepayCollection).Find(query).Limit(1).Count()
case lendingstate.TopUp:
count, err = sc.DB(db.dbName).C(lendingTopUpCollection).Find(query).Limit(1).Count()
case lendingstate.Recall:
count, err = sc.DB(db.dbName).C(lendingRecallCollection).Find(query).Limit(1).Count()
default:
count, err = sc.DB(db.dbName).C(lendingItemsCollection).Find(query).Limit(1).Count()
}
if err != nil {
return false, err
}
if count == 1 {
return true, nil
}
case *lendingstate.LendingTrade:
// Find key in lendingTradesCollection collection
count, err = sc.DB(db.dbName).C(lendingTradesCollection).Find(query).Limit(1).Count()
if err != nil {
return false, err
}
if count == 1 {
return true, nil
}
}
return false, nil
}
func (db *MongoDatabase) GetObject(hash common.Hash, val interface{}) (interface{}, error) {
if db.IsEmptyKey(hash.Bytes()) {
return nil, nil
}
cacheKey := db.getCacheKey(hash.Bytes())
if cached, ok := db.cacheItems.Get(cacheKey); ok {
return cached, nil
} else {
sc := db.Session.Copy()
defer sc.Close()
query := bson.M{"hash": hash.Hex()}
switch val.(type) {
case *tradingstate.OrderItem:
var oi *tradingstate.OrderItem
err := sc.DB(db.dbName).C(ordersCollection).Find(query).One(&oi)
if err != nil {
return nil, err
}
db.cacheItems.Add(cacheKey, oi)
return oi, nil
case *tradingstate.Trade:
var t *tradingstate.Trade
err := sc.DB(db.dbName).C(tradesCollection).Find(query).One(&t)
if err != nil {
return nil, err
}
db.cacheItems.Add(cacheKey, t)
return t, nil
case *lendingstate.LendingItem:
var li *lendingstate.LendingItem
var err error
item := val.(*lendingstate.LendingItem)
switch item.Type {
case lendingstate.Repay:
err = sc.DB(db.dbName).C(lendingRepayCollection).Find(query).One(&li)
case lendingstate.TopUp:
err = sc.DB(db.dbName).C(lendingTopUpCollection).Find(query).One(&li)
case lendingstate.Recall:
err = sc.DB(db.dbName).C(lendingRecallCollection).Find(query).One(&li)
default:
err = sc.DB(db.dbName).C(lendingItemsCollection).Find(query).One(&li)
}
if err != nil {
return nil, err
}
db.cacheItems.Add(cacheKey, li)
return li, nil
case *lendingstate.LendingTrade:
var t *lendingstate.LendingTrade
err := sc.DB(db.dbName).C(lendingTradesCollection).Find(query).One(&t)
if err != nil {
return nil, err
}
db.cacheItems.Add(cacheKey, t)
return t, nil
default:
return nil, nil
}
}
}
func (db *MongoDatabase) PutObject(hash common.Hash, val interface{}) error {
cacheKey := db.getCacheKey(hash.Bytes())
db.cacheItems.Add(cacheKey, val)
switch val.(type) {
case *tradingstate.Trade:
// PutObject trade into tradesCollection collection
db.tradeBulk.Insert(val.(*tradingstate.Trade))
case *tradingstate.OrderItem:
// PutObject order into ordersCollection collection
o := val.(*tradingstate.OrderItem)
if o.Status == tradingstate.OrderStatusOpen {
db.orderBulk.Insert(o)
} else {
query := bson.M{"hash": o.Hash.Hex()}
db.orderBulk.Upsert(query, o)
}
return nil
case *tradingstate.EpochPriceItem:
item := val.(*tradingstate.EpochPriceItem)
query := bson.M{"hash": item.Hash.Hex()}
db.epochPriceBulk.Upsert(query, item)
return nil
case *lendingstate.LendingTrade:
lt := val.(*lendingstate.LendingTrade)
// PutObject LendingTrade into tradesCollection collection
if existed, err := db.HasObject(hash, val); err == nil && existed {
query := bson.M{"hash": lt.Hash.Hex()}
db.lendingTradeBulk.Upsert(query, lt)
} else {
db.lendingTradeBulk.Insert(lt)
}
case *lendingstate.LendingItem:
// PutObject order into ordersCollection collection
li := val.(*lendingstate.LendingItem)
switch li.Type {
case lendingstate.Repay:
if li.Status != lendingstate.LendingStatusReject {
li.Status = lendingstate.Repay
}
db.repayBulk.Insert(li)
return nil
case lendingstate.TopUp:
if li.Status != lendingstate.LendingStatusReject {
li.Status = lendingstate.TopUp
}
db.topUpBulk.Insert(li)
return nil
case lendingstate.Recall:
if li.Status != lendingstate.LendingStatusReject {
li.Status = lendingstate.Recall
}
db.recallBulk.Insert(li)
return nil
default:
if li.Status == lendingstate.LendingStatusOpen {
db.lendingItemBulk.Insert(li)
} else {
query := bson.M{"hash": li.Hash.Hex()}
db.lendingItemBulk.Upsert(query, li)
}
return nil
}
default:
log.Error("PutObject: unknown type of object", "val", val)
}
return nil
}
func (db *MongoDatabase) DeleteObject(hash common.Hash, val interface{}) error {
cacheKey := db.getCacheKey(hash.Bytes())
db.cacheItems.Remove(cacheKey)
sc := db.Session.Copy()
defer sc.Close()
query := bson.M{"hash": hash.Hex()}
found, err := db.HasObject(hash, val)
if err != nil {
return err
}
if found {
var err error
switch val.(type) {
case *tradingstate.OrderItem:
err = sc.DB(db.dbName).C(ordersCollection).Remove(query)
if err != nil && err != mgo.ErrNotFound {
return fmt.Errorf("failed to delete orderItem. Err: %v", err)
}
case *tradingstate.Trade:
err = sc.DB(db.dbName).C(tradesCollection).Remove(query)
if err != nil && err != mgo.ErrNotFound {
return fmt.Errorf("failed to delete XDCx trade. Err: %v", err)
}
case *lendingstate.LendingItem:
item := val.(*lendingstate.LendingItem)
switch item.Type {
case lendingstate.Repay:
err = sc.DB(db.dbName).C(lendingRepayCollection).Remove(query)
case lendingstate.TopUp:
err = sc.DB(db.dbName).C(lendingTopUpCollection).Remove(query)
case lendingstate.Recall:
err = sc.DB(db.dbName).C(lendingRecallCollection).Remove(query)
default:
err = sc.DB(db.dbName).C(lendingItemsCollection).Remove(query)
}
if err != nil && err != mgo.ErrNotFound {
return fmt.Errorf("failed to delete lendingItem. Err: %v", err)
}
case *lendingstate.LendingTrade:
err = sc.DB(db.dbName).C(lendingTradesCollection).Remove(query)
if err != nil && err != mgo.ErrNotFound {
return fmt.Errorf("failed to delete lendingTrade. Err: %v", err)
}
}
}
return nil
}
func (db *MongoDatabase) InitBulk() {
sc := db.Session
db.orderBulk = sc.DB(db.dbName).C(ordersCollection).Bulk()
db.tradeBulk = sc.DB(db.dbName).C(tradesCollection).Bulk()
db.epochPriceBulk = sc.DB(db.dbName).C(epochPriceCollection).Bulk()
}
func (db *MongoDatabase) InitLendingBulk() {
sc := db.Session
db.lendingItemBulk = sc.DB(db.dbName).C(lendingItemsCollection).Bulk()
db.lendingTradeBulk = sc.DB(db.dbName).C(lendingTradesCollection).Bulk()
db.topUpBulk = sc.DB(db.dbName).C(lendingTopUpCollection).Bulk()
db.repayBulk = sc.DB(db.dbName).C(lendingRepayCollection).Bulk()
db.recallBulk = sc.DB(db.dbName).C(lendingRecallCollection).Bulk()
}
func (db *MongoDatabase) CommitBulk() error {
if _, err := db.orderBulk.Run(); err != nil && !mgo.IsDup(err) {
return err
}
if _, err := db.tradeBulk.Run(); err != nil && !mgo.IsDup(err) {
return err
}
if _, err := db.epochPriceBulk.Run(); err != nil && !mgo.IsDup(err) {
return err
}
return nil
}
func (db *MongoDatabase) CommitLendingBulk() error {
if _, err := db.lendingItemBulk.Run(); err != nil && !mgo.IsDup(err) {
return err
}
if _, err := db.lendingTradeBulk.Run(); err != nil && !mgo.IsDup(err) {
return err
}
if _, err := db.topUpBulk.Run(); err != nil && !mgo.IsDup(err) {
return err
}
if _, err := db.repayBulk.Run(); err != nil && !mgo.IsDup(err) {
return err
}
if _, err := db.recallBulk.Run(); err != nil && !mgo.IsDup(err) {
return err
}
return nil
}
func (db *MongoDatabase) Put(key []byte, val []byte) error {
// for levelDB only
return nil
}
func (db *MongoDatabase) Delete(key []byte) error {
// for levelDB only
return nil
}
func (db *MongoDatabase) Has(key []byte) (bool, error) {
// for levelDB only
return false, nil
}
func (db *MongoDatabase) Get(key []byte) ([]byte, error) {
// for levelDB only
return nil, nil
}
func (db *MongoDatabase) DeleteItemByTxHash(txhash common.Hash, val interface{}) {
sc := db.Session.Copy()
defer sc.Close()
query := bson.M{"txHash": txhash.Hex()}
switch val.(type) {
case *tradingstate.OrderItem:
if err := sc.DB(db.dbName).C(ordersCollection).Remove(query); err != nil && err != mgo.ErrNotFound {
log.Error("DeleteItemByTxHash: failed to delete order", "txhash", txhash, "err", err)
}
case *tradingstate.Trade:
if err := sc.DB(db.dbName).C(tradesCollection).Remove(query); err != nil && err != mgo.ErrNotFound {
log.Error("DeleteItemByTxHash: failed to delete trade", "txhash", txhash, "err", err)
}
case *lendingstate.LendingItem:
item := val.(*lendingstate.LendingItem)
switch item.Type {
case lendingstate.Repay:
if err := sc.DB(db.dbName).C(lendingRepayCollection).Remove(query); err != nil && err != mgo.ErrNotFound {
log.Error("DeleteItemByTxHash: failed to delete repayItem", "txhash", txhash, "err", err)
}
return
case lendingstate.TopUp:
if err := sc.DB(db.dbName).C(lendingTopUpCollection).Remove(query); err != nil && err != mgo.ErrNotFound {
log.Error("DeleteItemByTxHash: failed to delete topupItem", "txhash", txhash, "err", err)
}
return
case lendingstate.Recall:
if err := sc.DB(db.dbName).C(lendingRecallCollection).Remove(query); err != nil && err != mgo.ErrNotFound {
log.Error("DeleteItemByTxHash: failed to delete recallItem", "txhash", txhash, "err", err)
}
return
default:
if err := sc.DB(db.dbName).C(lendingItemsCollection).Remove(query); err != nil && err != mgo.ErrNotFound {
log.Error("DeleteItemByTxHash: failed to delete lendingItem", "txhash", txhash, "err", err)
}
return
}
case *lendingstate.LendingTrade:
if err := sc.DB(db.dbName).C(lendingTradesCollection).Remove(query); err != nil && err != mgo.ErrNotFound {
log.Error("DeleteItemByTxHash: failed to delete lendingTrade", "txhash", txhash, "err", err)
}
default:
log.Error("DeleteItemByTxHash: Unknown object type", "txhash", txhash, "object", val)
}
}
func (db *MongoDatabase) GetListItemByTxHash(txhash common.Hash, val interface{}) interface{} {
sc := db.Session.Copy()
defer sc.Close()
query := bson.M{"txHash": txhash.Hex()}
switch val.(type) {
case *tradingstate.OrderItem:
result := []*tradingstate.OrderItem{}
if err := sc.DB(db.dbName).C(ordersCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
log.Error("failed to GetListItemByTxHash (orders)", "err", err, "Txhash", txhash)
}
return result
case *tradingstate.Trade:
result := []*tradingstate.Trade{}
if err := sc.DB(db.dbName).C(tradesCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
log.Error("failed to GetListItemByTxHash (trades)", "err", err, "Txhash", txhash)
}
return result
case *lendingstate.LendingItem:
item := val.(*lendingstate.LendingItem)
result := []*lendingstate.LendingItem{}
switch item.Type {
case lendingstate.Repay:
if err := sc.DB(db.dbName).C(lendingRepayCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
log.Error("failed to GetListItemByTxHash (repayItems)", "err", err, "txhash", txhash)
}
return result
case lendingstate.TopUp:
if err := sc.DB(db.dbName).C(lendingTopUpCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
log.Error("failed to GetListItemByTxHash (topupItems)", "err", err, "txhash", txhash)
}
return result
case lendingstate.Recall:
if err := sc.DB(db.dbName).C(lendingRecallCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
log.Error("failed to GetListItemByTxHash (recallItems)", "err", err, "txhash", txhash)
}
return result
default:
if err := sc.DB(db.dbName).C(lendingItemsCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
log.Error("failed to GetListItemByTxHash (lendingItems)", "err", err, "txhash", txhash)
}
return result
}
case *lendingstate.LendingTrade:
result := []*lendingstate.LendingTrade{}
if err := sc.DB(db.dbName).C(lendingTradesCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
log.Error("failed to GetListItemByTxHash (lendingTrades)", "err", err, "Txhash", txhash)
}
return result
default:
log.Error("GetListItemByTxHash: Unknown object type", "txhash", txhash, "object", val)
}
return nil
}
func (db *MongoDatabase) GetListItemByHashes(hashes []string, val interface{}) interface{} {
sc := db.Session.Copy()
defer sc.Close()
query := bson.M{"hash": bson.M{"$in": hashes}}
switch val.(type) {
case *tradingstate.OrderItem:
result := []*tradingstate.OrderItem{}
if err := sc.DB(db.dbName).C(ordersCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
log.Error("failed to GetListItemByHashes (orders)", "err", err, "hashes", hashes)
}
return result
case *tradingstate.Trade:
result := []*tradingstate.Trade{}
if err := sc.DB(db.dbName).C(tradesCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
log.Error("failed to GetListItemByHashes (trades)", "err", err, "hashes", hashes)
}
return result
case *lendingstate.LendingItem:
item := val.(*lendingstate.LendingItem)
result := []*lendingstate.LendingItem{}
switch item.Type {
case lendingstate.Repay:
if err := sc.DB(db.dbName).C(lendingRepayCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
log.Error("failed to GetListItemByHashes (repayItems)", "err", err, "hashes", hashes)
}
return result
case lendingstate.TopUp:
if err := sc.DB(db.dbName).C(lendingTopUpCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
log.Error("failed to GetListItemByHashes (topupItems)", "err", err, "hashes", hashes)
}
return result
case lendingstate.Recall:
if err := sc.DB(db.dbName).C(lendingRecallCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
log.Error("failed to GetListItemByHashes (recallItems)", "err", err, "hashes", hashes)
}
return result
default:
if err := sc.DB(db.dbName).C(lendingItemsCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
log.Error("failed to GetListItemByHashes (lendingItems)", "err", err, "hashes", hashes)
}
return result
}
case *lendingstate.LendingTrade:
result := []*lendingstate.LendingTrade{}
if err := sc.DB(db.dbName).C(lendingTradesCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
log.Error("failed to GetListItemByHashes (lendingTrades)", "err", err, "hashes", hashes)
}
return result
default:
log.Error("GetListItemByHashes: Unknown object type", "hashes", hashes, "object", val)
}
return nil
}
func (db *MongoDatabase) EnsureIndexes() error {
orderHashIndex := mgo.Index{
Key: []string{"hash"},
Unique: true,
DropDups: true,
Background: true,
Sparse: true,
Name: "index_order_hash",
}
orderTxHashIndex := mgo.Index{
Key: []string{"txHash"},
DropDups: true,
Background: true,
Sparse: true,
Name: "index_order_tx_hash",
}
tradeHashIndex := mgo.Index{
Key: []string{"hash"},
Unique: true,
DropDups: true,
Background: true,
Sparse: true,
Name: "index_trade_hash",
}
tradeTxHashIndex := mgo.Index{
Key: []string{"txHash"},
DropDups: true,
Background: true,
Sparse: true,
Name: "index_trade_tx_hash",
}
lendingItemHashIndex := mgo.Index{
Key: []string{"hash"},
Unique: true,
DropDups: true,
Background: true,
Sparse: true,
Name: "index_lending_item_hash",
}
lendingItemTxHashIndex := mgo.Index{
Key: []string{"txHash"},
DropDups: true,
Background: true,
Sparse: true,
Name: "index_lending_item_tx_hash",
}
lendingTradeHashIndex := mgo.Index{
Key: []string{"hash"},
Unique: true,
DropDups: true,
Background: true,
Sparse: true,
Name: "index_lending_trade_hash",
}
lendingTradeTxHashIndex := mgo.Index{
Key: []string{"txHash"},
DropDups: true,
Background: true,
Sparse: true,
Name: "index_lending_trade_tx_hash",
}
repayHashIndex := mgo.Index{
Key: []string{"hash"},
DropDups: true,
Background: true,
Sparse: true,
Name: "index_lending_repay_hash",
}
repayTxHashIndex := mgo.Index{
Key: []string{"txHash"},
DropDups: true,
Background: true,
Sparse: true,
Name: "index_lending_repay_tx_hash",
}
repayUniqueIndex := mgo.Index{
Key: []string{"txHash", "hash"},
Unique: true,
DropDups: true,
Background: true,
Sparse: true,
Name: "index_lending_repay_unique",
}
recallHashIndex := mgo.Index{
Key: []string{"hash"},
DropDups: true,
Background: true,
Sparse: true,
Name: "index_lending_recall_hash",
}
recallTxHashIndex := mgo.Index{
Key: []string{"txHash"},
DropDups: true,
Background: true,
Sparse: true,
Name: "index_lending_recall_tx_hash",
}
recallUniqueIndex := mgo.Index{
Key: []string{"txHash", "hash"},
Unique: true,
DropDups: true,
Background: true,
Sparse: true,
Name: "index_lending_recall_unique",
}
topupHashIndex := mgo.Index{
Key: []string{"hash"},
DropDups: true,
Background: true,
Sparse: true,
Name: "index_lending_topup_hash",
}
topupTxHashIndex := mgo.Index{
Key: []string{"txHash"},
DropDups: true,
Background: true,
Sparse: true,
Name: "index_lending_topup_tx_hash",
}
topUpUniqueIndex := mgo.Index{
Key: []string{"txHash", "hash"},
Unique: true,
DropDups: true,
Background: true,
Sparse: true,
Name: "index_lending_topup_unique",
}
epochPriceIndex := mgo.Index{
Key: []string{"hash"},
Unique: true,
DropDups: true,
Background: true,
Sparse: true,
Name: "index_epoch_price",
}
sc := db.Session.Copy()
defer sc.Close()
indexes, _ := sc.DB(db.dbName).C(ordersCollection).Indexes()
if !existingIndex(orderHashIndex.Name, indexes) {
if err := sc.DB(db.dbName).C(ordersCollection).EnsureIndex(orderHashIndex); err != nil {
return fmt.Errorf("failed to create index %s . Err: %v", orderHashIndex.Name, err)
}
}
if !existingIndex(orderTxHashIndex.Name, indexes) {
if err := sc.DB(db.dbName).C(ordersCollection).EnsureIndex(orderTxHashIndex); err != nil {
return fmt.Errorf("failed to create index %s . Err: %v", orderTxHashIndex.Name, err)
}
}
indexes, _ = sc.DB(db.dbName).C(tradesCollection).Indexes()
if !existingIndex(tradeHashIndex.Name, indexes) {
if err := sc.DB(db.dbName).C(tradesCollection).EnsureIndex(tradeHashIndex); err != nil {
return fmt.Errorf("failed to create index %s . Err: %v", tradeHashIndex.Name, err)
}
}
if !existingIndex(tradeTxHashIndex.Name, indexes) {
if err := sc.DB(db.dbName).C(tradesCollection).EnsureIndex(tradeTxHashIndex); err != nil {
return fmt.Errorf("failed to create index %s . Err: %v", tradeTxHashIndex.Name, err)
}
}
indexes, _ = sc.DB(db.dbName).C(lendingItemsCollection).Indexes()
if !existingIndex(lendingItemHashIndex.Name, indexes) {
if err := sc.DB(db.dbName).C(lendingItemsCollection).EnsureIndex(lendingItemHashIndex); err != nil {
return fmt.Errorf("failed to create index %s . Err: %v", lendingItemHashIndex.Name, err)
}
}
if !existingIndex(lendingItemTxHashIndex.Name, indexes) {
if err := sc.DB(db.dbName).C(lendingItemsCollection).EnsureIndex(lendingItemTxHashIndex); err != nil {
return fmt.Errorf("failed to create index %s . Err: %v", lendingItemTxHashIndex.Name, err)
}
}
indexes, _ = sc.DB(db.dbName).C(lendingTradesCollection).Indexes()
if !existingIndex(lendingTradeHashIndex.Name, indexes) {
if err := sc.DB(db.dbName).C(lendingTradesCollection).EnsureIndex(lendingTradeHashIndex); err != nil {
return fmt.Errorf("failed to create index %s . Err: %v", lendingTradeHashIndex.Name, err)
}
}
if !existingIndex(lendingTradeTxHashIndex.Name, indexes) {
if err := sc.DB(db.dbName).C(lendingTradesCollection).EnsureIndex(lendingTradeTxHashIndex); err != nil {
return fmt.Errorf("failed to create index %s . Err: %v", lendingTradeTxHashIndex.Name, err)
}
}
indexes, _ = sc.DB(db.dbName).C(lendingRepayCollection).Indexes()
if !existingIndex(repayHashIndex.Name, indexes) {
if err := sc.DB(db.dbName).C(lendingRepayCollection).EnsureIndex(repayHashIndex); err != nil {
return fmt.Errorf("failed to create index %s . Err: %v", repayHashIndex.Name, err)
}
}
if !existingIndex(repayTxHashIndex.Name, indexes) {
if err := sc.DB(db.dbName).C(lendingRepayCollection).EnsureIndex(repayTxHashIndex); err != nil {
return fmt.Errorf("failed to create index %s . Err: %v", repayTxHashIndex.Name, err)
}
}
if !existingIndex(repayUniqueIndex.Name, indexes) {
if err := sc.DB(db.dbName).C(lendingRepayCollection).EnsureIndex(repayUniqueIndex); err != nil {
return fmt.Errorf("failed to create index %s . Err: %v", repayUniqueIndex.Name, err)
}
}
indexes, _ = sc.DB(db.dbName).C(lendingRecallCollection).Indexes()
if !existingIndex(recallHashIndex.Name, indexes) {
if err := sc.DB(db.dbName).C(lendingRecallCollection).EnsureIndex(recallHashIndex); err != nil {
return fmt.Errorf("failed to create index %s . Err: %v", recallHashIndex.Name, err)
}
}
if !existingIndex(recallTxHashIndex.Name, indexes) {
if err := sc.DB(db.dbName).C(lendingRecallCollection).EnsureIndex(recallTxHashIndex); err != nil {
return fmt.Errorf("failed to create index %s . Err: %v", recallTxHashIndex.Name, err)
}
}
if !existingIndex(recallUniqueIndex.Name, indexes) {
if err := sc.DB(db.dbName).C(lendingRecallCollection).EnsureIndex(repayUniqueIndex); err != nil {
return fmt.Errorf("failed to create index %s . Err: %v", repayUniqueIndex.Name, err)
}
}
indexes, _ = sc.DB(db.dbName).C(lendingTopUpCollection).Indexes()
if !existingIndex(topupHashIndex.Name, indexes) {
if err := sc.DB(db.dbName).C(lendingTopUpCollection).EnsureIndex(topupHashIndex); err != nil {
return fmt.Errorf("failed to create index %s . Err: %v", topupHashIndex.Name, err)
}
}
if !existingIndex(topupTxHashIndex.Name, indexes) {
if err := sc.DB(db.dbName).C(lendingTopUpCollection).EnsureIndex(topupTxHashIndex); err != nil {
return fmt.Errorf("failed to create index %s . Err: %v", topupTxHashIndex.Name, err)
}
}
if !existingIndex(topUpUniqueIndex.Name, indexes) {
if err := sc.DB(db.dbName).C(lendingTopUpCollection).EnsureIndex(repayUniqueIndex); err != nil {
return fmt.Errorf("failed to create index %s . Err: %v", repayUniqueIndex.Name, err)
}
}
indexes, _ = sc.DB(db.dbName).C(epochPriceCollection).Indexes()
if !existingIndex(epochPriceIndex.Name, indexes) {
if err := sc.DB(db.dbName).C(epochPriceCollection).EnsureIndex(epochPriceIndex); err != nil {
return fmt.Errorf("failed to create index %s . Err: %v", epochPriceIndex.Name, err)
}
}
return nil
}
func (db *MongoDatabase) Close() error {
return db.Close()
}
// HasAncient returns an error as we don't have a backing chain freezer.
func (db *MongoDatabase) HasAncient(kind string, number uint64) (bool, error) {
return false, errNotSupported
}
// Ancient returns an error as we don't have a backing chain freezer.
func (db *MongoDatabase) Ancient(kind string, number uint64) ([]byte, error) {
return nil, errNotSupported
}
// Ancients returns an error as we don't have a backing chain freezer.
func (db *MongoDatabase) Ancients() (uint64, error) {
return 0, errNotSupported
}
// AncientSize returns an error as we don't have a backing chain freezer.
func (db *MongoDatabase) AncientSize(kind string) (uint64, error) {
return 0, errNotSupported
}
// AppendAncient returns an error as we don't have a backing chain freezer.
func (db *MongoDatabase) AppendAncient(number uint64, hash, header, body, receipts, td []byte) error {
return errNotSupported
}
// TruncateAncients returns an error as we don't have a backing chain freezer.
func (db *MongoDatabase) TruncateAncients(items uint64) error {
return errNotSupported
}
// Sync returns an error as we don't have a backing chain freezer.
func (db *MongoDatabase) Sync() error {
return errNotSupported
}
func (db *MongoDatabase) NewIterator(prefix []byte, start []byte) ethdb.Iterator {
return db.NewIterator(prefix, start)
}
func (db *MongoDatabase) Stat(property string) (string, error) {
return db.Stat(property)
}
func (db *MongoDatabase) Compact(start []byte, limit []byte) error {
return db.Compact(start, limit)
}
func (db *MongoDatabase) NewBatch() ethdb.Batch {
// for levelDB only
return nil
}
type keyvalue struct {
key []byte
value []byte
}
type Batch struct {
db *MongoDatabase
collection string
b []keyvalue
size int
}
func (b *Batch) SetCollection(collection string) {
// for levelDB only
}
func (b *Batch) Put(key, value []byte) error {
// for levelDB only
return nil
}
func (b *Batch) Write() error {
// for levelDB only
return nil
}
func (b *Batch) ValueSize() int {
// for levelDB only
return int(0)
}
func (b *Batch) Reset() {
// for levelDB only
}
func existingIndex(indexName string, indexes []mgo.Index) bool {
if len(indexes) == 0 {
return false
}
for _, index := range indexes {
if index.Name == indexName {
return true
}
}
return false
}

954
XDCxlending/XDCxlending.go Normal file
View file

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

37
XDCxlending/api.go Normal file
View file

@ -0,0 +1,37 @@
package XDCxlending
import (
"context"
"errors"
"sync"
"time"
)
// List of errors
var (
ErrOrderNonceTooLow = errors.New("OrderNonce too low")
ErrOrderNonceTooHigh = errors.New("OrderNonce too high")
)
// PublicXDCXLendingAPI provides the XDCX RPC service that can be
// use publicly without security implications.
type PublicXDCXLendingAPI struct {
t *Lending
mu sync.Mutex
lastUsed map[string]time.Time // keeps track when a filter was polled for the last time.
}
// NewPublicXDCXLendingAPI create a new RPC XDCX service.
func NewPublicXDCXLendingAPI(t *Lending) *PublicXDCXLendingAPI {
api := &PublicXDCXLendingAPI{
t: t,
lastUsed: make(map[string]time.Time),
}
return api
}
// Version returns the Lending sub-protocol version.
func (api *PublicXDCXLendingAPI) Version(ctx context.Context) string {
return ProtocolVersionStr
}

View file

@ -0,0 +1,216 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package lendingstate
import (
"fmt"
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/trie"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/log"
)
// XDCXTrie wraps a trie with key hashing. In a secure trie, all
// access operations hash the key using keccak256. This prevents
// calling code from creating long chains of nodes that
// increase the access time.
//
// Contrary to a regular trie, a XDCXTrie can only be created with
// New and must have an attached database. The database also stores
// the preimage of each key.
//
// XDCXTrie is not safe for concurrent use.
type XDCXTrie struct {
trie trie.Trie
hashKeyBuf [common.HashLength]byte
secKeyCache map[string][]byte
secKeyCacheOwner *XDCXTrie // Pointer to self, replace the key cache on mismatch
}
// NewXDCXTrie creates a trie with an existing root node from a backing database
// and optional intermediate in-memory node pool.
//
// If root is the zero hash or the sha3 hash of an empty string, the
// trie is initially empty. Otherwise, New will panic if db is nil
// and returns MissingNodeError if the root node cannot be found.
//
// Accessing the trie loads nodes from the database or node pool on demand.
// Loaded nodes are kept around until their 'cache generation' expires.
// A new cache generation is created by each call to Commit.
// cachelimit sets the number of past cache generations to keep.
func NewXDCXTrie(root common.Hash, db *trie.Database) (*XDCXTrie, error) {
if db == nil {
panic("trie.NewXDCXTrie called without a database")
}
trie, err := trie.New(root, db)
if err != nil {
return nil, err
}
return &XDCXTrie{trie: *trie}, nil
}
// Get returns the value for key stored in the trie.
// The value bytes must not be modified by the caller.
func (t *XDCXTrie) Get(key []byte) []byte {
res, err := t.TryGet(key)
if err != nil {
log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
}
return res
}
// TryGet returns the value for key stored in the trie.
// The value bytes must not be modified by the caller.
// If a node was not found in the database, a MissingNodeError is returned.
func (t *XDCXTrie) TryGet(key []byte) ([]byte, error) {
return t.trie.TryGet(key)
}
// TryGetBestLeftKey returns the value of max left leaf
// If a node was not found in the database, a MissingNodeError is returned.
func (t *XDCXTrie) TryGetBestLeftKeyAndValue() ([]byte, []byte, error) {
return t.trie.TryGetBestLeftKeyAndValue()
}
// TryGetBestRightKey returns the value of max left leaf
// If a node was not found in the database, a MissingNodeError is returned.
func (t *XDCXTrie) TryGetBestRightKeyAndValue() ([]byte, []byte, error) {
return t.trie.TryGetBestRightKeyAndValue()
}
// Update associates key with value in the trie. Subsequent calls to
// Get will return value. If value has length zero, any existing value
// is deleted from the trie and calls to Get will return nil.
//
// The value bytes must not be modified by the caller while they are
// stored in the trie.
func (t *XDCXTrie) Update(key, value []byte) {
if err := t.TryUpdate(key, value); err != nil {
log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
}
}
// TryUpdate associates key with value in the trie. Subsequent calls to
// Get will return value. If value has length zero, any existing value
// is deleted from the trie and calls to Get will return nil.
//
// The value bytes must not be modified by the caller while they are
// stored in the trie.
//
// If a node was not found in the database, a MissingNodeError is returned.
func (t *XDCXTrie) TryUpdate(key, value []byte) error {
err := t.trie.TryUpdate(key, value)
if err != nil {
return err
}
t.getSecKeyCache()[string(key)] = common.CopyBytes(key)
return nil
}
// Delete removes any existing value for key from the trie.
func (t *XDCXTrie) Delete(key []byte) {
if err := t.TryDelete(key); err != nil {
log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
}
}
// TryDelete removes any existing value for key from the trie.
// If a node was not found in the database, a MissingNodeError is returned.
func (t *XDCXTrie) TryDelete(key []byte) error {
delete(t.getSecKeyCache(), string(key))
return t.trie.TryDelete(key)
}
// GetKey returns the sha3 preimage of a hashed key that was
// previously used to store a value.
func (t *XDCXTrie) GetKey(shaKey []byte) []byte {
if key, ok := t.getSecKeyCache()[string(shaKey)]; ok {
return key
}
key, _ := t.trie.Db.Preimage(common.BytesToHash(shaKey))
return key
}
// Commit writes all nodes and the secure hash pre-images to the trie's database.
// Nodes are stored with their sha3 hash as the key.
//
// Committing flushes nodes from memory. Subsequent Get calls will load nodes
// from the database.
func (t *XDCXTrie) Commit(onleaf trie.LeafCallback) (root common.Hash, err error) {
// Write all the pre-images to the actual disk database
if len(t.getSecKeyCache()) > 0 {
t.trie.Db.Lock.Lock()
for hk, key := range t.secKeyCache {
t.trie.Db.InsertPreimage(common.BytesToHash([]byte(hk)), key)
}
t.trie.Db.Lock.Unlock()
t.secKeyCache = make(map[string][]byte)
}
// Commit the trie to its intermediate node database
return t.trie.Commit(onleaf)
}
func (t *XDCXTrie) Hash() common.Hash {
return t.trie.Hash()
}
func (t *XDCXTrie) Copy() *XDCXTrie {
cpy := *t
return &cpy
}
// NodeIterator returns an iterator that returns nodes of the underlying trie. Iteration
// starts at the key after the given start key.
func (t *XDCXTrie) NodeIterator(start []byte) trie.NodeIterator {
return t.trie.NodeIterator(start)
}
// hashKey returns the hash of key as an ephemeral buffer.
// The caller must not hold onto the return value because it will become
// invalid on the next call to hashKey or secKey.
//func (t *XDCXTrie) hashKey(key []byte) []byte {
// h := newHasher(0, 0, nil)
// h.sha.Reset()
// h.sha.Write(key)
// buf := h.sha.Sum(t.hashKeyBuf[:0])
// returnHasherToPool(h)
// return buf
//}
// getSecKeyCache returns the current secure key cache, creating a new one if
// ownership changed (i.e. the current secure trie is a copy of another owning
// the actual cache).
func (t *XDCXTrie) getSecKeyCache() map[string][]byte {
if t != t.secKeyCacheOwner {
t.secKeyCacheOwner = t
t.secKeyCache = make(map[string][]byte)
}
return t.secKeyCache
}
// Prove constructs a merkle proof for key. The result contains all encoded nodes
// on the path to the value at key. The value itself is also included in the last
// node and can be retrieved by verifying the proof.
//
// If the trie does not contain a value for key, the returned proof contains all
// nodes of the longest existing prefix of the key (at least the root node), ending
// with the node that proves the absence of the key.
func (t *XDCXTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error {
return t.trie.Prove(key, fromLevel, proofDb)
}

View file

@ -0,0 +1,251 @@
package lendingstate
import (
"encoding/json"
"github.com/XinFinOrg/XDPoSChain/crypto"
"math/big"
"time"
"github.com/XinFinOrg/XDPoSChain/common"
)
var (
EmptyAddress = "0x0000000000000000000000000000000000000000"
EmptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
)
var EmptyHash = common.Hash{}
var Zero = big.NewInt(0)
var One = big.NewInt(1)
var EmptyLendingOrder = LendingItem{
Quantity: Zero,
}
var EmptyLendingTrade = LendingTrade{
Amount: big.NewInt(0),
}
type itemList struct {
Volume *big.Int
Root common.Hash
}
type lendingObject struct {
Nonce uint64
TradeNonce uint64
InvestingRoot common.Hash
BorrowingRoot common.Hash
LiquidationTimeRoot common.Hash
LendingItemRoot common.Hash
LendingTradeRoot common.Hash
}
// liquidation reasons
const (
LiquidatedByTime = uint64(0)
LiquidatedByPrice = uint64(1)
)
type LiquidationData struct {
RecallAmount *big.Int
LiquidationAmount *big.Int
CollateralPrice *big.Int
Reason uint64
}
var (
TokenMappingSlot = map[string]uint64{
"balances": 0,
}
RelayerMappingSlot = map[string]uint64{
"CONTRACT_OWNER": 0,
"MaximumRelayers": 1,
"MaximumTokenList": 2,
"RELAYER_LIST": 3,
"RELAYER_COINBASES": 4,
"RESIGN_REQUESTS": 5,
"RELAYER_ON_SALE_LIST": 6,
"RelayerCount": 7,
"MinimumDeposit": 8,
}
RelayerStructMappingSlot = map[string]*big.Int{
"_deposit": big.NewInt(0),
"_fee": big.NewInt(1),
"_fromTokens": big.NewInt(2),
"_toTokens": big.NewInt(3),
"_index": big.NewInt(4),
"_owner": big.NewInt(5),
}
)
type TxLendingBatch struct {
Data []*LendingItem
Timestamp int64
TxHash common.Hash
}
type MatchingResult struct {
Trades []*LendingTrade
Rejects []*LendingItem
}
type FinalizedResult struct {
Liquidated []common.Hash
AutoRepay []common.Hash
AutoTopUp []common.Hash
AutoRecall []common.Hash
TxHash common.Hash
Timestamp int64
}
// use orderHash instead of tradeId
// because both takerOrders don't have tradeId
func GetLendingItemHistoryKey(lendingToken, collateralToken common.Address, lendingItemHash common.Hash) common.Hash {
return crypto.Keccak256Hash(lendingToken.Bytes(), collateralToken.Bytes(), lendingItemHash.Bytes())
}
type LendingItemHistoryItem struct {
TxHash common.Hash
FilledAmount *big.Int
Status string
UpdatedAt time.Time
}
type LendingTradeHistoryItem struct {
TxHash common.Hash
CollateralLockedAmount *big.Int
LiquidationPrice *big.Int
Status string
UpdatedAt time.Time
}
type LendingPair struct {
LendingToken common.Address
CollateralToken common.Address
}
// ToJSON : log json string
func ToJSON(object interface{}, args ...string) string {
var str []byte
if len(args) == 2 {
str, _ = json.MarshalIndent(object, args[0], args[1])
} else {
str, _ = json.Marshal(object)
}
return string(str)
}
func Mul(x, y *big.Int) *big.Int {
return big.NewInt(0).Mul(x, y)
}
func Div(x, y *big.Int) *big.Int {
return big.NewInt(0).Div(x, y)
}
func Add(x, y *big.Int) *big.Int {
return big.NewInt(0).Add(x, y)
}
func Sub(x, y *big.Int) *big.Int {
return big.NewInt(0).Sub(x, y)
}
func Neg(x *big.Int) *big.Int {
return big.NewInt(0).Neg(x)
}
func ToBigInt(s string) *big.Int {
res := big.NewInt(0)
res.SetString(s, 10)
return res
}
func CloneBigInt(bigInt *big.Int) *big.Int {
res := new(big.Int).SetBytes(bigInt.Bytes())
return res
}
func Exp(x, y *big.Int) *big.Int {
return big.NewInt(0).Exp(x, y, nil)
}
func Max(a, b *big.Int) *big.Int {
if a.Cmp(b) == 1 {
return a
} else {
return b
}
}
func GetLendingOrderBookHash(lendingToken common.Address, term uint64) common.Hash {
return crypto.Keccak256Hash(append(common.Uint64ToHash(term).Bytes(), lendingToken.Bytes()...))
}
func EncodeTxLendingBatch(batch TxLendingBatch) ([]byte, error) {
data, err := json.Marshal(batch)
if err != nil || data == nil {
return []byte{}, err
}
return data, nil
}
func DecodeTxLendingBatch(data []byte) (TxLendingBatch, error) {
txMatchResult := TxLendingBatch{}
if err := json.Unmarshal(data, &txMatchResult); err != nil {
return TxLendingBatch{}, err
}
return txMatchResult, nil
}
func EtherToWei(amount *big.Int) *big.Int {
return new(big.Int).Mul(amount, new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil))
}
func WeiToEther(amount *big.Int) *big.Int {
return new(big.Int).Div(amount, new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil))
}
func EncodeFinalizedResult(liquidatedTrades, autoRepayTrades, autoTopUpTrades, autoRecallTrades []*LendingTrade) ([]byte, error) {
liquidatedHashes := []common.Hash{}
autoRepayHashes := []common.Hash{}
autoTopUpHashes := []common.Hash{}
autoRecallHashes := []common.Hash{}
for _, trade := range liquidatedTrades {
liquidatedHashes = append(liquidatedHashes, trade.Hash)
}
for _, trade := range autoRepayTrades {
autoRepayHashes = append(autoRepayHashes, trade.Hash)
}
for _, trade := range autoTopUpTrades {
autoTopUpHashes = append(autoTopUpHashes, trade.Hash)
}
for _, trade := range autoRecallTrades {
autoRecallHashes = append(autoRecallHashes, trade.Hash)
}
result := FinalizedResult{
Liquidated: liquidatedHashes,
AutoRepay: autoRepayHashes,
AutoTopUp: autoTopUpHashes,
AutoRecall: autoRecallHashes,
Timestamp: time.Now().UnixNano(),
}
data, err := json.Marshal(result)
if err != nil || data == nil {
return []byte{}, err
}
return data, nil
}
func DecodeFinalizedResult(data []byte) (result FinalizedResult, err error) {
result = FinalizedResult{}
if err := json.Unmarshal(data, &result); err != nil {
return FinalizedResult{}, err
}
return result, nil
}
func GetLendingCacheKey(item *LendingItem) common.Hash {
return crypto.Keccak256Hash(item.UserAddress.Bytes(), item.Nonce.Bytes())
}

View file

@ -0,0 +1,139 @@
// Copyright 2017 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package lendingstate
import (
"fmt"
"sync"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/trie"
lru "github.com/hashicorp/golang-lru"
)
// Trie cache generation limit after which to evic trie nodes from memory.
var MaxTrieCacheGen = uint16(120)
const (
// Number of past tries to keep. This value is chosen such that
// reasonable chain reorg depths will hit an existing trie.
maxPastTries = 12
// Number of codehash->size associations to keep.
codeSizeCacheSize = 100000
)
// Database wraps access to tries and contract code.
type Database interface {
// OpenTrie opens the main account trie.
OpenTrie(root common.Hash) (Trie, error)
// OpenStorageTrie opens the storage trie of an account.
OpenStorageTrie(addrHash, root common.Hash) (Trie, error)
// CopyTrie returns an independent copy of the given trie.
CopyTrie(Trie) Trie
// ContractCode retrieves a particular contract's code.
ContractCode(addrHash, codeHash common.Hash) ([]byte, error)
// ContractCodeSize retrieves a particular contracts code's size.
ContractCodeSize(addrHash, codeHash common.Hash) (int, error)
// TrieDB retrieves the low level trie database used for data storage.
TrieDB() *trie.Database
}
// Trie is a Ethereum Merkle Trie.
type Trie interface {
TryGet(key []byte) ([]byte, error)
TryGetBestLeftKeyAndValue() ([]byte, []byte, error)
TryGetBestRightKeyAndValue() ([]byte, []byte, error)
TryUpdate(key, value []byte) error
TryDelete(key []byte) error
Commit(onleaf trie.LeafCallback) (common.Hash, error)
Hash() common.Hash
NodeIterator(startKey []byte) trie.NodeIterator
GetKey([]byte) []byte // TODO(fjl): remove this when XDCXTrie is removed
Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error
}
// NewDatabase creates a backing store for state. The returned database is safe for
// concurrent use and retains cached trie nodes in memory. The pool is an optional
// intermediate trie-node memory pool between the low level storage layer and the
// high level trie abstraction.
func NewDatabase(db ethdb.Database) Database {
csc, _ := lru.New(codeSizeCacheSize)
return &cachingDB{
db: trie.NewDatabase(db),
codeSizeCache: csc,
}
}
type cachingDB struct {
db *trie.Database
mu sync.Mutex
pastTries []*XDCXTrie
codeSizeCache *lru.Cache
}
// OpenTrie opens the main account trie.
func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
return NewXDCXTrie(root, db.db)
}
func (db *cachingDB) pushTrie(t *XDCXTrie) {
db.mu.Lock()
defer db.mu.Unlock()
if len(db.pastTries) >= maxPastTries {
copy(db.pastTries, db.pastTries[1:])
db.pastTries[len(db.pastTries)-1] = t
} else {
db.pastTries = append(db.pastTries, t)
}
}
// OpenStorageTrie opens the storage trie of an account.
func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) {
return NewXDCXTrie(root, db.db)
}
// CopyTrie returns an independent copy of the given trie.
func (db *cachingDB) CopyTrie(t Trie) Trie {
switch t := t.(type) {
case *XDCXTrie:
return t.Copy()
default:
panic(fmt.Errorf("unknown trie type %T", t))
}
}
// ContractCode retrieves a particular contract's code.
func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) {
return nil, nil
}
// ContractCodeSize retrieves a particular contracts code's size.
func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, error) {
return 0, nil
}
// TrieDB retrieves any intermediate trie-node caching layer.
func (db *cachingDB) TrieDB() *trie.Database {
return db.db
}

View file

@ -0,0 +1,417 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package lendingstate
import (
"fmt"
"github.com/XinFinOrg/XDPoSChain/rlp"
"math/big"
"sort"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/trie"
)
type DumpOrderList struct {
Volume *big.Int
Orders map[*big.Int]*big.Int
}
type DumpOrderBookInfo struct {
Nonce uint64
TradeNonce uint64
BestInvesting *big.Int
BestBorrowing *big.Int
LowestLiquidationTime *big.Int
}
func (self *LendingStateDB) DumpInvestingTrie(orderBook common.Hash) (map[*big.Int]DumpOrderList, error) {
exhangeObject := self.getLendingExchange(orderBook)
if exhangeObject == nil {
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
}
mapResult := map[*big.Int]DumpOrderList{}
it := trie.NewIterator(exhangeObject.getInvestingTrie(self.db).NodeIterator(nil))
for it.Next() {
interestHash := common.BytesToHash(it.Key)
if common.EmptyHash(interestHash) {
continue
}
interest := new(big.Int).SetBytes(interestHash.Bytes())
if _, exist := exhangeObject.investingStates[interestHash]; exist {
continue
} else {
var data itemList
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,interest :%v ", orderBook.Hex(), interest)
}
stateOrderList := newItemListState(orderBook, interestHash, data, nil)
mapResult[interest] = stateOrderList.DumpItemList(self.db)
}
}
for interestHash, itemList := range exhangeObject.investingStates {
if itemList.Volume().Sign() > 0 {
mapResult[new(big.Int).SetBytes(interestHash.Bytes())] = itemList.DumpItemList(self.db)
}
}
listInterest := []*big.Int{}
for interest := range mapResult {
listInterest = append(listInterest, interest)
}
sort.Slice(listInterest, func(i, j int) bool {
return listInterest[i].Cmp(listInterest[j]) < 0
})
result := map[*big.Int]DumpOrderList{}
for _, interest := range listInterest {
result[interest] = mapResult[interest]
}
return result, nil
}
func (self *LendingStateDB) DumpBorrowingTrie(orderBook common.Hash) (map[*big.Int]DumpOrderList, error) {
exhangeObject := self.getLendingExchange(orderBook)
if exhangeObject == nil {
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
}
mapResult := map[*big.Int]DumpOrderList{}
it := trie.NewIterator(exhangeObject.getBorrowingTrie(self.db).NodeIterator(nil))
for it.Next() {
interestHash := common.BytesToHash(it.Key)
if common.EmptyHash(interestHash) {
continue
}
interest := new(big.Int).SetBytes(interestHash.Bytes())
if _, exist := exhangeObject.borrowingStates[interestHash]; exist {
continue
} else {
var data itemList
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,interest :%v ", orderBook.Hex(), interest)
}
stateOrderList := newItemListState(orderBook, interestHash, data, nil)
mapResult[interest] = stateOrderList.DumpItemList(self.db)
}
}
for interestHash, itemList := range exhangeObject.borrowingStates {
if itemList.Volume().Sign() > 0 {
mapResult[new(big.Int).SetBytes(interestHash.Bytes())] = itemList.DumpItemList(self.db)
}
}
listInterest := []*big.Int{}
for interest := range mapResult {
listInterest = append(listInterest, interest)
}
sort.Slice(listInterest, func(i, j int) bool {
return listInterest[i].Cmp(listInterest[j]) < 0
})
result := map[*big.Int]DumpOrderList{}
for _, interest := range listInterest {
result[interest] = mapResult[interest]
}
return result, nil
}
func (self *LendingStateDB) GetInvestings(orderBook common.Hash) (map[*big.Int]*big.Int, error) {
exhangeObject := self.getLendingExchange(orderBook)
if exhangeObject == nil {
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
}
mapResult := map[*big.Int]*big.Int{}
it := trie.NewIterator(exhangeObject.getInvestingTrie(self.db).NodeIterator(nil))
for it.Next() {
interestHash := common.BytesToHash(it.Key)
if common.EmptyHash(interestHash) {
continue
}
interest := new(big.Int).SetBytes(interestHash.Bytes())
if _, exist := exhangeObject.investingStates[interestHash]; exist {
continue
} else {
var data itemList
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,interest :%v ", orderBook.Hex(), interest)
}
stateOrderList := newItemListState(orderBook, interestHash, data, nil)
mapResult[interest] = stateOrderList.data.Volume
}
}
for interestHash, itemList := range exhangeObject.investingStates {
if itemList.Volume().Sign() > 0 {
mapResult[new(big.Int).SetBytes(interestHash.Bytes())] = itemList.data.Volume
}
}
listInterest := []*big.Int{}
for interest := range mapResult {
listInterest = append(listInterest, interest)
}
sort.Slice(listInterest, func(i, j int) bool {
return listInterest[i].Cmp(listInterest[j]) < 0
})
result := map[*big.Int]*big.Int{}
for _, interest := range listInterest {
result[interest] = mapResult[interest]
}
return result, nil
}
func (self *LendingStateDB) GetBorrowings(orderBook common.Hash) (map[*big.Int]*big.Int, error) {
exhangeObject := self.getLendingExchange(orderBook)
if exhangeObject == nil {
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
}
mapResult := map[*big.Int]*big.Int{}
it := trie.NewIterator(exhangeObject.getBorrowingTrie(self.db).NodeIterator(nil))
for it.Next() {
interestHash := common.BytesToHash(it.Key)
if common.EmptyHash(interestHash) {
continue
}
interest := new(big.Int).SetBytes(interestHash.Bytes())
if _, exist := exhangeObject.borrowingStates[interestHash]; exist {
continue
} else {
var data itemList
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,interest :%v ", orderBook.Hex(), interest)
}
stateOrderList := newItemListState(orderBook, interestHash, data, nil)
mapResult[interest] = stateOrderList.data.Volume
}
}
for interestHash, itemList := range exhangeObject.borrowingStates {
if itemList.Volume().Sign() > 0 {
mapResult[new(big.Int).SetBytes(interestHash.Bytes())] = itemList.data.Volume
}
}
listInterest := []*big.Int{}
for interest := range mapResult {
listInterest = append(listInterest, interest)
}
sort.Slice(listInterest, func(i, j int) bool {
return listInterest[i].Cmp(listInterest[j]) < 0
})
result := map[*big.Int]*big.Int{}
for _, interest := range listInterest {
result[interest] = mapResult[interest]
}
return result, nil
}
func (self *itemListState) DumpItemList(db Database) DumpOrderList {
mapResult := DumpOrderList{Volume: self.Volume(), Orders: map[*big.Int]*big.Int{}}
orderListIt := trie.NewIterator(self.getTrie(db).NodeIterator(nil))
for orderListIt.Next() {
keyHash := common.BytesToHash(orderListIt.Key)
if common.EmptyHash(keyHash) {
continue
}
if _, exist := self.cachedStorage[keyHash]; exist {
continue
} else {
_, content, _, _ := rlp.Split(orderListIt.Value)
mapResult.Orders[new(big.Int).SetBytes(keyHash.Bytes())] = new(big.Int).SetBytes(content)
}
}
for key, value := range self.cachedStorage {
if !common.EmptyHash(value) {
mapResult.Orders[new(big.Int).SetBytes(key.Bytes())] = new(big.Int).SetBytes(value.Bytes())
}
}
listIds := []*big.Int{}
for id := range mapResult.Orders {
listIds = append(listIds, id)
}
sort.Slice(listIds, func(i, j int) bool {
return listIds[i].Cmp(listIds[j]) < 0
})
result := DumpOrderList{Volume: self.Volume(), Orders: map[*big.Int]*big.Int{}}
for _, id := range listIds {
result.Orders[id] = mapResult.Orders[id]
}
return result
}
func (self *LendingStateDB) DumpOrderBookInfo(orderBook common.Hash) (*DumpOrderBookInfo, error) {
exhangeObject := self.getLendingExchange(orderBook)
if exhangeObject == nil {
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
}
result := &DumpOrderBookInfo{}
result.Nonce = exhangeObject.data.Nonce
result.TradeNonce = exhangeObject.data.TradeNonce
result.BestInvesting = new(big.Int).SetBytes(exhangeObject.getBestInvestingInterest(self.db).Bytes())
result.BestBorrowing = new(big.Int).SetBytes(exhangeObject.getBestBorrowingInterest(self.db).Bytes())
lowestLiquidationTime, _ := exhangeObject.getLowestLiquidationTime(self.db)
result.LowestLiquidationTime = new(big.Int).SetBytes(lowestLiquidationTime.Bytes())
return result, nil
}
func (self *liquidationTimeState) DumpItemList(db Database) DumpOrderList {
mapResult := DumpOrderList{Volume: self.Volume(), Orders: map[*big.Int]*big.Int{}}
orderListIt := trie.NewIterator(self.getTrie(db).NodeIterator(nil))
for orderListIt.Next() {
keyHash := common.BytesToHash(orderListIt.Key)
if common.EmptyHash(keyHash) {
continue
}
if _, exist := self.cachedStorage[keyHash]; exist {
continue
} else {
_, content, _, _ := rlp.Split(orderListIt.Value)
mapResult.Orders[new(big.Int).SetBytes(keyHash.Bytes())] = new(big.Int).SetBytes(content)
}
}
for key, value := range self.cachedStorage {
if !common.EmptyHash(value) {
mapResult.Orders[new(big.Int).SetBytes(key.Bytes())] = new(big.Int).SetBytes(value.Bytes())
}
}
listIds := []*big.Int{}
for id := range mapResult.Orders {
listIds = append(listIds, id)
}
sort.Slice(listIds, func(i, j int) bool {
return listIds[i].Cmp(listIds[j]) < 0
})
result := DumpOrderList{Volume: self.Volume(), Orders: map[*big.Int]*big.Int{}}
for _, id := range listIds {
result.Orders[id] = mapResult.Orders[id]
}
return mapResult
}
func (self *LendingStateDB) DumpLiquidationTimeTrie(orderBook common.Hash) (map[*big.Int]DumpOrderList, error) {
exhangeObject := self.getLendingExchange(orderBook)
if exhangeObject == nil {
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
}
mapResult := map[*big.Int]DumpOrderList{}
it := trie.NewIterator(exhangeObject.getLiquidationTimeTrie(self.db).NodeIterator(nil))
for it.Next() {
unixTimeHash := common.BytesToHash(it.Key)
if common.EmptyHash(unixTimeHash) {
continue
}
unixTime := new(big.Int).SetBytes(unixTimeHash.Bytes())
if _, exist := exhangeObject.liquidationTimeStates[unixTimeHash]; exist {
continue
} else {
var data itemList
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,unixTime :%v ", orderBook.Hex(), unixTime)
}
stateOrderList := newLiquidationTimeState(orderBook, unixTimeHash, data, nil)
mapResult[unixTime] = stateOrderList.DumpItemList(self.db)
}
}
for unixTimeHash, itemList := range exhangeObject.liquidationTimeStates {
if itemList.Volume().Sign() > 0 {
mapResult[new(big.Int).SetBytes(unixTimeHash.Bytes())] = itemList.DumpItemList(self.db)
}
}
listUnixTime := []*big.Int{}
for unixTime := range mapResult {
listUnixTime = append(listUnixTime, unixTime)
}
sort.Slice(listUnixTime, func(i, j int) bool {
return listUnixTime[i].Cmp(listUnixTime[j]) < 0
})
result := map[*big.Int]DumpOrderList{}
for _, unixTime := range listUnixTime {
result[unixTime] = mapResult[unixTime]
}
return result, nil
}
func (self *LendingStateDB) DumpLendingOrderTrie(orderBook common.Hash) (map[*big.Int]LendingItem, error) {
exhangeObject := self.getLendingExchange(orderBook)
if exhangeObject == nil {
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
}
mapResult := map[*big.Int]LendingItem{}
it := trie.NewIterator(exhangeObject.getLendingItemTrie(self.db).NodeIterator(nil))
for it.Next() {
orderIdHash := common.BytesToHash(it.Key)
if common.EmptyHash(orderIdHash) {
continue
}
orderId := new(big.Int).SetBytes(orderIdHash.Bytes())
if _, exist := exhangeObject.lendingItemStates[orderIdHash]; exist {
continue
} else {
var data LendingItem
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,orderId :%v ", orderBook.Hex(), orderId)
}
mapResult[orderId] = data
}
}
for orderIdHash, lendingOrder := range exhangeObject.lendingItemStates {
mapResult[new(big.Int).SetBytes(orderIdHash.Bytes())] = lendingOrder.data
}
listOrderId := []*big.Int{}
for orderId := range mapResult {
listOrderId = append(listOrderId, orderId)
}
sort.Slice(listOrderId, func(i, j int) bool {
return listOrderId[i].Cmp(listOrderId[j]) < 0
})
result := map[*big.Int]LendingItem{}
for _, orderId := range listOrderId {
result[orderId] = mapResult[orderId]
}
return result, nil
}
func (self *LendingStateDB) DumpLendingTradeTrie(orderBook common.Hash) (map[*big.Int]LendingTrade, error) {
exhangeObject := self.getLendingExchange(orderBook)
if exhangeObject == nil {
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
}
mapResult := map[*big.Int]LendingTrade{}
it := trie.NewIterator(exhangeObject.getLendingTradeTrie(self.db).NodeIterator(nil))
for it.Next() {
tradeIdHash := common.BytesToHash(it.Key)
if common.EmptyHash(tradeIdHash) {
continue
}
tradeId := new(big.Int).SetBytes(tradeIdHash.Bytes())
if _, exist := exhangeObject.lendingTradeStates[tradeIdHash]; exist {
continue
} else {
var data LendingTrade
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,tradeId :%v ", orderBook.Hex(), tradeId)
}
mapResult[tradeId] = data
}
}
for tradeIdHash, lendingTrade := range exhangeObject.lendingTradeStates {
mapResult[new(big.Int).SetBytes(tradeIdHash.Bytes())] = lendingTrade.data
}
listTradeId := []*big.Int{}
for tradeId := range mapResult {
listTradeId = append(listTradeId, tradeId)
}
sort.Slice(listTradeId, func(i, j int) bool {
return listTradeId[i].Cmp(listTradeId[j]) < 0
})
result := map[*big.Int]LendingTrade{}
for _, tradeId := range listTradeId {
result[tradeId] = mapResult[tradeId]
}
return result, nil
}

View file

@ -0,0 +1,138 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package lendingstate
import (
"github.com/XinFinOrg/XDPoSChain/common"
"math/big"
)
type journalEntry interface {
undo(db *LendingStateDB)
}
type journal []journalEntry
type (
// Changes to the account trie.
insertOrder struct {
orderBook common.Hash
orderId common.Hash
order *LendingItem
}
insertTrading struct {
orderBook common.Hash
tradeId uint64
prvTrade *LendingTrade
}
cancelOrder struct {
orderBook common.Hash
orderId common.Hash
order LendingItem
}
cancelTrading struct {
orderBook common.Hash
tradeId uint64
order LendingTrade
}
subAmountOrder struct {
orderBook common.Hash
orderId common.Hash
order LendingItem
amount *big.Int
}
nonceChange struct {
hash common.Hash
prev uint64
}
tradeNonceChange struct {
hash common.Hash
prev uint64
}
liquidationPriceChange struct {
orderBook common.Hash
tradeId common.Hash
prev *big.Int
}
collateralLockedAmount struct {
orderBook common.Hash
tradeId common.Hash
prev *big.Int
}
)
func (ch insertOrder) undo(s *LendingStateDB) {
s.CancelLendingOrder(ch.orderBook, ch.order)
}
func (ch cancelOrder) undo(s *LendingStateDB) {
s.InsertLendingItem(ch.orderBook, ch.orderId, ch.order)
}
func (ch subAmountOrder) undo(s *LendingStateDB) {
interestHash := common.BigToHash(ch.order.Interest)
stateOrderBook := s.getLendingExchange(ch.orderBook)
var stateOrderList *itemListState
switch ch.order.Side {
case Investing:
stateOrderList = stateOrderBook.getInvestingOrderList(s.db, interestHash)
case Borrowing:
stateOrderList = stateOrderBook.getBorrowingOrderList(s.db, interestHash)
default:
return
}
stateOrderItem := stateOrderBook.getLendingItem(s.db, ch.orderId)
newAmount := new(big.Int).Add(stateOrderItem.Quantity(), ch.amount)
stateOrderItem.setVolume(newAmount)
stateOrderList.insertLendingItem(s.db, ch.orderId, common.BigToHash(newAmount))
stateOrderList.AddVolume(ch.amount)
}
func (ch nonceChange) undo(s *LendingStateDB) {
s.SetNonce(ch.hash, ch.prev)
}
func (ch tradeNonceChange) undo(s *LendingStateDB) {
s.SetTradeNonce(ch.hash, ch.prev)
}
func (ch cancelTrading) undo(s *LendingStateDB) {
s.InsertTradingItem(ch.orderBook, ch.tradeId, ch.order)
}
func (ch insertTrading) undo(s *LendingStateDB) {
s.InsertTradingItem(ch.orderBook, ch.tradeId, *ch.prvTrade)
}
func (ch liquidationPriceChange) undo(s *LendingStateDB) {
stateOrderBook := s.getLendingExchange(ch.orderBook)
if stateOrderBook == nil {
return
}
stateLendingTrade := stateOrderBook.getLendingTrade(s.db, ch.tradeId)
if stateLendingTrade == nil {
return
}
stateLendingTrade.SetLiquidationPrice(ch.prev)
}
func (ch collateralLockedAmount) undo(s *LendingStateDB) {
stateOrderBook := s.getLendingExchange(ch.orderBook)
if stateOrderBook == nil {
return
}
stateLendingTrade := stateOrderBook.getLendingTrade(s.db, ch.tradeId)
if stateLendingTrade == nil {
return
}
stateLendingTrade.SetCollateralLockedAmount(ch.prev)
}

View file

@ -0,0 +1,314 @@
package lendingstate
import (
"fmt"
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/log"
"math/big"
)
var (
LendingRelayerListSlot = uint64(0)
CollateralMapSlot = uint64(1)
DefaultCollateralSlot = uint64(2)
SupportedBaseSlot = uint64(3)
SupportedTermSlot = uint64(4)
ILOCollateralSlot = uint64(5)
LendingRelayerStructSlots = map[string]*big.Int{
"fee": big.NewInt(0),
"bases": big.NewInt(1),
"terms": big.NewInt(2),
"collaterals": big.NewInt(3),
}
CollateralStructSlots = map[string]*big.Int{
"depositRate": big.NewInt(0),
"liquidationRate": big.NewInt(1),
"recallRate": big.NewInt(2),
"price": big.NewInt(3),
}
PriceStructSlots = map[string]*big.Int{
"price": big.NewInt(0),
"blockNumber": big.NewInt(1),
}
)
// @function IsValidRelayer : return whether the given address is the coinbase of a valid relayer or not
// @param statedb : current state
// @param coinbase: coinbase address of relayer
// @return: true if it's a valid coinbase address of lending protocol, otherwise return false
func IsValidRelayer(statedb *state.StateDB, coinbase common.Address) bool {
locRelayerState := GetLocMappingAtKey(coinbase.Hash(), LendingRelayerListSlot)
// a valid relayer must have baseToken
locBaseToken := state.GetLocOfStructElement(locRelayerState, LendingRelayerStructSlots["bases"])
if v := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), common.BytesToHash(locBaseToken.Bytes())); v != (common.Hash{}) {
if tradingstate.IsResignedRelayer(coinbase, statedb) {
return false
}
slot := tradingstate.RelayerMappingSlot["RELAYER_LIST"]
locRelayerStateTrading := GetLocMappingAtKey(coinbase.Hash(), slot)
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locRelayerStateTrading, tradingstate.RelayerStructMappingSlot["_deposit"])
locHashDeposit := common.BigToHash(locBigDeposit)
balance := statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit).Big()
expectedFund := new(big.Int).Mul(common.BasePrice, common.RelayerLockedFund)
if balance.Cmp(expectedFund) <= 0 {
log.Debug("Relayer is not in relayer list", "relayer", coinbase.String(), "balance", balance, "expected", expectedFund)
return false
}
return true
}
return false
}
// @function GetFee
// @param statedb : current state
// @param coinbase: coinbase address of relayer
// @return: feeRate of lending
func GetFee(statedb *state.StateDB, coinbase common.Address) *big.Int {
locRelayerState := state.GetLocMappingAtKey(coinbase.Hash(), LendingRelayerListSlot)
locHash := common.BytesToHash(new(big.Int).Add(locRelayerState, LendingRelayerStructSlots["fee"]).Bytes())
return statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locHash).Big()
}
// @function GetBaseList
// @param statedb : current state
// @param coinbase: coinbase address of relayer
// @return: list of base tokens
func GetBaseList(statedb *state.StateDB, coinbase common.Address) []common.Address {
baseList := []common.Address{}
locRelayerState := state.GetLocMappingAtKey(coinbase.Hash(), LendingRelayerListSlot)
locBaseHash := state.GetLocOfStructElement(locRelayerState, LendingRelayerStructSlots["bases"])
length := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locBaseHash).Big().Uint64()
for i := uint64(0); i < length; i++ {
loc := state.GetLocDynamicArrAtElement(locBaseHash, i, 1)
addr := common.BytesToAddress(statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), loc).Bytes())
if addr != (common.Address{}) {
baseList = append(baseList, addr)
}
}
return baseList
}
// @function GetTerms
// @param statedb : current state
// @param coinbase: coinbase address of relayer
// @return: list of supported terms of the given relayer
func GetTerms(statedb *state.StateDB, coinbase common.Address) []uint64 {
terms := []uint64{}
locRelayerState := state.GetLocMappingAtKey(coinbase.Hash(), LendingRelayerListSlot)
locTermHash := state.GetLocOfStructElement(locRelayerState, LendingRelayerStructSlots["terms"])
length := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locTermHash).Big().Uint64()
for i := uint64(0); i < length; i++ {
loc := state.GetLocDynamicArrAtElement(locTermHash, i, 1)
t := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), loc).Big().Uint64()
if t != uint64(0) {
terms = append(terms, t)
}
}
return terms
}
// @function IsValidPair
// @param statedb : current state
// @param coinbase: coinbase address of relayer
// @param baseToken: address of baseToken
// @param terms: term
// @return: TRUE if the given baseToken, term organize a valid pair
func IsValidPair(statedb *state.StateDB, coinbase common.Address, baseToken common.Address, term uint64) (valid bool, pairIndex uint64) {
baseTokenList := GetBaseList(statedb, coinbase)
terms := GetTerms(statedb, coinbase)
baseIndexes := []uint64{}
for i := uint64(0); i < uint64(len(baseTokenList)); i++ {
if baseTokenList[i] == baseToken {
baseIndexes = append(baseIndexes, i)
}
}
for _, index := range baseIndexes {
if terms[index] == term {
pairIndex = index
return true, pairIndex
}
}
return false, pairIndex
}
// @function GetCollaterals
// @param statedb : current state
// @param coinbase: coinbase address of relayer
// @param baseToken: address of baseToken
// @param terms: term
// @return:
// - collaterals []common.Address : list of addresses of collateral
// - isSpecialCollateral : TRUE if collateral is a token which is NOT available for trading in XDCX, otherwise FALSE
func GetCollaterals(statedb *state.StateDB, coinbase common.Address, baseToken common.Address, term uint64) (collaterals []common.Address, isSpecialCollateral bool) {
validPair, _ := IsValidPair(statedb, coinbase, baseToken, term)
if !validPair {
return []common.Address{}, false
}
//TODO: ILO Collateral is not supported in release 2.2.0
//locRelayerState := state.GetLocMappingAtKey(coinbase.Hash(), LendingRelayerListSlot)
//locCollateralHash := state.GetLocOfStructElement(locRelayerState, LendingRelayerStructSlots["collaterals"])
//length := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locCollateralHash).Big().Uint64()
//
//loc := state.GetLocDynamicArrAtElement(locCollateralHash, pairIndex, 1)
//collateralAddr := common.BytesToAddress(statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), loc).Bytes())
//if collateralAddr != (common.Address{}) && collateralAddr != (common.HexToAddress("0x0")) {
// return []common.Address{collateralAddr}, true
//}
// if collaterals is not defined for the relayer, return default collaterals
locDefaultCollateralHash := state.GetLocSimpleVariable(DefaultCollateralSlot)
length := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locDefaultCollateralHash).Big().Uint64()
for i := uint64(0); i < length; i++ {
loc := state.GetLocDynamicArrAtElement(locDefaultCollateralHash, i, 1)
addr := common.BytesToAddress(statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), loc).Bytes())
if addr != (common.Address{}) {
collaterals = append(collaterals, addr)
}
}
return collaterals, false
}
// @function GetCollateralDetail
// @param statedb : current state
// @param token: address of collateral token
// @return: depositRate, liquidationRate, price of collateral
func GetCollateralDetail(statedb *state.StateDB, token common.Address) (depositRate, liquidationRate, recallRate *big.Int) {
collateralState := GetLocMappingAtKey(token.Hash(), CollateralMapSlot)
locDepositRate := state.GetLocOfStructElement(collateralState, CollateralStructSlots["depositRate"])
locLiquidationRate := state.GetLocOfStructElement(collateralState, CollateralStructSlots["liquidationRate"])
locRecallRate := state.GetLocOfStructElement(collateralState, CollateralStructSlots["recallRate"])
depositRate = statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locDepositRate).Big()
liquidationRate = statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locLiquidationRate).Big()
recallRate = statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locRecallRate).Big()
return depositRate, liquidationRate, recallRate
}
func GetCollateralPrice(statedb *state.StateDB, collateralToken common.Address, lendingToken common.Address) (price, blockNumber *big.Int) {
collateralState := GetLocMappingAtKey(collateralToken.Hash(), CollateralMapSlot)
locMapPrices := collateralState.Add(collateralState, CollateralStructSlots["price"])
locLendingTokenPriceByte := crypto.Keccak256(lendingToken.Hash().Bytes(), common.BigToHash(locMapPrices).Bytes())
locCollateralPrice := common.BigToHash(new(big.Int).Add(new(big.Int).SetBytes(locLendingTokenPriceByte), PriceStructSlots["price"]))
locBlockNumber := common.BigToHash(new(big.Int).Add(new(big.Int).SetBytes(locLendingTokenPriceByte), PriceStructSlots["blockNumber"]))
price = statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locCollateralPrice).Big()
blockNumber = statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locBlockNumber).Big()
return price, blockNumber
}
// @function GetSupportedTerms
// @param statedb : current state
// @return: list of terms which XDCxlending supports
func GetSupportedTerms(statedb *state.StateDB) []uint64 {
terms := []uint64{}
locSupportedTerm := state.GetLocSimpleVariable(SupportedTermSlot)
length := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locSupportedTerm).Big().Uint64()
for i := uint64(0); i < length; i++ {
loc := state.GetLocDynamicArrAtElement(locSupportedTerm, i, 1)
t := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), loc).Big().Uint64()
if t != 0 {
terms = append(terms, t)
}
}
return terms
}
// @function GetSupportedBaseToken
// @param statedb : current state
// @return: list of tokens which are available for lending
func GetSupportedBaseToken(statedb *state.StateDB) []common.Address {
baseTokens := []common.Address{}
locSupportedBaseToken := state.GetLocSimpleVariable(SupportedBaseSlot)
length := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locSupportedBaseToken).Big().Uint64()
for i := uint64(0); i < length; i++ {
loc := state.GetLocDynamicArrAtElement(locSupportedBaseToken, i, 1)
addr := common.BytesToAddress(statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), loc).Bytes())
if addr != (common.Address{}) {
baseTokens = append(baseTokens, addr)
}
}
return baseTokens
}
// @function GetAllCollateral
// @param statedb : current state
// @return: list of address of collateral token
func GetAllCollateral(statedb *state.StateDB) []common.Address {
collaterals := []common.Address{}
//TODO: ILO Collateral is not supported in release 2.2.0
//locILOCollateral := state.GetLocSimpleVariable(ILOCollateralSlot)
//length := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locILOCollateral).Big().Uint64()
//for i := uint64(0); i < length; i++ {
// loc := state.GetLocDynamicArrAtElement(locILOCollateral, i, 1)
// addr := common.BytesToAddress(statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), loc).Bytes())
// if addr != (common.Address{}) {
// collaterals = append(collaterals, addr)
// }
//}
locDefaultCollateralHash := state.GetLocSimpleVariable(DefaultCollateralSlot)
length := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locDefaultCollateralHash).Big().Uint64()
for i := uint64(0); i < length; i++ {
loc := state.GetLocDynamicArrAtElement(locDefaultCollateralHash, i, 1)
addr := common.BytesToAddress(statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), loc).Bytes())
if addr != (common.Address{}) {
collaterals = append(collaterals, addr)
}
}
return collaterals
}
// @function GetAllLendingBooks
// @param statedb : current state
// @return: a map to specify whether lendingBook (combination of baseToken and term) is valid or not
func GetAllLendingBooks(statedb *state.StateDB) (mapLendingBook map[common.Hash]bool, err error) {
mapLendingBook = make(map[common.Hash]bool)
baseTokens := GetSupportedBaseToken(statedb)
terms := GetSupportedTerms(statedb)
if len(baseTokens) == 0 {
return nil, fmt.Errorf("GetAllLendingBooks: empty baseToken list")
}
if len(terms) == 0 {
return nil, fmt.Errorf("GetAllLendingPairs: empty term list")
}
for _, baseToken := range baseTokens {
for _, term := range terms {
if (baseToken != common.Address{}) && (term > 0) {
mapLendingBook[GetLendingOrderBookHash(baseToken, term)] = true
}
}
}
return mapLendingBook, nil
}
// @function GetAllLendingPairs
// @param statedb : current state
// @return: list of lendingPair (combination of baseToken and collateralToken)
func GetAllLendingPairs(statedb *state.StateDB) (allPairs []LendingPair, err error) {
baseTokens := GetSupportedBaseToken(statedb)
collaterals := GetAllCollateral(statedb)
if len(baseTokens) == 0 {
return allPairs, fmt.Errorf("GetAllLendingPairs: empty baseToken list")
}
if len(collaterals) == 0 {
return allPairs, fmt.Errorf("GetAllLendingPairs: empty collateral list")
}
for _, baseToken := range baseTokens {
for _, collateral := range collaterals {
if (baseToken != common.Address{}) && (collateral != common.Address{}) {
allPairs = append(allPairs, LendingPair{
LendingToken: baseToken,
CollateralToken: collateral,
})
}
}
}
return allPairs, nil
}

View file

@ -0,0 +1,464 @@
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
}

View file

@ -0,0 +1,575 @@
package lendingstate
import (
"fmt"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
"github.com/XinFinOrg/XDPoSChain/rpc"
"math/big"
"math/rand"
"os"
"testing"
"time"
)
func TestLendingItem_VerifyLendingSide(t *testing.T) {
tests := []struct {
name string
fields *LendingItem
wantErr bool
}{
{"wrong side", &LendingItem{Side: "GIVE"}, true},
{"side: borrowing", &LendingItem{Side: Borrowing}, false},
{"side: investing", &LendingItem{Side: Investing}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := &LendingItem{
Side: tt.fields.Side,
}
if err := l.VerifyLendingSide(); (err != nil) != tt.wantErr {
t.Errorf("VerifyLendingSide() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestLendingItem_VerifyLendingInterest(t *testing.T) {
tests := []struct {
name string
fields *LendingItem
wantErr bool
}{
{"no interest information", &LendingItem{}, true},
{"negative interest", &LendingItem{Interest: big.NewInt(-1)}, true},
{"zero interest", &LendingItem{Interest: Zero}, true},
{"positive interest", &LendingItem{Interest: big.NewInt(2)}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := &LendingItem{
Interest: tt.fields.Interest,
}
if err := l.VerifyLendingInterest(); (err != nil) != tt.wantErr {
t.Errorf("VerifyLendingSide() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestLendingItem_VerifyLendingQuantity(t *testing.T) {
tests := []struct {
name string
fields *LendingItem
wantErr bool
}{
{"no quantity information", &LendingItem{}, true},
{"negative quantity", &LendingItem{Quantity: big.NewInt(-1)}, true},
{"zero quantity", &LendingItem{Quantity: Zero}, true},
{"positive quantity", &LendingItem{Quantity: big.NewInt(2)}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := &LendingItem{
Quantity: tt.fields.Quantity,
}
if err := l.VerifyLendingQuantity(); (err != nil) != tt.wantErr {
t.Errorf("VerifyLendingQuantity() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestLendingItem_VerifyLendingType(t *testing.T) {
tests := []struct {
name string
fields *LendingItem
wantErr bool
}{
{"type: stop limit", &LendingItem{Type: "stop limit"}, true},
{"type: take profit", &LendingItem{Type: "take profit"}, true},
{"type: limit", &LendingItem{Type: Limit}, false},
{"type: market", &LendingItem{Type: Market}, false},
{"type: topup", &LendingItem{Type: TopUp}, false},
{"type: repay", &LendingItem{Type: Repay}, false},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := &LendingItem{
Type: tt.fields.Type,
}
if err := l.VerifyLendingType(); (err != nil) != tt.wantErr {
t.Errorf("VerifyLendingType() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func TestLendingItem_VerifyLendingStatus(t *testing.T) {
tests := []struct {
name string
fields *LendingItem
wantErr bool
}{
{"status: new", &LendingItem{Status: LendingStatusNew}, false},
{"status: open", &LendingItem{Status: LendingStatusOpen}, true},
{"status: partial_filled", &LendingItem{Status: LendingStatusPartialFilled}, true},
{"status: filled", &LendingItem{Status: LendingStatusFilled}, true},
{"status: cancelled", &LendingItem{Status: LendingStatusCancelled}, false},
{"status: rejected", &LendingItem{Status: LendingStatusReject}, true},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
l := &LendingItem{
Status: tt.fields.Status,
}
if err := l.VerifyLendingStatus(); (err != nil) != tt.wantErr {
t.Errorf("VerifyLendingStatus() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
func SetFee(statedb *state.StateDB, coinbase common.Address, feeRate *big.Int) {
locRelayerState := state.GetLocMappingAtKey(coinbase.Hash(), LendingRelayerListSlot)
locHash := common.BytesToHash(new(big.Int).Add(locRelayerState, LendingRelayerStructSlots["fee"]).Bytes())
statedb.SetState(common.HexToAddress(common.LendingRegistrationSMC), locHash, common.BigToHash(feeRate))
}
func SetCollateralDetail(statedb *state.StateDB, token common.Address, depositRate *big.Int, liquidationRate *big.Int, price *big.Int) {
collateralState := GetLocMappingAtKey(token.Hash(), CollateralMapSlot)
locDepositRate := state.GetLocOfStructElement(collateralState, CollateralStructSlots["depositRate"])
locLiquidationRate := state.GetLocOfStructElement(collateralState, CollateralStructSlots["liquidationRate"])
locCollateralPrice := state.GetLocOfStructElement(collateralState, CollateralStructSlots["price"])
statedb.SetState(common.HexToAddress(common.LendingRegistrationSMC), locDepositRate, common.BigToHash(depositRate))
statedb.SetState(common.HexToAddress(common.LendingRegistrationSMC), locLiquidationRate, common.BigToHash(liquidationRate))
statedb.SetState(common.HexToAddress(common.LendingRegistrationSMC), locCollateralPrice, common.BigToHash(price))
}
func TestVerifyBalance(t *testing.T) {
db := rawdb.NewMemoryDatabase()
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
relayer := common.HexToAddress("0x0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e")
uAddr := common.HexToAddress("0xDeE6238780f98c0ca2c2C28453149bEA49a3Abc9")
lendingToken := common.HexToAddress("0xd9bb01454c85247B2ef35BB5BE57384cC275a8cf") // USD
collateralToken := common.HexToAddress("0x4d7eA2cE949216D6b120f3AA10164173615A2b6C") // BTC
SetFee(statedb, relayer, big.NewInt(100))
SetCollateralDetail(statedb, collateralToken, big.NewInt(150), big.NewInt(110), big.NewInt(8000)) // BTC price: 8k USD
// have 10k USD
statedb.GetOrNewStateObject(lendingToken)
if err := SetTokenBalance(uAddr, EtherToWei(big.NewInt(10000)), lendingToken, statedb); err != nil {
t.Error(err.Error())
}
// have 2 BTC
statedb.GetOrNewStateObject(collateralToken)
if err := SetTokenBalance(uAddr, EtherToWei(big.NewInt(2)), collateralToken, statedb); err != nil {
t.Error(err.Error())
}
lendingdb := rawdb.NewMemoryDatabase()
stateCache := NewDatabase(lendingdb)
lendingstatedb, _ := New(EmptyRoot, stateCache)
// insert lendingItem1 for testing cancel (side investing)
lendingItem1 := LendingItem{
Quantity: EtherToWei(big.NewInt(11000000000)),
Interest: big.NewInt(10),
Side: Investing,
Type: Limit,
LendingToken: lendingToken,
CollateralToken: collateralToken,
FilledAmount: nil,
Status: LendingStatusOpen,
Relayer: relayer,
Term: uint64(30),
UserAddress: uAddr,
Signature: nil,
Hash: common.Hash{},
TxHash: common.Hash{},
Nonce: nil,
CreatedAt: time.Time{},
UpdatedAt: time.Time{},
LendingId: uint64(1),
ExtraData: "",
}
lendingstatedb.InsertLendingItem(GetLendingOrderBookHash(lendingItem1.LendingToken, lendingItem1.Term), common.BigToHash(new(big.Int).SetUint64(lendingItem1.LendingId)), lendingItem1)
// insert lendingItem2 for testing cancel (side borrowing)
lendingItem2 := LendingItem{
Quantity: EtherToWei(big.NewInt(8000)),
Interest: big.NewInt(10),
Side: Borrowing,
Type: Limit,
LendingToken: lendingToken,
CollateralToken: collateralToken,
FilledAmount: nil,
Status: LendingStatusOpen,
Relayer: relayer,
Term: uint64(30),
UserAddress: uAddr,
Signature: nil,
Hash: common.Hash{},
TxHash: common.Hash{},
Nonce: nil,
CreatedAt: time.Time{},
UpdatedAt: time.Time{},
LendingId: uint64(2),
ExtraData: "",
}
lendingstatedb.InsertLendingItem(GetLendingOrderBookHash(lendingItem2.LendingToken, lendingItem2.Term), common.BigToHash(new(big.Int).SetUint64(lendingItem2.LendingId)), lendingItem2)
// insert lendingTrade for testing deposit (side: borrowing)
lendingstatedb.InsertTradingItem(
GetLendingOrderBookHash(lendingItem2.LendingToken, lendingItem2.Term),
uint64(1),
LendingTrade{
TradeId: uint64(1),
CollateralToken: collateralToken,
LendingToken: lendingToken,
Borrower: uAddr,
Amount: EtherToWei(big.NewInt(8000)),
LiquidationTime: uint64(time.Now().AddDate(0, 1, 0).UnixNano()),
},
)
// make a big lendingTrade to test case: not enough balance to process payment
lendingstatedb.InsertTradingItem(
GetLendingOrderBookHash(lendingItem2.LendingToken, lendingItem2.Term),
uint64(2),
LendingTrade{
TradeId: uint64(2),
CollateralToken: collateralToken,
LendingToken: lendingToken,
Borrower: uAddr,
Amount: EtherToWei(big.NewInt(20000)), // user have 10k USD, expect: fail
LiquidationTime: uint64(time.Now().AddDate(0, 1, 0).UnixNano()),
},
)
tests := []struct {
name string
fields *LendingItem
wantErr bool
}{
{"Investor doesn't have enough balance. side: investing, quantity 11k USD",
&LendingItem{
UserAddress: uAddr,
Relayer: relayer,
Side: Investing,
Type: Limit,
Status: LendingStatusNew,
Quantity: EtherToWei(big.NewInt(11000)),
LendingToken: lendingToken,
CollateralToken: collateralToken,
},
true,
},
{"Investor has enough balance. side: investing, quantity 10k USD",
&LendingItem{
UserAddress: uAddr,
Relayer: relayer,
Side: Investing,
Type: Limit,
Status: LendingStatusNew,
Quantity: EtherToWei(big.NewInt(10000)),
LendingToken: lendingToken,
CollateralToken: collateralToken,
},
false,
},
{"Investor cancel lendingItem",
&LendingItem{
UserAddress: uAddr,
Relayer: relayer,
Side: Investing,
Type: Limit,
Status: LendingStatusCancelled,
LendingToken: lendingToken,
CollateralToken: collateralToken,
Term: lendingItem1.Term,
LendingId: uint64(1),
},
true,
},
{"Invalid status",
&LendingItem{
Side: Investing,
Status: "wrong_status",
Type: Limit,
},
true,
},
// have 2BTC = 16k USD => max borrow = 16 / 1.5 = 10.66
{"Borrower doesn't have enough balance. side: borrowing, quantity 12k USD",
&LendingItem{
UserAddress: uAddr,
Relayer: relayer,
Side: Borrowing,
Type: Limit,
Status: LendingStatusNew,
Quantity: EtherToWei(big.NewInt(12000)),
LendingToken: lendingToken,
CollateralToken: collateralToken,
},
true,
},
// have 2BTC = 16k USD => max borrow = 16 / 1.5 = 10.66
{"Borrower has enough balance. side: borrowing, quantity 10k USD",
&LendingItem{
UserAddress: uAddr,
Relayer: relayer,
Side: Borrowing,
Type: Limit,
Status: LendingStatusNew,
Quantity: EtherToWei(big.NewInt(10000)),
LendingToken: lendingToken,
CollateralToken: collateralToken,
},
false,
},
{"Borrower has enough balance to pay cancel fee. side: borrowing",
&LendingItem{
UserAddress: uAddr,
Relayer: relayer,
Side: Borrowing,
Type: Limit,
Status: LendingStatusCancelled,
LendingToken: lendingToken,
CollateralToken: collateralToken,
Term: lendingItem2.Term,
LendingId: uint64(2),
},
false,
},
{"Make a deposit to an empty LendingTrade.",
&LendingItem{
UserAddress: uAddr,
Relayer: relayer,
Side: Borrowing,
Status: LendingStatusNew,
Type: TopUp,
Quantity: EtherToWei(big.NewInt(1)),
LendingToken: lendingToken,
CollateralToken: collateralToken,
ExtraData: common.BigToAddress(big.NewInt(0)).Hex(),
},
true,
},
// have 2BTC. make deposit 1 BTC
{"Borrower has enough balance to make a deposit. side: borrowing",
&LendingItem{
UserAddress: uAddr,
Relayer: relayer,
Side: Borrowing,
Status: LendingStatusNew,
Type: TopUp,
Quantity: EtherToWei(big.NewInt(1)),
LendingToken: lendingToken,
CollateralToken: collateralToken,
Term: uint64(30),
LendingTradeId: uint64(1),
},
false,
},
{"Make a payment to an empty LendingTrade.",
&LendingItem{
UserAddress: uAddr,
Relayer: relayer,
Side: Borrowing,
Status: LendingStatusNew,
Type: Repay,
Quantity: EtherToWei(big.NewInt(1)),
LendingToken: lendingToken,
CollateralToken: collateralToken,
LendingTradeId: uint64(0),
},
true,
},
// have 10k USDT
{"Borrower has enough balance to make a payment transaction. side: borrowing",
&LendingItem{
UserAddress: uAddr,
Relayer: relayer,
Side: Borrowing,
Status: LendingStatusNew,
Type: Repay,
LendingToken: lendingToken,
CollateralToken: collateralToken,
Term: uint64(30),
LendingTradeId: uint64(1),
},
false,
},
// have 10k USDT
{"Borrower doesn't haave enough balance to make a payment transaction. side: borrowing",
&LendingItem{
UserAddress: uAddr,
Relayer: relayer,
Side: Borrowing,
Status: LendingStatusNew,
Type: Repay,
LendingToken: lendingToken,
CollateralToken: collateralToken,
Term: uint64(30),
LendingTradeId: uint64(2),
},
true,
},
{"Invalid status",
&LendingItem{
Side: Borrowing,
Status: LendingStatusOpen,
},
true,
},
{"Invalid side",
&LendingItem{
Side: "abc",
},
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if err := VerifyBalance(true,
statedb,
lendingstatedb,
tt.fields.Type,
tt.fields.Side,
tt.fields.Status,
tt.fields.UserAddress,
tt.fields.Relayer,
tt.fields.LendingToken,
tt.fields.CollateralToken,
tt.fields.Quantity,
EtherToWei(big.NewInt(1)),
EtherToWei(big.NewInt(1)),
EtherToWei(big.NewInt(2)), // XDC price: 0.5 USD => USD/XDC = 2
EtherToWei(big.NewInt(8000)), // BTC = 8000 USD
tt.fields.Term,
tt.fields.LendingId,
tt.fields.LendingTradeId,
); (err != nil) != tt.wantErr {
t.Errorf("VerifyBalance() error = %v, wantErr %v", err, tt.wantErr)
}
})
}
}
type LendingOrderMsg struct {
AccountNonce uint64 `json:"nonce" gencodec:"required"`
Quantity *big.Int `json:"quantity,omitempty"`
RelayerAddress common.Address `json:"relayerAddress,omitempty"`
UserAddress common.Address `json:"userAddress,omitempty"`
CollateralToken common.Address `json:"collateralToken,omitempty"`
LendingToken common.Address `json:"lendingToken,omitempty"`
Interest uint64 `json:"interest,omitempty"`
Term uint64 `json:"term,omitempty"`
Status string `json:"status,omitempty"`
Side string `json:"side,omitempty"`
Type string `json:"type,omitempty"`
LendingID uint64 `json:"lendingID,omitempty"`
// Signature values
V *big.Int `json:"v" gencodec:"required"`
R *big.Int `json:"r" gencodec:"required"`
S *big.Int `json:"s" gencodec:"required"`
// This is only used when marshaling to JSON.
Hash common.Hash `json:"hash" rlp:"-"`
}
func Test_CreateOrder(t *testing.T) {
t.SkipNow()
for i := 0; i < 1; i++ {
sendOrder(uint64(i))
time.Sleep(time.Microsecond)
}
}
func sendOrder(nonce uint64) {
rpcClient, err := rpc.DialHTTP("http://localhost:8501")
defer rpcClient.Close()
if err != nil {
fmt.Println("rpc.DialHTTP failed", "err", err)
os.Exit(1)
}
rand.Seed(time.Now().UTC().UnixNano())
item := &LendingOrderMsg{
AccountNonce: nonce,
Quantity: EtherToWei(big.NewInt(1000)),
RelayerAddress: common.HexToAddress("0x0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e"),
UserAddress: common.HexToAddress("0x17F2beD710ba50Ed27aEa52fc4bD7Bda5ED4a037"),
CollateralToken: common.HexToAddress("0xC2fa1BA90b15E3612E0067A0020192938784D9C5"),
LendingToken: common.HexToAddress("0x45c25041b8e6CBD5c963E7943007187C3673C7c9"),
Interest: uint64(100),
Term: uint64(30 * 86400),
Status: LendingStatusNew,
Side: Borrowing,
Type: Limit,
V: common.Big0,
R: common.Big0,
S: common.Big0,
Hash: common.Hash{},
}
hash := computeHash(item)
if item.Status != LendingStatusCancelled {
item.Hash = hash
}
privKey, _ := crypto.HexToECDSA("65ec4d4dfbcac594a14c36baa462d6f73cd86134840f6cf7b80a1e1cd33473e2")
message := crypto.Keccak256(
[]byte("\x19Ethereum Signed Message:\n32"),
hash.Bytes(),
)
signatureBytes, _ := crypto.Sign(message, privKey)
sig := &Signature{
R: common.BytesToHash(signatureBytes[0:32]),
S: common.BytesToHash(signatureBytes[32:64]),
V: signatureBytes[64] + 27,
}
item.R = sig.R.Big()
item.S = sig.S.Big()
item.V = new(big.Int).SetUint64(uint64(sig.V))
var result interface{}
err = rpcClient.Call(&result, "XDCx_sendLending", item)
fmt.Println("sendLendingitem", "nonce", item.AccountNonce)
if err != nil {
fmt.Println("rpcClient.Call XDCx_sendLending failed", "err", err)
os.Exit(1)
}
}
func computeHash(l *LendingOrderMsg) common.Hash {
sha := sha3.NewKeccak256()
if l.Status == LendingStatusCancelled {
sha := sha3.NewKeccak256()
sha.Write(l.Hash.Bytes())
sha.Write(common.BigToHash(big.NewInt(int64(l.AccountNonce))).Bytes())
sha.Write(l.UserAddress.Bytes())
sha.Write(common.BigToHash(big.NewInt(int64(l.LendingID))).Bytes())
sha.Write([]byte(l.Status))
sha.Write(l.RelayerAddress.Bytes())
} else {
sha.Write(l.RelayerAddress.Bytes())
sha.Write(l.UserAddress.Bytes())
sha.Write(l.CollateralToken.Bytes())
sha.Write(l.LendingToken.Bytes())
sha.Write(common.BigToHash(l.Quantity).Bytes())
sha.Write(common.BigToHash(big.NewInt(int64(l.Term))).Bytes())
if l.Type == Limit {
sha.Write(common.BigToHash(big.NewInt(int64(l.Interest))).Bytes())
}
sha.Write([]byte(l.Side))
sha.Write([]byte(l.Status))
sha.Write([]byte(l.Type))
sha.Write(common.BigToHash(big.NewInt(int64(l.AccountNonce))).Bytes())
}
return common.BytesToHash(sha.Sum(nil))
}

View file

@ -0,0 +1,140 @@
// Copyright 2015 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package lendingstate
import (
"sync"
"github.com/XinFinOrg/XDPoSChain/common"
)
type exchanges struct {
stateObject *lendingExchangeState
nstart uint64
nonces []bool
}
type LendingManagedState struct {
*LendingStateDB
mu sync.RWMutex
lenddinges map[common.Hash]*exchanges
}
// LendingManagedState returns a new managed state with the statedb as it's backing layer
func ManageState(statedb *LendingStateDB) *LendingManagedState {
return &LendingManagedState{
LendingStateDB: statedb.Copy(),
lenddinges: make(map[common.Hash]*exchanges),
}
}
// SetState sets the backing layer of the managed state
func (ms *LendingManagedState) SetState(statedb *LendingStateDB) {
ms.mu.Lock()
defer ms.mu.Unlock()
ms.LendingStateDB = statedb
}
// RemoveNonce removed the nonce from the managed state and all future pending nonces
func (ms *LendingManagedState) RemoveNonce(addr common.Hash, n uint64) {
if ms.hasAccount(addr) {
ms.mu.Lock()
defer ms.mu.Unlock()
account := ms.getAccount(addr)
if n-account.nstart <= uint64(len(account.nonces)) {
reslice := make([]bool, n-account.nstart)
copy(reslice, account.nonces[:n-account.nstart])
account.nonces = reslice
}
}
}
// NewNonce returns the new canonical nonce for the managed tradeId
func (ms *LendingManagedState) NewNonce(addr common.Hash) uint64 {
ms.mu.Lock()
defer ms.mu.Unlock()
account := ms.getAccount(addr)
for i, nonce := range account.nonces {
if !nonce {
return account.nstart + uint64(i)
}
}
account.nonces = append(account.nonces, true)
return uint64(len(account.nonces)-1) + account.nstart
}
// GetNonce returns the canonical nonce for the managed or unmanaged tradeId.
//
// Because GetNonce mutates the DB, we must take a write lock.
func (ms *LendingManagedState) GetNonce(addr common.Hash) uint64 {
ms.mu.Lock()
defer ms.mu.Unlock()
if ms.hasAccount(addr) {
account := ms.getAccount(addr)
return uint64(len(account.nonces)) + account.nstart
} else {
return ms.LendingStateDB.GetNonce(addr)
}
}
// SetNonce sets the new canonical nonce for the managed state
func (ms *LendingManagedState) SetNonce(addr common.Hash, nonce uint64) {
ms.mu.Lock()
defer ms.mu.Unlock()
so := ms.GetOrNewLendingExchangeObject(addr)
so.setNonce(nonce)
ms.lenddinges[addr] = newAccount(so)
}
// HasAccount returns whether the given address is managed or not
func (ms *LendingManagedState) HasAccount(addr common.Hash) bool {
ms.mu.RLock()
defer ms.mu.RUnlock()
return ms.hasAccount(addr)
}
func (ms *LendingManagedState) hasAccount(addr common.Hash) bool {
_, ok := ms.lenddinges[addr]
return ok
}
// populate the managed state
func (ms *LendingManagedState) getAccount(addr common.Hash) *exchanges {
if account, ok := ms.lenddinges[addr]; !ok {
so := ms.GetOrNewLendingExchangeObject(addr)
ms.lenddinges[addr] = newAccount(so)
} else {
// Always make sure the state tradeId nonce isn't actually higher
// than the tracked one.
so := ms.LendingStateDB.getLendingExchange(addr)
if so != nil && uint64(len(account.nonces))+account.nstart < so.Nonce() {
ms.lenddinges[addr] = newAccount(so)
}
}
return ms.lenddinges[addr]
}
func newAccount(so *lendingExchangeState) *exchanges {
return &exchanges{so, so.Nonce(), nil}
}

View file

@ -0,0 +1,304 @@
package lendingstate
import (
"fmt"
"math/big"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/pkg/errors"
)
func GetLocMappingAtKey(key common.Hash, slot uint64) *big.Int {
slotHash := common.BigToHash(new(big.Int).SetUint64(slot))
retByte := crypto.Keccak256(key.Bytes(), slotHash.Bytes())
ret := new(big.Int)
ret.SetBytes(retByte)
return ret
}
func GetExRelayerFee(relayer common.Address, statedb *state.StateDB) *big.Int {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_fee"])
locHash := common.BigToHash(locBig)
return statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash).Big()
}
func GetRelayerOwner(relayer common.Address, statedb *state.StateDB) common.Address {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
log.Debug("GetRelayerOwner", "relayer", relayer.Hex(), "slot", slot, "locBig", locBig)
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_owner"])
locHash := common.BigToHash(locBig)
return common.BytesToAddress(statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash).Bytes())
}
// return true if relayer request to resign and have not withdraw locked fund
func IsResignedRelayer(relayer common.Address, statedb *state.StateDB) bool {
slot := RelayerMappingSlot["RESIGN_REQUESTS"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locHash := common.BigToHash(locBig)
if statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash) != (common.Hash{}) {
return true
}
return false
}
func GetBaseTokenLength(relayer common.Address, statedb *state.StateDB) uint64 {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_fromTokens"])
locHash := common.BigToHash(locBig)
return statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash).Big().Uint64()
}
func GetBaseTokenAtIndex(relayer common.Address, statedb *state.StateDB, index uint64) common.Address {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_fromTokens"])
locHash := common.BigToHash(locBig)
loc := state.GetLocDynamicArrAtElement(locHash, index, 1)
return common.BytesToAddress(statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), loc).Bytes())
}
func GetQuoteTokenLength(relayer common.Address, statedb *state.StateDB) uint64 {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_toTokens"])
locHash := common.BigToHash(locBig)
return statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash).Big().Uint64()
}
func GetQuoteTokenAtIndex(relayer common.Address, statedb *state.StateDB, index uint64) common.Address {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_toTokens"])
locHash := common.BigToHash(locBig)
loc := state.GetLocDynamicArrAtElement(locHash, index, 1)
return common.BytesToAddress(statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), loc).Bytes())
}
func SubRelayerFee(relayer common.Address, fee *big.Int, statedb *state.StateDB) error {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"])
locHashDeposit := common.BigToHash(locBigDeposit)
balance := statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit).Big()
log.Debug("ApplyXDCXMatchedTransaction settle balance: SubRelayerFee BEFORE", "relayer", relayer.String(), "balance", balance)
if balance.Cmp(fee) < 0 {
return errors.Errorf("relayer %s isn't enough XDC fee", relayer.String())
} else {
balance = new(big.Int).Sub(balance, fee)
statedb.SetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit, common.BigToHash(balance))
statedb.SubBalance(common.HexToAddress(common.RelayerRegistrationSMC), fee)
log.Debug("ApplyXDCXMatchedTransaction settle balance: SubRelayerFee AFTER", "relayer", relayer.String(), "balance", balance)
return nil
}
}
func CheckRelayerFee(relayer common.Address, fee *big.Int, statedb *state.StateDB) error {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"])
locHashDeposit := common.BigToHash(locBigDeposit)
balance := statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit).Big()
if new(big.Int).Sub(balance, fee).Cmp(new(big.Int).Mul(common.BasePrice, common.RelayerLockedFund)) < 0 {
return errors.Errorf("relayer %s isn't enough XDC fee : balance %d , fee : %d ", relayer.Hex(), balance.Uint64(), fee.Uint64())
}
return nil
}
func AddTokenBalance(addr common.Address, value *big.Int, token common.Address, statedb *state.StateDB) error {
// XDC native
if token.String() == common.XDCNativeAddress {
balance := statedb.GetBalance(addr)
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD TOKEN XDC NATIVE BEFORE", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
statedb.AddBalance(addr, value)
balance = statedb.GetBalance(addr)
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD XDC NATIVE BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
return nil
}
// TRC tokens
if statedb.Exist(token) {
slot := TokenMappingSlot["balances"]
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
balance := statedb.GetState(token, locHash).Big()
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD TOKEN BALANCE BEFORE", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
balance = new(big.Int).Add(balance, value)
statedb.SetState(token, locHash, common.BigToHash(balance))
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD TOKEN BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
return nil
} else {
return errors.Errorf("token %s isn't exist", token.String())
}
}
func SubTokenBalance(addr common.Address, value *big.Int, token common.Address, statedb *state.StateDB) error {
// XDC native
if token.String() == common.XDCNativeAddress {
balance := statedb.GetBalance(addr)
log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB XDC NATIVE BALANCE BEFORE", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
if balance.Cmp(value) < 0 {
return errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), token.String(), balance, value)
}
statedb.SubBalance(addr, value)
balance = statedb.GetBalance(addr)
log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB XDC NATIVE BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
return nil
}
// TRC tokens
if statedb.Exist(token) {
slot := TokenMappingSlot["balances"]
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
balance := statedb.GetState(token, locHash).Big()
log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB TOKEN BALANCE BEFORE", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
if balance.Cmp(value) < 0 {
return errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), token.String(), balance, value)
}
balance = new(big.Int).Sub(balance, value)
statedb.SetState(token, locHash, common.BigToHash(balance))
log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB TOKEN BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
return nil
} else {
return errors.Errorf("token %s isn't exist", token.String())
}
}
func CheckSubTokenBalance(addr common.Address, value *big.Int, token common.Address, statedb *state.StateDB, mapBalances map[common.Address]map[common.Address]*big.Int) (*big.Int, error) {
// XDC native
if token.String() == common.XDCNativeAddress {
var balance *big.Int
if value := mapBalances[token][addr]; value != nil {
balance = value
} else {
balance = statedb.GetBalance(addr)
}
if balance.Cmp(value) < 0 {
return nil, errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), token.String(), balance, value)
}
newBalance := new(big.Int).Sub(balance, value)
log.Debug("CheckSubTokenBalance settle balance: SUB XDC NATIVE BALANCE ", "token", token.String(), "address", addr.String(), "balance", balance, "value", value, "newBalance", newBalance)
return newBalance, nil
}
// TRC tokens
if statedb.Exist(token) {
var balance *big.Int
if value := mapBalances[token][addr]; value != nil {
balance = value
} else {
slot := TokenMappingSlot["balances"]
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
balance = statedb.GetState(token, locHash).Big()
}
if balance.Cmp(value) < 0 {
return nil, errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), token.String(), balance, value)
}
newBalance := new(big.Int).Sub(balance, value)
log.Debug("CheckSubTokenBalance settle balance: SUB TOKEN BALANCE ", "token", token.String(), "address", addr.String(), "balance", balance, "value", value, "newBalance", newBalance)
return newBalance, nil
} else {
return nil, errors.Errorf("token %s isn't exist", token.String())
}
}
func CheckAddTokenBalance(addr common.Address, value *big.Int, token common.Address, statedb *state.StateDB, mapBalances map[common.Address]map[common.Address]*big.Int) (*big.Int, error) {
// XDC native
if token.String() == common.XDCNativeAddress {
var balance *big.Int
if value := mapBalances[token][addr]; value != nil {
balance = value
} else {
balance = statedb.GetBalance(addr)
}
newBalance := new(big.Int).Add(balance, value)
log.Debug("CheckAddTokenBalance settle balance: ADD XDC NATIVE BALANCE ", "token", token.String(), "address", addr.String(), "balance", balance, "value", value, "newBalance", newBalance)
return newBalance, nil
}
// TRC tokens
if statedb.Exist(token) {
var balance *big.Int
if value := mapBalances[token][addr]; value != nil {
balance = value
} else {
slot := TokenMappingSlot["balances"]
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
balance = statedb.GetState(token, locHash).Big()
}
newBalance := new(big.Int).Add(balance, value)
log.Debug("CheckAddTokenBalance settle balance: ADD TOKEN BALANCE ", "token", token.String(), "address", addr.String(), "balance", balance, "value", value, "newBalance", newBalance)
if common.BigToHash(newBalance).Big().Cmp(newBalance) != 0 {
return nil, fmt.Errorf("Overflow when try add token balance , max is 2^256 , balance : %v , value:%v ", balance, value)
} else {
return newBalance, nil
}
} else {
return nil, errors.Errorf("token %s isn't exist", token.String())
}
}
func CheckSubRelayerFee(relayer common.Address, fee *big.Int, statedb *state.StateDB, mapBalances map[common.Address]*big.Int) (*big.Int, error) {
balance := mapBalances[relayer]
if balance == nil {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"])
locHashDeposit := common.BigToHash(locBigDeposit)
balance = statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit).Big()
}
log.Debug("CheckSubRelayerFee settle balance: SubRelayerFee ", "relayer", relayer.String(), "balance", balance, "fee", fee)
if balance.Cmp(fee) < 0 {
return nil, errors.Errorf("relayer %s isn't enough XDC fee", relayer.String())
} else {
return new(big.Int).Sub(balance, fee), nil
}
}
func GetTokenBalance(addr common.Address, token common.Address, statedb *state.StateDB) *big.Int {
// XDC native
if token.String() == common.XDCNativeAddress {
return statedb.GetBalance(addr)
}
// TRC tokens
if statedb.Exist(token) {
slot := TokenMappingSlot["balances"]
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
return statedb.GetState(token, locHash).Big()
} else {
return common.Big0
}
}
func SetTokenBalance(addr common.Address, balance *big.Int, token common.Address, statedb *state.StateDB) error {
// XDC native
if token.String() == common.XDCNativeAddress {
statedb.SetBalance(addr, balance)
return nil
}
// TRC tokens
if statedb.Exist(token) {
slot := TokenMappingSlot["balances"]
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
statedb.SetState(token, locHash, common.BigToHash(balance))
return nil
} else {
return errors.Errorf("token %s isn't exist", token.String())
}
}
func SetSubRelayerFee(relayer common.Address, balance *big.Int, fee *big.Int, statedb *state.StateDB) {
slot := RelayerMappingSlot["RELAYER_LIST"]
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"])
locHashDeposit := common.BigToHash(locBigDeposit)
statedb.SetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit, common.BigToHash(balance))
statedb.SubBalance(common.HexToAddress(common.RelayerRegistrationSMC), fee)
}

View file

@ -0,0 +1,256 @@
package lendingstate
import (
"encoding/json"
"errors"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/log"
"math/big"
)
const DefaultFeeRate = 100 // 100 / XDCXBaseFee = 100 / 10000 = 1%
var (
ErrQuantityTradeTooSmall = errors.New("quantity trade too small")
ErrInvalidCollateralPrice = errors.New("unable to retrieve price of this collateral. Please try another collateral")
)
type TradeResult struct {
Fee *big.Int
InToken common.Address
InTotal *big.Int
OutToken common.Address
OutTotal *big.Int
}
type LendingSettleBalance struct {
Taker TradeResult
Maker TradeResult
CollateralLockedAmount *big.Int
}
func (settleBalance *LendingSettleBalance) String() string {
jsonData, _ := json.Marshal(settleBalance)
return string(jsonData)
}
func GetSettleBalance(isXDCXLendingFork bool,
takerSide string,
lendTokenXDCPrice,
collateralPrice,
depositRate,
borrowFeeRate *big.Int,
lendingToken,
collateralToken common.Address,
lendTokenDecimal,
collateralTokenDecimal *big.Int,
quantityToLend *big.Int) (*LendingSettleBalance, error) {
log.Debug("GetSettleBalance", "takerSide", takerSide, "borrowFeeRate", borrowFeeRate, "lendingToken", lendingToken, "collateralToken", collateralToken, "quantityToLend", quantityToLend)
if collateralPrice == nil || collateralPrice.Sign() <= 0 {
return nil, ErrInvalidCollateralPrice
}
//use the defaultFee to validate small orders
defaultFee := new(big.Int).Mul(quantityToLend, new(big.Int).SetUint64(DefaultFeeRate))
defaultFee = new(big.Int).Div(defaultFee, common.XDCXBaseFee)
var result *LendingSettleBalance
//result = map[common.Address]map[string]interface{}{}
if !isXDCXLendingFork {
if takerSide == 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) // eg: depositRate = 150%
takerOutTotal = new(big.Int).Div(takerOutTotal, big.NewInt(100))
takerOutTotal = new(big.Int).Div(takerOutTotal, collateralPrice)
// Fee
// takerFee = quantityToLend*borrowFeeRate/baseFee
takerFee := new(big.Int).Mul(quantityToLend, borrowFeeRate)
takerFee = new(big.Int).Div(takerFee, common.XDCXBaseFee)
if quantityToLend.Cmp(takerFee) <= 0 || quantityToLend.Cmp(defaultFee) <= 0 {
log.Debug("quantity lending too small", "quantityToLend", quantityToLend, "takerFee", takerFee)
return result, ErrQuantityTradeTooSmall
}
if lendingToken.String() != common.XDCNativeAddress && lendTokenXDCPrice != nil && lendTokenXDCPrice.Cmp(common.Big0) > 0 {
exTakerReceivedFee := new(big.Int).Mul(takerFee, lendTokenXDCPrice)
exTakerReceivedFee = new(big.Int).Div(exTakerReceivedFee, lendTokenDecimal)
defaultFeeInXDC := new(big.Int).Mul(defaultFee, lendTokenXDCPrice)
defaultFeeInXDC = new(big.Int).Div(defaultFeeInXDC, lendTokenDecimal)
if (exTakerReceivedFee.Cmp(common.RelayerLendingFee) <= 0 && exTakerReceivedFee.Sign() > 0) || defaultFeeInXDC.Cmp(common.RelayerLendingFee) <= 0 {
log.Debug("takerFee too small", "quantityToLend", quantityToLend, "takerFee", takerFee, "exTakerReceivedFee", exTakerReceivedFee, "borrowFeeRate", borrowFeeRate, "defaultFeeInXDC", defaultFeeInXDC)
return result, ErrQuantityTradeTooSmall
}
} else if lendingToken.String() == common.XDCNativeAddress {
exTakerReceivedFee := takerFee
if (exTakerReceivedFee.Cmp(common.RelayerLendingFee) <= 0 && exTakerReceivedFee.Sign() > 0) || defaultFee.Cmp(common.RelayerLendingFee) <= 0 {
log.Debug("takerFee too small", "quantityToLend", quantityToLend, "takerFee", takerFee, "exTakerReceivedFee", exTakerReceivedFee, "borrowFeeRate", borrowFeeRate, "defaultFee", defaultFee)
return result, ErrQuantityTradeTooSmall
}
}
result = &LendingSettleBalance{
//Borrower
Taker: TradeResult{
Fee: takerFee,
InToken: lendingToken,
InTotal: new(big.Int).Sub(quantityToLend, takerFee),
OutToken: collateralToken,
OutTotal: takerOutTotal,
},
// Investor : makerOutTotal = quantityToLend
Maker: TradeResult{
Fee: common.Big0,
InToken: common.Address{},
InTotal: common.Big0,
OutToken: lendingToken,
OutTotal: quantityToLend,
},
CollateralLockedAmount: takerOutTotal,
}
} 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) // eg: depositRate = 150%
makerOutTotal = new(big.Int).Div(makerOutTotal, big.NewInt(100))
makerOutTotal = new(big.Int).Div(makerOutTotal, collateralPrice)
// Fee
makerFee := new(big.Int).Mul(quantityToLend, borrowFeeRate)
makerFee = new(big.Int).Div(makerFee, common.XDCXBaseFee)
if quantityToLend.Cmp(makerFee) <= 0 || quantityToLend.Cmp(defaultFee) <= 0 {
log.Debug("quantity lending too small", "quantityToLend", quantityToLend, "makerFee", makerFee)
return result, ErrQuantityTradeTooSmall
}
if lendingToken.String() != common.XDCNativeAddress && lendTokenXDCPrice != nil && lendTokenXDCPrice.Cmp(common.Big0) > 0 {
exMakerReceivedFee := new(big.Int).Mul(makerFee, lendTokenXDCPrice)
exMakerReceivedFee = new(big.Int).Div(exMakerReceivedFee, lendTokenDecimal)
defaultFeeInXDC := new(big.Int).Mul(defaultFee, lendTokenXDCPrice)
defaultFeeInXDC = new(big.Int).Div(defaultFeeInXDC, lendTokenDecimal)
if (exMakerReceivedFee.Cmp(common.RelayerLendingFee) <= 0 && exMakerReceivedFee.Sign() > 0) || defaultFeeInXDC.Cmp(common.RelayerLendingFee) <= 0 {
log.Debug("makerFee too small", "quantityToLend", quantityToLend, "makerFee", makerFee, "exMakerReceivedFee", exMakerReceivedFee, "borrowFeeRate", borrowFeeRate, "defaultFeeInXDC", defaultFeeInXDC)
return result, ErrQuantityTradeTooSmall
}
} else if lendingToken.String() == common.XDCNativeAddress {
exMakerReceivedFee := makerFee
if (exMakerReceivedFee.Cmp(common.RelayerLendingFee) <= 0 && exMakerReceivedFee.Sign() > 0) || defaultFee.Cmp(common.RelayerLendingFee) <= 0 {
log.Debug("makerFee too small", "quantityToLend", quantityToLend, "makerFee", makerFee, "exMakerReceivedFee", exMakerReceivedFee, "borrowFeeRate", borrowFeeRate, "defaultFee", defaultFee)
return result, ErrQuantityTradeTooSmall
}
}
result = &LendingSettleBalance{
Taker: TradeResult{
Fee: common.Big0,
InToken: common.Address{},
InTotal: common.Big0,
OutToken: lendingToken,
OutTotal: quantityToLend,
},
Maker: TradeResult{
Fee: makerFee,
InToken: lendingToken,
InTotal: new(big.Int).Add(quantityToLend, makerFee),
OutToken: collateralToken,
OutTotal: makerOutTotal,
},
CollateralLockedAmount: makerOutTotal,
}
}
} else {
collateralQuantity := new(big.Int).Mul(quantityToLend, collateralTokenDecimal)
collateralQuantity = new(big.Int).Mul(collateralQuantity, depositRate) // eg: depositRate = 150%
collateralQuantity = new(big.Int).Div(collateralQuantity, big.NewInt(100))
collateralQuantity = new(big.Int).Div(collateralQuantity, collateralPrice)
borrowFee := new(big.Int).Mul(quantityToLend, borrowFeeRate)
borrowFee = new(big.Int).Div(borrowFee, common.XDCXBaseFee)
if quantityToLend.Cmp(borrowFee) <= 0 || quantityToLend.Cmp(defaultFee) <= 0 {
log.Debug("quantity lending too small", "quantityToLend", quantityToLend, "borrowFee", borrowFee)
return result, ErrQuantityTradeTooSmall
}
if lendingToken.String() != common.XDCNativeAddress && lendTokenXDCPrice != nil && lendTokenXDCPrice.Cmp(common.Big0) > 0 {
// exReceivedFee: the fee amount which borrowingRelayer will receive
exReceivedFee := new(big.Int).Mul(borrowFee, lendTokenXDCPrice)
exReceivedFee = new(big.Int).Div(exReceivedFee, lendTokenDecimal)
defaultFeeInXDC := new(big.Int).Mul(defaultFee, lendTokenXDCPrice)
defaultFeeInXDC = new(big.Int).Div(defaultFeeInXDC, lendTokenDecimal)
if (exReceivedFee.Cmp(common.RelayerLendingFee) <= 0 && exReceivedFee.Sign() > 0) || defaultFeeInXDC.Cmp(common.RelayerLendingFee) <= 0 {
log.Debug("takerFee too small", "quantityToLend", quantityToLend, "borrowFee", borrowFee, "exReceivedFee", exReceivedFee, "borrowFeeRate", borrowFeeRate, "defaultFeeInXDC", defaultFeeInXDC)
return result, ErrQuantityTradeTooSmall
}
} else if lendingToken.String() == common.XDCNativeAddress {
exReceivedFee := borrowFee
if (exReceivedFee.Cmp(common.RelayerLendingFee) <= 0 && exReceivedFee.Sign() > 0) || defaultFee.Cmp(common.RelayerLendingFee) <= 0 {
log.Debug("takerFee too small", "quantityToLend", quantityToLend, "borrowFee", borrowFee, "exReceivedFee", exReceivedFee, "borrowFeeRate", borrowFeeRate, "defaultFee", defaultFee)
return result, ErrQuantityTradeTooSmall
}
}
borrowerReceivedQuantity := new(big.Int).Sub(quantityToLend, borrowFee)
borrowerTradeResult := TradeResult{
Fee: borrowFee,
InToken: lendingToken,
InTotal: borrowerReceivedQuantity,
OutToken: collateralToken,
OutTotal: collateralQuantity,
}
investorTradeResult := TradeResult{
Fee: common.Big0,
InToken: common.Address{},
InTotal: common.Big0,
OutToken: lendingToken,
OutTotal: quantityToLend,
}
if takerSide == Borrowing {
result = &LendingSettleBalance{
Taker: borrowerTradeResult,
Maker: investorTradeResult,
CollateralLockedAmount: collateralQuantity,
}
} else {
result = &LendingSettleBalance{
Taker: investorTradeResult,
Maker: borrowerTradeResult,
CollateralLockedAmount: collateralQuantity,
}
}
}
return result, nil
}
// apr: annual percentage rate
// this function returns actual interest rate base on borrowing time and apr
// I = APR *(T + T1) / 2 / 365
// T: term
// T1: borrowingTime
func CalculateInterestRate(finalizeTime, liquidationTime, term uint64, apr uint64) *big.Int {
startBorrowingTime := liquidationTime - term
borrowingTime := finalizeTime - startBorrowingTime
// the time interval which borrower have to pay interest
// (T + T1) / 2
timeToPayInterest := new(big.Int).Add(new(big.Int).SetUint64(term), new(big.Int).SetUint64(borrowingTime))
timeToPayInterest = new(big.Int).Div(timeToPayInterest, new(big.Int).SetUint64(2))
interestRate := new(big.Int).SetUint64(apr)
interestRate = new(big.Int).Mul(interestRate, timeToPayInterest)
interestRate = new(big.Int).Div(interestRate, new(big.Int).SetUint64(common.OneYear))
return interestRate
}
func CalculateTotalRepayValue(finalizeTime, liquidationTime, term uint64, apr uint64, tradeAmount *big.Int) *big.Int {
interestRate := CalculateInterestRate(finalizeTime, liquidationTime, term, apr)
// interest 10%
// user should send: 10 * common.BaseLendingInterest
// decimal = common.BaseLendingInterest * 100
baseInterestDecimal := new(big.Int).Mul(common.BaseLendingInterest, new(big.Int).SetUint64(100))
paymentBalance := new(big.Int).Mul(tradeAmount, new(big.Int).Add(baseInterestDecimal, interestRate))
paymentBalance = new(big.Int).Div(paymentBalance, baseInterestDecimal)
return paymentBalance
}

View file

@ -0,0 +1,461 @@
package lendingstate
import (
"github.com/XinFinOrg/XDPoSChain/common"
"math/big"
"reflect"
"testing"
)
func TestCalculateInterestRate(t *testing.T) {
type args struct {
repayTime uint64
liquidationTime uint64
term uint64
apr uint64
}
tests := []struct {
name string
args args
want *big.Int
}{
// apr = 10% per year
// term 365 days
// repay after one day
// have to pay interest for a half of year
// I = APR *(T + T1) / 2 / 365 = 10% * (365 + 1) / 2 /365 = 5,01369863 %
// 1e8 is decimal of interestRate
{
"term 365 days: early repay",
args{
repayTime: 86400,
liquidationTime: common.OneYear,
term: common.OneYear,
apr: 10 * 1e8,
},
new(big.Int).SetUint64(501369863),
},
// apr = 10% per year (365 days)
// term: 365 days
// repay at the end
// pay full interestRate 10%
// I = APR *(T + T1) / 2 / 365 = 10% * (365 + 365) / 2 /365 = 10 %
// 1e8 is decimal of interestRate
{
"term 365 days: repay at the end",
args{
repayTime: common.OneYear,
liquidationTime: common.OneYear,
term: common.OneYear,
apr: 10 * 1e8,
},
new(big.Int).SetUint64(10 * 1e8),
},
// apr = 10% per year
// term 30 days
// repay after one day
// have to pay interest for a half of year
// I = APR *(T + T1) / 2 / 365 = 10% * (30 + 1) / 2 /365 = 0,424657534 %
// 1e8 is decimal of interestRate
{
"term 30 days: early repay",
args{
repayTime: 86400,
liquidationTime: 30 * 86400,
term: 30 * 86400,
apr: 10 * 1e8,
},
new(big.Int).SetUint64(42465753),
},
// apr = 10% per year (365 days)
// term: 30 days
// repay at the end
// pay full interestRate 10%
// I = APR *(T + T1) / 2 / 365 = 10% * (30 + 30) / 2 /365 = 0,821917808 %
// 1e8 is decimal of interestRate
{
"term 30 days: repay at the end",
args{
repayTime: 30 * 86400,
liquidationTime: 30 * 86400,
term: 30 * 86400,
apr: 10 * 1e8,
},
new(big.Int).SetUint64(82191780),
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := CalculateInterestRate(tt.args.repayTime, tt.args.liquidationTime, tt.args.term, tt.args.apr); !reflect.DeepEqual(got, tt.want) {
t.Errorf("CalculateInterestRate() = %v, want %v", got, tt.want)
}
})
}
}
func TestGetSettleBalance(t *testing.T) {
lendQuantity, _ := new(big.Int).SetString("1000000000000000000000", 10) // 1000
fee, _ := new(big.Int).SetString("10000000000000000000", 10) // 10
lendQuantityExcluded, _ := new(big.Int).SetString("990000000000000000000", 10) // 990
lendTokenNotXDC := common.HexToAddress("0x0000000000000000000000000000000000000033")
collateral := common.HexToAddress("0x0000000000000000000000000000000000000022")
collateralLocked, _ := new(big.Int).SetString("1000000000000000000000", 10) // 1000
collateralLocked = new(big.Int).Mul(big.NewInt(150), collateralLocked)
collateralLocked = new(big.Int).Div(collateralLocked, big.NewInt(100))
type GetSettleBalanceArg struct {
isXDCXLendingFork bool
takerSide string
lendTokenXDCPrice *big.Int
collateralPrice *big.Int
depositRate *big.Int
borrowFeeRate *big.Int
lendingToken common.Address
collateralToken common.Address
lendTokenDecimal *big.Int
collateralTokenDecimal *big.Int
quantityToLend *big.Int
}
tests := []struct {
name string
args GetSettleBalanceArg
want *LendingSettleBalance
wantErr bool
}{
{
"collateralPrice nil",
GetSettleBalanceArg{
true,
Borrowing,
common.BasePrice,
common.Big0,
big.NewInt(150),
big.NewInt(10000), // 100%
common.Address{},
common.Address{},
common.BasePrice,
common.BasePrice,
lendQuantity,
},
nil,
true,
},
{
"quantityToLend = borrowFee, taker BORROW",
GetSettleBalanceArg{
true,
Borrowing,
common.BasePrice,
common.BasePrice,
big.NewInt(150),
big.NewInt(10000), // 100%
common.Address{},
common.Address{},
common.BasePrice,
common.BasePrice,
lendQuantity,
},
nil,
true,
},
{
"LendToken is XDC, quantity too small, taker BORROW",
GetSettleBalanceArg{
true,
Borrowing,
common.BasePrice,
common.BasePrice,
big.NewInt(150),
big.NewInt(100), // 1%
common.HexToAddress(common.XDCNativeAddress),
common.Address{},
common.BasePrice,
common.BasePrice,
common.BasePrice,
},
nil,
true,
},
{
"LendToken is not XDC, quantity too small, taker BORROW",
GetSettleBalanceArg{
true,
Borrowing,
common.BasePrice,
common.BasePrice,
big.NewInt(150),
big.NewInt(100), // 1%
common.Address{},
common.Address{},
common.BasePrice,
common.BasePrice,
common.BasePrice,
},
nil,
true,
},
{
"LendToken is not XDC, no error, taker BORROW",
GetSettleBalanceArg{
true,
Borrowing,
common.BasePrice,
common.BasePrice,
big.NewInt(150),
big.NewInt(100), // 1%
lendTokenNotXDC,
collateral,
common.BasePrice,
common.BasePrice,
lendQuantity,
},
&LendingSettleBalance{
Taker: TradeResult{
Fee: fee,
InToken: lendTokenNotXDC,
InTotal: lendQuantityExcluded,
OutToken: collateral,
OutTotal: collateralLocked,
},
Maker: TradeResult{
Fee: common.Big0,
InToken: common.Address{},
InTotal: common.Big0,
OutToken: lendTokenNotXDC,
OutTotal: lendQuantity,
},
CollateralLockedAmount: collateralLocked,
},
false,
},
{
"LendToken is not XDC, no error, taker INVEST",
GetSettleBalanceArg{
true,
Investing,
common.BasePrice,
common.BasePrice,
big.NewInt(150),
big.NewInt(100), // 1%
lendTokenNotXDC,
collateral,
common.BasePrice,
common.BasePrice,
lendQuantity,
},
&LendingSettleBalance{
Maker: TradeResult{
Fee: fee,
InToken: lendTokenNotXDC,
InTotal: lendQuantityExcluded,
OutToken: collateral,
OutTotal: collateralLocked,
},
Taker: TradeResult{
Fee: common.Big0,
InToken: common.Address{},
InTotal: common.Big0,
OutToken: lendTokenNotXDC,
OutTotal: lendQuantity,
},
CollateralLockedAmount: collateralLocked,
},
false,
},
{
"LendToken is XDC, no error, taker invest",
GetSettleBalanceArg{
true,
Investing,
common.BasePrice,
common.BasePrice,
big.NewInt(150),
big.NewInt(100), // 1%
common.HexToAddress(common.XDCNativeAddress),
collateral,
common.BasePrice,
common.BasePrice,
lendQuantity,
},
&LendingSettleBalance{
Taker: TradeResult{
Fee: common.Big0,
InToken: common.Address{},
InTotal: common.Big0,
OutToken: common.HexToAddress(common.XDCNativeAddress),
OutTotal: lendQuantity,
},
Maker: TradeResult{
Fee: fee,
InToken: common.HexToAddress(common.XDCNativeAddress),
InTotal: lendQuantityExcluded,
OutToken: collateral,
OutTotal: collateralLocked,
},
CollateralLockedAmount: collateralLocked,
},
false,
},
{
"LendToken is XDC, no error, taker Borrow",
GetSettleBalanceArg{
true,
Borrowing,
common.BasePrice,
common.BasePrice,
big.NewInt(150),
big.NewInt(100), // 1%
common.HexToAddress(common.XDCNativeAddress),
collateral,
common.BasePrice,
common.BasePrice,
lendQuantity,
},
&LendingSettleBalance{
Maker: TradeResult{
Fee: common.Big0,
InToken: common.Address{},
InTotal: common.Big0,
OutToken: common.HexToAddress(common.XDCNativeAddress),
OutTotal: lendQuantity,
},
Taker: TradeResult{
Fee: fee,
InToken: common.HexToAddress(common.XDCNativeAddress),
InTotal: lendQuantityExcluded,
OutToken: collateral,
OutTotal: collateralLocked,
},
CollateralLockedAmount: collateralLocked,
},
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := GetSettleBalance(tt.args.isXDCXLendingFork, tt.args.takerSide, tt.args.lendTokenXDCPrice, tt.args.collateralPrice, tt.args.depositRate, tt.args.borrowFeeRate, tt.args.lendingToken, tt.args.collateralToken, tt.args.lendTokenDecimal, tt.args.collateralTokenDecimal, tt.args.quantityToLend)
if (err != nil) != tt.wantErr {
t.Errorf("GetSettleBalance() error = %v, wantErr %v", err, tt.wantErr)
return
}
if tt.want != nil {
t.Log(tt.want.String())
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("GetSettleBalance() got = %v, want %v", got, tt.want)
}
})
}
}
func TestCalculateTotalRepayValue(t *testing.T) {
type CalculateTotalRepayValueArg struct {
finalizeTime uint64
liquidationTime uint64
term uint64
apr uint64
tradeAmount *big.Int
}
totalRepayOneYearRepayEarly, _ := new(big.Int).SetString("1050136986300000000000", 10)
totalRepayOneYearRepayInTime, _ := new(big.Int).SetString("1100000000000000000000", 10)
totalRepay30DaysRepayEarly, _ := new(big.Int).SetString("1004246575300000000000", 10)
totalRepay30DaysRepayInTime, _ := new(big.Int).SetString("1008219178000000000000", 10)
tradeAmount := new(big.Int).Mul(big.NewInt(1000), common.BasePrice)
tests := []struct {
name string
args CalculateTotalRepayValueArg
want *big.Int
}{
// apr = 10% per year
// term 365 days
// repay after one day
// have to pay interest for a half of year
// I = APR *(T + T1) / 2 / 365 = 10% * (365 + 1) / 2 /365 = 5,01369863 %
// 1e8 is decimal of interestRate
// amount 1000 USDT
// -> totalRepay: 1000 * (1 + 5,01369863 %) = 1050,1369863
{
"term 365 days, 1000 USDT: early repay",
CalculateTotalRepayValueArg{
finalizeTime: 86400,
liquidationTime: common.OneYear,
term: common.OneYear,
apr: 10 * 1e8,
tradeAmount: tradeAmount,
},
totalRepayOneYearRepayEarly,
},
// apr = 10% per year (365 days)
// term: 365 days
// repay at the end
// pay full interestRate 10%
// I = APR *(T + T1) / 2 / 365 = 10% * (365 + 365) / 2 /365 = 10 %
// 1e8 is decimal of interestRate
// -> totalRepay: 1000 * (1 + 10 %) = 1100
{
"term 365 days: repay at the end",
CalculateTotalRepayValueArg{
finalizeTime: common.OneYear,
liquidationTime: common.OneYear,
term: common.OneYear,
apr: 10 * 1e8,
tradeAmount: tradeAmount,
},
totalRepayOneYearRepayInTime,
},
// apr = 10% per year
// term 30 days
// repay after one day
// have to pay interest for a half of year
// I = APR *(T + T1) / 2 / 365 = 10% * (30 + 1) / 2 /365 = 0,424657534 %
// 1e8 is decimal of interestRate
// -> totalRepay: 1000 * (1 + 0,424657534 %) = 1004,2465753
{
"term 30 days: early repay",
CalculateTotalRepayValueArg{
finalizeTime: 86400,
liquidationTime: 30 * 86400,
term: 30 * 86400,
apr: 10 * 1e8,
tradeAmount: tradeAmount,
},
totalRepay30DaysRepayEarly,
},
// apr = 10% per year (365 days)
// term: 30 days
// repay at the end
// pay full interestRate 10%
// I = APR *(T + T1) / 2 / 365 = 10% * (30 + 30) / 2 /365 = 0,821917808 %
// 1e8 is decimal of interestRate
// -> totalRepay: 1000 * (1 + 0,821917808 %) = 1008,2191780
{
"term 30 days: repay at the end",
CalculateTotalRepayValueArg{
finalizeTime: 30 * 86400,
liquidationTime: 30 * 86400,
term: 30 * 86400,
apr: 10 * 1e8,
tradeAmount: tradeAmount,
},
totalRepay30DaysRepayInTime,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := CalculateTotalRepayValue(tt.args.finalizeTime, tt.args.liquidationTime, tt.args.term, tt.args.apr, tt.args.tradeAmount); !reflect.DeepEqual(got, tt.want) {
t.Errorf("CalculateTotalRepayValue() = %v, want %v", got, tt.want)
}
})
}
}

View file

@ -0,0 +1,193 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package lendingstate
import (
"bytes"
"fmt"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/rlp"
"io"
"math/big"
)
type itemListState struct {
lendingBook common.Hash
key common.Hash
data itemList
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by LendingStateDB.Commit.
dbErr error
// Write caches.
trie Trie // storage trie, which becomes non-nil on first access
cachedStorage map[common.Hash]common.Hash // Storage entry cache to avoid duplicate reads
dirtyStorage map[common.Hash]common.Hash // Storage entries that need to be flushed to disk
onDirty func(price common.Hash) // Callback method to mark a state object newly dirty
}
func (s *itemListState) empty() bool {
return s.data.Volume == nil || s.data.Volume.Sign() == 0
}
func newItemListState(lendingBook common.Hash, key common.Hash, data itemList, onDirty func(price common.Hash)) *itemListState {
return &itemListState{
lendingBook: lendingBook,
key: key,
data: data,
cachedStorage: make(map[common.Hash]common.Hash),
dirtyStorage: make(map[common.Hash]common.Hash),
onDirty: onDirty,
}
}
// EncodeRLP implements rlp.Encoder.
func (c *itemListState) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, c.data)
}
// setError remembers the first non-nil error it is called with.
func (self *itemListState) setError(err error) {
if self.dbErr == nil {
self.dbErr = err
}
}
func (c *itemListState) getTrie(db Database) Trie {
if c.trie == nil {
var err error
c.trie, err = db.OpenStorageTrie(c.key, c.data.Root)
if err != nil {
c.trie, _ = db.OpenStorageTrie(c.key, EmptyHash)
c.setError(fmt.Errorf("can't create storage trie: %v", err))
}
}
return c.trie
}
func (self *itemListState) GetOrderAmount(db Database, orderId common.Hash) common.Hash {
amount, exists := self.cachedStorage[orderId]
if exists {
return amount
}
// Load from DB in case it is missing.
enc, err := self.getTrie(db).TryGet(orderId[:])
if err != nil {
self.setError(err)
return EmptyHash
}
if len(enc) > 0 {
_, content, _, err := rlp.Split(enc)
if err != nil {
self.setError(err)
}
amount.SetBytes(content)
}
if (amount != common.Hash{}) {
self.cachedStorage[orderId] = amount
}
return amount
}
func (self *itemListState) insertLendingItem(db Database, orderId common.Hash, amount common.Hash) {
self.setOrderItem(orderId, amount)
self.setError(self.getTrie(db).TryUpdate(orderId[:], amount[:]))
}
func (self *itemListState) removeOrderItem(db Database, orderId common.Hash) {
tr := self.getTrie(db)
self.setError(tr.TryDelete(orderId[:]))
self.setOrderItem(orderId, EmptyHash)
}
func (self *itemListState) setOrderItem(orderId common.Hash, amount common.Hash) {
self.cachedStorage[orderId] = amount
self.dirtyStorage[orderId] = amount
if self.onDirty != nil {
self.onDirty(self.key)
self.onDirty = nil
}
}
// updateAskTrie writes cached storage modifications into the object's storage trie.
func (self *itemListState) updateTrie(db Database) Trie {
tr := self.getTrie(db)
for orderId, amount := range self.dirtyStorage {
delete(self.dirtyStorage, orderId)
if amount == EmptyHash {
self.setError(tr.TryDelete(orderId[:]))
continue
}
v, _ := rlp.EncodeToBytes(bytes.TrimLeft(amount[:], "\x00"))
self.setError(tr.TryUpdate(orderId[:], v))
}
return tr
}
// UpdateRoot sets the trie root to the current root tradeId of
func (self *itemListState) updateRoot(db Database) error {
self.updateTrie(db)
if self.dbErr != nil {
return self.dbErr
}
root, err := self.trie.Commit(nil)
if err == nil {
self.data.Root = root
}
return err
}
func (self *itemListState) deepCopy(db *LendingStateDB, onDirty func(price common.Hash)) *itemListState {
stateOrderList := newItemListState(self.lendingBook, self.key, self.data, onDirty)
if self.trie != nil {
stateOrderList.trie = db.db.CopyTrie(self.trie)
}
for orderId, amount := range self.dirtyStorage {
stateOrderList.dirtyStorage[orderId] = amount
}
for orderId, amount := range self.cachedStorage {
stateOrderList.cachedStorage[orderId] = amount
}
return stateOrderList
}
func (c *itemListState) AddVolume(amount *big.Int) {
c.setVolume(new(big.Int).Add(c.data.Volume, amount))
}
func (c *itemListState) subVolume(amount *big.Int) {
c.setVolume(new(big.Int).Sub(c.data.Volume, amount))
}
func (self *itemListState) setVolume(volume *big.Int) {
self.data.Volume = volume
if self.onDirty != nil {
self.onDirty(self.key)
self.onDirty = nil
}
}
func (self *itemListState) Volume() *big.Int {
return self.data.Volume
}

View file

@ -0,0 +1,786 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package lendingstate
import (
"fmt"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/rlp"
"io"
"math/big"
)
type lendingExchangeState struct {
lendingBook common.Hash
data lendingObject
db *LendingStateDB
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by LendingStateDB.Commit.
dbErr error
investingTrie Trie
borrowingTrie Trie
lendingItemTrie Trie
lendingTradeTrie Trie
liquidationTimeTrie Trie
liquidationTimeStates map[common.Hash]*liquidationTimeState
liquidationTimestatesDirty map[common.Hash]struct{}
investingStates map[common.Hash]*itemListState
investingStatesDirty map[common.Hash]struct{}
borrowingStates map[common.Hash]*itemListState
borrowingStatesDirty map[common.Hash]struct{}
lendingItemStates map[common.Hash]*lendingItemState
lendingItemStatesDirty map[common.Hash]struct{}
lendingTradeStates map[common.Hash]*lendingTradeState
lendingTradeStatesDirty map[common.Hash]struct{}
onDirty func(hash common.Hash) // Callback method to mark a state object newly dirty
}
// empty returns whether the tradeId is considered empty.
func (s *lendingExchangeState) empty() bool {
if s.data.Nonce != 0 {
return false
}
if s.data.TradeNonce != 0 {
return false
}
if !common.EmptyHash(s.data.InvestingRoot) {
return false
}
if !common.EmptyHash(s.data.BorrowingRoot) {
return false
}
if !common.EmptyHash(s.data.LendingItemRoot) {
return false
}
if !common.EmptyHash(s.data.LendingTradeRoot) {
return false
}
if !common.EmptyHash(s.data.LiquidationTimeRoot) {
return false
}
return true
}
func newStateExchanges(db *LendingStateDB, hash common.Hash, data lendingObject, onDirty func(addr common.Hash)) *lendingExchangeState {
return &lendingExchangeState{
db: db,
lendingBook: hash,
data: data,
investingStates: make(map[common.Hash]*itemListState),
borrowingStates: make(map[common.Hash]*itemListState),
lendingItemStates: make(map[common.Hash]*lendingItemState),
lendingTradeStates: make(map[common.Hash]*lendingTradeState),
liquidationTimeStates: make(map[common.Hash]*liquidationTimeState),
investingStatesDirty: make(map[common.Hash]struct{}),
borrowingStatesDirty: make(map[common.Hash]struct{}),
lendingItemStatesDirty: make(map[common.Hash]struct{}),
lendingTradeStatesDirty: make(map[common.Hash]struct{}),
liquidationTimestatesDirty: make(map[common.Hash]struct{}),
onDirty: onDirty,
}
}
// EncodeRLP implements rlp.Encoder.
func (self *lendingExchangeState) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, self.data)
}
// setError remembers the first non-nil error it is called with.
func (self *lendingExchangeState) setError(err error) {
if self.dbErr == nil {
self.dbErr = err
}
}
/**
Get Trie
*/
func (self *lendingExchangeState) getLendingItemTrie(db Database) Trie {
if self.lendingItemTrie == nil {
var err error
self.lendingItemTrie, err = db.OpenStorageTrie(self.lendingBook, self.data.LendingItemRoot)
if err != nil {
self.lendingItemTrie, _ = db.OpenStorageTrie(self.lendingBook, EmptyHash)
self.setError(fmt.Errorf("can't create Lendings trie: %v", err))
}
}
return self.lendingItemTrie
}
func (self *lendingExchangeState) getLendingTradeTrie(db Database) Trie {
if self.lendingTradeTrie == nil {
var err error
self.lendingTradeTrie, err = db.OpenStorageTrie(self.lendingBook, self.data.LendingTradeRoot)
if err != nil {
self.lendingTradeTrie, _ = db.OpenStorageTrie(self.lendingBook, EmptyHash)
self.setError(fmt.Errorf("can't create Lendings trie: %v", err))
}
}
return self.lendingTradeTrie
}
func (self *lendingExchangeState) getInvestingTrie(db Database) Trie {
if self.investingTrie == nil {
var err error
self.investingTrie, err = db.OpenStorageTrie(self.lendingBook, self.data.InvestingRoot)
if err != nil {
self.investingTrie, _ = db.OpenStorageTrie(self.lendingBook, EmptyHash)
self.setError(fmt.Errorf("can't create Lendings trie: %v", err))
}
}
return self.investingTrie
}
func (self *lendingExchangeState) getBorrowingTrie(db Database) Trie {
if self.borrowingTrie == nil {
var err error
self.borrowingTrie, err = db.OpenStorageTrie(self.lendingBook, self.data.BorrowingRoot)
if err != nil {
self.borrowingTrie, _ = db.OpenStorageTrie(self.lendingBook, EmptyHash)
self.setError(fmt.Errorf("can't create bids trie: %v", err))
}
}
return self.borrowingTrie
}
func (self *lendingExchangeState) getLiquidationTimeTrie(db Database) Trie {
if self.liquidationTimeTrie == nil {
var err error
self.liquidationTimeTrie, err = db.OpenStorageTrie(self.lendingBook, self.data.LiquidationTimeRoot)
if err != nil {
self.liquidationTimeTrie, _ = db.OpenStorageTrie(self.lendingBook, EmptyHash)
self.setError(fmt.Errorf("can't create bids trie: %v", err))
}
}
return self.liquidationTimeTrie
}
/**
Get State
*/
func (self *lendingExchangeState) getBorrowingOrderList(db Database, rate common.Hash) (stateOrderList *itemListState) {
// Prefer 'live' objects.
if obj := self.borrowingStates[rate]; obj != nil {
return obj
}
// Load the object from the database.
enc, err := self.getBorrowingTrie(db).TryGet(rate[:])
if len(enc) == 0 {
self.setError(err)
return nil
}
var data itemList
if err := rlp.DecodeBytes(enc, &data); err != nil {
log.Error("Failed to decode state order list object", "rate", rate, "err", err)
return nil
}
// Insert into the live set.
obj := newItemListState(self.lendingBook, rate, data, self.MarkBorrowingDirty)
self.borrowingStates[rate] = obj
return obj
}
func (self *lendingExchangeState) getInvestingOrderList(db Database, rate common.Hash) (stateOrderList *itemListState) {
// Prefer 'live' objects.
if obj := self.investingStates[rate]; obj != nil {
return obj
}
// Load the object from the database.
enc, err := self.getInvestingTrie(db).TryGet(rate[:])
if len(enc) == 0 {
self.setError(err)
return nil
}
var data itemList
if err := rlp.DecodeBytes(enc, &data); err != nil {
log.Error("Failed to decode state order list object", "rate", rate, "err", err)
return nil
}
// Insert into the live set.
obj := newItemListState(self.lendingBook, rate, data, self.MarkInvestingDirty)
self.investingStates[rate] = obj
return obj
}
func (self *lendingExchangeState) getLiquidationTimeOrderList(db Database, time common.Hash) (stateObject *liquidationTimeState) {
// Prefer 'live' objects.
if obj := self.liquidationTimeStates[time]; obj != nil {
return obj
}
// Load the object from the database.
enc, err := self.getLiquidationTimeTrie(db).TryGet(time[:])
if len(enc) == 0 {
self.setError(err)
return nil
}
var data itemList
if err := rlp.DecodeBytes(enc, &data); err != nil {
log.Error("Failed to decode state liquidation time", "time", time, "err", err)
return nil
}
// Insert into the live set.
obj := newLiquidationTimeState(self.lendingBook, time, data, self.MarkLiquidationTimeDirty)
self.liquidationTimeStates[time] = obj
return obj
}
func (self *lendingExchangeState) getLendingItem(db Database, lendingId common.Hash) (stateObject *lendingItemState) {
// Prefer 'live' objects.
if obj := self.lendingItemStates[lendingId]; obj != nil {
return obj
}
// Load the object from the database.
enc, err := self.getLendingItemTrie(db).TryGet(lendingId[:])
if len(enc) == 0 {
self.setError(err)
return nil
}
var data LendingItem
if err := rlp.DecodeBytes(enc, &data); err != nil {
log.Error("Failed to decode state lending item", "tradeId", lendingId, "err", err)
return nil
}
// Insert into the live set.
obj := newLendinItemState(self.lendingBook, lendingId, data, self.MarkLendingItemDirty)
self.lendingItemStates[lendingId] = obj
return obj
}
func (self *lendingExchangeState) getLendingTrade(db Database, tradeId common.Hash) (stateObject *lendingTradeState) {
// Prefer 'live' objects.
if obj := self.lendingTradeStates[tradeId]; obj != nil {
return obj
}
// Load the object from the database.
enc, err := self.getLendingTradeTrie(db).TryGet(tradeId[:])
if len(enc) == 0 {
self.setError(err)
return nil
}
var data LendingTrade
if err := rlp.DecodeBytes(enc, &data); err != nil {
log.Error("Failed to decode state lending trade", "tradeId", tradeId, "err", err)
return nil
}
// Insert into the live set.
obj := newLendingTradeState(self.lendingBook, tradeId, data, self.MarkLendingTradeDirty)
self.lendingTradeStates[tradeId] = obj
return obj
}
/**
Update Trie
*/
func (self *lendingExchangeState) updateLendingTimeTrie(db Database) Trie {
tr := self.getLendingItemTrie(db)
for lendingId, lendingItem := range self.lendingItemStates {
if _, isDirty := self.lendingItemStatesDirty[lendingId]; isDirty {
delete(self.lendingItemStatesDirty, lendingId)
if lendingItem.empty() {
self.setError(tr.TryDelete(lendingId[:]))
continue
}
// Encoding []byte cannot fail, ok to ignore the error.
v, _ := rlp.EncodeToBytes(lendingItem)
self.setError(tr.TryUpdate(lendingId[:], v))
}
}
return tr
}
func (self *lendingExchangeState) updateLendingTradeTrie(db Database) Trie {
tr := self.getLendingTradeTrie(db)
for tradeId, lendingTradeItem := range self.lendingTradeStates {
if _, isDirty := self.lendingTradeStatesDirty[tradeId]; isDirty {
delete(self.lendingTradeStatesDirty, tradeId)
if lendingTradeItem.empty() {
self.setError(tr.TryDelete(tradeId[:]))
continue
}
// Encoding []byte cannot fail, ok to ignore the error.
v, _ := rlp.EncodeToBytes(lendingTradeItem)
self.setError(tr.TryUpdate(tradeId[:], v))
}
}
return tr
}
func (self *lendingExchangeState) updateBorrowingTrie(db Database) Trie {
tr := self.getBorrowingTrie(db)
for rate, orderList := range self.borrowingStates {
if _, isDirty := self.borrowingStatesDirty[rate]; isDirty {
delete(self.borrowingStatesDirty, rate)
if orderList.empty() {
self.setError(tr.TryDelete(rate[:]))
continue
}
orderList.updateRoot(db)
// Encoding []byte cannot fail, ok to ignore the error.
v, _ := rlp.EncodeToBytes(orderList)
self.setError(tr.TryUpdate(rate[:], v))
}
}
return tr
}
func (self *lendingExchangeState) updateInvestingTrie(db Database) Trie {
tr := self.getInvestingTrie(db)
for rate, orderList := range self.investingStates {
if _, isDirty := self.investingStatesDirty[rate]; isDirty {
delete(self.investingStatesDirty, rate)
if orderList.empty() {
self.setError(tr.TryDelete(rate[:]))
continue
}
orderList.updateRoot(db)
// Encoding []byte cannot fail, ok to ignore the error.
v, _ := rlp.EncodeToBytes(orderList)
self.setError(tr.TryUpdate(rate[:], v))
}
}
return tr
}
func (self *lendingExchangeState) updateLiquidationTimeTrie(db Database) Trie {
tr := self.getLiquidationTimeTrie(db)
for time, itemList := range self.liquidationTimeStates {
if _, isDirty := self.liquidationTimestatesDirty[time]; isDirty {
delete(self.liquidationTimestatesDirty, time)
if itemList.empty() {
self.setError(tr.TryDelete(time[:]))
continue
}
itemList.updateRoot(db)
// Encoding []byte cannot fail, ok to ignore the error.
v, _ := rlp.EncodeToBytes(itemList)
self.setError(tr.TryUpdate(time[:], v))
}
}
return tr
}
/**
Update Root
*/
func (self *lendingExchangeState) updateOrderRoot(db Database) {
self.updateLendingTimeTrie(db)
self.data.LendingItemRoot = self.lendingItemTrie.Hash()
}
func (self *lendingExchangeState) updateInvestingRoot(db Database) error {
self.updateInvestingTrie(db)
if self.dbErr != nil {
return self.dbErr
}
self.data.InvestingRoot = self.investingTrie.Hash()
return nil
}
func (self *lendingExchangeState) updateBorrowingRoot(db Database) {
self.updateBorrowingTrie(db)
self.data.BorrowingRoot = self.borrowingTrie.Hash()
}
func (self *lendingExchangeState) updateLiquidationTimeRoot(db Database) {
self.updateLiquidationTimeTrie(db)
self.data.LiquidationTimeRoot = self.liquidationTimeTrie.Hash()
}
func (self *lendingExchangeState) updateLendingTradeRoot(db Database) {
self.updateLendingTradeTrie(db)
self.data.LendingTradeRoot = self.lendingTradeTrie.Hash()
}
/**
Commit Trie
*/
func (self *lendingExchangeState) CommitLendingItemTrie(db Database) error {
self.updateLendingTimeTrie(db)
if self.dbErr != nil {
return self.dbErr
}
root, err := self.lendingItemTrie.Commit(nil)
if err == nil {
self.data.LendingItemRoot = root
}
return err
}
func (self *lendingExchangeState) CommitLendingTradeTrie(db Database) error {
self.updateLendingTradeTrie(db)
if self.dbErr != nil {
return self.dbErr
}
root, err := self.lendingTradeTrie.Commit(nil)
if err == nil {
self.data.LendingTradeRoot = root
}
return err
}
func (self *lendingExchangeState) CommitInvestingTrie(db Database) error {
self.updateInvestingTrie(db)
if self.dbErr != nil {
return self.dbErr
}
root, err := self.investingTrie.Commit(func(leaf []byte, parent common.Hash) error {
var orderList itemList
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
return nil
}
if orderList.Root != EmptyRoot {
db.TrieDB().Reference(orderList.Root, parent)
}
return nil
})
if err == nil {
self.data.InvestingRoot = root
}
return err
}
func (self *lendingExchangeState) CommitBorrowingTrie(db Database) error {
self.updateBorrowingTrie(db)
if self.dbErr != nil {
return self.dbErr
}
root, err := self.borrowingTrie.Commit(func(leaf []byte, parent common.Hash) error {
var orderList itemList
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
return nil
}
if orderList.Root != EmptyRoot {
db.TrieDB().Reference(orderList.Root, parent)
}
return nil
})
if err == nil {
self.data.BorrowingRoot = root
}
return err
}
func (self *lendingExchangeState) CommitLiquidationTimeTrie(db Database) error {
self.updateLiquidationTimeTrie(db)
if self.dbErr != nil {
return self.dbErr
}
root, err := self.liquidationTimeTrie.Commit(func(leaf []byte, parent common.Hash) error {
var orderList itemList
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
return nil
}
if orderList.Root != EmptyRoot {
db.TrieDB().Reference(orderList.Root, parent)
}
return nil
})
if err == nil {
self.data.LiquidationTimeRoot = root
}
return err
}
/**
Get Trie Data
*/
func (self *lendingExchangeState) getBestInvestingInterest(db Database) common.Hash {
trie := self.getInvestingTrie(db)
encKey, encValue, err := trie.TryGetBestLeftKeyAndValue()
if err != nil {
log.Error("Failed find best investing rate", "orderbook", self.lendingBook.Hex())
return EmptyHash
}
if len(encKey) == 0 || len(encValue) == 0 {
log.Debug("Not found get best investing rate", "encKey", encKey, "encValue", encValue)
return EmptyHash
}
// Insert into the live set.
interest := common.BytesToHash(encKey)
if _, exist := self.investingStates[interest]; !exist {
var data itemList
if err := rlp.DecodeBytes(encValue, &data); err != nil {
log.Error("Failed to decode state get best investing rate", "err", err)
return EmptyHash
}
obj := newItemListState(self.lendingBook, interest, data, self.MarkInvestingDirty)
self.investingStates[interest] = obj
}
return interest
}
func (self *lendingExchangeState) getBestBorrowingInterest(db Database) common.Hash {
trie := self.getBorrowingTrie(db)
encKey, encValue, err := trie.TryGetBestRightKeyAndValue()
if err != nil {
log.Error("Failed find best key bid trie ", "orderbook", self.lendingBook.Hex())
return EmptyHash
}
if len(encKey) == 0 || len(encValue) == 0 {
log.Debug("Not found get best bid trie", "encKey", encKey, "encValue", encValue)
return EmptyHash
}
// Insert into the live set.
interest := common.BytesToHash(encKey)
if _, exist := self.borrowingStates[interest]; !exist {
var data itemList
if err := rlp.DecodeBytes(encValue, &data); err != nil {
log.Error("Failed to decode state get best bid trie", "err", err)
return EmptyHash
}
obj := newItemListState(self.lendingBook, interest, data, self.MarkBorrowingDirty)
self.borrowingStates[interest] = obj
}
return interest
}
func (self *lendingExchangeState) getLowestLiquidationTime(db Database) (common.Hash, *liquidationTimeState) {
trie := self.getLiquidationTimeTrie(db)
encKey, encValue, err := trie.TryGetBestLeftKeyAndValue()
if err != nil {
log.Error("Failed find best liquidation time trie ", "orderBook", self.lendingBook.Hex())
return EmptyHash, nil
}
if len(encKey) == 0 || len(encValue) == 0 {
log.Debug("Not found get liquidation time trie", "encKey", encKey, "encValue", encValue)
return EmptyHash, nil
}
price := common.BytesToHash(encKey)
obj, exist := self.liquidationTimeStates[price]
if !exist {
var data itemList
if err := rlp.DecodeBytes(encValue, &data); err != nil {
log.Error("Failed to decode state get liquidation time trie", "err", err)
return EmptyHash, nil
}
obj = newLiquidationTimeState(self.lendingBook, price, data, self.MarkLiquidationTimeDirty)
self.liquidationTimeStates[price] = obj
}
if obj.empty() {
return EmptyHash, nil
}
return price, obj
}
func (self *lendingExchangeState) deepCopy(db *LendingStateDB, onDirty func(hash common.Hash)) *lendingExchangeState {
stateExchanges := newStateExchanges(db, self.lendingBook, self.data, onDirty)
if self.investingTrie != nil {
stateExchanges.investingTrie = db.db.CopyTrie(self.investingTrie)
}
if self.borrowingTrie != nil {
stateExchanges.borrowingTrie = db.db.CopyTrie(self.borrowingTrie)
}
if self.lendingItemTrie != nil {
stateExchanges.lendingItemTrie = db.db.CopyTrie(self.lendingItemTrie)
}
for key, value := range self.borrowingStates {
stateExchanges.borrowingStates[key] = value.deepCopy(db, self.MarkBorrowingDirty)
}
for key := range self.borrowingStatesDirty {
stateExchanges.borrowingStatesDirty[key] = struct{}{}
}
for key, value := range self.investingStates {
stateExchanges.investingStates[key] = value.deepCopy(db, self.MarkInvestingDirty)
}
for key := range self.investingStatesDirty {
stateExchanges.investingStatesDirty[key] = struct{}{}
}
for key, value := range self.lendingItemStates {
stateExchanges.lendingItemStates[key] = value.deepCopy(self.MarkLendingItemDirty)
}
for orderId := range self.lendingItemStatesDirty {
stateExchanges.lendingItemStatesDirty[orderId] = struct{}{}
}
for key, value := range self.lendingTradeStates {
stateExchanges.lendingTradeStates[key] = value.deepCopy(self.MarkLendingTradeDirty)
}
for orderId := range self.lendingTradeStatesDirty {
stateExchanges.lendingTradeStatesDirty[orderId] = struct{}{}
}
for time, orderList := range self.liquidationTimeStates {
stateExchanges.liquidationTimeStates[time] = orderList.deepCopy(db, self.MarkLiquidationTimeDirty)
}
for time := range self.liquidationTimestatesDirty {
stateExchanges.liquidationTimestatesDirty[time] = struct{}{}
}
return stateExchanges
}
// Returns the address of the contract/tradeId
func (self *lendingExchangeState) Hash() common.Hash {
return self.lendingBook
}
func (self *lendingExchangeState) setNonce(nonce uint64) {
self.data.Nonce = nonce
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
}
func (self *lendingExchangeState) Nonce() uint64 {
return self.data.Nonce
}
func (self *lendingExchangeState) setTradeNonce(nonce uint64) {
self.data.TradeNonce = nonce
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
}
func (self *lendingExchangeState) TradeNonce() uint64 {
return self.data.TradeNonce
}
func (self *lendingExchangeState) removeInvestingOrderList(db Database, stateOrderList *itemListState) {
self.setError(self.investingTrie.TryDelete(stateOrderList.key[:]))
}
func (self *lendingExchangeState) removeBorrowingOrderList(db Database, stateOrderList *itemListState) {
self.setError(self.borrowingTrie.TryDelete(stateOrderList.key[:]))
}
func (self *lendingExchangeState) createInvestingOrderList(db Database, price common.Hash) (newobj *itemListState) {
newobj = newItemListState(self.lendingBook, price, itemList{Volume: Zero}, self.MarkInvestingDirty)
self.investingStates[price] = newobj
self.investingStatesDirty[price] = struct{}{}
data, err := rlp.EncodeToBytes(newobj)
if err != nil {
panic(fmt.Errorf("can't encode order list object at %x: %v", price[:], err))
}
self.setError(self.getInvestingTrie(db).TryUpdate(price[:], data))
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
return newobj
}
func (self *lendingExchangeState) MarkBorrowingDirty(price common.Hash) {
self.borrowingStatesDirty[price] = struct{}{}
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
}
func (self *lendingExchangeState) MarkInvestingDirty(price common.Hash) {
self.investingStatesDirty[price] = struct{}{}
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
}
func (self *lendingExchangeState) MarkLendingItemDirty(lending common.Hash) {
self.lendingItemStatesDirty[lending] = struct{}{}
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
}
func (self *lendingExchangeState) MarkLendingTradeDirty(tradeId common.Hash) {
self.lendingTradeStatesDirty[tradeId] = struct{}{}
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
}
func (self *lendingExchangeState) MarkLiquidationTimeDirty(orderId common.Hash) {
self.liquidationTimestatesDirty[orderId] = struct{}{}
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
}
func (self *lendingExchangeState) createBorrowingOrderList(db Database, price common.Hash) (newobj *itemListState) {
newobj = newItemListState(self.lendingBook, price, itemList{Volume: Zero}, self.MarkBorrowingDirty)
self.borrowingStates[price] = newobj
self.borrowingStatesDirty[price] = struct{}{}
data, err := rlp.EncodeToBytes(newobj)
if err != nil {
panic(fmt.Errorf("can't encode order list object at %x: %v", price[:], err))
}
self.setError(self.getBorrowingTrie(db).TryUpdate(price[:], data))
if self.onDirty != nil {
self.onDirty(self.Hash())
self.onDirty = nil
}
return newobj
}
func (self *lendingExchangeState) createLendingItem(db Database, orderId common.Hash, order LendingItem) (newobj *lendingItemState) {
newobj = newLendinItemState(self.lendingBook, orderId, order, self.MarkLendingItemDirty)
orderIdHash := common.BigToHash(new(big.Int).SetUint64(order.LendingId))
self.lendingItemStates[orderIdHash] = newobj
self.lendingItemStatesDirty[orderIdHash] = struct{}{}
if self.onDirty != nil {
self.onDirty(self.lendingBook)
self.onDirty = nil
}
return newobj
}
func (self *lendingExchangeState) createLiquidationTime(db Database, time common.Hash) (newobj *liquidationTimeState) {
newobj = newLiquidationTimeState(time, self.lendingBook, itemList{Volume: Zero}, self.MarkLiquidationTimeDirty)
self.liquidationTimeStates[time] = newobj
self.liquidationTimestatesDirty[time] = struct{}{}
data, err := rlp.EncodeToBytes(newobj)
if err != nil {
panic(fmt.Errorf("can't encode liquidation time at %x: %v", time[:], err))
}
self.setError(self.getLiquidationTimeTrie(db).TryUpdate(time[:], data))
if self.onDirty != nil {
self.onDirty(self.lendingBook)
self.onDirty = nil
}
return newobj
}
func (self *lendingExchangeState) insertLendingTrade(tradeId common.Hash, order LendingTrade) (newobj *lendingTradeState) {
newobj = newLendingTradeState(self.lendingBook, tradeId, order, self.MarkLendingTradeDirty)
self.lendingTradeStates[tradeId] = newobj
self.lendingTradeStatesDirty[tradeId] = struct{}{}
if self.onDirty != nil {
self.onDirty(self.lendingBook)
self.onDirty = nil
}
return newobj
}

View file

@ -0,0 +1,67 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package lendingstate
import (
"io"
"math/big"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/rlp"
)
type lendingItemState struct {
orderBook common.Hash
orderId common.Hash
data LendingItem
onDirty func(orderId common.Hash) // Callback method to mark a state object newly dirty
}
func (s *lendingItemState) empty() bool {
return s.data.Quantity == nil || s.data.Quantity.Cmp(Zero) == 0
}
func newLendinItemState(orderBook common.Hash, orderId common.Hash, data LendingItem, onDirty func(orderId common.Hash)) *lendingItemState {
return &lendingItemState{
orderBook: orderBook,
orderId: orderId,
data: data,
onDirty: onDirty,
}
}
// EncodeRLP implements rlp.Encoder.
func (c *lendingItemState) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, c.data)
}
func (self *lendingItemState) deepCopy(onDirty func(orderId common.Hash)) *lendingItemState {
stateOrderList := newLendinItemState(self.orderBook, self.orderId, self.data, onDirty)
return stateOrderList
}
func (self *lendingItemState) setVolume(volume *big.Int) {
self.data.Quantity = volume
if self.onDirty != nil {
self.onDirty(self.orderId)
self.onDirty = nil
}
}
func (self *lendingItemState) Quantity() *big.Int {
return self.data.Quantity
}

View file

@ -0,0 +1,78 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package lendingstate
import (
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/rlp"
"io"
"math/big"
)
type lendingTradeState struct {
orderBook common.Hash
tradeId common.Hash
data LendingTrade
onDirty func(orderId common.Hash) // Callback method to mark a state object newly dirty
}
func (s *lendingTradeState) empty() bool {
return s.data.Amount.Sign() == 0
}
func newLendingTradeState(orderBook common.Hash, tradeId common.Hash, data LendingTrade, onDirty func(orderId common.Hash)) *lendingTradeState {
return &lendingTradeState{
orderBook: orderBook,
tradeId: tradeId,
data: data,
onDirty: onDirty,
}
}
// EncodeRLP implements rlp.Encoder.
func (c *lendingTradeState) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, c.data)
}
func (self *lendingTradeState) deepCopy(onDirty func(orderId common.Hash)) *lendingTradeState {
stateOrderList := newLendingTradeState(self.orderBook, self.tradeId, self.data, onDirty)
return stateOrderList
}
func (self *lendingTradeState) SetCollateralLockedAmount(amount *big.Int) {
self.data.CollateralLockedAmount = amount
if self.onDirty != nil {
self.onDirty(self.tradeId)
self.onDirty = nil
}
}
func (self *lendingTradeState) SetLiquidationPrice(price *big.Int) {
self.data.LiquidationPrice = price
if self.onDirty != nil {
self.onDirty(self.tradeId)
self.onDirty = nil
}
}
func (self *lendingTradeState) SetAmount(amount *big.Int) {
self.data.Amount = amount
if self.onDirty != nil {
self.onDirty(self.tradeId)
self.onDirty = nil
}
}

View file

@ -0,0 +1,212 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package lendingstate
import (
"bytes"
"fmt"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/rlp"
"github.com/XinFinOrg/XDPoSChain/trie"
"io"
"math/big"
)
type liquidationTimeState struct {
time common.Hash
lendingBook common.Hash
data itemList
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by TradingStateDB.Commit.
dbErr error
// Write caches.
trie Trie // storage trie, which becomes non-nil on first access
cachedStorage map[common.Hash]common.Hash
dirtyStorage map[common.Hash]common.Hash
onDirty func(time common.Hash) // Callback method to mark a state object newly dirty
}
func (s *liquidationTimeState) empty() bool {
return s.data.Volume == nil || s.data.Volume.Sign() == 0
}
func newLiquidationTimeState(time common.Hash, lendingBook common.Hash, data itemList, onDirty func(time common.Hash)) *liquidationTimeState {
return &liquidationTimeState{
lendingBook: lendingBook,
time: time,
data: data,
cachedStorage: make(map[common.Hash]common.Hash),
dirtyStorage: make(map[common.Hash]common.Hash),
onDirty: onDirty,
}
}
func (self *liquidationTimeState) EncodeRLP(w io.Writer) error {
return rlp.Encode(w, self.data)
}
func (self *liquidationTimeState) setError(err error) {
if self.dbErr == nil {
self.dbErr = err
}
}
func (self *liquidationTimeState) getTrie(db Database) Trie {
if self.trie == nil {
var err error
self.trie, err = db.OpenStorageTrie(self.lendingBook, self.data.Root)
if err != nil {
self.trie, _ = db.OpenStorageTrie(self.time, EmptyHash)
self.setError(fmt.Errorf("can't create storage trie: %v", err))
}
}
return self.trie
}
func (self *liquidationTimeState) Exist(db Database, tradeId common.Hash) bool {
amount, exists := self.cachedStorage[tradeId]
if exists {
return true
}
// Load from DB in case it is missing.
enc, err := self.getTrie(db).TryGet(tradeId[:])
if err != nil {
self.setError(err)
return false
}
if len(enc) > 0 {
_, content, _, err := rlp.Split(enc)
if err != nil {
self.setError(err)
}
amount.SetBytes(content)
}
if (amount != common.Hash{}) {
self.cachedStorage[tradeId] = amount
}
return true
}
func (self *liquidationTimeState) getAllTradeIds(db Database) []common.Hash {
tradeIds := []common.Hash{}
lendingBookTrie := self.getTrie(db)
if lendingBookTrie == nil {
return tradeIds
}
for id, value := range self.cachedStorage {
if !common.EmptyHash(value) {
tradeIds = append(tradeIds, id)
}
}
orderListIt := trie.NewIterator(lendingBookTrie.NodeIterator(nil))
for orderListIt.Next() {
id := common.BytesToHash(orderListIt.Key)
if _, exist := self.cachedStorage[id]; exist {
continue
}
tradeIds = append(tradeIds, id)
}
return tradeIds
}
func (self *liquidationTimeState) insertTradeId(db Database, tradeId common.Hash) {
self.setTradeId(tradeId, tradeId)
self.setError(self.getTrie(db).TryUpdate(tradeId[:], tradeId[:]))
}
func (self *liquidationTimeState) removeTradeId(db Database, tradeId common.Hash) {
tr := self.getTrie(db)
self.setError(tr.TryDelete(tradeId[:]))
self.setTradeId(tradeId, EmptyHash)
}
func (self *liquidationTimeState) setTradeId(tradeId common.Hash, value common.Hash) {
self.cachedStorage[tradeId] = value
self.dirtyStorage[tradeId] = value
if self.onDirty != nil {
self.onDirty(self.lendingBook)
self.onDirty = nil
}
}
func (self *liquidationTimeState) updateTrie(db Database) Trie {
tr := self.getTrie(db)
for key, value := range self.dirtyStorage {
delete(self.dirtyStorage, key)
if value == EmptyHash {
self.setError(tr.TryDelete(key[:]))
continue
}
v, _ := rlp.EncodeToBytes(bytes.TrimLeft(value[:], "\x00"))
self.setError(tr.TryUpdate(key[:], v))
}
return tr
}
func (self *liquidationTimeState) updateRoot(db Database) error {
self.updateTrie(db)
if self.dbErr != nil {
return self.dbErr
}
root, err := self.trie.Commit(nil)
if err == nil {
self.data.Root = root
}
return err
}
func (self *liquidationTimeState) deepCopy(db *LendingStateDB, onDirty func(time common.Hash)) *liquidationTimeState {
stateLendingBook := newLiquidationTimeState(self.lendingBook, self.time, self.data, onDirty)
if self.trie != nil {
stateLendingBook.trie = db.db.CopyTrie(self.trie)
}
for key, value := range self.dirtyStorage {
stateLendingBook.dirtyStorage[key] = value
}
for key, value := range self.cachedStorage {
stateLendingBook.cachedStorage[key] = value
}
return stateLendingBook
}
func (c *liquidationTimeState) AddVolume(amount *big.Int) {
c.setVolume(new(big.Int).Add(c.data.Volume, amount))
}
func (c *liquidationTimeState) subVolume(amount *big.Int) {
c.setVolume(new(big.Int).Sub(c.data.Volume, amount))
}
func (self *liquidationTimeState) setVolume(volume *big.Int) {
self.data.Volume = volume
if self.onDirty != nil {
self.onDirty(self.lendingBook)
self.onDirty = nil
}
}
func (self *liquidationTimeState) Volume() *big.Int {
return self.data.Volume
}

View file

@ -0,0 +1,668 @@
// Copyright 2014 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
// Package state provides a caching layer atop the Ethereum state trie.
package lendingstate
import (
"fmt"
"math/big"
"sort"
"sync"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/rlp"
)
type revision struct {
id int
journalIndex int
}
type LendingStateDB struct {
db Database
trie Trie
// This map holds 'live' objects, which will get modified while processing a state transition.
lendingExchangeStates map[common.Hash]*lendingExchangeState
lendingExchangeStatesDirty map[common.Hash]struct{}
// DB error.
// State objects are used by the consensus core and VM which are
// unable to deal with database-level errors. Any error that occurs
// during a database read is memoized here and will eventually be returned
// by LendingStateDB.Commit.
dbErr error
// Journal of state modifications. This is the backbone of
// Snapshot and RevertToSnapshot.
journal journal
validRevisions []revision
nextRevisionId int
lock sync.Mutex
}
// Create a new state from a given trie.
func New(root common.Hash, db Database) (*LendingStateDB, error) {
tr, err := db.OpenTrie(root)
if err != nil {
log.Error("Error when init new lending state trie ", "root", root.Hex(), "err", err)
return nil, err
}
return &LendingStateDB{
db: db,
trie: tr,
lendingExchangeStates: make(map[common.Hash]*lendingExchangeState),
lendingExchangeStatesDirty: make(map[common.Hash]struct{}),
}, nil
}
// setError remembers the first non-nil error it is called with.
func (self *LendingStateDB) setError(err error) {
if self.dbErr == nil {
self.dbErr = err
}
}
func (self *LendingStateDB) Error() error {
return self.dbErr
}
// Exist reports whether the given tradeId address exists in the state.
// Notably this also returns true for suicided lenddinges.
func (self *LendingStateDB) Exist(addr common.Hash) bool {
return self.getLendingExchange(addr) != nil
}
// Empty returns whether the state object is either non-existent
// or empty according to the EIP161 specification (balance = nonce = code = 0)
func (self *LendingStateDB) Empty(addr common.Hash) bool {
so := self.getLendingExchange(addr)
return so == nil || so.empty()
}
func (self *LendingStateDB) GetNonce(addr common.Hash) uint64 {
stateObject := self.getLendingExchange(addr)
if stateObject != nil {
return stateObject.Nonce()
}
return 0
}
func (self *LendingStateDB) GetTradeNonce(addr common.Hash) uint64 {
stateObject := self.getLendingExchange(addr)
if stateObject != nil {
return stateObject.TradeNonce()
}
return 0
}
// Database retrieves the low level database supporting the lower level trie ops.
func (self *LendingStateDB) Database() Database {
return self.db
}
func (self *LendingStateDB) SetNonce(addr common.Hash, nonce uint64) {
stateObject := self.GetOrNewLendingExchangeObject(addr)
if stateObject != nil {
self.journal = append(self.journal, nonceChange{
hash: addr,
prev: stateObject.Nonce(),
})
stateObject.setNonce(nonce)
}
}
func (self *LendingStateDB) SetTradeNonce(addr common.Hash, nonce uint64) {
stateObject := self.GetOrNewLendingExchangeObject(addr)
if stateObject != nil {
self.journal = append(self.journal, tradeNonceChange{
hash: addr,
prev: stateObject.TradeNonce(),
})
stateObject.setTradeNonce(nonce)
}
}
func (self *LendingStateDB) InsertLendingItem(orderBook common.Hash, orderId common.Hash, order LendingItem) {
interestHash := common.BigToHash(order.Interest)
stateExchange := self.getLendingExchange(orderBook)
if stateExchange == nil {
stateExchange = self.createLendingExchangeObject(orderBook)
}
var stateOrderList *itemListState
switch order.Side {
case Investing:
stateOrderList = stateExchange.getInvestingOrderList(self.db, interestHash)
if stateOrderList == nil {
stateOrderList = stateExchange.createInvestingOrderList(self.db, interestHash)
}
case Borrowing:
stateOrderList = stateExchange.getBorrowingOrderList(self.db, interestHash)
if stateOrderList == nil {
stateOrderList = stateExchange.createBorrowingOrderList(self.db, interestHash)
}
default:
return
}
self.journal = append(self.journal, insertOrder{
orderBook: orderBook,
orderId: orderId,
order: &order,
})
stateExchange.createLendingItem(self.db, orderId, order)
stateOrderList.insertLendingItem(self.db, orderId, common.BigToHash(order.Quantity))
stateOrderList.AddVolume(order.Quantity)
}
func (self *LendingStateDB) InsertTradingItem(orderBook common.Hash, tradeId uint64, order LendingTrade) {
tradeIdHash := common.Uint64ToHash(tradeId)
stateExchange := self.getLendingExchange(orderBook)
if stateExchange == nil {
stateExchange = self.createLendingExchangeObject(orderBook)
}
prvTrade := self.GetLendingTrade(orderBook, tradeIdHash)
self.journal = append(self.journal, insertTrading{
orderBook: orderBook,
tradeId: tradeId,
prvTrade: &prvTrade,
})
stateExchange.insertLendingTrade(tradeIdHash, order)
}
func (self *LendingStateDB) UpdateLiquidationPrice(orderBook common.Hash, tradeId uint64, price *big.Int) {
tradeIdHash := common.Uint64ToHash(tradeId)
stateExchange := self.getLendingExchange(orderBook)
if stateExchange == nil {
stateExchange = self.createLendingExchangeObject(orderBook)
}
stateLendingTrade := stateExchange.getLendingTrade(self.db, tradeIdHash)
self.journal = append(self.journal, liquidationPriceChange{
orderBook: orderBook,
tradeId: tradeIdHash,
prev: stateLendingTrade.data.LiquidationPrice,
})
stateLendingTrade.SetLiquidationPrice(price)
}
func (self *LendingStateDB) UpdateCollateralLockedAmount(orderBook common.Hash, tradeId uint64, amount *big.Int) {
tradeIdHash := common.Uint64ToHash(tradeId)
stateExchange := self.getLendingExchange(orderBook)
if stateExchange == nil {
stateExchange = self.createLendingExchangeObject(orderBook)
}
stateLendingTrade := stateExchange.getLendingTrade(self.db, tradeIdHash)
self.journal = append(self.journal, collateralLockedAmount{
orderBook: orderBook,
tradeId: tradeIdHash,
prev: stateLendingTrade.data.CollateralLockedAmount,
})
stateLendingTrade.SetCollateralLockedAmount(amount)
}
func (self *LendingStateDB) GetLendingOrder(orderBook common.Hash, orderId common.Hash) LendingItem {
stateObject := self.GetOrNewLendingExchangeObject(orderBook)
if stateObject == nil {
return EmptyLendingOrder
}
stateOrderItem := stateObject.getLendingItem(self.db, orderId)
if stateOrderItem == nil {
return EmptyLendingOrder
}
return stateOrderItem.data
}
func (self *LendingStateDB) GetLendingTrade(orderBook common.Hash, tradeId common.Hash) LendingTrade {
stateObject := self.GetOrNewLendingExchangeObject(orderBook)
if stateObject == nil {
return EmptyLendingTrade
}
stateOrderItem := stateObject.getLendingTrade(self.db, tradeId)
if stateOrderItem == nil || stateOrderItem.empty() {
return EmptyLendingTrade
}
return stateOrderItem.data
}
func (self *LendingStateDB) SubAmountLendingItem(orderBook common.Hash, orderId common.Hash, price *big.Int, amount *big.Int, side string) error {
priceHash := common.BigToHash(price)
lendingExchange := self.GetOrNewLendingExchangeObject(orderBook)
if lendingExchange == nil {
return fmt.Errorf("Order book not found : %s ", orderBook.Hex())
}
var orderList *itemListState
switch side {
case Investing:
orderList = lendingExchange.getInvestingOrderList(self.db, priceHash)
case Borrowing:
orderList = lendingExchange.getBorrowingOrderList(self.db, priceHash)
default:
return fmt.Errorf("Order type not found : %s ", side)
}
if orderList == nil || orderList.empty() {
return fmt.Errorf("Order list empty order book : %s , order id : %s , key : %s ", orderBook, orderId.Hex(), priceHash.Hex())
}
lendingItem := lendingExchange.getLendingItem(self.db, orderId)
if lendingItem == nil || lendingItem.empty() {
return fmt.Errorf("Order item empty order book : %s , order id : %s , key : %s ", orderBook, orderId.Hex(), priceHash.Hex())
}
currentAmount := new(big.Int).SetBytes(orderList.GetOrderAmount(self.db, orderId).Bytes()[:])
if currentAmount.Cmp(amount) < 0 {
return fmt.Errorf("Order amount not enough : %s , have : %d , want : %d ", orderId.Hex(), currentAmount, amount)
}
self.journal = append(self.journal, subAmountOrder{
orderBook: orderBook,
orderId: orderId,
order: self.GetLendingOrder(orderBook, orderId),
amount: amount,
})
newAmount := new(big.Int).Sub(currentAmount, amount)
lendingItem.setVolume(newAmount)
log.Debug("SubAmountOrderItem", "tradeId", orderId.Hex(), "side", side, "key", price.Uint64(), "amount", amount.Uint64(), "new amount", newAmount.Uint64())
orderList.subVolume(amount)
if newAmount.Sign() == 0 {
orderList.removeOrderItem(self.db, orderId)
} else {
orderList.setOrderItem(orderId, common.BigToHash(newAmount))
}
if orderList.empty() {
switch side {
case Investing:
lendingExchange.removeInvestingOrderList(self.db, orderList)
case Borrowing:
lendingExchange.removeBorrowingOrderList(self.db, orderList)
default:
}
}
return nil
}
func (self *LendingStateDB) CancelLendingOrder(orderBook common.Hash, order *LendingItem) error {
interestHash := common.BigToHash(order.Interest)
orderIdHash := common.BigToHash(new(big.Int).SetUint64(order.LendingId))
stateObject := self.GetOrNewLendingExchangeObject(orderBook)
if stateObject == nil {
return fmt.Errorf("Order book not found : %s ", orderBook.Hex())
}
lendingItem := stateObject.getLendingItem(self.db, orderIdHash)
var orderList *itemListState
switch lendingItem.data.Side {
case Investing:
orderList = stateObject.getInvestingOrderList(self.db, interestHash)
case Borrowing:
orderList = stateObject.getBorrowingOrderList(self.db, interestHash)
default:
return fmt.Errorf("Order side not found : %s ", order.Side)
}
if orderList == nil || orderList.empty() {
return fmt.Errorf("Order list empty order book : %s , order id : %s , key : %s ", orderBook, orderIdHash.Hex(), interestHash.Hex())
}
if lendingItem == nil || lendingItem.empty() {
return fmt.Errorf("Order item empty order book : %s , order id : %s , key : %s ", orderBook, orderIdHash.Hex(), interestHash.Hex())
}
if lendingItem.data.UserAddress != order.UserAddress {
return fmt.Errorf("Error Order User Address mismatch when cancel order book : %s , order id : %s , got : %s , expect : %s ", orderBook, orderIdHash.Hex(), lendingItem.data.UserAddress.Hex(), order.UserAddress.Hex())
}
self.journal = append(self.journal, cancelOrder{
orderBook: orderBook,
orderId: orderIdHash,
order: self.GetLendingOrder(orderBook, orderIdHash),
})
lendingItem.setVolume(big.NewInt(0))
currentAmount := new(big.Int).SetBytes(orderList.GetOrderAmount(self.db, orderIdHash).Bytes()[:])
orderList.subVolume(currentAmount)
orderList.removeOrderItem(self.db, orderIdHash)
if orderList.empty() {
switch order.Side {
case Investing:
stateObject.removeInvestingOrderList(self.db, orderList)
case Borrowing:
stateObject.removeBorrowingOrderList(self.db, orderList)
default:
}
}
return nil
}
func (self *LendingStateDB) GetBestInvestingRate(orderBook common.Hash) (*big.Int, *big.Int) {
stateObject := self.getLendingExchange(orderBook)
if stateObject != nil {
investingHash := stateObject.getBestInvestingInterest(self.db)
if common.EmptyHash(investingHash) {
return Zero, Zero
}
orderList := stateObject.getInvestingOrderList(self.db, investingHash)
if orderList == nil {
log.Error("order list investing not found", "key", investingHash.Hex())
return Zero, Zero
}
return new(big.Int).SetBytes(investingHash.Bytes()), orderList.Volume()
}
return Zero, Zero
}
func (self *LendingStateDB) GetBestBorrowRate(orderBook common.Hash) (*big.Int, *big.Int) {
stateObject := self.getLendingExchange(orderBook)
if stateObject != nil {
priceHash := stateObject.getBestBorrowingInterest(self.db)
if common.EmptyHash(priceHash) {
return Zero, Zero
}
orderList := stateObject.getBorrowingOrderList(self.db, priceHash)
if orderList == nil {
log.Error("order list ask not found", "key", priceHash.Hex())
return Zero, Zero
}
return new(big.Int).SetBytes(priceHash.Bytes()), orderList.Volume()
}
return Zero, Zero
}
func (self *LendingStateDB) GetBestLendingIdAndAmount(orderBook common.Hash, price *big.Int, side string) (common.Hash, *big.Int, error) {
stateObject := self.GetOrNewLendingExchangeObject(orderBook)
if stateObject != nil {
var stateOrderList *itemListState
switch side {
case Investing:
stateOrderList = stateObject.getInvestingOrderList(self.db, common.BigToHash(price))
case Borrowing:
stateOrderList = stateObject.getBorrowingOrderList(self.db, common.BigToHash(price))
default:
return EmptyHash, Zero, fmt.Errorf("not found side :%s ", side)
}
if stateOrderList != nil {
key, _, err := stateOrderList.getTrie(self.db).TryGetBestLeftKeyAndValue()
if err != nil {
return EmptyHash, Zero, err
}
orderId := common.BytesToHash(key)
amount := stateOrderList.GetOrderAmount(self.db, orderId)
return orderId, new(big.Int).SetBytes(amount.Bytes()), nil
}
return EmptyHash, Zero, fmt.Errorf("not found order list with orderBook : %s , key : %d , side :%s ", orderBook.Hex(), price, side)
}
return EmptyHash, Zero, fmt.Errorf("not found orderBook : %s ", orderBook.Hex())
}
// updateLendingExchange writes the given object to the trie.
func (self *LendingStateDB) updateLendingExchange(stateObject *lendingExchangeState) {
addr := stateObject.Hash()
data, err := rlp.EncodeToBytes(stateObject)
if err != nil {
panic(fmt.Errorf("can't encode object at %x: %v", addr[:], err))
}
self.setError(self.trie.TryUpdate(addr[:], data))
}
// Retrieve a state object given my the address. Returns nil if not found.
func (self *LendingStateDB) getLendingExchange(addr common.Hash) (stateObject *lendingExchangeState) {
// Prefer 'live' objects.
if obj := self.lendingExchangeStates[addr]; obj != nil {
return obj
}
// Load the object from the database.
enc, err := self.trie.TryGet(addr[:])
if len(enc) == 0 {
self.setError(err)
return nil
}
var data lendingObject
if err := rlp.DecodeBytes(enc, &data); err != nil {
log.Error("Failed to decode state object", "addr", addr, "err", err)
return nil
}
// Insert into the live set.
obj := newStateExchanges(self, addr, data, self.MarkLendingExchangeObjectDirty)
self.lendingExchangeStates[addr] = obj
return obj
}
func (self *LendingStateDB) setLendingExchangeObject(object *lendingExchangeState) {
self.lendingExchangeStates[object.Hash()] = object
self.lendingExchangeStatesDirty[object.Hash()] = struct{}{}
}
// Retrieve a state object or create a new state object if nil.
func (self *LendingStateDB) GetOrNewLendingExchangeObject(addr common.Hash) *lendingExchangeState {
stateExchangeObject := self.getLendingExchange(addr)
if stateExchangeObject == nil {
stateExchangeObject = self.createLendingExchangeObject(addr)
}
return stateExchangeObject
}
// MarkStateLendObjectDirty adds the specified object to the dirty map to avoid costly
// state object cache iteration to find a handful of modified ones.
func (self *LendingStateDB) MarkLendingExchangeObjectDirty(addr common.Hash) {
self.lendingExchangeStatesDirty[addr] = struct{}{}
}
// createStateOrderListObject creates a new state object. If there is an existing tradeId with
// the given address, it is overwritten and returned as the second return value.
func (self *LendingStateDB) createLendingExchangeObject(hash common.Hash) (newobj *lendingExchangeState) {
newobj = newStateExchanges(self, hash, lendingObject{}, self.MarkLendingExchangeObjectDirty)
newobj.setNonce(0) // sets the object to dirty
self.setLendingExchangeObject(newobj)
return newobj
}
// Copy creates a deep, independent copy of the state.
// Snapshots of the copied state cannot be applied to the copy.
func (self *LendingStateDB) Copy() *LendingStateDB {
self.lock.Lock()
defer self.lock.Unlock()
// Copy all the basic fields, initialize the memory ones
state := &LendingStateDB{
db: self.db,
trie: self.db.CopyTrie(self.trie),
lendingExchangeStates: make(map[common.Hash]*lendingExchangeState, len(self.lendingExchangeStatesDirty)),
lendingExchangeStatesDirty: make(map[common.Hash]struct{}, len(self.lendingExchangeStatesDirty)),
}
// Copy the dirty states, logs, and preimages
for addr := range self.lendingExchangeStatesDirty {
state.lendingExchangeStatesDirty[addr] = struct{}{}
}
for addr, exchangeObject := range self.lendingExchangeStates {
state.lendingExchangeStates[addr] = exchangeObject.deepCopy(state, state.MarkLendingExchangeObjectDirty)
}
return state
}
func (s *LendingStateDB) clearJournalAndRefund() {
s.journal = nil
s.validRevisions = s.validRevisions[:0]
}
// Snapshot returns an identifier for the current revision of the state.
func (self *LendingStateDB) Snapshot() int {
id := self.nextRevisionId
self.nextRevisionId++
self.validRevisions = append(self.validRevisions, revision{id, len(self.journal)})
return id
}
// RevertToSnapshot reverts all state changes made since the given revision.
func (self *LendingStateDB) RevertToSnapshot(revid int) {
// Find the snapshot in the stack of valid snapshots.
idx := sort.Search(len(self.validRevisions), func(i int) bool {
return self.validRevisions[i].id >= revid
})
if idx == len(self.validRevisions) || self.validRevisions[idx].id != revid {
panic(fmt.Errorf("revision id %v cannot be reverted", revid))
}
snapshot := self.validRevisions[idx].journalIndex
// Replay the journal to undo changes.
for i := len(self.journal) - 1; i >= snapshot; i-- {
self.journal[i].undo(self)
}
self.journal = self.journal[:snapshot]
// Remove invalidated snapshots from the stack.
self.validRevisions = self.validRevisions[:idx]
}
// Finalise finalises the state by removing the self destructed objects
// and clears the journal as well as the refunds.
func (s *LendingStateDB) Finalise() {
// Commit objects to the trie.
for addr, stateObject := range s.lendingExchangeStates {
if _, isDirty := s.lendingExchangeStatesDirty[addr]; isDirty {
// Write any storage changes in the state object to its storage trie.
stateObject.updateInvestingRoot(s.db)
stateObject.updateBorrowingRoot(s.db)
stateObject.updateOrderRoot(s.db)
stateObject.updateLendingTradeRoot(s.db)
stateObject.updateLiquidationTimeRoot(s.db)
// Update the object in the main tradeId trie.
s.updateLendingExchange(stateObject)
//delete(s.investingStatesDirty, addr)
}
}
s.clearJournalAndRefund()
}
// IntermediateRoot computes the current root orderBook of the state trie.
// It is called in between transactions to get the root orderBook that
// goes into transaction receipts.
func (s *LendingStateDB) IntermediateRoot() common.Hash {
s.Finalise()
return s.trie.Hash()
}
// Commit writes the state to the underlying in-memory trie database.
func (s *LendingStateDB) Commit() (root common.Hash, err error) {
defer s.clearJournalAndRefund()
// Commit objects to the trie.
for addr, stateObject := range s.lendingExchangeStates {
if _, isDirty := s.lendingExchangeStatesDirty[addr]; isDirty {
// Write any storage changes in the state object to its storage trie.
if err := stateObject.CommitInvestingTrie(s.db); err != nil {
return EmptyHash, err
}
if err := stateObject.CommitBorrowingTrie(s.db); err != nil {
return EmptyHash, err
}
if err := stateObject.CommitLendingItemTrie(s.db); err != nil {
return EmptyHash, err
}
if err := stateObject.CommitLendingTradeTrie(s.db); err != nil {
return EmptyHash, err
}
if err := stateObject.CommitLiquidationTimeTrie(s.db); err != nil {
return EmptyHash, err
}
// Update the object in the main tradeId trie.
s.updateLendingExchange(stateObject)
delete(s.lendingExchangeStatesDirty, addr)
}
}
// Write trie changes.
root, err = s.trie.Commit(func(leaf []byte, parent common.Hash) error {
var exchange lendingObject
if err := rlp.DecodeBytes(leaf, &exchange); err != nil {
return nil
}
if exchange.InvestingRoot != EmptyRoot {
s.db.TrieDB().Reference(exchange.InvestingRoot, parent)
}
if exchange.BorrowingRoot != EmptyRoot {
s.db.TrieDB().Reference(exchange.BorrowingRoot, parent)
}
if exchange.LendingItemRoot != EmptyRoot {
s.db.TrieDB().Reference(exchange.LendingItemRoot, parent)
}
if exchange.LendingTradeRoot != EmptyRoot {
s.db.TrieDB().Reference(exchange.LendingTradeRoot, parent)
}
if exchange.LiquidationTimeRoot != EmptyRoot {
s.db.TrieDB().Reference(exchange.LiquidationTimeRoot, parent)
}
return nil
})
log.Debug("Lending State Trie cache stats after commit", "root", root.Hex())
return root, err
}
func (self *LendingStateDB) InsertLiquidationTime(lendingBook common.Hash, time *big.Int, tradeId uint64) {
timeHash := common.BigToHash(time)
lendingExchangeState := self.getLendingExchange(lendingBook)
if lendingExchangeState == nil {
lendingExchangeState = self.createLendingExchangeObject(lendingBook)
}
liquidationTime := lendingExchangeState.getLiquidationTimeOrderList(self.db, timeHash)
if liquidationTime == nil {
liquidationTime = lendingExchangeState.createLiquidationTime(self.db, timeHash)
}
liquidationTime.insertTradeId(self.db, common.Uint64ToHash(tradeId))
liquidationTime.AddVolume(One)
}
func (self *LendingStateDB) RemoveLiquidationTime(lendingBook common.Hash, tradeId uint64, time uint64) error {
timeHash := common.Uint64ToHash(time)
tradeIdHash := common.Uint64ToHash(tradeId)
lendingExchangeState := self.getLendingExchange(lendingBook)
if lendingExchangeState == nil {
return fmt.Errorf("lending book not found : %s ", lendingBook.Hex())
}
liquidationTime := lendingExchangeState.getLiquidationTimeOrderList(self.db, timeHash)
if liquidationTime == nil {
return fmt.Errorf("liquidation time not found : %s , %d ", lendingBook.Hex(), time)
}
if !liquidationTime.Exist(self.db, tradeIdHash) {
return fmt.Errorf("tradeId not exist : %s , %d , %d ", lendingBook.Hex(), time, tradeId)
}
liquidationTime.removeTradeId(self.db, tradeIdHash)
liquidationTime.subVolume(One)
if liquidationTime.Volume().Sign() == 0 {
lendingExchangeState.getLiquidationTimeTrie(self.db).TryDelete(timeHash[:])
}
return nil
}
func (self *LendingStateDB) GetLowestLiquidationTime(lendingBook common.Hash, time *big.Int) (*big.Int, []common.Hash) {
liquidationData := []common.Hash{}
lendingExchangeState := self.getLendingExchange(lendingBook)
if lendingExchangeState == nil {
return common.Big0, liquidationData
}
lowestPriceHash, liquidationState := lendingExchangeState.getLowestLiquidationTime(self.db)
lowestTime := new(big.Int).SetBytes(lowestPriceHash[:])
if liquidationState != nil && lowestTime.Sign() > 0 && lowestTime.Cmp(time) <= 0 {
liquidationData = liquidationState.getAllTradeIds(self.db)
}
return lowestTime, liquidationData
}
func (self *LendingStateDB) CancelLendingTrade(orderBook common.Hash, tradeId uint64) error {
tradeIdHash := common.Uint64ToHash(tradeId)
stateObject := self.GetOrNewLendingExchangeObject(orderBook)
if stateObject == nil {
return fmt.Errorf("Order book not found : %s ", orderBook.Hex())
}
lendingTrade := stateObject.getLendingTrade(self.db, tradeIdHash)
if lendingTrade == nil || lendingTrade.empty() {
return fmt.Errorf("lending trade empty order book : %s , trade id : %s , trade id hash : %s ", orderBook, tradeIdHash.Hex(), tradeIdHash.Hex())
}
self.journal = append(self.journal, cancelTrading{
orderBook: orderBook,
order: self.GetLendingTrade(orderBook, tradeIdHash),
})
lendingTrade.SetAmount(Zero)
return nil
}

View file

@ -0,0 +1,260 @@
// Copyright 2016 The go-ethereum Authors
// This file is part of the go-ethereum library.
//
// The go-ethereum library is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
//
// The go-ethereum library is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
package lendingstate
import (
"fmt"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"math/big"
"testing"
)
func TestEchangeStates(t *testing.T) {
t.SkipNow()
orderBook := common.StringToHash("BTC/XDC")
numberOrder := 20000
orderItems := []LendingItem{}
relayers := []common.Hash{}
for i := 0; i < numberOrder; i++ {
relayers = append(relayers, common.BigToHash(big.NewInt(int64(i))))
id := new(big.Int).SetUint64(uint64(i) + 1)
orderItems = append(orderItems, LendingItem{LendingId: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Interest: big.NewInt(int64(2*i + 1)), Side: Investing, Signature: &Signature{V: 1, R: common.HexToHash("111111"), S: common.HexToHash("222222222222")}})
orderItems = append(orderItems, LendingItem{LendingId: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Interest: big.NewInt(int64(2*i + 1)), Side: Borrowing, Signature: &Signature{V: 1, R: common.HexToHash("3333333333"), S: common.HexToHash("22222222222222222")}})
}
// Create an empty statedb database
db := rawdb.NewMemoryDatabase()
stateCache := NewDatabase(db)
statedb, _ := New(common.Hash{}, stateCache)
// Update it with some lenddinges
for i := 0; i < numberOrder; i++ {
statedb.SetNonce(relayers[i], uint64(1))
}
mapPriceSell := map[uint64]uint64{}
mapPriceBuy := map[uint64]uint64{}
for i := 0; i < len(orderItems); i++ {
amount := orderItems[i].Quantity.Uint64()
orderIdHash := common.BigToHash(new(big.Int).SetUint64(orderItems[i].LendingId))
statedb.InsertLendingItem(orderBook, orderIdHash, orderItems[i])
switch orderItems[i].Side {
case Investing:
old := mapPriceSell[amount]
mapPriceSell[amount] = old + amount
case Borrowing:
old := mapPriceBuy[amount]
mapPriceBuy[amount] = old + amount
default:
}
statedb.InsertLiquidationTime(orderBook, big.NewInt(int64(i)), uint64(i))
order := LendingTrade{TradeId: uint64(i), Amount: big.NewInt(int64(i))}
statedb.InsertTradingItem(orderBook, order.TradeId, order)
root := statedb.IntermediateRoot()
size, _ := stateCache.TrieDB().Size()
fmt.Println(i, "size", size)
statedb.Commit()
size, _ = stateCache.TrieDB().Size()
fmt.Println(i, "size", size)
statedb, _ = New(root, stateCache)
}
statedb.InsertLiquidationTime(orderBook, big.NewInt(1), 1)
order := LendingTrade{TradeId: 1, Amount: big.NewInt(2)}
statedb.InsertTradingItem(orderBook, 1, order)
root := statedb.IntermediateRoot()
size, _ := stateCache.TrieDB().Size()
fmt.Println("size", size)
statedb.Commit()
size, _ = stateCache.TrieDB().Size()
fmt.Println("size", size)
err := stateCache.TrieDB().Commit(root, false)
if err != nil {
t.Errorf("Error when commit into database: %v", err)
}
stateCache.TrieDB().Reference(root, common.Hash{})
statedb, err = New(root, stateCache)
if err != nil {
t.Fatalf("Error when get trie in database: %s , err: %v", root.Hex(), err)
}
_, liquidationData := statedb.GetLowestLiquidationTime(orderBook, big.NewInt(2))
if len(liquidationData) == 0 {
t.Fatalf("Error when get liquidation data save in database: got : %s ", liquidationData)
}
for i := 0; i < numberOrder; i++ {
nonce := statedb.GetNonce(relayers[i])
if nonce != uint64(1) {
t.Fatalf("Error when get nonce save in database: got : %d , wanted : %d ", nonce, i)
}
}
for i := 0; i < len(orderItems); i++ {
amount := statedb.GetLendingOrder(orderBook, common.BigToHash(new(big.Int).SetUint64(orderItems[i].LendingId))).Quantity
if orderItems[i].Quantity.Cmp(amount) != 0 {
t.Fatalf("Error when get amount save in database: tradeId %d , lendingType %s,got : %d , wanted : %d ", orderItems[i].LendingId, orderItems[i].Side, amount.Uint64(), orderItems[i].Quantity.Uint64())
}
}
fmt.Println(statedb.GetLendingTrade(orderBook, common.BigToHash(big.NewInt(1))).Amount)
db.Close()
}
func TestRevertStates(t *testing.T) {
orderBook := common.StringToHash("BTC/XDC")
numberOrder := 20
orderItems := []LendingItem{}
relayers := []common.Hash{}
for i := 0; i < numberOrder; i++ {
relayers = append(relayers, common.BigToHash(big.NewInt(int64(i))))
id := new(big.Int).SetUint64(uint64(i) + 1)
orderItems = append(orderItems, LendingItem{LendingId: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Interest: big.NewInt(int64(2*i + 1)), Side: Investing, Signature: &Signature{V: 1, R: common.HexToHash("111111"), S: common.HexToHash("222222222222")}})
orderItems = append(orderItems, LendingItem{LendingId: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Interest: big.NewInt(int64(2*i + 1)), Side: Borrowing, Signature: &Signature{V: 1, R: common.HexToHash("3333333333"), S: common.HexToHash("22222222222222222")}})
}
// Create an empty statedb database
db := rawdb.NewMemoryDatabase()
stateCache := NewDatabase(db)
statedb, _ := New(common.Hash{}, stateCache)
// Update it with some lenddinges
for i := 0; i < numberOrder; i++ {
statedb.SetNonce(relayers[i], uint64(1))
}
mapPriceSell := map[uint64]uint64{}
mapPriceBuy := map[uint64]uint64{}
for i := 0; i < len(orderItems); i++ {
amount := orderItems[i].Quantity.Uint64()
orderIdHash := common.BigToHash(new(big.Int).SetUint64(orderItems[i].LendingId))
statedb.InsertLendingItem(orderBook, orderIdHash, orderItems[i])
switch orderItems[i].Side {
case Investing:
old := mapPriceSell[amount]
mapPriceSell[amount] = old + amount
case Borrowing:
old := mapPriceBuy[amount]
mapPriceBuy[amount] = old + amount
default:
}
}
statedb.InsertLiquidationTime(orderBook, big.NewInt(1), 1)
root := statedb.IntermediateRoot()
statedb.Commit()
//err := stateCache.TrieDB().Commit(root, false)
//if err != nil {
// t.Errorf("Error when commit into database: %v", err)
//}
stateCache.TrieDB().Reference(root, common.Hash{})
statedb, err := New(root, stateCache)
if err != nil {
t.Fatalf("Error when get trie in database: %s , err: %v", root.Hex(), err)
}
orderIdHash := common.BigToHash(new(big.Int).SetUint64(orderItems[0].LendingId))
// set nonce
wantedNonce := statedb.GetNonce(relayers[1])
snap := statedb.Snapshot()
statedb.SetNonce(relayers[1], 0)
statedb.RevertToSnapshot(snap)
gotNonce := statedb.GetNonce(relayers[1])
if wantedNonce != gotNonce {
t.Fatalf(" err get nonce addr: %v after try revert snap shot , got : %d ,want : %d", relayers[1].Hex(), gotNonce, wantedNonce)
}
// cancel order
wantedOrder := statedb.GetLendingOrder(orderBook, orderIdHash)
snap = statedb.Snapshot()
statedb.CancelLendingOrder(orderBook, &wantedOrder)
statedb.RevertToSnapshot(snap)
gotOrder := statedb.GetLendingOrder(orderBook, orderIdHash)
if gotOrder.Quantity.Cmp(wantedOrder.Quantity) != 0 {
t.Fatalf(" err cancel order info : %v after try revert snap shot , got : %v ,want : %v", orderIdHash.Hex(), gotOrder, wantedOrder)
}
// insert order
i := 2*numberOrder + 1
id := new(big.Int).SetUint64(uint64(i) + 1)
testOrder := LendingItem{LendingId: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Interest: big.NewInt(int64(2*i + 1)), Side: Investing, Signature: &Signature{V: 1, R: common.HexToHash("111111"), S: common.HexToHash("222222222222")}}
orderIdHash = common.BigToHash(new(big.Int).SetUint64(testOrder.LendingId))
snap = statedb.Snapshot()
statedb.InsertLendingItem(orderBook, orderIdHash, testOrder)
statedb.RevertToSnapshot(snap)
gotOrder = statedb.GetLendingOrder(orderBook, orderIdHash)
if gotOrder.Quantity.Cmp(EmptyLendingOrder.Quantity) != 0 {
t.Fatalf(" err insert order info : %v after try revert snap shot , got : %v ,want Empty Order", orderIdHash.Hex(), gotOrder)
}
// insert trade order
i = 2*numberOrder + 1
id = new(big.Int).SetUint64(uint64(i) + 1)
order := LendingTrade{TradeId: id.Uint64(), Amount: big.NewInt(int64(2*i + 1))}
orderIdHash = common.BigToHash(new(big.Int).SetUint64(order.TradeId))
snap = statedb.Snapshot()
statedb.InsertTradingItem(orderBook, order.TradeId, order)
statedb.RevertToSnapshot(snap)
gotLendingTrade := statedb.GetLendingTrade(orderBook, orderIdHash)
if gotLendingTrade.Amount.Cmp(EmptyLendingTrade.Amount) != 0 {
t.Fatalf(" err insert lending trade : %s after try revert snap shot , got : %v ,want Empty Order", orderIdHash.Hex(), gotLendingTrade.Amount)
}
// insert trade order
time, data := statedb.GetLowestLiquidationTime(orderBook, big.NewInt(1))
fmt.Println(time, data)
statedb.RemoveLiquidationTime(orderBook, 1, 1)
time, data = statedb.GetLowestLiquidationTime(orderBook, big.NewInt(1))
fmt.Println(time, data)
// change key
db.Close()
}
func TestDumpStates(t *testing.T) {
orderBook := common.StringToHash("BTC/XDC")
numberOrder := 20
orderItems := []LendingItem{}
relayers := []common.Hash{}
for i := 0; i < numberOrder; i++ {
relayers = append(relayers, common.BigToHash(big.NewInt(int64(i))))
id := new(big.Int).SetUint64(uint64(i) + 1)
orderItems = append(orderItems, LendingItem{LendingId: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Interest: big.NewInt(1), Side: Investing, Signature: &Signature{V: 1, R: common.HexToHash("111111"), S: common.HexToHash("222222222222")}})
orderItems = append(orderItems, LendingItem{LendingId: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Interest: big.NewInt(1), Side: Borrowing, Signature: &Signature{V: 1, R: common.HexToHash("3333333333"), S: common.HexToHash("22222222222222222")}})
}
// Create an empty statedb database
db := rawdb.NewMemoryDatabase()
stateCache := NewDatabase(db)
statedb, _ := New(common.Hash{}, stateCache)
for i := 0; i < len(orderItems); i++ {
orderIdHash := common.BigToHash(new(big.Int).SetUint64(orderItems[i].LendingId))
statedb.InsertLendingItem(orderBook, orderIdHash, orderItems[i])
}
statedb.InsertLiquidationTime(orderBook, big.NewInt(1), 1)
order := LendingTrade{TradeId: 1, Amount: big.NewInt(2)}
statedb.InsertTradingItem(orderBook, 1, order)
root := statedb.IntermediateRoot()
statedb.Commit()
//err := stateCache.TrieDB().Commit(root, false)
//if err != nil {
// t.Errorf("Error when commit into database: %v", err)
//}
stateCache.TrieDB().Reference(root, common.Hash{})
statedb, err := New(root, stateCache)
if err != nil {
t.Fatalf("Error when get trie in database: %s , err: %v", root.Hex(), err)
}
fmt.Println(statedb.DumpBorrowingTrie(orderBook))
db.Close()
}

View file

@ -0,0 +1,190 @@
package lendingstate
import (
"fmt"
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
"math/big"
"strconv"
"time"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/globalsign/mgo/bson"
)
const (
TradeStatusOpen = "OPEN"
TradeStatusClosed = "CLOSED"
TradeStatusLiquidated = "LIQUIDATED"
)
type LendingTrade struct {
Borrower common.Address `bson:"borrower" json:"borrower"`
Investor common.Address `bson:"investor" json:"investor"`
LendingToken common.Address `bson:"lendingToken" json:"lendingToken"`
CollateralToken common.Address `bson:"collateralToken" json:"collateralToken"`
BorrowingOrderHash common.Hash `bson:"borrowingOrderHash" json:"borrowingOrderHash"`
InvestingOrderHash common.Hash `bson:"investingOrderHash" json:"investingOrderHash"`
BorrowingRelayer common.Address `bson:"borrowingRelayer" json:"borrowingRelayer"`
InvestingRelayer common.Address `bson:"investingRelayer" json:"investingRelayer"`
Term uint64 `bson:"term" json:"term"`
Interest uint64 `bson:"interest" json:"interest"`
CollateralPrice *big.Int `bson:"collateralPrice" json:"collateralPrice"`
LiquidationPrice *big.Int `bson:"liquidationPrice" json:"liquidationPrice"`
CollateralLockedAmount *big.Int `bson:"collateralLockedAmount" json:"collateralLockedAmount"`
AutoTopUp bool `bson:"autoTopUp" json:"autoTopUp"`
LiquidationTime uint64 `bson:"liquidationTime" json:"liquidationTime"`
DepositRate *big.Int `bson:"depositRate" json:"depositRate"`
LiquidationRate *big.Int `bson:"liquidationRate" json:"liquidationRate"`
RecallRate *big.Int `bson:"recallRate" json:"recallRate"`
Amount *big.Int `bson:"amount" json:"amount"`
BorrowingFee *big.Int `bson:"borrowingFee" json:"borrowingFee"`
InvestingFee *big.Int `bson:"investingFee" json:"investingFee"`
Status string `bson:"status" json:"status"`
TakerOrderSide string `bson:"takerOrderSide" json:"takerOrderSide"`
TakerOrderType string `bson:"takerOrderType" json:"takerOrderType"`
MakerOrderType string `bson:"makerOrderType" json:"makerOrderType"`
TradeId uint64 `bson:"tradeId" json:"tradeId"`
Hash common.Hash `bson:"hash" json:"hash"`
TxHash common.Hash `bson:"txHash" json:"txHash"`
ExtraData string `bson:"extraData" json:"extraData"`
CreatedAt time.Time `bson:"createdAt" json:"createdAt"`
UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"`
}
type LendingTradeBSON struct {
Borrower string `bson:"borrower" json:"borrower"`
Investor string `bson:"investor" json:"investor"`
LendingToken string `bson:"lendingToken" json:"lendingToken"`
CollateralToken string `bson:"collateralToken" json:"collateralToken"`
BorrowingOrderHash string `bson:"borrowingOrderHash" json:"borrowingOrderHash"`
InvestingOrderHash string `bson:"investingOrderHash" json:"investingOrderHash"`
BorrowingRelayer string `bson:"borrowingRelayer" json:"borrowingRelayer"`
InvestingRelayer string `bson:"investingRelayer" json:"investingRelayer"`
Term string `bson:"term" json:"term"`
Interest string `bson:"interest" json:"interest"`
CollateralPrice string `bson:"collateralPrice" json:"collateralPrice"`
LiquidationPrice string `bson:"liquidationPrice" json:"liquidationPrice"`
LiquidationTime string `bson:"liquidationTime" json:"liquidationTime"`
CollateralLockedAmount string `bson:"collateralLockedAmount" json:"collateralLockedAmount"`
AutoTopUp bool `bson:"autoTopUp" json:"autoTopUp"`
DepositRate string `bson:"depositRate" json:"depositRate"`
LiquidationRate string `bson:"liquidationRate" json:"liquidationRate"`
RecallRate string `bson:"recallRate" json:"recallRate"`
Amount string `bson:"amount" json:"amount"`
BorrowingFee string `bson:"borrowingFee" json:"borrowingFee"`
InvestingFee string `bson:"investingFee" json:"investingFee"`
Status string `bson:"status" json:"status"`
TakerOrderSide string `bson:"takerOrderSide" json:"takerOrderSide"`
TakerOrderType string `bson:"takerOrderType" json:"takerOrderType"`
MakerOrderType string `bson:"makerOrderType" json:"makerOrderType"`
TradeId string `bson:"tradeId" json:"tradeId"`
Hash string `bson:"hash" json:"hash"`
TxHash string `bson:"txHash" json:"txHash"`
ExtraData string `bson:"extraData" json:"extraData"`
UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"`
}
func (t *LendingTrade) GetBSON() (interface{}, error) {
return bson.M{
"$setOnInsert": bson.M{
"createdAt": t.CreatedAt,
},
"$set": LendingTradeBSON{
Borrower: t.Borrower.Hex(),
Investor: t.Investor.Hex(),
LendingToken: t.LendingToken.Hex(),
CollateralToken: t.CollateralToken.Hex(),
BorrowingOrderHash: t.BorrowingOrderHash.Hex(),
InvestingOrderHash: t.InvestingOrderHash.Hex(),
BorrowingRelayer: t.BorrowingRelayer.Hex(),
InvestingRelayer: t.InvestingRelayer.Hex(),
Term: strconv.FormatUint(t.Term, 10),
Interest: strconv.FormatUint(t.Interest, 10),
CollateralPrice: t.CollateralPrice.String(),
LiquidationPrice: t.LiquidationPrice.String(),
LiquidationTime: strconv.FormatUint(t.LiquidationTime, 10),
CollateralLockedAmount: t.CollateralLockedAmount.String(),
AutoTopUp: t.AutoTopUp,
DepositRate: t.DepositRate.String(),
LiquidationRate: t.LiquidationRate.String(),
RecallRate: t.RecallRate.String(),
Amount: t.Amount.String(),
BorrowingFee: t.BorrowingFee.String(),
InvestingFee: t.InvestingFee.String(),
Status: t.Status,
TakerOrderSide: t.TakerOrderSide,
TakerOrderType: t.TakerOrderType,
MakerOrderType: t.MakerOrderType,
TradeId: strconv.FormatUint(t.TradeId, 10),
Hash: t.Hash.Hex(),
TxHash: t.TxHash.Hex(),
ExtraData: t.ExtraData,
UpdatedAt: t.UpdatedAt,
},
}, nil
}
func (t *LendingTrade) SetBSON(raw bson.Raw) error {
decoded := new(LendingTradeBSON)
err := raw.Unmarshal(decoded)
if err != nil {
return err
}
tradeId, err := strconv.ParseInt(decoded.TradeId, 10, 64)
if err != nil {
return fmt.Errorf("failed to parse lendingItem.TradeId. Err: %v", err)
}
t.TradeId = uint64(tradeId)
t.Borrower = common.HexToAddress(decoded.Borrower)
t.Investor = common.HexToAddress(decoded.Investor)
t.LendingToken = common.HexToAddress(decoded.LendingToken)
t.CollateralToken = common.HexToAddress(decoded.CollateralToken)
t.AutoTopUp = decoded.AutoTopUp
t.BorrowingOrderHash = common.HexToHash(decoded.BorrowingOrderHash)
t.InvestingOrderHash = common.HexToHash(decoded.InvestingOrderHash)
t.BorrowingRelayer = common.HexToAddress(decoded.BorrowingRelayer)
t.InvestingRelayer = common.HexToAddress(decoded.InvestingRelayer)
term, err := strconv.ParseInt(decoded.Term, 10, 64)
if err != nil {
return fmt.Errorf("failed to parse lendingItem.term. Err: %v", err)
}
t.Term = uint64(term)
interest, err := strconv.ParseInt(decoded.Interest, 10, 64)
if err != nil {
return fmt.Errorf("failed to parse lendingItem.interest. Err: %v", err)
}
t.Interest = uint64(interest)
t.CollateralPrice = ToBigInt(decoded.CollateralPrice)
t.LiquidationPrice = ToBigInt(decoded.LiquidationPrice)
liquidationTime, err := strconv.ParseInt(decoded.LiquidationTime, 10, 64)
if err != nil {
return fmt.Errorf("failed to parse lendingItem.LiquidationTime. Err: %v", err)
}
t.LiquidationTime = uint64(liquidationTime)
t.CollateralLockedAmount = ToBigInt(decoded.CollateralLockedAmount)
t.DepositRate = ToBigInt(decoded.DepositRate)
t.LiquidationRate = ToBigInt(decoded.LiquidationRate)
t.RecallRate = ToBigInt(decoded.RecallRate)
t.Amount = tradingstate.ToBigInt(decoded.Amount)
t.BorrowingFee = tradingstate.ToBigInt(decoded.BorrowingFee)
t.InvestingFee = tradingstate.ToBigInt(decoded.InvestingFee)
t.Status = decoded.Status
t.TakerOrderSide = decoded.TakerOrderSide
t.TakerOrderType = decoded.TakerOrderType
t.MakerOrderType = decoded.MakerOrderType
t.ExtraData = decoded.ExtraData
t.Hash = common.HexToHash(decoded.Hash)
t.TxHash = common.HexToHash(decoded.TxHash)
t.UpdatedAt = decoded.UpdatedAt
return nil
}
func (t *LendingTrade) ComputeHash() common.Hash {
sha := sha3.NewKeccak256()
sha.Write(t.InvestingOrderHash.Bytes())
sha.Write(t.BorrowingOrderHash.Bytes())
return common.BytesToHash(sha.Sum(nil))
}

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,541 @@
package XDCxlending
import (
"github.com/XinFinOrg/XDPoSChain/XDCx"
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
"github.com/XinFinOrg/XDPoSChain/XDCxlending/lendingstate"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"math/big"
"reflect"
"testing"
)
func Test_getCancelFeeV1(t *testing.T) {
type CancelFeeArg struct {
collateralTokenDecimal *big.Int
collateralPrice *big.Int
borrowFeeRate *big.Int
order *lendingstate.LendingItem
}
tests := []struct {
name string
args CancelFeeArg
want *big.Int
}{
// zero fee test: LEND
{
"zero fee getCancelFeeV1: LEND",
CancelFeeArg{
collateralTokenDecimal: common.Big1,
collateralPrice: common.Big1,
borrowFeeRate: common.Big0,
order: &lendingstate.LendingItem{
Quantity: new(big.Int).SetUint64(10000),
Side: tradingstate.Ask,
},
},
common.Big0,
},
// zero fee test: BORROW
{
"zero fee getCancelFeeV1: BORROW",
CancelFeeArg{
collateralTokenDecimal: common.Big1,
collateralPrice: common.Big1,
borrowFeeRate: common.Big0,
order: &lendingstate.LendingItem{
Quantity: new(big.Int).SetUint64(10000),
Side: tradingstate.Bid,
},
},
common.Big0,
},
// test getCancelFee: LEND
{
"test getCancelFeeV1:: LEND",
CancelFeeArg{
collateralTokenDecimal: common.Big1,
collateralPrice: common.Big1,
borrowFeeRate: new(big.Int).SetUint64(30), // 30/10000= 0.3%
order: &lendingstate.LendingItem{
Quantity: new(big.Int).SetUint64(10000),
Side: tradingstate.Ask,
},
},
common.Big3,
},
// test getCancelFee:: BORROW
{
"test getCancelFeeV1:: BORROW",
CancelFeeArg{
collateralTokenDecimal: common.Big1,
collateralPrice: common.Big1,
borrowFeeRate: new(big.Int).SetUint64(30), // 30/10000= 0.3%
order: &lendingstate.LendingItem{
Quantity: new(big.Int).SetUint64(10000),
Side: tradingstate.Bid,
},
},
common.Big3,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := getCancelFeeV1(tt.args.collateralTokenDecimal, tt.args.collateralPrice, tt.args.borrowFeeRate, tt.args.order); !reflect.DeepEqual(got, tt.want) {
t.Errorf("getCancelFeeV1() = %v, want %v", got, tt.want)
}
})
}
}
func Test_getCancelFee(t *testing.T) {
XDCx := XDCx.New(&XDCx.DefaultConfig)
db := rawdb.NewMemoryDatabase()
stateCache := tradingstate.NewDatabase(db)
tradingStateDb, _ := tradingstate.New(common.Hash{}, stateCache)
testTokenA := common.HexToAddress("0x1200000000000000000000000000000000000002")
testTokenB := common.HexToAddress("0x1300000000000000000000000000000000000003")
// set decimal
// tokenA has decimal 10^18
XDCx.SetTokenDecimal(testTokenA, common.BasePrice)
// tokenB has decimal 10^8
XDCx.SetTokenDecimal(testTokenB, new(big.Int).Exp(big.NewInt(10), big.NewInt(8), nil))
// set tokenAPrice = 1 XDC
tradingStateDb.SetMediumPriceBeforeEpoch(tradingstate.GetTradingOrderBookHash(testTokenA, common.HexToAddress(common.XDCNativeAddress)), common.BasePrice)
// set tokenBPrice = 1 XDC
tradingStateDb.SetMediumPriceBeforeEpoch(tradingstate.GetTradingOrderBookHash(testTokenB, common.HexToAddress(common.XDCNativeAddress)), common.BasePrice)
l := New(XDCx)
type CancelFeeArg struct {
borrowFeeRate *big.Int
order *lendingstate.LendingItem
}
tests := []struct {
name string
args CancelFeeArg
want *big.Int
}{
// LENDING TOKEN: testTokenA
// COLLATERAL TOKEN: XDC
// zero fee test: LEND
{
"TokenA/XDCzero fee test: LEND",
CancelFeeArg{
borrowFeeRate: common.Big0,
order: &lendingstate.LendingItem{
LendingToken: testTokenA,
CollateralToken: common.HexToAddress(common.XDCNativeAddress),
Quantity: new(big.Int).SetUint64(10000),
Side: lendingstate.Investing,
},
},
common.Big0,
},
// zero fee test: BORROW
{
"TokenA/XDC zero fee test: BORROW",
CancelFeeArg{
borrowFeeRate: common.Big0,
order: &lendingstate.LendingItem{
LendingToken: testTokenA,
CollateralToken: common.HexToAddress(common.XDCNativeAddress),
Quantity: new(big.Int).SetUint64(10000),
Side: lendingstate.Borrowing,
},
},
common.Big0,
},
// test getCancelFee: LEND
{
"TokenA/XDC test getCancelFee:: LEND",
CancelFeeArg{
borrowFeeRate: new(big.Int).SetUint64(30), // 30/10000= 0.3%
order: &lendingstate.LendingItem{
LendingToken: testTokenA,
CollateralToken: common.HexToAddress(common.XDCNativeAddress),
Quantity: new(big.Int).SetUint64(10000),
Side: lendingstate.Investing,
},
},
common.RelayerLendingCancelFee,
},
// test getCancelFee:: BORROW
{
"TokenA/XDC test getCancelFee:: BORROW",
CancelFeeArg{
borrowFeeRate: new(big.Int).SetUint64(30), // 30/10000= 0.3%
order: &lendingstate.LendingItem{
LendingToken: testTokenA,
CollateralToken: common.HexToAddress(common.XDCNativeAddress),
Quantity: new(big.Int).SetUint64(10000),
Side: lendingstate.Borrowing,
},
},
common.RelayerLendingCancelFee,
},
// LENDING TOKEN: XDC
// COLLATERAL TOKEN: testTokenA
// zero fee test: LEND
{
"XDC/TokenA zero fee test: LEND",
CancelFeeArg{
borrowFeeRate: common.Big0,
order: &lendingstate.LendingItem{
LendingToken: common.HexToAddress(common.XDCNativeAddress),
CollateralToken: testTokenA,
Quantity: new(big.Int).SetUint64(10000),
Side: lendingstate.Investing,
},
},
common.Big0,
},
// zero fee test: BORROW
{
"XDC/TokenA zero fee test: BORROW",
CancelFeeArg{
borrowFeeRate: common.Big0,
order: &lendingstate.LendingItem{
LendingToken: common.HexToAddress(common.XDCNativeAddress),
CollateralToken: testTokenA,
Quantity: new(big.Int).SetUint64(10000),
Side: lendingstate.Borrowing,
},
},
common.Big0,
},
// test getCancelFee: LEND
{
"XDC/TokenA test getCancelFee:: LEND",
CancelFeeArg{
borrowFeeRate: new(big.Int).SetUint64(30), // 30/10000= 0.3%
order: &lendingstate.LendingItem{
LendingToken: common.HexToAddress(common.XDCNativeAddress),
CollateralToken: testTokenA,
Quantity: new(big.Int).SetUint64(10000),
Side: lendingstate.Investing,
},
},
common.RelayerLendingCancelFee,
},
// test getCancelFee:: BORROW
{
"XDC/TokenA test getCancelFee:: BORROW",
CancelFeeArg{
borrowFeeRate: new(big.Int).SetUint64(30), // 30/10000= 0.3%
order: &lendingstate.LendingItem{
LendingToken: common.HexToAddress(common.XDCNativeAddress),
CollateralToken: testTokenA,
Quantity: new(big.Int).SetUint64(10000),
Side: lendingstate.Borrowing,
},
},
common.RelayerLendingCancelFee,
},
// LENDING TOKEN: testTokenB
// COLLATERAL TOKEN: testTokenA
// zero fee test: LEND
{
"TokenB/TokenA zero fee test: LEND",
CancelFeeArg{
borrowFeeRate: common.Big0,
order: &lendingstate.LendingItem{
LendingToken: testTokenB,
CollateralToken: testTokenA,
Quantity: new(big.Int).SetUint64(10000),
Side: lendingstate.Investing,
},
},
common.Big0,
},
// zero fee test: BORROW
{
"TokenB/TokenA zero fee test: BORROW",
CancelFeeArg{
borrowFeeRate: common.Big0,
order: &lendingstate.LendingItem{
LendingToken: testTokenB,
CollateralToken: testTokenA,
Quantity: new(big.Int).SetUint64(10000),
Side: lendingstate.Borrowing,
},
},
common.Big0,
},
// test getCancelFee: LEND
{
"TokenB/TokenA test getCancelFee:: LEND",
CancelFeeArg{
borrowFeeRate: new(big.Int).SetUint64(30), // 30/10000= 0.3%
order: &lendingstate.LendingItem{
LendingToken: testTokenB,
CollateralToken: testTokenA,
Quantity: new(big.Int).SetUint64(10000),
Side: lendingstate.Investing,
},
},
new(big.Int).Exp(big.NewInt(10), big.NewInt(5), nil),
},
// test getCancelFee:: BORROW
{
"TokenB/TokenA test getCancelFee:: BORROW",
CancelFeeArg{
borrowFeeRate: new(big.Int).SetUint64(30), // 30/10000= 0.3%
order: &lendingstate.LendingItem{
LendingToken: testTokenB,
CollateralToken: testTokenA,
Quantity: new(big.Int).SetUint64(10000),
Side: lendingstate.Borrowing,
},
},
common.RelayerLendingCancelFee,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got, _ := l.getCancelFee(nil, nil, tradingStateDb, tt.args.order, tt.args.borrowFeeRate); !reflect.DeepEqual(got, tt.want) {
t.Errorf("getCancelFee() = %v, want %v", got, tt.want)
}
})
}
// testcase: can't get price of token in XDC
testTokenC := common.HexToAddress("0x1400000000000000000000000000000000000004")
XDCx.SetTokenDecimal(testTokenC, big.NewInt(1))
tokenCOrder := CancelFeeArg{
borrowFeeRate: new(big.Int).SetUint64(100), // 100/10000= 1%
order: &lendingstate.LendingItem{
Quantity: new(big.Int).SetUint64(10000),
CollateralToken: testTokenC,
LendingToken: testTokenA,
Side: lendingstate.Borrowing,
},
}
if fee, _ := l.getCancelFee(nil, nil, tradingStateDb, tokenCOrder.order, tokenCOrder.borrowFeeRate); fee != nil && fee.Sign() != 0 {
t.Errorf("getCancelFee() = %v, want %v", fee, common.Big0)
}
}
func TestGetLendQuantity(t *testing.T) {
depositRate := big.NewInt(150)
lendQuantity := new(big.Int).Mul(big.NewInt(1000), common.BasePrice)
collateralLocked, _ := new(big.Int).SetString("1000000000000000000000", 10) // 1000
collateralLocked = new(big.Int).Mul(big.NewInt(150), collateralLocked)
collateralLocked = new(big.Int).Div(collateralLocked, big.NewInt(100))
type GetLendQuantityArg struct {
takerSide string
collateralTokenDecimal *big.Int
depositRate *big.Int
collateralPrice *big.Int
takerBalance *big.Int
makerBalance *big.Int
quantityToLend *big.Int
}
tests := []struct {
name string
args GetLendQuantityArg
lendQuantity *big.Int
rejectMaker bool
}{
{
"taker: BORROW, takerBalance = 0, reject taker, makerBalance = 0",
GetLendQuantityArg{
lendingstate.Borrowing,
common.BasePrice,
depositRate,
common.BasePrice,
common.Big0,
common.Big0,
lendQuantity,
},
common.Big0,
false,
},
{
"taker: BORROW, takerBalance = 0, reject taker, makerBalance > 0",
GetLendQuantityArg{
lendingstate.Borrowing,
common.BasePrice,
depositRate,
common.BasePrice,
common.Big0,
lendQuantity,
lendQuantity,
},
common.Big0,
false,
},
{
"taker: BORROW, takerBalance not enough, reject partial of taker",
GetLendQuantityArg{
lendingstate.Borrowing,
common.BasePrice,
depositRate,
common.BasePrice,
new(big.Int).Div(collateralLocked, big.NewInt(2)), // 1/2
lendQuantity,
lendQuantity,
},
new(big.Int).Div(lendQuantity, big.NewInt(2)),
false,
},
{
"taker: BORROW, makerBalance = 0, reject maker",
GetLendQuantityArg{
lendingstate.Borrowing,
common.BasePrice,
depositRate,
common.BasePrice,
new(big.Int).Div(collateralLocked, big.NewInt(2)),
common.Big0,
lendQuantity,
},
common.Big0,
true,
},
{
"taker: BORROW, makerBalance not enough, reject partial of maker",
GetLendQuantityArg{
lendingstate.Borrowing,
common.BasePrice,
depositRate,
common.BasePrice,
collateralLocked,
new(big.Int).Div(lendQuantity, big.NewInt(2)),
lendQuantity,
},
new(big.Int).Div(lendQuantity, big.NewInt(2)),
true,
},
{
"taker: BORROW, don't reject",
GetLendQuantityArg{
lendingstate.Borrowing,
common.BasePrice,
depositRate,
common.BasePrice,
collateralLocked,
lendQuantity,
lendQuantity,
},
lendQuantity,
false,
},
{
"taker: INVEST, makerBalance = 0, reject maker",
GetLendQuantityArg{
lendingstate.Investing,
common.BasePrice,
depositRate,
common.BasePrice,
new(big.Int).Div(collateralLocked, big.NewInt(2)),
common.Big0,
lendQuantity,
},
common.Big0,
true,
},
{
"taker: INVEST, takerBalance not enough, reject partial of taker",
GetLendQuantityArg{
lendingstate.Investing,
common.BasePrice,
depositRate,
common.BasePrice,
new(big.Int).Div(lendQuantity, big.NewInt(2)), // 1/2
collateralLocked,
lendQuantity,
},
new(big.Int).Div(lendQuantity, big.NewInt(2)),
false,
},
{
"taker: INVEST, makerBalance = 0, reject maker",
GetLendQuantityArg{
lendingstate.Investing,
common.BasePrice,
depositRate,
common.BasePrice,
common.Big0,
new(big.Int).Div(collateralLocked, big.NewInt(2)),
lendQuantity,
},
common.Big0,
false,
},
{
"taker: INVEST, makerBalance is enough, takerBalance = 0 -> reject taker",
GetLendQuantityArg{
lendingstate.Investing,
common.BasePrice,
depositRate,
common.BasePrice,
common.Big0,
collateralLocked,
lendQuantity,
},
common.Big0,
false,
},
{
"taker: INVEST, makerBalance not enough, reject partial of maker",
GetLendQuantityArg{
lendingstate.Investing,
common.BasePrice,
depositRate,
common.BasePrice,
collateralLocked,
new(big.Int).Div(collateralLocked, big.NewInt(2)),
lendQuantity,
},
new(big.Int).Div(lendQuantity, big.NewInt(2)),
true,
},
{
"taker: INVEST, don't reject",
GetLendQuantityArg{
lendingstate.Investing,
common.BasePrice,
depositRate,
common.BasePrice,
lendQuantity,
collateralLocked,
lendQuantity,
},
lendQuantity,
false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, got1 := GetLendQuantity(tt.args.takerSide, tt.args.collateralTokenDecimal, tt.args.depositRate, tt.args.collateralPrice, tt.args.takerBalance, tt.args.makerBalance, tt.args.quantityToLend)
if !reflect.DeepEqual(got, tt.lendQuantity) {
t.Errorf("GetLendQuantity() got = %v, want %v", got, tt.lendQuantity)
}
if got1 != tt.rejectMaker {
t.Errorf("GetLendQuantity() got1 = %v, want %v", got1, tt.rejectMaker)
}
})
}
}

View file

@ -27,8 +27,8 @@ import (
"reflect"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/crypto"
)
const jsondata = `

View file

@ -22,10 +22,10 @@ import (
"io"
"io/ioutil"
"github.com/ethereum/go-ethereum/accounts/keystore"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/XinFinOrg/XDPoSChain/accounts/keystore"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto"
)
// NewTransactor is a utility method to easily create a transaction signer from

View file

@ -21,9 +21,9 @@ import (
"errors"
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/XinFinOrg/XDPoSChain"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
)
var (
@ -49,7 +49,7 @@ type ContractCaller interface {
CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)
// ContractCall executes an Ethereum contract call with the specified data as the
// input.
CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
CallContract(ctx context.Context, call XDPoSChain.CallMsg, blockNumber *big.Int) ([]byte, error)
}
// PendingContractCaller defines methods to perform contract calls on the pending state.
@ -59,7 +59,7 @@ type PendingContractCaller interface {
// PendingCodeAt returns the code of the given account in the pending state.
PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error)
// PendingCallContract executes an Ethereum contract call against the pending state.
PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error)
PendingCallContract(ctx context.Context, call XDPoSChain.CallMsg) ([]byte, error)
}
// ContractTransactor defines the methods needed to allow operating with contract
@ -79,7 +79,7 @@ type ContractTransactor interface {
// There is no guarantee that this is the true gas limit requirement as other
// transactions may be added or removed by miners, but it should provide a basis
// for setting a reasonable default.
EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error)
EstimateGas(ctx context.Context, call XDPoSChain.CallMsg) (gas uint64, err error)
// SendTransaction injects the transaction into the pending pool for execution.
SendTransaction(ctx context.Context, tx *types.Transaction) error
}
@ -91,11 +91,11 @@ type ContractFilterer interface {
// returning all the results in one batch.
//
// TODO(karalabe): Deprecate when the subscription one can return past data too.
FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error)
FilterLogs(ctx context.Context, query XDPoSChain.FilterQuery) ([]types.Log, error)
// SubscribeFilterLogs creates a background log filtering operation, returning
// a subscription immediately, which can be used to stream the found events.
SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error)
SubscribeFilterLogs(ctx context.Context, query XDPoSChain.FilterQuery, ch chan<- types.Log) (XDPoSChain.Subscription, error)
}
// DeployBackend wraps the operations needed by WaitMined and WaitDeployed.

View file

@ -20,25 +20,27 @@ import (
"context"
"errors"
"fmt"
"github.com/XinFinOrg/XDPoSChain/consensus"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"math/big"
"sync"
"time"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/consensus/ethash"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/bloombits"
"github.com/ethereum/go-ethereum/core/state"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/core/vm"
"github.com/ethereum/go-ethereum/eth/filters"
"github.com/ethereum/go-ethereum/ethdb"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/params"
"github.com/ethereum/go-ethereum/rpc"
"github.com/XinFinOrg/XDPoSChain"
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/math"
"github.com/XinFinOrg/XDPoSChain/consensus/ethash"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/bloombits"
"github.com/XinFinOrg/XDPoSChain/core/state"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/core/vm"
"github.com/XinFinOrg/XDPoSChain/eth/filters"
"github.com/XinFinOrg/XDPoSChain/ethdb"
"github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/params"
"github.com/XinFinOrg/XDPoSChain/rpc"
)
// This nil assignment ensures compile time that SimulatedBackend implements bind.ContractBackend.
@ -65,7 +67,7 @@ type SimulatedBackend struct {
// NewSimulatedBackend creates a new binding backend using a simulated blockchain
// for testing purposes.
func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend {
database, _ := ethdb.NewMemDatabase()
database := rawdb.NewMemoryDatabase()
genesis := core.Genesis{Config: params.AllEthashProtocolChanges, Alloc: alloc, GasLimit: 42000000}
genesis.MustCommit(database)
blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{})
@ -185,7 +187,7 @@ func (b *SimulatedBackend) PendingCodeAt(ctx context.Context, contract common.Ad
}
// CallContract executes a contract call.
func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
func (b *SimulatedBackend) CallContract(ctx context.Context, call XDPoSChain.CallMsg, blockNumber *big.Int) ([]byte, error) {
b.mu.Lock()
defer b.mu.Unlock()
@ -200,8 +202,41 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallM
return rval, err
}
//FIXME: please use copyState for this function
// CallContractWithState executes a contract call at the given state.
func (b *SimulatedBackend) CallContractWithState(call XDPoSChain.CallMsg, chain consensus.ChainContext, statedb *state.StateDB) ([]byte, error) {
// Ensure message is initialized properly.
call.GasPrice = big.NewInt(0)
if call.Gas == 0 {
call.Gas = 1000000
}
if call.Value == nil {
call.Value = new(big.Int)
}
// Execute the call.
msg := callmsg{call}
feeCapacity := state.GetTRC21FeeCapacityFromState(statedb)
if msg.To() != nil {
if value, ok := feeCapacity[*msg.To()]; ok {
msg.CallMsg.BalanceTokenFee = value
}
}
evmContext := core.NewEVMContext(msg, chain.CurrentHeader(), chain, nil)
// Create a new environment which holds all relevant information
// about the transaction and calling mechanisms.
vmenv := vm.NewEVM(evmContext, statedb, nil, chain.Config(), vm.Config{})
gaspool := new(core.GasPool).AddGas(1000000)
owner := common.Address{}
rval, _, _, err := core.NewStateTransition(vmenv, msg, gaspool).TransitionDb(owner)
if err != nil {
return nil, err
}
return rval, err
}
// PendingCallContract executes a contract call on the pending state.
func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error) {
func (b *SimulatedBackend) PendingCallContract(ctx context.Context, call XDPoSChain.CallMsg) ([]byte, error) {
b.mu.Lock()
defer b.mu.Unlock()
defer b.pendingState.RevertToSnapshot(b.pendingState.Snapshot())
@ -227,7 +262,7 @@ func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error
// EstimateGas executes the requested code against the currently pending block/state and
// returns the used amount of gas.
func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMsg) (uint64, error) {
func (b *SimulatedBackend) EstimateGas(ctx context.Context, call XDPoSChain.CallMsg) (uint64, error) {
b.mu.Lock()
defer b.mu.Unlock()
@ -250,6 +285,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
snapshot := b.pendingState.Snapshot()
_, _, failed, err := b.callContract(ctx, call, b.pendingBlock, b.pendingState)
fmt.Println("EstimateGas", err, failed)
b.pendingState.RevertToSnapshot(snapshot)
if err != nil || failed {
@ -277,7 +313,7 @@ func (b *SimulatedBackend) EstimateGas(ctx context.Context, call ethereum.CallMs
// callContract implements common code between normal and pending contract calls.
// state is modified during execution, make sure to copy it if necessary.
func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallMsg, block *types.Block, statedb *state.StateDB) ([]byte, uint64, bool, error) {
func (b *SimulatedBackend) callContract(ctx context.Context, call XDPoSChain.CallMsg, block *types.Block, statedb *state.StateDB) ([]byte, uint64, bool, error) {
// Ensure message is initialized properly.
if call.GasPrice == nil {
call.GasPrice = big.NewInt(1)
@ -302,7 +338,7 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
evmContext := core.NewEVMContext(msg, block.Header(), b.blockchain, nil)
// Create a new environment which holds all relevant information
// about the transaction and calling mechanisms.
vmenv := vm.NewEVM(evmContext, statedb, b.config, vm.Config{})
vmenv := vm.NewEVM(evmContext, statedb, nil, b.config, vm.Config{})
gaspool := new(core.GasPool).AddGas(math.MaxUint64)
owner := common.Address{}
return core.NewStateTransition(vmenv, msg, gaspool).TransitionDb(owner)
@ -340,7 +376,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
// returning all the results in one batch.
//
// TODO(karalabe): Deprecate when the subscription one can return past data too.
func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error) {
func (b *SimulatedBackend) FilterLogs(ctx context.Context, query XDPoSChain.FilterQuery) ([]types.Log, error) {
// Initialize unset filter boundaried to run from genesis to chain head
from := int64(0)
if query.FromBlock != nil {
@ -366,7 +402,7 @@ func (b *SimulatedBackend) FilterLogs(ctx context.Context, query ethereum.Filter
// SubscribeFilterLogs creates a background log filtering operation, returning a
// subscription immediately, which can be used to stream the found events.
func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error) {
func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query XDPoSChain.FilterQuery, ch chan<- types.Log) (XDPoSChain.Subscription, error) {
// Subscribe to contract events
sink := make(chan []*types.Log)
@ -418,7 +454,7 @@ func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
// callmsg implements core.Message to allow passing it as a transaction simulator.
type callmsg struct {
ethereum.CallMsg
XDPoSChain.CallMsg
}
func (m callmsg) From() common.Address { return m.CallMsg.From }

View file

@ -22,12 +22,12 @@ import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/event"
"github.com/XinFinOrg/XDPoSChain"
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/event"
)
// SignerFn is a signer function callback when a contract requires a method to
@ -128,7 +128,7 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string,
return err
}
var (
msg = ethereum.CallMsg{From: opts.From, To: &c.address, Data: input, GasPrice: common.MinGasPrice, Gas: uint64(4200000)}
msg = XDPoSChain.CallMsg{From: opts.From, To: &c.address, Data: input, GasPrice: common.MinGasPrice, Gas: uint64(4200000)}
ctx = ensureContext(opts.Context)
code []byte
output []byte
@ -218,7 +218,7 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
}
}
// If the contract surely has code (or code is not needed), estimate the transaction
msg := ethereum.CallMsg{From: opts.From, To: contract, Value: value, Data: input}
msg := XDPoSChain.CallMsg{From: opts.From, To: contract, Value: value, Data: input}
gasLimit, err = c.transactor.EstimateGas(ensureContext(opts.Context), msg)
if err != nil {
return nil, fmt.Errorf("failed to estimate gas needed: %v", err)
@ -261,7 +261,7 @@ func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]int
// Start the background filtering
logs := make(chan types.Log, 128)
config := ethereum.FilterQuery{
config := XDPoSChain.FilterQuery{
Addresses: []common.Address{c.address},
Topics: topics,
FromBlock: new(big.Int).SetUint64(opts.Start),
@ -310,7 +310,7 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]inter
// Start the background filtering
logs := make(chan types.Log, 128)
config := ethereum.FilterQuery{
config := XDPoSChain.FilterQuery{
Addresses: []common.Address{c.address},
Topics: topics,
}

View file

@ -17,7 +17,7 @@
// Package bind generates Ethereum contract Go bindings.
//
// Detailed usage document and tutorial available on the go-ethereum Wiki page:
// https://github.com/ethereum/go-ethereum/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts
// https://github.com/XinFinOrg/XDPoSChain/wiki/Native-DApps:-Go-bindings-to-Ethereum-contracts
package bind
import (
@ -28,7 +28,7 @@ import (
"text/template"
"unicode"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
"golang.org/x/tools/imports"
)

View file

@ -26,7 +26,7 @@ import (
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/XinFinOrg/XDPoSChain/common"
"golang.org/x/tools/imports"
)

View file

@ -16,7 +16,7 @@
package bind
import "github.com/ethereum/go-ethereum/accounts/abi"
import "github.com/XinFinOrg/XDPoSChain/accounts/abi"
// tmplData is the data structure required to fill the binding template.
type tmplData struct {

View file

@ -22,9 +22,9 @@ import (
"math/big"
"reflect"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/crypto"
)
// makeTopics converts a filter query argument list into a filter topic set.

View file

@ -21,9 +21,9 @@ import (
"fmt"
"time"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/log"
)
// WaitMined waits for tx to be mined on the blockchain.

View file

@ -22,12 +22,12 @@ import (
"testing"
"time"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/accounts/abi/bind/backends"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind"
"github.com/XinFinOrg/XDPoSChain/accounts/abi/bind/backends"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto"
)
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")

View file

@ -20,8 +20,8 @@ import (
"fmt"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/crypto"
)
// Event is an event potentially triggered by the EVM's LOG mechanism. The Event

View file

@ -25,8 +25,8 @@ import (
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -244,7 +244,7 @@ func unpackTestEventData(dest interface{}, hexData string, jsonEvent []byte, ass
/*
Taken from
https://github.com/ethereum/go-ethereum/pull/15568
https://github.com/XinFinOrg/XDPoSChain/pull/15568
*/
type testResult struct {

View file

@ -20,7 +20,7 @@ import (
"fmt"
"strings"
"github.com/ethereum/go-ethereum/crypto"
"github.com/XinFinOrg/XDPoSChain/crypto"
)
// Method represents a callable given a `Name` and whether the method is a constant.

View file

@ -20,8 +20,8 @@ import (
"math/big"
"reflect"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/math"
)
var (

View file

@ -20,8 +20,8 @@ import (
"math/big"
"reflect"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/math"
)
// packBytesSlice packs the given bytes as [L, V] as the canonical representation

View file

@ -24,7 +24,7 @@ import (
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/XinFinOrg/XDPoSChain/common"
)
func TestPack(t *testing.T) {

View file

@ -21,8 +21,8 @@ import (
"reflect"
"testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/common"
)
// typeWithoutStringer is a alias for the Type type which simply doesn't implement

View file

@ -22,7 +22,7 @@ import (
"math/big"
"reflect"
"github.com/ethereum/go-ethereum/common"
"github.com/XinFinOrg/XDPoSChain/common"
)
// reads the integer based on its kind

View file

@ -26,7 +26,7 @@ import (
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/stretchr/testify/require"
)

View file

@ -20,10 +20,10 @@ package accounts
import (
"math/big"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
ethereum "github.com/XinFinOrg/XDPoSChain"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/event"
)
// Account represents an Ethereum account located at a specific location defined

View file

@ -27,10 +27,10 @@ import (
"sync"
"time"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
"gopkg.in/fatih/set.v0"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/log"
mapset "github.com/deckarep/golang-set"
)
// Minimum amount of time between cache reloads. This limit applies if the platform does
@ -79,7 +79,7 @@ func newAccountCache(keydir string) (*accountCache, chan struct{}) {
keydir: keydir,
byAddr: make(map[common.Address][]accounts.Account),
notify: make(chan struct{}, 1),
fileC: fileCache{all: set.NewNonTS()},
fileC: fileCache{all: mapset.NewThreadUnsafeSet()},
}
ac.watcher = newWatcher(ac)
return ac, ac.notify
@ -237,7 +237,7 @@ func (ac *accountCache) scanAccounts() error {
log.Debug("Failed to reload keystore contents", "err", err)
return err
}
if creates.Size() == 0 && deletes.Size() == 0 && updates.Size() == 0 {
if creates.Cardinality() == 0 && deletes.Cardinality() == 0 && updates.Cardinality() == 0 {
return nil
}
// Create a helper method to scan the contents of the key files
@ -272,15 +272,15 @@ func (ac *accountCache) scanAccounts() error {
// Process all the file diffs
start := time.Now()
for _, p := range creates.List() {
for _, p := range creates.ToSlice() {
if a := readAccount(p.(string)); a != nil {
ac.add(*a)
}
}
for _, p := range deletes.List() {
for _, p := range deletes.ToSlice() {
ac.deleteByFile(p.(string))
}
for _, p := range updates.List() {
for _, p := range updates.ToSlice() {
path := p.(string)
ac.deleteByFile(path)
if a := readAccount(path); a != nil {

View file

@ -27,10 +27,10 @@ import (
"testing"
"time"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/cespare/cp"
"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
)
var (

View file

@ -24,20 +24,20 @@ import (
"sync"
"time"
"github.com/ethereum/go-ethereum/log"
set "gopkg.in/fatih/set.v0"
"github.com/XinFinOrg/XDPoSChain/log"
mapset "github.com/deckarep/golang-set"
)
// fileCache is a cache of files seen during scan of keystore.
type fileCache struct {
all *set.SetNonTS // Set of all files from the keystore folder
lastMod time.Time // Last time instance when a file was modified
all mapset.Set // Set of all files from the keystore folder
lastMod time.Time // Last time instance when a file was modified
mu sync.RWMutex
}
// scan performs a new scan on the given directory, compares against the already
// cached filenames, and returns file sets: creates, deletes, updates.
func (fc *fileCache) scan(keyDir string) (set.Interface, set.Interface, set.Interface, error) {
func (fc *fileCache) scan(keyDir string) (mapset.Set, mapset.Set, mapset.Set, error) {
t0 := time.Now()
// List all the failes from the keystore folder
@ -51,8 +51,8 @@ func (fc *fileCache) scan(keyDir string) (set.Interface, set.Interface, set.Inte
defer fc.mu.Unlock()
// Iterate all the files and gather their metadata
all := set.NewNonTS()
mods := set.NewNonTS()
all := mapset.NewThreadUnsafeSet()
mods := mapset.NewThreadUnsafeSet()
var newLastMod time.Time
for _, fi := range files {
@ -76,9 +76,9 @@ func (fc *fileCache) scan(keyDir string) (set.Interface, set.Interface, set.Inte
t2 := time.Now()
// Update the tracked files and return the three sets
deletes := set.Difference(fc.all, all) // Deletes = previous - current
creates := set.Difference(all, fc.all) // Creates = current - previous
updates := set.Difference(mods, creates) // Updates = modified - creates
deletes := fc.all.Difference(all) // Deletes = previous - current
creates := all.Difference(fc.all) // Creates = current - previous
updates := mods.Difference(creates) // Updates = modified - creates
fc.all, fc.lastMod = all, newLastMod
t3 := time.Now()

View file

@ -29,9 +29,9 @@ import (
"strings"
"time"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/pborman/uuid"
)

View file

@ -33,11 +33,11 @@ import (
"sync"
"time"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/event"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/event"
)
var (

View file

@ -36,10 +36,10 @@ import (
"io/ioutil"
"path/filepath"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/math"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/crypto/randentropy"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/math"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/crypto/randentropy"
"github.com/pborman/uuid"
"golang.org/x/crypto/pbkdf2"
"golang.org/x/crypto/scrypt"

View file

@ -20,7 +20,7 @@ import (
"io/ioutil"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/XinFinOrg/XDPoSChain/common"
)
const (

View file

@ -22,7 +22,7 @@ import (
"os"
"path/filepath"
"github.com/ethereum/go-ethereum/common"
"github.com/XinFinOrg/XDPoSChain/common"
)
type keyStorePlain struct {

View file

@ -27,8 +27,8 @@ import (
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/crypto"
)
func tmpKeyStoreIface(t *testing.T, encrypted bool) (dir string, ks keyStore) {

View file

@ -26,9 +26,9 @@ import (
"testing"
"time"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/event"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/event"
)
var testSigData = make([]byte, 32)

View file

@ -19,9 +19,9 @@ package keystore
import (
"math/big"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/core/types"
ethereum "github.com/XinFinOrg/XDPoSChain"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/core/types"
)
// keystoreWallet implements the accounts.Wallet interface for the original

View file

@ -25,8 +25,8 @@ import (
"errors"
"fmt"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/crypto"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/pborman/uuid"
"golang.org/x/crypto/pbkdf2"
)

View file

@ -21,7 +21,7 @@ package keystore
import (
"time"
"github.com/ethereum/go-ethereum/log"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/rjeczalik/notify"
)

View file

@ -21,7 +21,7 @@ import (
"sort"
"sync"
"github.com/ethereum/go-ethereum/event"
"github.com/XinFinOrg/XDPoSChain/event"
)
// Manager is an overarching account manager that can communicate with various

View file

@ -22,9 +22,9 @@ import (
"sync"
"time"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/event"
"github.com/ethereum/go-ethereum/log"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/event"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/karalabe/hid"
)

View file

@ -28,12 +28,12 @@ import (
"io"
"math/big"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/ethereum/go-ethereum/rlp"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/XinFinOrg/XDPoSChain/rlp"
)
// ledgerOpcode is an enumeration encoding the supported Ledger opcodes.

View file

@ -27,13 +27,13 @@ import (
"io"
"math/big"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/accounts/usbwallet/internal/trezor"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/common/hexutil"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
"github.com/golang/protobuf/proto"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/accounts/usbwallet/internal/trezor"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/common/hexutil"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/log"
)
// ErrTrezorPINNeeded is returned if opening the trezor requires a PIN code. In

View file

@ -25,11 +25,11 @@ import (
"sync"
"time"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/log"
ethereum "github.com/XinFinOrg/XDPoSChain"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/log"
"github.com/karalabe/hid"
)

View file

@ -28,7 +28,7 @@ import (
"testing"
"time"
"github.com/ethereum/go-ethereum/crypto/sha3"
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
)
const (

Some files were not shown because too many files have changed in this diff Show more