mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-19 13:21:37 +00:00
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:
parent
8daf35ff1c
commit
b5abbfed79
2118 changed files with 63833 additions and 717044 deletions
110
.travis.yml
Normal file
110
.travis.yml
Normal 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
|
||||
14
Dockerfile
14
Dockerfile
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -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 ./
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
34
Makefile
34
Makefile
|
|
@ -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)
|
||||
|
|
|
|||
10
README.md
10
README.md
|
|
@ -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
663
XDCx/XDCx.go
Normal 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
42
XDCx/api.go
Normal 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
783
XDCx/order_processor.go
Normal 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
|
||||
}
|
||||
574
XDCx/order_processor_test.go
Normal file
574
XDCx/order_processor_test.go
Normal 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
78
XDCx/token.go
Normal 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)
|
||||
}
|
||||
221
XDCx/tradingstate/XDCx_trie.go
Normal file
221
XDCx/tradingstate/XDCx_trie.go
Normal 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)
|
||||
}
|
||||
47
XDCx/tradingstate/XDCx_trie_test.go
Normal file
47
XDCx/tradingstate/XDCx_trie_test.go
Normal 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
220
XDCx/tradingstate/common.go
Normal 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())
|
||||
}
|
||||
43
XDCx/tradingstate/common_test.go
Normal file
43
XDCx/tradingstate/common_test.go
Normal 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)
|
||||
}
|
||||
}
|
||||
140
XDCx/tradingstate/database.go
Normal file
140
XDCx/tradingstate/database.go
Normal 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
384
XDCx/tradingstate/dump.go
Normal 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
|
||||
}
|
||||
14
XDCx/tradingstate/encoding.go
Normal file
14
XDCx/tradingstate/encoding.go
Normal 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)
|
||||
|
||||
}
|
||||
58
XDCx/tradingstate/epochpriceitem.go
Normal file
58
XDCx/tradingstate/epochpriceitem.go
Normal 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())
|
||||
}
|
||||
121
XDCx/tradingstate/journal.go
Normal file
121
XDCx/tradingstate/journal.go
Normal 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)
|
||||
}
|
||||
140
XDCx/tradingstate/managed_state.go
Normal file
140
XDCx/tradingstate/managed_state.go
Normal 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}
|
||||
}
|
||||
415
XDCx/tradingstate/orderitem.go
Normal file
415
XDCx/tradingstate/orderitem.go
Normal 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
|
||||
}
|
||||
352
XDCx/tradingstate/relayer_state.go
Normal file
352
XDCx/tradingstate/relayer_state.go
Normal 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)
|
||||
}
|
||||
165
XDCx/tradingstate/settle_balance.go
Normal file
165
XDCx/tradingstate/settle_balance.go
Normal 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
|
||||
}
|
||||
263
XDCx/tradingstate/settle_balance_test.go
Normal file
263
XDCx/tradingstate/settle_balance_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
215
XDCx/tradingstate/state_lendingbook.go
Normal file
215
XDCx/tradingstate/state_lendingbook.go
Normal 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
|
||||
}
|
||||
235
XDCx/tradingstate/state_liquidationprice.go
Normal file
235
XDCx/tradingstate/state_liquidationprice.go
Normal 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
|
||||
}
|
||||
75
XDCx/tradingstate/state_orderItem.go
Normal file
75
XDCx/tradingstate/state_orderItem.go
Normal 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
|
||||
}
|
||||
218
XDCx/tradingstate/state_orderList.go
Normal file
218
XDCx/tradingstate/state_orderList.go
Normal 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
|
||||
}
|
||||
805
XDCx/tradingstate/state_orderbook.go
Normal file
805
XDCx/tradingstate/state_orderbook.go
Normal 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
|
||||
}
|
||||
}
|
||||
726
XDCx/tradingstate/statedb.go
Normal file
726
XDCx/tradingstate/statedb.go
Normal 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
|
||||
}
|
||||
302
XDCx/tradingstate/statedb_test.go
Normal file
302
XDCx/tradingstate/statedb_test.go
Normal 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
143
XDCx/tradingstate/trade.go
Normal 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
59
XDCxDAO/interfaces.go
Normal 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
183
XDCxDAO/leveldb.go
Normal 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
935
XDCxDAO/mongodb.go
Normal 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
954
XDCxlending/XDCxlending.go
Normal 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
37
XDCxlending/api.go
Normal 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
|
||||
}
|
||||
216
XDCxlending/lendingstate/XDCx_trie.go
Normal file
216
XDCxlending/lendingstate/XDCx_trie.go
Normal 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)
|
||||
}
|
||||
251
XDCxlending/lendingstate/common.go
Normal file
251
XDCxlending/lendingstate/common.go
Normal 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())
|
||||
}
|
||||
139
XDCxlending/lendingstate/database.go
Normal file
139
XDCxlending/lendingstate/database.go
Normal 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
|
||||
}
|
||||
417
XDCxlending/lendingstate/dump.go
Normal file
417
XDCxlending/lendingstate/dump.go
Normal 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
|
||||
}
|
||||
138
XDCxlending/lendingstate/journal.go
Normal file
138
XDCxlending/lendingstate/journal.go
Normal 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)
|
||||
}
|
||||
314
XDCxlending/lendingstate/lendingcontract.go
Normal file
314
XDCxlending/lendingstate/lendingcontract.go
Normal 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
|
||||
}
|
||||
464
XDCxlending/lendingstate/lendingitem.go
Normal file
464
XDCxlending/lendingstate/lendingitem.go
Normal 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
|
||||
}
|
||||
575
XDCxlending/lendingstate/lendingitem_test.go
Normal file
575
XDCxlending/lendingstate/lendingitem_test.go
Normal 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))
|
||||
|
||||
}
|
||||
140
XDCxlending/lendingstate/managed_state.go
Normal file
140
XDCxlending/lendingstate/managed_state.go
Normal 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}
|
||||
}
|
||||
304
XDCxlending/lendingstate/relayer.go
Normal file
304
XDCxlending/lendingstate/relayer.go
Normal 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)
|
||||
}
|
||||
256
XDCxlending/lendingstate/settle_balance.go
Normal file
256
XDCxlending/lendingstate/settle_balance.go
Normal 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
|
||||
}
|
||||
461
XDCxlending/lendingstate/settle_balance_test.go
Normal file
461
XDCxlending/lendingstate/settle_balance_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
193
XDCxlending/lendingstate/state_itemList.go
Normal file
193
XDCxlending/lendingstate/state_itemList.go
Normal 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
|
||||
}
|
||||
786
XDCxlending/lendingstate/state_lendingbook.go
Normal file
786
XDCxlending/lendingstate/state_lendingbook.go
Normal 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
|
||||
}
|
||||
67
XDCxlending/lendingstate/state_lendingitem.go
Normal file
67
XDCxlending/lendingstate/state_lendingitem.go
Normal 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
|
||||
}
|
||||
78
XDCxlending/lendingstate/state_lendingtrade.go
Normal file
78
XDCxlending/lendingstate/state_lendingtrade.go
Normal 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
|
||||
}
|
||||
}
|
||||
212
XDCxlending/lendingstate/state_liquidationtime.go
Normal file
212
XDCxlending/lendingstate/state_liquidationtime.go
Normal 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
|
||||
}
|
||||
668
XDCxlending/lendingstate/statedb.go
Normal file
668
XDCxlending/lendingstate/statedb.go
Normal 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
|
||||
}
|
||||
260
XDCxlending/lendingstate/statedb_test.go
Normal file
260
XDCxlending/lendingstate/statedb_test.go
Normal 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()
|
||||
}
|
||||
190
XDCxlending/lendingstate/trade.go
Normal file
190
XDCxlending/lendingstate/trade.go
Normal 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))
|
||||
}
|
||||
1216
XDCxlending/order_processor.go
Normal file
1216
XDCxlending/order_processor.go
Normal file
File diff suppressed because it is too large
Load diff
541
XDCxlending/order_processor_test.go
Normal file
541
XDCxlending/order_processor_test.go
Normal 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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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 = `
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"golang.org/x/tools/imports"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -24,7 +24,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
)
|
||||
|
||||
func TestPack(t *testing.T) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -26,7 +26,7 @@ import (
|
|||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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 (
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import (
|
|||
"io/ioutil"
|
||||
"testing"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
)
|
||||
|
||||
const (
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ import (
|
|||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
)
|
||||
|
||||
type keyStorePlain struct {
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ package keystore
|
|||
import (
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/rjeczalik/notify"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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"
|
||||
)
|
||||
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue