Merge pull request #138 from hash-laboratories-au/hashlab-master

Hashlab master
This commit is contained in:
Anil Chinchawale 2021-11-03 16:13:36 +05:30 committed by GitHub
commit 2869165165
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
2856 changed files with 136842 additions and 937206 deletions

1
.gitattributes vendored
View file

@ -1,3 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
*.sol linguist-language=Solidity

19
.github/CODEOWNERS vendored
View file

@ -1,13 +1,12 @@
# Lines starting with '#' are comments.
# Each line is a file pattern followed by one or more owners.
accounts/usbwallet @karalabe
accounts/abi @gballet
consensus @karalabe
core/ @karalabe @holiman
eth/ @karalabe
les/ @zsfelfoldi
light/ @zsfelfoldi
mobile/ @karalabe
p2p/ @fjl @zsfelfoldi
whisper/ @gballet @gluk256
accounts/usbwallet @karalabe
consensus @karalabe
core/ @karalabe @holiman
eth/ @karalabe
les/ @zsfelfoldi
light/ @zsfelfoldi
mobile/ @karalabe
p2p/ @fjl @zsfelfoldi
whisper/ @gballet @gluk256

View file

@ -1,40 +1,16 @@
# Contributing
Thank you for considering to help out with the source code! We welcome
contributions from anyone on the internet, and are grateful for even the
smallest of fixes!
If you'd like to contribute to go-ethereum, please fork, fix, commit and send a
pull request for the maintainers to review and merge into the main code base. If
you wish to submit more complex changes though, please check up with the core
devs first on [our gitter channel](https://gitter.im/ethereum/go-ethereum) to
ensure those changes are in line with the general philosophy of the project
and/or get some early feedback which can make both your efforts much lighter as
well as our review and merge procedures quick and simple.
## Coding guidelines
Please make sure your contributions adhere to our coding guidelines:
* Code must adhere to the official Go
[formatting](https://golang.org/doc/effective_go.html#formatting) guidelines
(i.e. uses [gofmt](https://golang.org/cmd/gofmt/)).
* Code must be documented adhering to the official Go
[commentary](https://golang.org/doc/effective_go.html#commentary) guidelines.
* Pull requests need to be based on and opened against the `master` branch.
* Commit messages should be prefixed with the package(s) they modify.
* E.g. "eth, rpc: make trace configs optional"
## Can I have feature X
Before you submit a feature request, please check and make sure that it isn't
possible through some other means. The JavaScript-enabled console is a powerful
feature in the right hands. Please check our
[Wiki page](https://github.com/ethereum/go-ethereum/wiki) for more info
Before you do a feature request please check and make sure that it isn't possible
through some other means. The JavaScript enabled console is a powerful feature
in the right hands. Please check our [Wiki page](https://github.com/ethereum/go-ethereum/wiki) for more info
and help.
## Configuration, dependencies, and tests
## Contributing
Please see the [Developers' Guide](https://github.com/ethereum/go-ethereum/wiki/Developers'-Guide)
for more details on configuring your environment, managing project dependencies
and testing procedures.
If you'd like to contribute to go-ethereum please fork, fix, commit and
send a pull request. Commits which do not comply with the coding standards
are ignored (use gofmt!).
See [Developers' Guide](https://github.com/ethereum/go-ethereum/wiki/Developers'-Guide)
for more details on configuring your environment, testing, and
dependency management.

View file

@ -1,11 +1,11 @@
# Number of days of inactivity before an Issue is closed for lack of response
daysUntilClose: 30
# Label requiring a response
responseRequiredLabel: "need:more-information"
responseRequiredLabel: more-information-needed
# Comment to post when closing an Issue for lack of response. Set to `false` to disable
closeComment: >
This issue has been automatically closed because there has been no response
to our request for more information from the original author. With only the
information that is currently in the issue, we don't have enough information
to take action. Please reach out if you have more relevant information or
answers to our questions so that we can investigate further.
to take action. Please reach out if you have or find the answers we need so
that we can investigate further.

2
.github/stale.yml vendored
View file

@ -7,7 +7,7 @@ exemptLabels:
- pinned
- security
# Label to use when marking an issue as stale
staleLabel: "status:inactive"
staleLabel: stale
# Comment to post when marking an issue as stale. Set to `false` to disable
markComment: >
This issue has been automatically marked as stale because it has not had

6
.gitignore vendored
View file

@ -14,7 +14,7 @@
*/**/*tx_database*
*/**/*dapps*
build/_vendor/pkg
/devnet
#*
.#*
*#
@ -48,5 +48,5 @@ profile.cov
/dashboard/assets/package-lock.json
**/yarn-error.log
/build/cache/
coverage.txt
go.sum

View file

@ -1,52 +0,0 @@
# This file configures github.com/golangci/golangci-lint.
run:
timeout: 3m
tests: true
# default is true. Enables skipping of directories:
# vendor$, third_party$, testdata$, examples$, Godeps$, builtin$
skip-dirs-use-default: true
skip-files:
- core/genesis_alloc.go
- cmd/swarm/fs_test.go
# TODO: Too many errors, turn on one by one
linters:
disable-all: true
enable:
- goimports
- typecheck
# - deadcode
# - goconst
# - govet
# - ineffassign
# - misspell
# - staticcheck
# - unconvert
# - unused
# - varcheck
linters-settings:
gofmt:
simplify: true
goconst:
min-len: 3 # minimum length of string constant
min-occurrences: 6 # minimum number of occurrences
issues:
exclude-rules:
- path: crypto/blake2b/
linters:
- deadcode
- path: crypto/bn256/cloudflare
linters:
- deadcode
- path: p2p/discv5/
linters:
- deadcode
- path: core/vm/instructions_test.go
linters:
- goconst
- path: cmd/faucet/
linters:
- deadcode

123
.mailmap
View file

@ -1,123 +0,0 @@
Jeffrey Wilcke <jeffrey@ethereum.org>
Jeffrey Wilcke <jeffrey@ethereum.org> <geffobscura@gmail.com>
Jeffrey Wilcke <jeffrey@ethereum.org> <obscuren@obscura.com>
Jeffrey Wilcke <jeffrey@ethereum.org> <obscuren@users.noreply.github.com>
Viktor Trón <viktor.tron@gmail.com>
Joseph Goulden <joegoulden@gmail.com>
Nick Savers <nicksavers@gmail.com>
Maran Hidskes <maran.hidskes@gmail.com>
Taylor Gerring <taylor.gerring@gmail.com>
Taylor Gerring <taylor.gerring@gmail.com> <taylor.gerring@ethereum.org>
Bas van Kervel <bas@ethdev.com>
Bas van Kervel <bas@ethdev.com> <basvankervel@ziggo.nl>
Bas van Kervel <bas@ethdev.com> <basvankervel@gmail.com>
Bas van Kervel <bas@ethdev.com> <bas-vk@users.noreply.github.com>
Sven Ehlert <sven@ethdev.com>
Vitalik Buterin <v@buterin.com>
Marian Oancea <contact@siteshop.ro>
Christoph Jentzsch <jentzsch.software@gmail.com>
Heiko Hees <heiko@heiko.org>
Alex Leverington <alex@ethdev.com>
Alex Leverington <alex@ethdev.com> <subtly@users.noreply.github.com>
Zsolt Felföldi <zsfelfoldi@gmail.com>
Gavin Wood <i@gavwood.com>
Martin Becze <mjbecze@gmail.com>
Martin Becze <mjbecze@gmail.com> <wanderer@users.noreply.github.com>
Dimitry Khokhlov <winsvega@mail.ru>
Roman Mandeleil <roman.mandeleil@gmail.com>
Alec Perseghin <aperseghin@gmail.com>
Alon Muroch <alonmuroch@gmail.com>
Arkadiy Paronyan <arkadiy@ethdev.com>
Jae Kwon <jkwon.work@gmail.com>
Aaron Kumavis <kumavis@users.noreply.github.com>
Nick Dodson <silentcicero@outlook.com>
Jason Carver <jacarver@linkedin.com>
Jason Carver <jacarver@linkedin.com> <ut96caarrs@snkmail.com>
Joseph Chow <ethereum@outlook.com>
Joseph Chow <ethereum@outlook.com> ethers <TODO>
Enrique Fynn <enriquefynn@gmail.com>
Vincent G <caktux@gmail.com>
RJ Catalano <catalanor0220@gmail.com>
RJ Catalano <catalanor0220@gmail.com> <rj@erisindustries.com>
Nchinda Nchinda <nchinda2@gmail.com>
Aron Fischer <github@aron.guru> <homotopycolimit@users.noreply.github.com>
Vlad Gluhovsky <gluk256@users.noreply.github.com>
Ville Sundell <github@solarius.fi>
Elliot Shepherd <elliot@identitii.com>
Yohann Léon <sybiload@gmail.com>
Gregg Dourgarian <greggd@tempworks.com>
Casey Detrio <cdetrio@gmail.com>
Jens Agerberg <github@agerberg.me>
Nick Johnson <arachnid@notdot.net>
Henning Diedrich <hd@eonblast.com>
Henning Diedrich <hd@eonblast.com> Drake Burroughs <wildfyre@hotmail.com>
Felix Lange <fjl@twurst.com>
Felix Lange <fjl@twurst.com> <fjl@users.noreply.github.com>
Максим Чусовлянов <mchusovlianov@gmail.com>
Louis Holbrook <dev@holbrook.no>
Louis Holbrook <dev@holbrook.no> <nolash@users.noreply.github.com>
Thomas Bocek <tom@tomp2p.net>
Victor Tran <vu.tran54@gmail.com>
Justin Drake <drakefjustin@gmail.com>
Frank Wang <eternnoir@gmail.com>
Gary Rong <garyrong0905@gmail.com>
Guillaume Nicolas <guin56@gmail.com>
Sorin Neacsu <sorin.neacsu@gmail.com>
Sorin Neacsu <sorin.neacsu@gmail.com> <sorin@users.noreply.github.com>
Valentin Wüstholz <wuestholz@gmail.com>
Valentin Wüstholz <wuestholz@gmail.com> <wuestholz@users.noreply.github.com>
Armin Braun <me@obrown.io>
Ernesto del Toro <ernesto.deltoro@gmail.com>
Ernesto del Toro <ernesto.deltoro@gmail.com> <ernestodeltoro@users.noreply.github.com>

View file

@ -1,81 +1,151 @@
sudo: required
language: go
go_import_path: github.com/ethereum/go-ethereum
sudo: false
go_import_path: github.com/XinFinOrg/XDPoSChain
env:
global:
- GOPROXY=https://proxy.golang.org
- GO111MODULE=on
jobs:
include:
# This builder only tests code linters on latest version of Go
- stage: lint
os: linux
dist: bionic
go: 1.14.x
env:
- lint
git:
submodules: false # avoid cloning ethereum/tests
script:
- go run build/ci.go lint
# TODO: temporary turn off linting to help fix all the tests. We will turn it back on once the branch is stable
# - stage: Lint
# sudo: false
# go: '1.14.x'
# git:
# submodules: false
# script:
# - go run build/ci.go lint
- stage: Tests
os: linux
dist: bionic
go: 1.14.x
env:
- GO111MODULE=auto
name: A-B tests
script: travis_retry go run build/ci.go test -coverage $(go list ./... | grep "github.com\/ethereum\/go-ethereum\/[a-b].*")
- script: travis_retry go run build/ci.go test -coverage $(go list ./... | grep "github.com\/ethereum\/go-ethereum\/c[a-m].*")
os: linux
dist: bionic
go: 1.14.x
env:
- GO111MODULE=auto
name: C-[a-m] tests
- script: travis_retry go run build/ci.go test -coverage $(go list ./... | grep "github.com\/ethereum\/go-ethereum\/c[n-o].*")
os: linux
dist: bionic
go: 1.14.x
env:
- GO111MODULE=auto
name: C-[n-o] tests
- script: travis_retry go run build/ci.go test -coverage $(go list ./... | grep "github.com\/ethereum\/go-ethereum\/c[p-z].*")
os: linux
dist: bionic
go: 1.14.x
env:
- GO111MODULE=auto
name: C-[p-z] tests
- script: travis_retry go run build/ci.go test -coverage $(go list ./... | grep "github.com\/ethereum\/go-ethereum\/[d-i].*")
os: linux
dist: bionic
go: 1.14.x
env:
- GO111MODULE=auto
name: D-I tests
- script: travis_retry go run build/ci.go test -coverage $(go list ./... | grep "github.com\/ethereum\/go-ethereum\/[j-n].*")
os: linux
dist: bionic
go: 1.14.x
env:
- GO111MODULE=auto
name: J-N tests
- script: travis_retry go run build/ci.go test -coverage $(go list ./... | grep "github.com\/ethereum\/go-ethereum\/[o-r].*")
os: linux
dist: bionic
go: 1.14.x
env:
- GO111MODULE=auto
name: O-R tests
- script: travis_retry go run build/ci.go test -v -coverage $(go list ./... | grep "github.com\/ethereum\/go-ethereum\/s.*")
os: linux
dist: bionic
go: 1.14.x
env:
- GO111MODULE=auto
name: S tests
- script: travis_retry go run build/ci.go test -coverage $(go list ./... | grep "github.com\/ethereum\/go-ethereum\/[t-z].*")
os: linux
dist: bionic
go: 1.14.x
env:
- GO111MODULE=auto
name: T-Z tests
- stage: Tests
os: linux
dist: bionic
go: 1.14.x
env:
- GO111MODULE=auto
name: A-B tests
script: travis_retry go run build/ci.go test -coverage $(go list ./... | grep "github.com\/XinFinOrg\/XDPoSChain\/[a-b].*")
- script: travis_retry go run build/ci.go test -coverage $(go list ./... | grep "github.com\/XinFinOrg\/XDPoSChain\/c[a-m].*")
os: linux
dist: bionic
go: 1.14.x
env:
- GO111MODULE=auto
name: C-[a-m] tests
- script: travis_retry go run build/ci.go test -coverage $(go list ./... | grep "github.com\/XinFinOrg\/XDPoSChain\/c[n-o].*")
os: linux
dist: bionic
go: 1.14.x
env:
- GO111MODULE=auto
name: C-[n-o] tests
- script: travis_retry go run build/ci.go test -coverage $(go list ./... | grep "github.com\/XinFinOrg\/XDPoSChain\/c[p-z].*")
os: linux
dist: bionic
go: 1.14.x
env:
- GO111MODULE=auto
name: C-[p-z] tests
- script: travis_retry go run build/ci.go test -coverage $(go list ./... | grep "github.com\/XinFinOrg\/XDPoSChain\/[d-i].*")
os: linux
dist: bionic
go: 1.14.x
env:
- GO111MODULE=auto
name: D-I tests
- script: travis_retry go run build/ci.go test -coverage $(go list ./... | grep "github.com\/XinFinOrg\/XDPoSChain\/[j-n].*")
os: linux
dist: bionic
go: 1.14.x
env:
- GO111MODULE=auto
name: J-N tests
- script: travis_retry go run build/ci.go test -coverage $(go list ./... | grep "github.com\/XinFinOrg\/XDPoSChain\/[o-r].*")
os: linux
dist: bionic
go: 1.14.x
env:
- GO111MODULE=auto
name: O-R tests
- script: travis_retry go run build/ci.go test -coverage $(go list ./... | grep "github.com\/XinFinOrg\/XDPoSChain\/s.*")
os: linux
dist: bionic
go: 1.14.x
env:
- GO111MODULE=auto
name: S tests
- script: travis_retry go run build/ci.go test -coverage $(go list ./... | grep "github.com\/XinFinOrg\/XDPoSChain\/[t-z].*")
os: linux
dist: bionic
go: 1.14.x
env:
- GO111MODULE=auto
name: T-Z tests
- stage: Github release
go: '1.14.x'
script:
- GOARCH=amd64 GOOS=linux go build -o ./build/bin/XDC-linux-amd64 ./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 XinFinOrg/XDPoSChain .
- docker build -t XinFinOrg/node -f Dockerfile.node .
script:
- echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin
- docker tag XinFinOrg/XDPoSChain XinFinOrg/XDPoSChain:latest
- docker push XinFinOrg/XDPoSChain:latest
- docker tag XinFinOrg/XDPoSChain XinFinOrg/XDPoSChain:$TRAVIS_BUILD_ID
- docker push XinFinOrg/XDPoSChain:$TRAVIS_BUILD_ID
- docker tag XinFinOrg/node XinFinOrg/node:latest
- docker push XinFinOrg/node:latest
- docker tag XinFinOrg/node XinFinOrg/node:$TRAVIS_BUILD_ID
- docker push XinFinOrg/node:$TRAVIS_BUILD_ID
- stage: Build and push image (tagged)
services:
- docker
install: skip
before_script:
- docker build -t XinFinOrg/XDPoSChain .
- docker build -t XinFinOrg/XDPoSChain -f Dockerfile.node .
script:
- echo "$DOCKER_PASSWORD" | docker login --username "$DOCKER_USERNAME" --password-stdin
- docker tag XinFinOrg/XDPoSChain XinFinOrg/XDPoSChain:latest
- docker push XinFinOrg/XDPoSChain:latest
- docker tag XinFinOrg/XDPoSChain XinFinOrg/XDPoSChain:$TRAVIS_TAG
- docker push XinFinOrg/XDPoSChain:$TRAVIS_TAG
- docker tag XinFinOrg/XDPoSChain XinFinOrg/node:latest
- docker push XinFinOrg/node:latest
- docker tag XinFinOrg/node XinFinOrg/node:$TRAVIS_TAG
- docker push XinFinOrg/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 = XinFinOrg/XDPoSChain
- name: Build and push image
if: type != pull_request AND branch = master AND tag IS blank AND repo = XinFinOrg/XDPoSChain
- name: Build and push image (tagged)
if: type != pull_request AND branch =~ ^v AND tag IS present AND repo = XinFinOrg/XDPoSChain
notifications:
slack:
rooms:
secure:
on_success: change
on_failure: always

174
AUTHORS
View file

@ -1,174 +0,0 @@
# This is the official list of go-ethereum authors for copyright purposes.
Afri Schoedon <5chdn@users.noreply.github.com>
Agustin Armellini Fischer <armellini13@gmail.com>
Airead <fgh1987168@gmail.com>
Alan Chen <alanchchen@users.noreply.github.com>
Alejandro Isaza <alejandro.isaza@gmail.com>
Ales Katona <ales@coinbase.com>
Alex Leverington <alex@ethdev.com>
Alex Wu <wuyiding@gmail.com>
Alexandre Van de Sande <alex.vandesande@ethdev.com>
Ali Hajimirza <Ali92hm@users.noreply.github.com>
Anton Evangelatov <anton.evangelatov@gmail.com>
Arba Sasmoyo <arba.sasmoyo@gmail.com>
Armani Ferrante <armaniferrante@berkeley.edu>
Armin Braun <me@obrown.io>
Aron Fischer <github@aron.guru>
Bas van Kervel <bas@ethdev.com>
Benjamin Brent <benjamin@benjaminbrent.com>
Benoit Verkindt <benoit.verkindt@gmail.com>
Bo <bohende@gmail.com>
Bo Ye <boy.e.computer.1982@outlook.com>
Bob Glickstein <bobg@users.noreply.github.com>
Brian Schroeder <bts@gmail.com>
Casey Detrio <cdetrio@gmail.com>
Chase Wright <mysticryuujin@gmail.com>
Christoph Jentzsch <jentzsch.software@gmail.com>
Daniel A. Nagy <nagy.da@gmail.com>
Daniel Sloof <goapsychadelic@gmail.com>
Darrel Herbst <dherbst@gmail.com>
Dave Appleton <calistralabs@gmail.com>
Diego Siqueira <DiSiqueira@users.noreply.github.com>
Dmitry Shulyak <yashulyak@gmail.com>
Egon Elbre <egonelbre@gmail.com>
Elias Naur <elias.naur@gmail.com>
Elliot Shepherd <elliot@identitii.com>
Enrique Fynn <enriquefynn@gmail.com>
Ernesto del Toro <ernesto.deltoro@gmail.com>
Ethan Buchman <ethan@coinculture.info>
Eugene Valeyev <evgen.povt@gmail.com>
Evangelos Pappas <epappas@evalonlabs.com>
Evgeny Danilenko <6655321@bk.ru>
Fabian Vogelsteller <fabian@frozeman.de>
Fabio Barone <fabio.barone.co@gmail.com>
Fabio Berger <fabioberger1991@gmail.com>
FaceHo <facehoshi@gmail.com>
Felix Lange <fjl@twurst.com>
Fiisio <liangcszzu@163.com>
Frank Wang <eternnoir@gmail.com>
Furkan KAMACI <furkankamaci@gmail.com>
Gary Rong <garyrong0905@gmail.com>
George Ornbo <george@shapeshed.com>
Gregg Dourgarian <greggd@tempworks.com>
Guillaume Ballet <gballet@gmail.com>
Guillaume Nicolas <guin56@gmail.com>
Gustav Simonsson <gustav.simonsson@gmail.com>
Hao Bryan Cheng <haobcheng@gmail.com>
Henning Diedrich <hd@eonblast.com>
Isidoro Ghezzi <isidoro.ghezzi@icloud.com>
Ivan Daniluk <ivan.daniluk@gmail.com>
Jae Kwon <jkwon.work@gmail.com>
Jamie Pitts <james.pitts@gmail.com>
Janoš Guljaš <janos@users.noreply.github.com>
Jason Carver <jacarver@linkedin.com>
Jay Guo <guojiannan1101@gmail.com>
Jeff R. Allen <jra@nella.org>
Jeffrey Wilcke <jeffrey@ethereum.org>
Jens Agerberg <github@agerberg.me>
Jia Chenhui <jiachenhui1989@gmail.com>
Jim McDonald <Jim@mcdee.net>
Joel Burget <joelburget@gmail.com>
Jonathan Brown <jbrown@bluedroplet.com>
Joseph Chow <ethereum@outlook.com>
Justin Clark-Casey <justincc@justincc.org>
Justin Drake <drakefjustin@gmail.com>
Kenji Siu <kenji@isuntv.com>
Kobi Gurkan <kobigurk@gmail.com>
Konrad Feldmeier <konrad@brainbot.com>
Kurkó Mihály <kurkomisi@users.noreply.github.com>
Kyuntae Ethan Kim <ethan.kyuntae.kim@gmail.com>
Lefteris Karapetsas <lefteris@refu.co>
Leif Jurvetson <leijurv@gmail.com>
Leo Shklovskii <leo@thermopylae.net>
Lewis Marshall <lewis@lmars.net>
Lio李欧 <lionello@users.noreply.github.com>
Louis Holbrook <dev@holbrook.no>
Luca Zeug <luclu@users.noreply.github.com>
Magicking <s@6120.eu>
Maran Hidskes <maran.hidskes@gmail.com>
Marek Kotewicz <marek.kotewicz@gmail.com>
Mark <markya0616@gmail.com>
Martin Holst Swende <martin@swende.se>
Matthew Di Ferrante <mattdf@users.noreply.github.com>
Matthew Wampler-Doty <matthew.wampler.doty@gmail.com>
Maximilian Meister <mmeister@suse.de>
Micah Zoltu <micah@zoltu.net>
Michael Ruminer <michael.ruminer+github@gmail.com>
Miguel Mota <miguelmota2@gmail.com>
Miya Chen <miyatlchen@gmail.com>
Nchinda Nchinda <nchinda2@gmail.com>
Nick Dodson <silentcicero@outlook.com>
Nick Johnson <arachnid@notdot.net>
Nicolas Guillaume <gunicolas@sqli.com>
Noman <noman@noman.land>
Oli Bye <olibye@users.noreply.github.com>
Paul Litvak <litvakpol@012.net.il>
Paulo L F Casaretto <pcasaretto@gmail.com>
Paweł Bylica <chfast@gmail.com>
Peter Pratscher <pratscher@gmail.com>
Petr Mikusek <petr@mikusek.info>
Péter Szilágyi <peterke@gmail.com>
RJ Catalano <catalanor0220@gmail.com>
Ramesh Nair <ram@hiddentao.com>
Ricardo Catalinas Jiménez <r@untroubled.be>
Ricardo Domingos <ricardohsd@gmail.com>
Richard Hart <richardhart92@gmail.com>
Rob <robert@rojotek.com>
Robert Zaremba <robert.zaremba@scale-it.pl>
Russ Cox <rsc@golang.org>
Rémy Roy <remyroy@remyroy.com>
S. Matthew English <s-matthew-english@users.noreply.github.com>
Shintaro Kaneko <kaneshin0120@gmail.com>
Sorin Neacsu <sorin.neacsu@gmail.com>
Stein Dekker <dekker.stein@gmail.com>
Steve Waldman <swaldman@mchange.com>
Steven Roose <stevenroose@gmail.com>
Taylor Gerring <taylor.gerring@gmail.com>
Thomas Bocek <tom@tomp2p.net>
Ti Zhou <tizhou1986@gmail.com>
Tosh Camille <tochecamille@gmail.com>
Valentin Wüstholz <wuestholz@gmail.com>
Victor Farazdagi <simple.square@gmail.com>
Victor Tran <vu.tran54@gmail.com>
Viktor Trón <viktor.tron@gmail.com>
Ville Sundell <github@solarius.fi>
Vincent G <caktux@gmail.com>
Vitalik Buterin <v@buterin.com>
Vitaly V <vvelikodny@gmail.com>
Vivek Anand <vivekanand1101@users.noreply.github.com>
Vlad Gluhovsky <gluk256@users.noreply.github.com>
Yohann Léon <sybiload@gmail.com>
Yoichi Hirai <i@yoichihirai.com>
Yondon Fu <yondon.fu@gmail.com>
Zach <zach.ramsay@gmail.com>
Zahoor Mohamed <zahoor@zahoor.in>
Zoe Nolan <github@zoenolan.org>
Zsolt Felföldi <zsfelfoldi@gmail.com>
am2rican5 <am2rican5@gmail.com>
ayeowch <ayeowch@gmail.com>
b00ris <b00ris@mail.ru>
bailantaotao <Edwin@maicoin.com>
baizhenxuan <nkbai@163.com>
bloonfield <bloonfield@163.com>
changhong <changhong.yu@shanbay.com>
evgk <evgeniy.kamyshev@gmail.com>
ferhat elmas <elmas.ferhat@gmail.com>
holisticode <holistic.computing@gmail.com>
jtakalai <juuso.takalainen@streamr.com>
ken10100147 <sunhongping@kanjian.com>
ligi <ligi@ligi.de>
mark.lin <mark@maicoin.com>
necaremus <necaremus@gmail.com>
njupt-moon <1015041018@njupt.edu.cn>
nkbai <nkbai@163.com>
rhaps107 <dod-source@yandex.ru>
slumber1122 <slumber1122@gmail.com>
sunxiaojun2014 <sunxiaojun-xy@360.cn>
terasum <terasum@163.com>
tsarpaul <Litvakpol@012.net.il>
xiekeyang <xiekeyang@users.noreply.github.com>
yoza <yoza.is12s@gmail.com>
ΞTHΞЯSPHΞЯΞ <{viktor.tron,nagydani,zsfelfoldi}@gmail.com>
Максим Чусовлянов <mchusovlianov@gmail.com>
Ralph Caraveo <deckarep@gmail.com>

View file

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

View file

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

View file

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

View file

@ -4,48 +4,44 @@
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
lint: ## Run linters.
build/env.sh go run build/ci.go lint
go run build/ci.go test
clean:
./build/clean_go_build_cache.sh
rm -fr build/_workspace/pkg/ $(GOBIN)/*
# Cross Compilation Targets (xgo)
XDC-cross: XDC-linux XDC-darwin
XDC-cross: XDC-windows-amd64 XDC-darwin-amd64 XDC-linux
@echo "Full cross compilation done:"
@ls -ld $(GOBIN)/XDC-*
@ -54,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
@ -88,15 +84,19 @@ 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:
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)
$(GIT) checkout vendor

View file

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

663
XDCx/XDCx.go Normal file
View file

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

42
XDCx/api.go Normal file
View file

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

783
XDCx/order_processor.go Normal file
View file

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

View file

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

78
XDCx/token.go Normal file
View file

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

View file

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

View file

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

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

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

View file

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

View file

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

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

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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

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

59
XDCxDAO/interfaces.go Normal file
View file

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

183
XDCxDAO/leveldb.go Normal file
View file

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

935
XDCxDAO/mongodb.go Normal file
View file

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

955
XDCxlending/XDCxlending.go Normal file
View file

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

37
XDCxlending/api.go Normal file
View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

File diff suppressed because it is too large Load diff

View file

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

View file

@ -58,11 +58,13 @@ func (abi ABI) Pack(name string, args ...interface{}) ([]byte, error) {
return nil, err
}
return arguments, nil
}
method, exist := abi.Methods[name]
if !exist {
return nil, fmt.Errorf("method '%s' not found", name)
}
arguments, err := method.Inputs.Pack(args...)
if err != nil {
return nil, err
@ -80,7 +82,7 @@ func (abi ABI) Unpack(v interface{}, name string, output []byte) (err error) {
// we need to decide whether we're calling a method or an event
if method, ok := abi.Methods[name]; ok {
if len(output)%32 != 0 {
return fmt.Errorf("abi: improperly formatted output: %s - Bytes: [%+v]", string(output), output)
return fmt.Errorf("abi: improperly formatted output")
}
return method.Outputs.Unpack(v, output)
} else if event, ok := abi.Events[name]; ok {
@ -135,9 +137,6 @@ func (abi *ABI) UnmarshalJSON(data []byte) error {
// MethodById looks up a method by the 4-byte id
// returns nil if none found
func (abi *ABI) MethodById(sigdata []byte) (*Method, error) {
if len(sigdata) < 4 {
return nil, fmt.Errorf("data too short (% bytes) for abi method lookup", len(sigdata))
}
for _, method := range abi.Methods {
if bytes.Equal(method.Id(), sigdata[:4]) {
return &method, nil

View file

@ -22,12 +22,13 @@ import (
"fmt"
"log"
"math/big"
"reflect"
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"reflect"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/crypto"
)
const jsondata = `
@ -51,14 +52,11 @@ const jsondata2 = `
{ "type" : "function", "name" : "slice", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint32[2]" } ] },
{ "type" : "function", "name" : "slice256", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "uint256[2]" } ] },
{ "type" : "function", "name" : "sliceAddress", "constant" : false, "inputs" : [ { "name" : "inputs", "type" : "address[]" } ] },
{ "type" : "function", "name" : "sliceMultiAddress", "constant" : false, "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] },
{ "type" : "function", "name" : "nestedArray", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint256[2][2]" }, { "name" : "b", "type" : "address[]" } ] },
{ "type" : "function", "name" : "nestedArray2", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint8[][2]" } ] },
{ "type" : "function", "name" : "nestedSlice", "constant" : false, "inputs" : [ { "name" : "a", "type" : "uint8[][]" } ] }
{ "type" : "function", "name" : "sliceMultiAddress", "constant" : false, "inputs" : [ { "name" : "a", "type" : "address[]" }, { "name" : "b", "type" : "address[]" } ] }
]`
func TestReader(t *testing.T) {
Uint256, _ := NewType("uint256", nil)
Uint256, _ := NewType("uint256")
exp := ABI{
Methods: map[string]Method{
"balance": {
@ -179,7 +177,7 @@ func TestTestSlice(t *testing.T) {
}
func TestMethodSignature(t *testing.T) {
String, _ := NewType("string", nil)
String, _ := NewType("string")
m := Method{"foo", false, []Argument{{"bar", String, false}, {"baz", String, false}}, nil}
exp := "foo(string,string)"
if m.Sig() != exp {
@ -191,31 +189,12 @@ func TestMethodSignature(t *testing.T) {
t.Errorf("expected ids to match %x != %x", m.Id(), idexp)
}
uintt, _ := NewType("uint256", nil)
uintt, _ := NewType("uint256")
m = Method{"foo", false, []Argument{{"bar", uintt, false}}, nil}
exp = "foo(uint256)"
if m.Sig() != exp {
t.Error("signature mismatch", exp, "!=", m.Sig())
}
// Method with tuple arguments
s, _ := NewType("tuple", []ArgumentMarshaling{
{Name: "a", Type: "int256"},
{Name: "b", Type: "int256[]"},
{Name: "c", Type: "tuple[]", Components: []ArgumentMarshaling{
{Name: "x", Type: "int256"},
{Name: "y", Type: "int256"},
}},
{Name: "d", Type: "tuple[2]", Components: []ArgumentMarshaling{
{Name: "x", Type: "int256"},
{Name: "y", Type: "int256"},
}},
})
m = Method{"foo", false, []Argument{{"s", s, false}, {"bar", String, false}}, nil}
exp = "foo((int256,int256[],(int256,int256)[],(int256,int256)[2]),string)"
if m.Sig() != exp {
t.Error("signature mismatch", exp, "!=", m.Sig())
}
}
func TestMultiPack(t *testing.T) {
@ -585,13 +564,11 @@ func TestBareEvents(t *testing.T) {
const definition = `[
{ "type" : "event", "name" : "balance" },
{ "type" : "event", "name" : "anon", "anonymous" : true},
{ "type" : "event", "name" : "args", "inputs" : [{ "indexed":false, "name":"arg0", "type":"uint256" }, { "indexed":true, "name":"arg1", "type":"address" }] },
{ "type" : "event", "name" : "tuple", "inputs" : [{ "indexed":false, "name":"t", "type":"tuple", "components":[{"name":"a", "type":"uint256"}] }, { "indexed":true, "name":"arg1", "type":"address" }] }
{ "type" : "event", "name" : "args", "inputs" : [{ "indexed":false, "name":"arg0", "type":"uint256" }, { "indexed":true, "name":"arg1", "type":"address" }] }
]`
arg0, _ := NewType("uint256", nil)
arg1, _ := NewType("address", nil)
tuple, _ := NewType("tuple", []ArgumentMarshaling{{Name: "a", Type: "uint256"}})
arg0, _ := NewType("uint256")
arg1, _ := NewType("address")
expectedEvents := map[string]struct {
Anonymous bool
@ -603,10 +580,6 @@ func TestBareEvents(t *testing.T) {
{Name: "arg0", Type: arg0, Indexed: false},
{Name: "arg1", Type: arg1, Indexed: true},
}},
"tuple": {false, []Argument{
{Name: "t", Type: tuple, Indexed: false},
{Name: "arg1", Type: arg1, Indexed: true},
}},
}
abi, err := JSON(strings.NewReader(definition))
@ -673,24 +646,28 @@ func TestUnpackEvent(t *testing.T) {
}
type ReceivedEvent struct {
Sender common.Address
Amount *big.Int
Memo []byte
Address common.Address
Amount *big.Int
Memo []byte
}
var ev ReceivedEvent
err = abi.Unpack(&ev, "received", data)
if err != nil {
t.Error(err)
} else {
t.Logf("len(data): %d; received event: %+v", len(data), ev)
}
type ReceivedAddrEvent struct {
Sender common.Address
Address common.Address
}
var receivedAddrEv ReceivedAddrEvent
err = abi.Unpack(&receivedAddrEv, "receivedAddr", data)
if err != nil {
t.Error(err)
} else {
t.Logf("len(data): %d; received event: %+v", len(data), receivedAddrEv)
}
}
@ -734,14 +711,5 @@ func TestABI_MethodById(t *testing.T) {
t.Errorf("Method %v (id %v) not 'findable' by id in ABI", name, common.ToHex(m.Id()))
}
}
// Also test empty
if _, err := abi.MethodById([]byte{0x00}); err == nil {
t.Errorf("Expected error, too short to decode data")
}
if _, err := abi.MethodById([]byte{}); err == nil {
t.Errorf("Expected error, too short to decode data")
}
if _, err := abi.MethodById(nil); err == nil {
t.Errorf("Expected error, nil is short to decode data")
}
}

View file

@ -33,27 +33,24 @@ type Argument struct {
type Arguments []Argument
type ArgumentMarshaling struct {
Name string
Type string
Components []ArgumentMarshaling
Indexed bool
}
// UnmarshalJSON implements json.Unmarshaler interface
func (argument *Argument) UnmarshalJSON(data []byte) error {
var arg ArgumentMarshaling
err := json.Unmarshal(data, &arg)
var extarg struct {
Name string
Type string
Indexed bool
}
err := json.Unmarshal(data, &extarg)
if err != nil {
return fmt.Errorf("argument json err: %v", err)
}
argument.Type, err = NewType(arg.Type, arg.Components)
argument.Type, err = NewType(extarg.Type)
if err != nil {
return err
}
argument.Name = arg.Name
argument.Indexed = arg.Indexed
argument.Name = extarg.Name
argument.Indexed = extarg.Indexed
return nil
}
@ -88,6 +85,7 @@ func (arguments Arguments) isTuple() bool {
// Unpack performs the operation hexdata -> Go format
func (arguments Arguments) Unpack(v interface{}, data []byte) error {
// make sure the passed value is arguments pointer
if reflect.Ptr != reflect.ValueOf(v).Kind() {
return fmt.Errorf("abi: Unpack(non-pointer %T)", v)
@ -99,123 +97,34 @@ func (arguments Arguments) Unpack(v interface{}, data []byte) error {
if arguments.isTuple() {
return arguments.unpackTuple(v, marshalledValues)
}
return arguments.unpackAtomic(v, marshalledValues[0])
return arguments.unpackAtomic(v, marshalledValues)
}
// unpack sets the unmarshalled value to go format.
// Note the dst here must be settable.
func unpack(t *Type, dst interface{}, src interface{}) error {
var (
dstVal = reflect.ValueOf(dst).Elem()
srcVal = reflect.ValueOf(src)
)
if t.T != TupleTy && !((t.T == SliceTy || t.T == ArrayTy) && t.Elem.T == TupleTy) {
return set(dstVal, srcVal)
}
switch t.T {
case TupleTy:
if dstVal.Kind() != reflect.Struct {
return fmt.Errorf("abi: invalid dst value for unpack, want struct, got %s", dstVal.Kind())
}
fieldmap, err := mapArgNamesToStructFields(t.TupleRawNames, dstVal)
if err != nil {
return err
}
for i, elem := range t.TupleElems {
fname := fieldmap[t.TupleRawNames[i]]
field := dstVal.FieldByName(fname)
if !field.IsValid() {
return fmt.Errorf("abi: field %s can't found in the given value", t.TupleRawNames[i])
}
if err := unpack(elem, field.Addr().Interface(), srcVal.Field(i).Interface()); err != nil {
return err
}
}
return nil
case SliceTy:
if dstVal.Kind() != reflect.Slice {
return fmt.Errorf("abi: invalid dst value for unpack, want slice, got %s", dstVal.Kind())
}
slice := reflect.MakeSlice(dstVal.Type(), srcVal.Len(), srcVal.Len())
for i := 0; i < slice.Len(); i++ {
if err := unpack(t.Elem, slice.Index(i).Addr().Interface(), srcVal.Index(i).Interface()); err != nil {
return err
}
}
dstVal.Set(slice)
case ArrayTy:
if dstVal.Kind() != reflect.Array {
return fmt.Errorf("abi: invalid dst value for unpack, want array, got %s", dstVal.Kind())
}
array := reflect.New(dstVal.Type()).Elem()
for i := 0; i < array.Len(); i++ {
if err := unpack(t.Elem, array.Index(i).Addr().Interface(), srcVal.Index(i).Interface()); err != nil {
return err
}
}
dstVal.Set(array)
}
return nil
}
// unpackAtomic unpacks ( hexdata -> go ) a single value
func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues interface{}) error {
if arguments.LengthNonIndexed() == 0 {
return nil
}
argument := arguments.NonIndexed()[0]
elem := reflect.ValueOf(v).Elem()
if elem.Kind() == reflect.Struct {
fieldmap, err := mapArgNamesToStructFields([]string{argument.Name}, elem)
if err != nil {
return err
}
field := elem.FieldByName(fieldmap[argument.Name])
if !field.IsValid() {
return fmt.Errorf("abi: field %s can't be found in the given value", argument.Name)
}
return unpack(&argument.Type, field.Addr().Interface(), marshalledValues)
}
return unpack(&argument.Type, elem.Addr().Interface(), marshalledValues)
}
// unpackTuple unpacks ( hexdata -> go ) a batch of values.
func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interface{}) error {
var (
value = reflect.ValueOf(v).Elem()
typ = value.Type()
kind = value.Kind()
)
if err := requireUnpackKind(value, typ, kind, arguments); err != nil {
return err
}
// If the interface is a struct, get of abi->struct_field mapping
var abi2struct map[string]string
// If the output interface is a struct, make sure names don't collide
if kind == reflect.Struct {
var (
argNames []string
err error
)
for _, arg := range arguments.NonIndexed() {
argNames = append(argNames, arg.Name)
}
abi2struct, err = mapArgNamesToStructFields(argNames, value)
if err != nil {
if err := requireUniqueStructFieldNames(arguments); err != nil {
return err
}
}
for i, arg := range arguments.NonIndexed() {
reflectValue := reflect.ValueOf(marshalledValues[i])
switch kind {
case reflect.Struct:
field := value.FieldByName(abi2struct[arg.Name])
if !field.IsValid() {
return fmt.Errorf("abi: field %s can't be found in the given value", arg.Name)
}
if err := unpack(&arg.Type, field.Addr().Interface(), marshalledValues[i]); err != nil {
err := unpackStruct(value, reflectValue, arg)
if err != nil {
return err
}
case reflect.Slice, reflect.Array:
@ -223,10 +132,11 @@ func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interfa
return fmt.Errorf("abi: insufficient number of arguments for unpack, want %d, got %d", len(arguments), value.Len())
}
v := value.Index(i)
if err := requireAssignable(v, reflect.ValueOf(marshalledValues[i])); err != nil {
if err := requireAssignable(v, reflectValue); err != nil {
return err
}
if err := unpack(&arg.Type, v.Addr().Interface(), marshalledValues[i]); err != nil {
if err := set(v.Elem(), reflectValue, arg); err != nil {
return err
}
default:
@ -234,7 +144,43 @@ func (arguments Arguments) unpackTuple(v interface{}, marshalledValues []interfa
}
}
return nil
}
// unpackAtomic unpacks ( hexdata -> go ) a single value
func (arguments Arguments) unpackAtomic(v interface{}, marshalledValues []interface{}) error {
if len(marshalledValues) != 1 {
return fmt.Errorf("abi: wrong length, expected single value, got %d", len(marshalledValues))
}
elem := reflect.ValueOf(v).Elem()
kind := elem.Kind()
reflectValue := reflect.ValueOf(marshalledValues[0])
if kind == reflect.Struct {
//make sure names don't collide
if err := requireUniqueStructFieldNames(arguments); err != nil {
return err
}
return unpackStruct(elem, reflectValue, arguments[0])
}
return set(elem, reflectValue, arguments.NonIndexed()[0])
}
// Computes the full size of an array;
// i.e. counting nested arrays, which count towards size for unpacking.
func getArraySize(arr *Type) int {
size := arr.Size
// Arrays can be nested, with each element being the same size
arr = arr.Elem
for arr.T == ArrayTy {
// Keep multiplying by elem.Size while the elem is an array.
size *= arr.Size
arr = arr.Elem
}
// Now we have the full array size, including its children.
return size
}
// UnpackValues can be used to unpack ABI-encoded hexdata according to the ABI-specification,
@ -245,7 +191,7 @@ func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) {
virtualArgs := 0
for index, arg := range arguments.NonIndexed() {
marshalledValue, err := toGoType((index+virtualArgs)*32, arg.Type, data)
if arg.Type.T == ArrayTy && !isDynamicType(arg.Type) {
if arg.Type.T == ArrayTy {
// If we have a static array, like [3]uint256, these are coded as
// just like uint256,uint256,uint256.
// This means that we need to add two 'virtual' arguments when
@ -256,11 +202,7 @@ func (arguments Arguments) UnpackValues(data []byte) ([]interface{}, error) {
//
// Calculate the full array size to get the correct offset for the next argument.
// Decrement it by 1, as the normal index increment is still applied.
virtualArgs += getTypeSize(arg.Type)/32 - 1
} else if arg.Type.T == TupleTy && !isDynamicType(arg.Type) {
// If we have a static tuple, like (uint256, bool, uint256), these are
// coded as just like uint256,bool,uint256
virtualArgs += getTypeSize(arg.Type)/32 - 1
virtualArgs += getArraySize(&arg.Type) - 1
}
if err != nil {
return nil, err
@ -290,7 +232,11 @@ func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
// input offset is the bytes offset for packed output
inputOffset := 0
for _, abiArg := range abiArgs {
inputOffset += getTypeSize(abiArg.Type)
if abiArg.Type.T == ArrayTy {
inputOffset += 32 * abiArg.Type.Size
} else {
inputOffset += 32
}
}
var ret []byte
for i, a := range args {
@ -300,13 +246,14 @@ func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
if err != nil {
return nil, err
}
// check for dynamic types
if isDynamicType(input.Type) {
// check for a slice type (string, bytes, slice)
if input.Type.requiresLengthPrefix() {
// calculate the offset
offset := inputOffset + len(variableInput)
// set the offset
ret = append(ret, packNum(reflect.ValueOf(inputOffset))...)
// calculate next offset
inputOffset += len(packed)
// append to variable input
ret = append(ret, packNum(reflect.ValueOf(offset))...)
// Append the packed output to the variable input. The variable input
// will be appended at the end of the input.
variableInput = append(variableInput, packed...)
} else {
// append the packed value to the input
@ -319,13 +266,29 @@ func (arguments Arguments) Pack(args ...interface{}) ([]byte, error) {
return ret, nil
}
// ToCamelCase converts an under-score string to a camel-case string
func ToCamelCase(input string) string {
parts := strings.Split(input, "_")
for i, s := range parts {
if len(s) > 0 {
parts[i] = strings.ToUpper(s[:1]) + s[1:]
// capitalise makes the first character of a string upper case, also removing any
// prefixing underscores from the variable names.
func capitalise(input string) string {
for len(input) > 0 && input[0] == '_' {
input = input[1:]
}
if len(input) == 0 {
return ""
}
return strings.ToUpper(input[:1]) + input[1:]
}
//unpackStruct extracts each argument into its corresponding struct field
func unpackStruct(value, reflectValue reflect.Value, arg Argument) error {
name := capitalise(arg.Name)
typ := value.Type()
for j := 0; j < typ.NumField(); j++ {
// TODO read tags: `abi:"fieldName"`
if typ.Field(j).Name == name {
if err := set(value.Field(j), reflectValue, arg); err != nil {
return err
}
}
}
return strings.Join(parts, "")
return nil
}

View file

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

View file

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

View file

@ -24,22 +24,28 @@ import (
"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/rawdb"
"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/XDCx"
"github.com/XinFinOrg/XDPoSChain/XDCxlending"
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
"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/XDPoS"
"github.com/XinFinOrg/XDPoSChain/consensus/XDPoS/utils"
"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.
@ -63,13 +69,54 @@ type SimulatedBackend struct {
config *params.ChainConfig
}
// XDC simulated backend for testing purpose.
func NewXDCSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64, chainConfig *params.ChainConfig) *SimulatedBackend {
// database := ethdb.NewMemDatabase()
database := rawdb.NewMemoryDatabase()
genesis := core.Genesis{
GasLimit: gasLimit, // need this big, support initial smart contract
Config: chainConfig,
Alloc: alloc,
ExtraData: append(make([]byte, 32), make([]byte, 65)...),
}
genesis.MustCommit(database)
consensus := XDPoS.NewFaker(database, chainConfig)
// Attach mock trading and lending service
var DefaultConfig = XDCx.Config{
DataDir: "",
}
XDCXServ := XDCx.New(&DefaultConfig)
lendingServ := XDCxlending.New(XDCXServ)
consensus.GetXDCXService = func() utils.TradingService {
return XDCXServ
}
consensus.GetLendingService = func() utils.LendingService {
return lendingServ
}
blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, consensus, vm.Config{})
backend := &SimulatedBackend{
database: database,
blockchain: blockchain,
config: genesis.Config,
events: filters.NewEventSystem(new(event.TypeMux), &filterBackend{database, blockchain}, false),
}
blockchain.Client = backend
backend.rollback()
return backend
}
// NewSimulatedBackend creates a new binding backend using a simulated blockchain
// for testing purposes.
func NewSimulatedBackend(alloc core.GenesisAlloc, gasLimit uint64) *SimulatedBackend {
database := ethdb.NewMemDatabase()
genesis := core.Genesis{Config: params.AllEthashProtocolChanges, GasLimit: gasLimit, Alloc: alloc}
func NewSimulatedBackend(alloc core.GenesisAlloc) *SimulatedBackend {
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{}, nil)
blockchain, _ := core.NewBlockChain(database, nil, genesis.Config, ethash.NewFaker(), vm.Config{})
backend := &SimulatedBackend{
database: database,
@ -102,7 +149,7 @@ func (b *SimulatedBackend) Rollback() {
}
func (b *SimulatedBackend) rollback() {
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(int, *core.BlockGen) {})
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.blockchain.Engine(), b.database, 1, func(int, *core.BlockGen) {})
statedb, _ := b.blockchain.State()
b.pendingBlock = blocks[0]
@ -173,7 +220,7 @@ func (b *SimulatedBackend) ForEachStorageAt(ctx context.Context, contract common
// TransactionReceipt returns the receipt of a transaction.
func (b *SimulatedBackend) TransactionReceipt(ctx context.Context, txHash common.Hash) (*types.Receipt, error) {
receipt, _, _, _ := rawdb.ReadReceipt(b.database, txHash)
receipt, _, _, _ := core.GetReceipt(b.database, txHash)
return receipt, nil
}
@ -186,7 +233,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()
@ -202,7 +249,7 @@ func (b *SimulatedBackend) CallContract(ctx context.Context, call ethereum.CallM
}
// 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())
@ -221,14 +268,14 @@ func (b *SimulatedBackend) PendingNonceAt(ctx context.Context, account common.Ad
}
// SuggestGasPrice implements ContractTransactor.SuggestGasPrice. Since the simulated
// chain doesn't have miners, we just return a gas price of 1 for any call.
// chain doens't have miners, we just return a gas price of 1 for any call.
func (b *SimulatedBackend) SuggestGasPrice(ctx context.Context) (*big.Int, error) {
return big.NewInt(1), nil
}
// 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()
@ -278,7 +325,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)
@ -294,14 +341,19 @@ func (b *SimulatedBackend) callContract(ctx context.Context, call ethereum.CallM
from.SetBalance(math.MaxBig256)
// 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, 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)
return core.NewStateTransition(vmenv, msg, gaspool).TransitionDb()
owner := common.Address{}
return core.NewStateTransition(vmenv, msg, gaspool).TransitionDb(owner)
}
// SendTransaction updates the pending block to include the given transaction.
@ -319,7 +371,7 @@ func (b *SimulatedBackend) SendTransaction(ctx context.Context, tx *types.Transa
panic(fmt.Errorf("invalid transaction nonce: got %d, want %d", tx.Nonce(), nonce))
}
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.blockchain.Engine(), b.database, 1, func(number int, block *core.BlockGen) {
for _, tx := range b.pendingBlock.Transactions() {
block.AddTxWithChain(b.blockchain, tx)
}
@ -336,25 +388,19 @@ 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) {
var filter *filters.Filter
if query.BlockHash != nil {
// Block filter requested, construct a single-shot filter
filter = filters.NewBlockFilter(&filterBackend{b.database, b.blockchain}, *query.BlockHash, query.Addresses, query.Topics)
} else {
// Initialize unset filter boundaried to run from genesis to chain head
from := int64(0)
if query.FromBlock != nil {
from = query.FromBlock.Int64()
}
to := int64(-1)
if query.ToBlock != nil {
to = query.ToBlock.Int64()
}
// Construct the range filter
filter = filters.NewRangeFilter(&filterBackend{b.database, b.blockchain}, from, to, query.Addresses, query.Topics)
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 {
from = query.FromBlock.Int64()
}
// Run the filter and return all the logs
to := int64(-1)
if query.ToBlock != nil {
to = query.ToBlock.Int64()
}
// Construct and execute the filter
filter := filters.New(&filterBackend{b.database, b.blockchain}, from, to, query.Addresses, query.Topics)
logs, err := filter.Logs(ctx)
if err != nil {
return nil, err
@ -368,7 +414,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)
@ -404,7 +450,8 @@ func (b *SimulatedBackend) SubscribeFilterLogs(ctx context.Context, query ethere
func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
b.mu.Lock()
defer b.mu.Unlock()
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), ethash.NewFaker(), b.database, 1, func(number int, block *core.BlockGen) {
blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.blockchain.Engine(), b.database, 1, func(number int, block *core.BlockGen) {
// blocks, _ := core.GenerateChain(b.config, b.blockchain.CurrentBlock(), b.blockchain.Engine(), b.database, 1, func(number int, block *core.BlockGen) {
for _, tx := range b.pendingBlock.Transactions() {
block.AddTx(tx)
}
@ -418,19 +465,24 @@ func (b *SimulatedBackend) AdjustTime(adjustment time.Duration) error {
return nil
}
// callmsg implements core.Message to allow passing it as a transaction simulator.
type callmsg struct {
ethereum.CallMsg
func (b *SimulatedBackend) GetBlockChain() *core.BlockChain {
return b.blockchain
}
func (m callmsg) From() common.Address { return m.CallMsg.From }
func (m callmsg) Nonce() uint64 { return 0 }
func (m callmsg) CheckNonce() bool { return false }
func (m callmsg) To() *common.Address { return m.CallMsg.To }
func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice }
func (m callmsg) Gas() uint64 { return m.CallMsg.Gas }
func (m callmsg) Value() *big.Int { return m.CallMsg.Value }
func (m callmsg) Data() []byte { return m.CallMsg.Data }
// callmsg implements core.Message to allow passing it as a transaction simulator.
type callmsg struct {
XDPoSChain.CallMsg
}
func (m callmsg) From() common.Address { return m.CallMsg.From }
func (m callmsg) Nonce() uint64 { return 0 }
func (m callmsg) CheckNonce() bool { return false }
func (m callmsg) To() *common.Address { return m.CallMsg.To }
func (m callmsg) GasPrice() *big.Int { return m.CallMsg.GasPrice }
func (m callmsg) Gas() uint64 { return m.CallMsg.Gas }
func (m callmsg) Value() *big.Int { return m.CallMsg.Value }
func (m callmsg) Data() []byte { return m.CallMsg.Data }
func (m callmsg) BalanceTokenFee() *big.Int { return m.CallMsg.BalanceTokenFee }
// filterBackend implements filters.Backend to support filtering for logs without
// taking bloom-bits acceleration structures into account.
@ -449,24 +501,12 @@ func (fb *filterBackend) HeaderByNumber(ctx context.Context, block rpc.BlockNumb
return fb.bc.GetHeaderByNumber(uint64(block.Int64())), nil
}
func (fb *filterBackend) HeaderByHash(ctx context.Context, hash common.Hash) (*types.Header, error) {
return fb.bc.GetHeaderByHash(hash), nil
}
func (fb *filterBackend) GetReceipts(ctx context.Context, hash common.Hash) (types.Receipts, error) {
number := rawdb.ReadHeaderNumber(fb.db, hash)
if number == nil {
return nil, nil
}
return rawdb.ReadReceipts(fb.db, hash, *number), nil
return core.GetBlockReceipts(fb.db, hash, core.GetBlockNumber(fb.db, hash)), nil
}
func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*types.Log, error) {
number := rawdb.ReadHeaderNumber(fb.db, hash)
if number == nil {
return nil, nil
}
receipts := rawdb.ReadReceipts(fb.db, hash, *number)
receipts := core.GetBlockReceipts(fb.db, hash, core.GetBlockNumber(fb.db, hash))
if receipts == nil {
return nil, nil
}
@ -477,7 +517,7 @@ func (fb *filterBackend) GetLogs(ctx context.Context, hash common.Hash) ([][]*ty
return logs, nil
}
func (fb *filterBackend) SubscribeNewTxsEvent(ch chan<- core.NewTxsEvent) event.Subscription {
func (fb *filterBackend) SubscribeTxPreEvent(ch chan<- core.TxPreEvent) event.Subscription {
return event.NewSubscription(func(quit <-chan struct{}) error {
<-quit
return nil

View file

@ -22,12 +22,12 @@ import (
"fmt"
"math/big"
"github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/crypto"
"github.com/ethereum/go-ethereum/event"
"github.com/XinFinOrg/XDPoSChain"
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/XinFinOrg/XDPoSChain/event"
)
// SignerFn is a signer function callback when a contract requires a method to
@ -36,10 +36,10 @@ type SignerFn func(types.Signer, common.Address, *types.Transaction) (*types.Tra
// CallOpts is the collection of options to fine tune a contract call request.
type CallOpts struct {
Pending bool // Whether to operate on the pending state or the last known one
From common.Address // Optional the sender address, otherwise the first account is used
BlockNumber *big.Int // Optional the block number on which the call should be performed
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
Pending bool // Whether to operate on the pending state or the last known one
From common.Address // Optional the sender address, otherwise the first account is used
Context context.Context // Network context to support cancellation and timeouts (nil = no timeout)
}
// TransactOpts is the collection of authorization data required to create a
@ -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}
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
@ -148,10 +148,10 @@ func (c *BoundContract) Call(opts *CallOpts, result interface{}, method string,
}
}
} else {
output, err = c.caller.CallContract(ctx, msg, opts.BlockNumber)
output, err = c.caller.CallContract(ctx, msg, nil)
if err == nil && len(output) == 0 {
// Make sure we have a contract to operate on, and bail out otherwise.
if code, err = c.caller.CodeAt(ctx, c.address, opts.BlockNumber); err != nil {
if code, err = c.caller.CodeAt(ctx, c.address, nil); err != nil {
return err
} else if len(code) == 0 {
return ErrNoCode
@ -218,7 +218,7 @@ func (c *BoundContract) transact(opts *TransactOpts, contract *common.Address, i
}
}
// If the contract surely has code (or code is not needed), estimate the transaction
msg := ethereum.CallMsg{From: opts.From, To: contract, Value: value, Data: input}
msg := XDPoSChain.CallMsg{From: opts.From, To: contract, Value: value, Data: input}
gasLimit, err = c.transactor.EstimateGas(ensureContext(opts.Context), msg)
if err != nil {
return nil, fmt.Errorf("failed to estimate gas needed: %v", err)
@ -261,7 +261,7 @@ func (c *BoundContract) FilterLogs(opts *FilterOpts, name string, query ...[]int
// Start the background filtering
logs := make(chan types.Log, 128)
config := ethereum.FilterQuery{
config := XDPoSChain.FilterQuery{
Addresses: []common.Address{c.address},
Topics: topics,
FromBlock: new(big.Int).SetUint64(opts.Start),
@ -310,7 +310,7 @@ func (c *BoundContract) WatchLogs(opts *WatchOpts, name string, query ...[]inter
// Start the background filtering
logs := make(chan types.Log, 128)
config := ethereum.FilterQuery{
config := XDPoSChain.FilterQuery{
Addresses: []common.Address{c.address},
Topics: topics,
}

View file

@ -1,64 +0,0 @@
package bind_test
import (
"context"
"math/big"
"testing"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
)
type mockCaller struct {
codeAtBlockNumber *big.Int
callContractBlockNumber *big.Int
}
func (mc *mockCaller) CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error) {
mc.codeAtBlockNumber = blockNumber
return []byte{1, 2, 3}, nil
}
func (mc *mockCaller) CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error) {
mc.callContractBlockNumber = blockNumber
return nil, nil
}
func TestPassingBlockNumber(t *testing.T) {
mc := &mockCaller{}
bc := bind.NewBoundContract(common.HexToAddress("0x0"), abi.ABI{
Methods: map[string]abi.Method{
"something": {
Name: "something",
Outputs: abi.Arguments{},
},
},
}, mc, nil, nil)
var ret string
blockNumber := big.NewInt(42)
bc.Call(&bind.CallOpts{BlockNumber: blockNumber}, &ret, "something")
if mc.callContractBlockNumber != blockNumber {
t.Fatalf("CallContract() was not passed the block number")
}
if mc.codeAtBlockNumber != blockNumber {
t.Fatalf("CodeAt() was not passed the block number")
}
bc.Call(&bind.CallOpts{}, &ret, "something")
if mc.callContractBlockNumber != nil {
t.Fatalf("CallContract() was passed a block number when it should not have been")
}
if mc.codeAtBlockNumber != nil {
t.Fatalf("CodeAt() was passed a block number when it should not have been")
}
}

View file

@ -17,19 +17,19 @@
// 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 (
"bytes"
"fmt"
"go/format"
"regexp"
"strings"
"text/template"
"unicode"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
"golang.org/x/tools/imports"
)
// Lang is a target programming language selector to generate bindings for.
@ -145,9 +145,9 @@ func Bind(types []string, abis []string, bytecodes []string, pkg string, lang La
if err := tmpl.Execute(buffer, data); err != nil {
return "", err
}
// For Go bindings pass the code through gofmt to clean it up
// For Go bindings pass the code through goimports to clean it up and double check
if lang == LangGo {
code, err := format.Source(buffer.Bytes())
code, err := imports.Process(".", buffer.Bytes(), nil)
if err != nil {
return "", fmt.Errorf("%v\n%s", err, buffer)
}
@ -207,7 +207,7 @@ func bindTypeGo(kind abi.Type) string {
// The inner function of bindTypeGo, this finds the inner type of stringKind.
// (Or just the type itself if it is not an array or slice)
// The length of the matched part is returned, with the translated type.
// The length of the matched part is returned, with the the translated type.
func bindUnnestedTypeGo(stringKind string) (int, string) {
switch {
@ -255,7 +255,7 @@ func bindTypeJava(kind abi.Type) string {
// The inner function of bindTypeJava, this finds the inner type of stringKind.
// (Or just the type itself if it is not an array or slice)
// The length of the matched part is returned, with the translated type.
// The length of the matched part is returned, with the the translated type.
func bindUnnestedTypeJava(stringKind string) (int, string) {
switch {
@ -381,23 +381,54 @@ func namedTypeJava(javaKind string, solKind abi.Type) string {
// methodNormalizer is a name transformer that modifies Solidity method names to
// conform to target language naming concentions.
var methodNormalizer = map[Lang]func(string) string{
LangGo: abi.ToCamelCase,
LangGo: capitalise,
LangJava: decapitalise,
}
// capitalise makes a camel-case string which starts with an upper case character.
func capitalise(input string) string {
return abi.ToCamelCase(input)
for len(input) > 0 && input[0] == '_' {
input = input[1:]
}
if len(input) == 0 {
return ""
}
return toCamelCase(strings.ToUpper(input[:1]) + input[1:])
}
// decapitalise makes a camel-case string which starts with a lower case character.
func decapitalise(input string) string {
if len(input) == 0 {
return input
for len(input) > 0 && input[0] == '_' {
input = input[1:]
}
if len(input) == 0 {
return ""
}
return toCamelCase(strings.ToLower(input[:1]) + input[1:])
}
goForm := abi.ToCamelCase(input)
return strings.ToLower(goForm[:1]) + goForm[1:]
// toCamelCase converts an under-score string to a camel-case string
func toCamelCase(input string) string {
toupper := false
result := ""
for k, v := range input {
switch {
case k == 0:
result = strings.ToUpper(string(input[0]))
case toupper:
result += strings.ToUpper(string(v))
toupper = false
case v == '_':
toupper = true
default:
result += string(v)
}
}
return result
}
// structured checks whether a list of ABI data types has enough information to

File diff suppressed because one or more lines are too long

View file

@ -16,7 +16,7 @@
package bind
import "github.com/ethereum/go-ethereum/accounts/abi"
import "github.com/XinFinOrg/XDPoSChain/accounts/abi"
// tmplData is the data structure required to fill the binding template.
type tmplData struct {
@ -64,30 +64,6 @@ const tmplSourceGo = `
package {{.Package}}
import (
"math/big"
"strings"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/accounts/abi"
"github.com/ethereum/go-ethereum/accounts/abi/bind"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
)
// Reference imports to suppress errors if they are not otherwise used.
var (
_ = big.NewInt
_ = strings.NewReader
_ = ethereum.NotFound
_ = abi.U256
_ = bind.Bind
_ = common.Big1
_ = types.BloomLookup
_ = event.NewSubscription
)
{{range $contract := .Contracts}}
// {{.Type}}ABI is the input ABI used to generate the binding from.
const {{.Type}}ABI = "{{.InputABI}}"

View file

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

View file

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

View file

@ -22,12 +22,13 @@ 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"
"github.com/XinFinOrg/XDPoSChain/params"
)
var testKey, _ = crypto.HexToECDSA("b71c71a67e1177ad4e901695e1b4b9ee17ae16c6668d313eac2f96dbcda3f291")
@ -53,10 +54,10 @@ var waitDeployedTests = map[string]struct {
func TestWaitDeployed(t *testing.T) {
for name, test := range waitDeployedTests {
backend := backends.NewSimulatedBackend(
backend := backends.NewXDCSimulatedBackend(
core.GenesisAlloc{
crypto.PubkeyToAddress(testKey.PublicKey): {Balance: big.NewInt(10000000000)},
}, 10000000,
}, 10000000, params.TestXDPoSMockChainConfig,
)
// Create the transaction.

View file

@ -20,8 +20,8 @@ import (
"fmt"
"strings"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/crypto"
)
// Event is an event potentially triggered by the EVM's LOG mechanism. The Event
@ -33,15 +33,15 @@ type Event struct {
Inputs Arguments
}
func (e Event) String() string {
inputs := make([]string, len(e.Inputs))
for i, input := range e.Inputs {
inputs[i] = fmt.Sprintf("%v %v", input.Type, input.Name)
func (event Event) String() string {
inputs := make([]string, len(event.Inputs))
for i, input := range event.Inputs {
inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type)
if input.Indexed {
inputs[i] = fmt.Sprintf("%v indexed %v", input.Type, input.Name)
inputs[i] = fmt.Sprintf("%v indexed %v", input.Name, input.Type)
}
}
return fmt.Sprintf("event %v(%v)", e.Name, strings.Join(inputs, ", "))
return fmt.Sprintf("event %v(%v)", event.Name, strings.Join(inputs, ", "))
}
// Id returns the canonical representation of the event's signature used by the

View file

@ -25,8 +25,8 @@ import (
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/crypto"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/crypto"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)
@ -58,28 +58,12 @@ var jsonEventPledge = []byte(`{
"type": "event"
}`)
var jsonEventMixedCase = []byte(`{
"anonymous": false,
"inputs": [{
"indexed": false, "name": "value", "type": "uint256"
}, {
"indexed": false, "name": "_value", "type": "uint256"
}, {
"indexed": false, "name": "Value", "type": "uint256"
}],
"name": "MixedCase",
"type": "event"
}`)
// 1000000
var transferData1 = "00000000000000000000000000000000000000000000000000000000000f4240"
// "0x00Ce0d46d924CC8437c806721496599FC3FFA268", 2218516807680, "usd"
var pledgeData1 = "00000000000000000000000000ce0d46d924cc8437c806721496599fc3ffa2680000000000000000000000000000000000000000000000000000020489e800007573640000000000000000000000000000000000000000000000000000000000"
// 1000000,2218516807680,1000001
var mixedCaseData1 = "00000000000000000000000000000000000000000000000000000000000f42400000000000000000000000000000000000000000000000000000020489e8000000000000000000000000000000000000000000000000000000000000000f4241"
func TestEventId(t *testing.T) {
var table = []struct {
definition string
@ -87,12 +71,12 @@ func TestEventId(t *testing.T) {
}{
{
definition: `[
{ "type" : "event", "name" : "Balance", "inputs": [{ "name" : "in", "type": "uint256" }] },
{ "type" : "event", "name" : "Check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] }
{ "type" : "event", "name" : "balance", "inputs": [{ "name" : "in", "type": "uint256" }] },
{ "type" : "event", "name" : "check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] }
]`,
expectations: map[string]common.Hash{
"Balance": crypto.Keccak256Hash([]byte("Balance(uint256)")),
"Check": crypto.Keccak256Hash([]byte("Check(address,uint256)")),
"balance": crypto.Keccak256Hash([]byte("balance(uint256)")),
"check": crypto.Keccak256Hash([]byte("check(address,uint256)")),
},
},
}
@ -111,39 +95,6 @@ func TestEventId(t *testing.T) {
}
}
func TestEventString(t *testing.T) {
var table = []struct {
definition string
expectations map[string]string
}{
{
definition: `[
{ "type" : "event", "name" : "Balance", "inputs": [{ "name" : "in", "type": "uint256" }] },
{ "type" : "event", "name" : "Check", "inputs": [{ "name" : "t", "type": "address" }, { "name": "b", "type": "uint256" }] },
{ "type" : "event", "name" : "Transfer", "inputs": [{ "name": "from", "type": "address", "indexed": true }, { "name": "to", "type": "address", "indexed": true }, { "name": "value", "type": "uint256" }] }
]`,
expectations: map[string]string{
"Balance": "event Balance(uint256 in)",
"Check": "event Check(address t, uint256 b)",
"Transfer": "event Transfer(address indexed from, address indexed to, uint256 value)",
},
},
}
for _, test := range table {
abi, err := JSON(strings.NewReader(test.definition))
if err != nil {
t.Fatal(err)
}
for name, event := range abi.Events {
if event.String() != test.expectations[name] {
t.Errorf("expected string to be %s, got %s", test.expectations[name], event.String())
}
}
}
}
// TestEventMultiValueWithArrayUnpack verifies that array fields will be counted after parsing array.
func TestEventMultiValueWithArrayUnpack(t *testing.T) {
definition := `[{"name": "test", "type": "event", "inputs": [{"indexed": false, "name":"value1", "type":"uint8[2]"},{"indexed": false, "name":"value2", "type":"uint8"}]}]`
@ -170,27 +121,6 @@ func TestEventTupleUnpack(t *testing.T) {
Value *big.Int
}
type EventTransferWithTag struct {
// this is valid because `value` is not exportable,
// so value is only unmarshalled into `Value1`.
value *big.Int
Value1 *big.Int `abi:"value"`
}
type BadEventTransferWithSameFieldAndTag struct {
Value *big.Int
Value1 *big.Int `abi:"value"`
}
type BadEventTransferWithDuplicatedTag struct {
Value1 *big.Int `abi:"value"`
Value2 *big.Int `abi:"value"`
}
type BadEventTransferWithEmptyTag struct {
Value *big.Int `abi:""`
}
type EventPledge struct {
Who common.Address
Wad *big.Int
@ -203,16 +133,9 @@ func TestEventTupleUnpack(t *testing.T) {
Currency [3]byte
}
type EventMixedCase struct {
Value1 *big.Int `abi:"value"`
Value2 *big.Int `abi:"_value"`
Value3 *big.Int `abi:"Value"`
}
bigint := new(big.Int)
bigintExpected := big.NewInt(1000000)
bigintExpected2 := big.NewInt(2218516807680)
bigintExpected3 := big.NewInt(1000001)
addr := common.HexToAddress("0x00Ce0d46d924CC8437c806721496599FC3FFA268")
var testCases = []struct {
data string
@ -235,34 +158,6 @@ func TestEventTupleUnpack(t *testing.T) {
jsonEventTransfer,
"",
"Can unpack ERC20 Transfer event into slice",
}, {
transferData1,
&EventTransferWithTag{},
&EventTransferWithTag{Value1: bigintExpected},
jsonEventTransfer,
"",
"Can unpack ERC20 Transfer event into structure with abi: tag",
}, {
transferData1,
&BadEventTransferWithDuplicatedTag{},
&BadEventTransferWithDuplicatedTag{},
jsonEventTransfer,
"struct: abi tag in 'Value2' already mapped",
"Can not unpack ERC20 Transfer event with duplicated abi tag",
}, {
transferData1,
&BadEventTransferWithSameFieldAndTag{},
&BadEventTransferWithSameFieldAndTag{},
jsonEventTransfer,
"abi: multiple variables maps to the same abi field 'value'",
"Can not unpack ERC20 Transfer event with a field and a tag mapping to the same abi variable",
}, {
transferData1,
&BadEventTransferWithEmptyTag{},
&BadEventTransferWithEmptyTag{},
jsonEventTransfer,
"struct: abi tag in 'Value' is empty",
"Can not unpack ERC20 Transfer event with an empty tag",
}, {
pledgeData1,
&EventPledge{},
@ -321,13 +216,6 @@ func TestEventTupleUnpack(t *testing.T) {
jsonEventPledge,
"abi: cannot unmarshal tuple into map[string]interface {}",
"Can not unpack Pledge event into map",
}, {
mixedCaseData1,
&EventMixedCase{},
&EventMixedCase{Value1: bigintExpected, Value2: bigintExpected2, Value3: bigintExpected3},
jsonEventMixedCase,
"",
"Can unpack abi variables with mixed case",
}}
for _, tc := range testCases {
@ -339,7 +227,7 @@ func TestEventTupleUnpack(t *testing.T) {
assert.Nil(err, "Should be able to unpack event data.")
assert.Equal(tc.expected, tc.dest, tc.name)
} else {
assert.EqualError(err, tc.error, tc.name)
assert.EqualError(err, tc.error)
}
})
}
@ -356,7 +244,7 @@ func unpackTestEventData(dest interface{}, hexData string, jsonEvent []byte, ass
/*
Taken from
https://github.com/ethereum/go-ethereum/pull/15568
https://github.com/XinFinOrg/XDPoSChain/pull/15568
*/
type testResult struct {

View file

@ -20,7 +20,7 @@ import (
"fmt"
"strings"
"github.com/ethereum/go-ethereum/crypto"
"github.com/XinFinOrg/XDPoSChain/crypto"
)
// Method represents a callable given a `Name` and whether the method is a constant.
@ -47,8 +47,10 @@ type Method struct {
// Please note that "int" is substitute for its canonical representation "int256"
func (method Method) Sig() string {
types := make([]string, len(method.Inputs))
for i, input := range method.Inputs {
i := 0
for _, input := range method.Inputs {
types[i] = input.Type.String()
i++
}
return fmt.Sprintf("%v(%v)", method.Name, strings.Join(types, ","))
}
@ -56,14 +58,14 @@ func (method Method) Sig() string {
func (method Method) String() string {
inputs := make([]string, len(method.Inputs))
for i, input := range method.Inputs {
inputs[i] = fmt.Sprintf("%v %v", input.Type, input.Name)
inputs[i] = fmt.Sprintf("%v %v", input.Name, input.Type)
}
outputs := make([]string, len(method.Outputs))
for i, output := range method.Outputs {
outputs[i] = output.Type.String()
if len(output.Name) > 0 {
outputs[i] += fmt.Sprintf(" %v", output.Name)
outputs[i] = fmt.Sprintf("%v ", output.Name)
}
outputs[i] += output.Type.String()
}
constant := ""
if method.Const {

View file

@ -1,61 +0,0 @@
// 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 abi
import (
"strings"
"testing"
)
const methoddata = `
[
{ "type" : "function", "name" : "balance", "constant" : true },
{ "type" : "function", "name" : "send", "constant" : false, "inputs" : [ { "name" : "amount", "type" : "uint256" } ] },
{ "type" : "function", "name" : "transfer", "constant" : false, "inputs" : [ { "name" : "from", "type" : "address" }, { "name" : "to", "type" : "address" }, { "name" : "value", "type" : "uint256" } ], "outputs" : [ { "name" : "success", "type" : "bool" } ] }
]`
func TestMethodString(t *testing.T) {
var table = []struct {
method string
expectation string
}{
{
method: "balance",
expectation: "function balance() constant returns()",
},
{
method: "send",
expectation: "function send(uint256 amount) returns()",
},
{
method: "transfer",
expectation: "function transfer(address from, address to, uint256 value) returns(bool success)",
},
}
abi, err := JSON(strings.NewReader(methoddata))
if err != nil {
t.Fatal(err)
}
for _, test := range table {
got := abi.Methods[test.method].String()
if got != test.expectation {
t.Errorf("expected string to be %s, got %s", test.expectation, got)
}
}
}

View file

@ -20,25 +20,40 @@ 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 (
bigT = reflect.TypeOf(&big.Int{})
derefbigT = reflect.TypeOf(big.Int{})
uint8T = reflect.TypeOf(uint8(0))
uint16T = reflect.TypeOf(uint16(0))
uint32T = reflect.TypeOf(uint32(0))
uint64T = reflect.TypeOf(uint64(0))
int8T = reflect.TypeOf(int8(0))
int16T = reflect.TypeOf(int16(0))
int32T = reflect.TypeOf(int32(0))
int64T = reflect.TypeOf(int64(0))
addressT = reflect.TypeOf(common.Address{})
big_t = reflect.TypeOf(&big.Int{})
derefbig_t = reflect.TypeOf(big.Int{})
uint8_t = reflect.TypeOf(uint8(0))
uint16_t = reflect.TypeOf(uint16(0))
uint32_t = reflect.TypeOf(uint32(0))
uint64_t = reflect.TypeOf(uint64(0))
int_t = reflect.TypeOf(int(0))
int8_t = reflect.TypeOf(int8(0))
int16_t = reflect.TypeOf(int16(0))
int32_t = reflect.TypeOf(int32(0))
int64_t = reflect.TypeOf(int64(0))
address_t = reflect.TypeOf(common.Address{})
int_ts = reflect.TypeOf([]int(nil))
int8_ts = reflect.TypeOf([]int8(nil))
int16_ts = reflect.TypeOf([]int16(nil))
int32_ts = reflect.TypeOf([]int32(nil))
int64_ts = reflect.TypeOf([]int64(nil))
)
// U256 converts a big Int into a 256bit EVM number.
func U256(n *big.Int) []byte {
return math.PaddedBigBytes(math.U256(n), 32)
}
// checks whether the given reflect value is signed. This also works for slices with a number type
func isSigned(v reflect.Value) bool {
switch v.Type() {
case int_ts, int8_ts, int16_ts, int32_ts, int64_ts, int_t, int8_t, int16_t, int32_t, int64_t:
return true
}
return false
}

View file

@ -19,6 +19,7 @@ package abi
import (
"bytes"
"math/big"
"reflect"
"testing"
)
@ -31,3 +32,13 @@ func TestNumberTypes(t *testing.T) {
t.Errorf("expected %x got %x", ubytes, unsigned)
}
}
func TestSigned(t *testing.T) {
if isSigned(reflect.ValueOf(uint(10))) {
t.Error("signed")
}
if !isSigned(reflect.ValueOf(int(10))) {
t.Error("not signed")
}
}

View file

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

View file

@ -24,606 +24,319 @@ import (
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/XinFinOrg/XDPoSChain/common"
)
func TestPack(t *testing.T) {
for i, test := range []struct {
typ string
components []ArgumentMarshaling
input interface{}
output []byte
typ string
input interface{}
output []byte
}{
{
"uint8",
nil,
uint8(2),
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
},
{
"uint8[]",
nil,
[]uint8{1, 2},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
},
{
"uint16",
nil,
uint16(2),
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
},
{
"uint16[]",
nil,
[]uint16{1, 2},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
},
{
"uint32",
nil,
uint32(2),
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
},
{
"uint32[]",
nil,
[]uint32{1, 2},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
},
{
"uint64",
nil,
uint64(2),
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
},
{
"uint64[]",
nil,
[]uint64{1, 2},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
},
{
"uint256",
nil,
big.NewInt(2),
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
},
{
"uint256[]",
nil,
[]*big.Int{big.NewInt(1), big.NewInt(2)},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int8",
nil,
int8(2),
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int8[]",
nil,
[]int8{1, 2},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int16",
nil,
int16(2),
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int16[]",
nil,
[]int16{1, 2},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int32",
nil,
int32(2),
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int32[]",
nil,
[]int32{1, 2},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int64",
nil,
int64(2),
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int64[]",
nil,
[]int64{1, 2},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int256",
nil,
big.NewInt(2),
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002"),
},
{
"int256[]",
nil,
[]*big.Int{big.NewInt(1), big.NewInt(2)},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002"),
},
{
"bytes1",
nil,
[1]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes2",
nil,
[2]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes3",
nil,
[3]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes4",
nil,
[4]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes5",
nil,
[5]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes6",
nil,
[6]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes7",
nil,
[7]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes8",
nil,
[8]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes9",
nil,
[9]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes10",
nil,
[10]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes11",
nil,
[11]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes12",
nil,
[12]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes13",
nil,
[13]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes14",
nil,
[14]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes15",
nil,
[15]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes16",
nil,
[16]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes17",
nil,
[17]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes18",
nil,
[18]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes19",
nil,
[19]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes20",
nil,
[20]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes21",
nil,
[21]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes22",
nil,
[22]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes23",
nil,
[23]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes24",
nil,
[24]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes24",
[24]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes25",
nil,
[25]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes26",
nil,
[26]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes27",
nil,
[27]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes28",
nil,
[28]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes29",
nil,
[29]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes30",
nil,
[30]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes31",
nil,
[31]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"bytes32",
nil,
[32]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"uint32[2][3][4]",
nil,
[4][3][2]uint32{{{1, 2}, {3, 4}, {5, 6}}, {{7, 8}, {9, 10}, {11, 12}}, {{13, 14}, {15, 16}, {17, 18}}, {{19, 20}, {21, 22}, {23, 24}}},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000050000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000000000000000000000700000000000000000000000000000000000000000000000000000000000000080000000000000000000000000000000000000000000000000000000000000009000000000000000000000000000000000000000000000000000000000000000a000000000000000000000000000000000000000000000000000000000000000b000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000000d000000000000000000000000000000000000000000000000000000000000000e000000000000000000000000000000000000000000000000000000000000000f000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000110000000000000000000000000000000000000000000000000000000000000012000000000000000000000000000000000000000000000000000000000000001300000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000015000000000000000000000000000000000000000000000000000000000000001600000000000000000000000000000000000000000000000000000000000000170000000000000000000000000000000000000000000000000000000000000018"),
},
{
"address[]",
nil,
[]common.Address{{1}, {2}},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000200000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000"),
},
{
"bytes32[]",
nil,
[]common.Hash{{1}, {2}},
common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000201000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000"),
},
{
"function",
nil,
[24]byte{1},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000"),
},
{
"string",
nil,
"foobar",
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000006666f6f6261720000000000000000000000000000000000000000000000000000"),
},
{
"string[]",
nil,
[]string{"hello", "foobar"},
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2
"0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
"0000000000000000000000000000000000000000000000000000000000000080" + // offset 128 to i = 1
"0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5
"68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0]
"0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6
"666f6f6261720000000000000000000000000000000000000000000000000000"), // str[1]
},
{
"string[2]",
nil,
[]string{"hello", "foobar"},
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // offset to i = 0
"0000000000000000000000000000000000000000000000000000000000000080" + // offset to i = 1
"0000000000000000000000000000000000000000000000000000000000000005" + // len(str[0]) = 5
"68656c6c6f000000000000000000000000000000000000000000000000000000" + // str[0]
"0000000000000000000000000000000000000000000000000000000000000006" + // len(str[1]) = 6
"666f6f6261720000000000000000000000000000000000000000000000000000"), // str[1]
},
{
"bytes32[][]",
nil,
[][]common.Hash{{{1}, {2}}, {{3}, {4}, {5}}},
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // len(array) = 2
"0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
"00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1
"0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2
"0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
"0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
"0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2]
},
{
"bytes32[][2]",
nil,
[][]common.Hash{{{1}, {2}}, {{3}, {4}, {5}}},
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // offset 64 to i = 0
"00000000000000000000000000000000000000000000000000000000000000a0" + // offset 160 to i = 1
"0000000000000000000000000000000000000000000000000000000000000002" + // len(array[0]) = 2
"0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
"0000000000000000000000000000000000000000000000000000000000000003" + // len(array[1]) = 3
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
"0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2]
},
{
"bytes32[3][2]",
nil,
[][]common.Hash{{{1}, {2}, {3}}, {{3}, {4}, {5}}},
common.Hex2Bytes("0100000000000000000000000000000000000000000000000000000000000000" + // array[0][0]
"0200000000000000000000000000000000000000000000000000000000000000" + // array[0][1]
"0300000000000000000000000000000000000000000000000000000000000000" + // array[0][2]
"0300000000000000000000000000000000000000000000000000000000000000" + // array[1][0]
"0400000000000000000000000000000000000000000000000000000000000000" + // array[1][1]
"0500000000000000000000000000000000000000000000000000000000000000"), // array[1][2]
},
{
// static tuple
"tuple",
[]ArgumentMarshaling{
{Name: "a", Type: "int64"},
{Name: "b", Type: "int256"},
{Name: "c", Type: "int256"},
{Name: "d", Type: "bool"},
{Name: "e", Type: "bytes32[3][2]"},
},
struct {
A int64
B *big.Int
C *big.Int
D bool
E [][]common.Hash
}{1, big.NewInt(1), big.NewInt(-1), true, [][]common.Hash{{{1}, {2}, {3}}, {{3}, {4}, {5}}}},
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001" + // struct[a]
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[b]
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // struct[c]
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[d]
"0100000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][0]
"0200000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][1]
"0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[0][2]
"0300000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][0]
"0400000000000000000000000000000000000000000000000000000000000000" + // struct[e] array[1][1]
"0500000000000000000000000000000000000000000000000000000000000000"), // struct[e] array[1][2]
},
{
// dynamic tuple
"tuple",
[]ArgumentMarshaling{
{Name: "a", Type: "string"},
{Name: "b", Type: "int64"},
{Name: "c", Type: "bytes"},
{Name: "d", Type: "string[]"},
{Name: "e", Type: "int256[]"},
{Name: "f", Type: "address[]"},
},
struct {
FieldA string `abi:"a"` // Test whether abi tag works
FieldB int64 `abi:"b"`
C []byte
D []string
E []*big.Int
F []common.Address
}{"foobar", 1, []byte{1}, []string{"foo", "bar"}, []*big.Int{big.NewInt(1), big.NewInt(-1)}, []common.Address{{1}, {2}}},
common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000c0" + // struct[a] offset
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[b]
"0000000000000000000000000000000000000000000000000000000000000100" + // struct[c] offset
"0000000000000000000000000000000000000000000000000000000000000140" + // struct[d] offset
"0000000000000000000000000000000000000000000000000000000000000220" + // struct[e] offset
"0000000000000000000000000000000000000000000000000000000000000280" + // struct[f] offset
"0000000000000000000000000000000000000000000000000000000000000006" + // struct[a] length
"666f6f6261720000000000000000000000000000000000000000000000000000" + // struct[a] "foobar"
"0000000000000000000000000000000000000000000000000000000000000001" + // struct[c] length
"0100000000000000000000000000000000000000000000000000000000000000" + // []byte{1}
"0000000000000000000000000000000000000000000000000000000000000002" + // struct[d] length
"0000000000000000000000000000000000000000000000000000000000000040" + // foo offset
"0000000000000000000000000000000000000000000000000000000000000080" + // bar offset
"0000000000000000000000000000000000000000000000000000000000000003" + // foo length
"666f6f0000000000000000000000000000000000000000000000000000000000" + // foo
"0000000000000000000000000000000000000000000000000000000000000003" + // bar offset
"6261720000000000000000000000000000000000000000000000000000000000" + // bar
"0000000000000000000000000000000000000000000000000000000000000002" + // struct[e] length
"0000000000000000000000000000000000000000000000000000000000000001" + // 1
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // -1
"0000000000000000000000000000000000000000000000000000000000000002" + // struct[f] length
"0000000000000000000000000100000000000000000000000000000000000000" + // common.Address{1}
"0000000000000000000000000200000000000000000000000000000000000000"), // common.Address{2}
},
{
// nested tuple
"tuple",
[]ArgumentMarshaling{
{Name: "a", Type: "tuple", Components: []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256[]"}}},
{Name: "b", Type: "int256[]"},
},
struct {
A struct {
FieldA *big.Int `abi:"a"`
B []*big.Int
}
B []*big.Int
}{
A: struct {
FieldA *big.Int `abi:"a"` // Test whether abi tag works for nested tuple
B []*big.Int
}{big.NewInt(1), []*big.Int{big.NewInt(1), big.NewInt(0)}},
B: []*big.Int{big.NewInt(1), big.NewInt(0)}},
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // a offset
"00000000000000000000000000000000000000000000000000000000000000e0" + // b offset
"0000000000000000000000000000000000000000000000000000000000000001" + // a.a value
"0000000000000000000000000000000000000000000000000000000000000040" + // a.b offset
"0000000000000000000000000000000000000000000000000000000000000002" + // a.b length
"0000000000000000000000000000000000000000000000000000000000000001" + // a.b[0] value
"0000000000000000000000000000000000000000000000000000000000000000" + // a.b[1] value
"0000000000000000000000000000000000000000000000000000000000000002" + // b length
"0000000000000000000000000000000000000000000000000000000000000001" + // b[0] value
"0000000000000000000000000000000000000000000000000000000000000000"), // b[1] value
},
{
// tuple slice
"tuple[]",
[]ArgumentMarshaling{
{Name: "a", Type: "int256"},
{Name: "b", Type: "int256[]"},
},
[]struct {
A *big.Int
B []*big.Int
}{
{big.NewInt(-1), []*big.Int{big.NewInt(1), big.NewInt(0)}},
{big.NewInt(1), []*big.Int{big.NewInt(2), big.NewInt(-1)}},
},
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002" + // tuple length
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset
"00000000000000000000000000000000000000000000000000000000000000e0" + // tuple[1] offset
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0].B offset
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].B length
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].B[0] value
"0000000000000000000000000000000000000000000000000000000000000000" + // tuple[0].B[1] value
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A
"0000000000000000000000000000000000000000000000000000000000000040" + // tuple[1].B offset
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B length
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].B[0] value
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].B[1] value
},
{
// static tuple array
"tuple[2]",
[]ArgumentMarshaling{
{Name: "a", Type: "int256"},
{Name: "b", Type: "int256"},
},
[2]struct {
A *big.Int
B *big.Int
}{
{big.NewInt(-1), big.NewInt(1)},
{big.NewInt(1), big.NewInt(-1)},
},
common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].a
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].b
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].a
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].b
},
{
// dynamic tuple array
"tuple[2]",
[]ArgumentMarshaling{
{Name: "a", Type: "int256[]"},
},
[2]struct {
A []*big.Int
}{
{[]*big.Int{big.NewInt(-1), big.NewInt(1)}},
{[]*big.Int{big.NewInt(1), big.NewInt(-1)}},
},
common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040" + // tuple[0] offset
"00000000000000000000000000000000000000000000000000000000000000c0" + // tuple[1] offset
"0000000000000000000000000000000000000000000000000000000000000020" + // tuple[0].A offset
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[0].A length
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff" + // tuple[0].A[0]
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[0].A[1]
"0000000000000000000000000000000000000000000000000000000000000020" + // tuple[1].A offset
"0000000000000000000000000000000000000000000000000000000000000002" + // tuple[1].A length
"0000000000000000000000000000000000000000000000000000000000000001" + // tuple[1].A[0]
"ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff"), // tuple[1].A[1]
},
} {
typ, err := NewType(test.typ, test.components)
typ, err := NewType(test.typ)
if err != nil {
t.Fatalf("%v failed. Unexpected parse error: %v", i, err)
}
output, err := typ.pack(reflect.ValueOf(test.input))
if err != nil {
t.Fatalf("%v failed. Unexpected pack error: %v", i, err)
}
if !bytes.Equal(output, test.output) {
t.Errorf("input %d for typ: %v failed. Expected bytes: '%x' Got: '%x'", i, typ.String(), test.output, output)
t.Errorf("%d failed. Expected bytes: '%x' Got: '%x'", i, test.output, output)
}
}
}
@ -693,59 +406,6 @@ func TestMethodPack(t *testing.T) {
if !bytes.Equal(packed, sig) {
t.Errorf("expected %x got %x", sig, packed)
}
a := [2][2]*big.Int{{big.NewInt(1), big.NewInt(1)}, {big.NewInt(2), big.NewInt(0)}}
sig = abi.Methods["nestedArray"].Id()
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{0}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{0xa0}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
sig = append(sig, common.LeftPadBytes(addrC[:], 32)...)
sig = append(sig, common.LeftPadBytes(addrD[:], 32)...)
packed, err = abi.Pack("nestedArray", a, []common.Address{addrC, addrD})
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(packed, sig) {
t.Errorf("expected %x got %x", sig, packed)
}
sig = abi.Methods["nestedArray2"].Id()
sig = append(sig, common.LeftPadBytes([]byte{0x20}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{0x40}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{0x80}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
packed, err = abi.Pack("nestedArray2", [2][]uint8{{1}, {1}})
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(packed, sig) {
t.Errorf("expected %x got %x", sig, packed)
}
sig = abi.Methods["nestedSlice"].Id()
sig = append(sig, common.LeftPadBytes([]byte{0x20}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{0x02}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{0x40}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{0xa0}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{1}, 32)...)
sig = append(sig, common.LeftPadBytes([]byte{2}, 32)...)
packed, err = abi.Pack("nestedSlice", [][]uint8{{1, 2}, {1, 2}})
if err != nil {
t.Fatal(err)
}
if !bytes.Equal(packed, sig) {
t.Errorf("expected %x got %x", sig, packed)
}
}
func TestPackNumber(t *testing.T) {

View file

@ -19,13 +19,12 @@ package abi
import (
"fmt"
"reflect"
"strings"
)
// indirect recursively dereferences the value until it either gets the value
// or finds a big.Int
func indirect(v reflect.Value) reflect.Value {
if v.Kind() == reflect.Ptr && v.Elem().Type() != derefbigT {
if v.Kind() == reflect.Ptr && v.Elem().Type() != derefbig_t {
return indirect(v.Elem())
}
return v
@ -37,26 +36,26 @@ func reflectIntKindAndType(unsigned bool, size int) (reflect.Kind, reflect.Type)
switch size {
case 8:
if unsigned {
return reflect.Uint8, uint8T
return reflect.Uint8, uint8_t
}
return reflect.Int8, int8T
return reflect.Int8, int8_t
case 16:
if unsigned {
return reflect.Uint16, uint16T
return reflect.Uint16, uint16_t
}
return reflect.Int16, int16T
return reflect.Int16, int16_t
case 32:
if unsigned {
return reflect.Uint32, uint32T
return reflect.Uint32, uint32_t
}
return reflect.Int32, int32T
return reflect.Int32, int32_t
case 64:
if unsigned {
return reflect.Uint64, uint64T
return reflect.Uint64, uint64_t
}
return reflect.Int64, int64T
return reflect.Int64, int64_t
}
return reflect.Ptr, bigT
return reflect.Ptr, big_t
}
// mustArrayToBytesSlice creates a new byte slice with the exact same size as value
@ -71,36 +70,22 @@ func mustArrayToByteSlice(value reflect.Value) reflect.Value {
//
// set is a bit more lenient when it comes to assignment and doesn't force an as
// strict ruleset as bare `reflect` does.
func set(dst, src reflect.Value) error {
dstType, srcType := dst.Type(), src.Type()
func set(dst, src reflect.Value, output Argument) error {
dstType := dst.Type()
srcType := src.Type()
switch {
case dstType.Kind() == reflect.Interface:
return set(dst.Elem(), src)
case dstType.Kind() == reflect.Ptr && dstType.Elem() != derefbigT:
return set(dst.Elem(), src)
case srcType.AssignableTo(dstType) && dst.CanSet():
case dstType.AssignableTo(srcType):
dst.Set(src)
case dstType.Kind() == reflect.Slice && srcType.Kind() == reflect.Slice:
return setSlice(dst, src)
case dstType.Kind() == reflect.Interface:
dst.Set(src)
case dstType.Kind() == reflect.Ptr:
return set(dst.Elem(), src, output)
default:
return fmt.Errorf("abi: cannot unmarshal %v in to %v", src.Type(), dst.Type())
}
return nil
}
// setSlice attempts to assign src to dst when slices are not assignable by default
// e.g. src: [][]byte -> dst: [][15]byte
func setSlice(dst, src reflect.Value) error {
slice := reflect.MakeSlice(dst.Type(), src.Len(), src.Len())
for i := 0; i < src.Len(); i++ {
v := src.Index(i)
reflect.Copy(slice.Index(i), v)
}
dst.Set(slice)
return nil
}
// requireAssignable assures that `dest` is a pointer and it's not an interface.
func requireAssignable(dst, src reflect.Value) error {
if dst.Kind() != reflect.Ptr && dst.Kind() != reflect.Interface {
@ -126,93 +111,18 @@ func requireUnpackKind(v reflect.Value, t reflect.Type, k reflect.Kind,
return nil
}
// mapArgNamesToStructFields maps a slice of argument names to struct fields.
// first round: for each Exportable field that contains a `abi:""` tag
// and this field name exists in the given argument name list, pair them together.
// second round: for each argument name that has not been already linked,
// find what variable is expected to be mapped into, if it exists and has not been
// used, pair them.
// Note this function assumes the given value is a struct value.
func mapArgNamesToStructFields(argNames []string, value reflect.Value) (map[string]string, error) {
typ := value.Type()
abi2struct := make(map[string]string)
struct2abi := make(map[string]string)
// first round ~~~
for i := 0; i < typ.NumField(); i++ {
structFieldName := typ.Field(i).Name
// skip private struct fields.
if structFieldName[:1] != strings.ToUpper(structFieldName[:1]) {
continue
// requireUniqueStructFieldNames makes sure field names don't collide
func requireUniqueStructFieldNames(args Arguments) error {
exists := make(map[string]bool)
for _, arg := range args {
field := capitalise(arg.Name)
if field == "" {
return fmt.Errorf("abi: purely underscored output cannot unpack to struct")
}
// skip fields that have no abi:"" tag.
var ok bool
var tagName string
if tagName, ok = typ.Field(i).Tag.Lookup("abi"); !ok {
continue
}
// check if tag is empty.
if tagName == "" {
return nil, fmt.Errorf("struct: abi tag in '%s' is empty", structFieldName)
}
// check which argument field matches with the abi tag.
found := false
for _, arg := range argNames {
if arg == tagName {
if abi2struct[arg] != "" {
return nil, fmt.Errorf("struct: abi tag in '%s' already mapped", structFieldName)
}
// pair them
abi2struct[arg] = structFieldName
struct2abi[structFieldName] = arg
found = true
}
}
// check if this tag has been mapped.
if !found {
return nil, fmt.Errorf("struct: abi tag '%s' defined but not found in abi", tagName)
if exists[field] {
return fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", field)
}
exists[field] = true
}
// second round ~~~
for _, argName := range argNames {
structFieldName := ToCamelCase(argName)
if structFieldName == "" {
return nil, fmt.Errorf("abi: purely underscored output cannot unpack to struct")
}
// this abi has already been paired, skip it... unless there exists another, yet unassigned
// struct field with the same field name. If so, raise an error:
// abi: [ { "name": "value" } ]
// struct { Value *big.Int , Value1 *big.Int `abi:"value"`}
if abi2struct[argName] != "" {
if abi2struct[argName] != structFieldName &&
struct2abi[structFieldName] == "" &&
value.FieldByName(structFieldName).IsValid() {
return nil, fmt.Errorf("abi: multiple variables maps to the same abi field '%s'", argName)
}
continue
}
// return an error if this struct field has already been paired.
if struct2abi[structFieldName] != "" {
return nil, fmt.Errorf("abi: multiple outputs mapping to the same struct field '%s'", structFieldName)
}
if value.FieldByName(structFieldName).IsValid() {
// pair them
abi2struct[argName] = structFieldName
struct2abi[structFieldName] = argName
} else {
// not paired, but annotate as used, to detect cases like
// abi : [ { "name": "value" }, { "name": "_value" } ]
// struct { Value *big.Int }
struct2abi[structFieldName] = argName
}
}
return abi2struct, nil
return nil
}

View file

@ -1,191 +0,0 @@
// Copyright 2019 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 abi
import (
"reflect"
"testing"
)
type reflectTest struct {
name string
args []string
struc interface{}
want map[string]string
err string
}
var reflectTests = []reflectTest{
{
name: "OneToOneCorrespondance",
args: []string{"fieldA"},
struc: struct {
FieldA int `abi:"fieldA"`
}{},
want: map[string]string{
"fieldA": "FieldA",
},
},
{
name: "MissingFieldsInStruct",
args: []string{"fieldA", "fieldB"},
struc: struct {
FieldA int `abi:"fieldA"`
}{},
want: map[string]string{
"fieldA": "FieldA",
},
},
{
name: "MoreFieldsInStructThanArgs",
args: []string{"fieldA"},
struc: struct {
FieldA int `abi:"fieldA"`
FieldB int
}{},
want: map[string]string{
"fieldA": "FieldA",
},
},
{
name: "MissingFieldInArgs",
args: []string{"fieldA"},
struc: struct {
FieldA int `abi:"fieldA"`
FieldB int `abi:"fieldB"`
}{},
err: "struct: abi tag 'fieldB' defined but not found in abi",
},
{
name: "NoAbiDescriptor",
args: []string{"fieldA"},
struc: struct {
FieldA int
}{},
want: map[string]string{
"fieldA": "FieldA",
},
},
{
name: "NoArgs",
args: []string{},
struc: struct {
FieldA int `abi:"fieldA"`
}{},
err: "struct: abi tag 'fieldA' defined but not found in abi",
},
{
name: "DifferentName",
args: []string{"fieldB"},
struc: struct {
FieldA int `abi:"fieldB"`
}{},
want: map[string]string{
"fieldB": "FieldA",
},
},
{
name: "DifferentName",
args: []string{"fieldB"},
struc: struct {
FieldA int `abi:"fieldB"`
}{},
want: map[string]string{
"fieldB": "FieldA",
},
},
{
name: "MultipleFields",
args: []string{"fieldA", "fieldB"},
struc: struct {
FieldA int `abi:"fieldA"`
FieldB int `abi:"fieldB"`
}{},
want: map[string]string{
"fieldA": "FieldA",
"fieldB": "FieldB",
},
},
{
name: "MultipleFieldsABIMissing",
args: []string{"fieldA", "fieldB"},
struc: struct {
FieldA int `abi:"fieldA"`
FieldB int
}{},
want: map[string]string{
"fieldA": "FieldA",
"fieldB": "FieldB",
},
},
{
name: "NameConflict",
args: []string{"fieldB"},
struc: struct {
FieldA int `abi:"fieldB"`
FieldB int
}{},
err: "abi: multiple variables maps to the same abi field 'fieldB'",
},
{
name: "Underscored",
args: []string{"_"},
struc: struct {
FieldA int
}{},
err: "abi: purely underscored output cannot unpack to struct",
},
{
name: "DoubleMapping",
args: []string{"fieldB", "fieldC", "fieldA"},
struc: struct {
FieldA int `abi:"fieldC"`
FieldB int
}{},
err: "abi: multiple outputs mapping to the same struct field 'FieldA'",
},
{
name: "AlreadyMapped",
args: []string{"fieldB", "fieldB"},
struc: struct {
FieldB int `abi:"fieldB"`
}{},
err: "struct: abi tag in 'FieldB' already mapped",
},
}
func TestReflectNameToStruct(t *testing.T) {
for _, test := range reflectTests {
t.Run(test.name, func(t *testing.T) {
m, err := mapArgNamesToStructFields(test.args, reflect.ValueOf(test.struc))
if len(test.err) > 0 {
if err == nil || err.Error() != test.err {
t.Fatalf("Invalid error: expected %v, got %v", test.err, err)
}
} else {
if err != nil {
t.Fatalf("Unexpected error: %v", err)
}
for fname := range test.want {
if m[fname] != test.want[fname] {
t.Fatalf("Incorrect value for field %s: expected %v, got %v", fname, test.want[fname], m[fname])
}
}
}
})
}
}

View file

@ -17,7 +17,6 @@
package abi
import (
"errors"
"fmt"
"reflect"
"regexp"
@ -33,7 +32,6 @@ const (
StringTy
SliceTy
ArrayTy
TupleTy
AddressTy
FixedBytesTy
BytesTy
@ -45,16 +43,13 @@ const (
// Type is the reflection of the supported argument type
type Type struct {
Elem *Type
Kind reflect.Kind
Type reflect.Type
Size int
T byte // Our own type checking
stringKind string // holds the unparsed string for deriving signatures
// Tuple relative fields
TupleElems []*Type // Type information of all tuple fields
TupleRawNames []string // Raw field name of all tuple fields
}
var (
@ -63,7 +58,7 @@ var (
)
// NewType creates a new reflection type of abi type given in t.
func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
func NewType(t string) (typ Type, err error) {
// check that array brackets are equal if they exist
if strings.Count(t, "[") != strings.Count(t, "]") {
return Type{}, fmt.Errorf("invalid arg type in abi")
@ -76,7 +71,7 @@ func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
if strings.Count(t, "[") != 0 {
i := strings.LastIndex(t, "[")
// recursively embed the type
embeddedType, err := NewType(t[:i], components)
embeddedType, err := NewType(t[:i])
if err != nil {
return Type{}, err
}
@ -92,9 +87,6 @@ func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
typ.Kind = reflect.Slice
typ.Elem = &embeddedType
typ.Type = reflect.SliceOf(embeddedType.Type)
if embeddedType.T == TupleTy {
typ.stringKind = embeddedType.stringKind + sliced
}
} else if len(intz) == 1 {
// is a array
typ.T = ArrayTy
@ -105,21 +97,13 @@ func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
return Type{}, fmt.Errorf("abi: error parsing variable size: %v", err)
}
typ.Type = reflect.ArrayOf(typ.Size, embeddedType.Type)
if embeddedType.T == TupleTy {
typ.stringKind = embeddedType.stringKind + sliced
}
} else {
return Type{}, fmt.Errorf("invalid formatting of array type")
}
return typ, err
}
// parse the type and size of the abi-type.
matches := typeRegex.FindAllStringSubmatch(t, -1)
if len(matches) == 0 {
return Type{}, fmt.Errorf("invalid type '%v'", t)
}
parsedType := matches[0]
parsedType := typeRegex.FindAllStringSubmatch(t, -1)[0]
// varSize is the size of the variable
var varSize int
if len(parsedType[3]) > 0 {
@ -151,7 +135,7 @@ func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
typ.Type = reflect.TypeOf(bool(false))
case "address":
typ.Kind = reflect.Array
typ.Type = addressT
typ.Type = address_t
typ.Size = 20
typ.T = AddressTy
case "string":
@ -169,40 +153,6 @@ func NewType(t string, components []ArgumentMarshaling) (typ Type, err error) {
typ.Size = varSize
typ.Type = reflect.ArrayOf(varSize, reflect.TypeOf(byte(0)))
}
case "tuple":
var (
fields []reflect.StructField
elems []*Type
names []string
expression string // canonical parameter expression
)
expression += "("
for idx, c := range components {
cType, err := NewType(c.Type, c.Components)
if err != nil {
return Type{}, err
}
if ToCamelCase(c.Name) == "" {
return Type{}, errors.New("abi: purely anonymous or underscored field is not supported")
}
fields = append(fields, reflect.StructField{
Name: ToCamelCase(c.Name), // reflect.StructOf will panic for any exported field.
Type: cType.Type,
})
elems = append(elems, &cType)
names = append(names, c.Name)
expression += cType.stringKind
if idx != len(components)-1 {
expression += ","
}
}
expression += ")"
typ.Kind = reflect.Struct
typ.Type = reflect.StructOf(fields)
typ.TupleElems = elems
typ.TupleRawNames = names
typ.T = TupleTy
typ.stringKind = expression
case "function":
typ.Kind = reflect.Array
typ.T = FunctionTy
@ -223,82 +173,28 @@ func (t Type) String() (out string) {
func (t Type) pack(v reflect.Value) ([]byte, error) {
// dereference pointer first if it's a pointer
v = indirect(v)
if err := typeCheck(t, v); err != nil {
return nil, err
}
switch t.T {
case SliceTy, ArrayTy:
var ret []byte
if t.T == SliceTy || t.T == ArrayTy {
var packed []byte
if t.requiresLengthPrefix() {
// append length
ret = append(ret, packNum(reflect.ValueOf(v.Len()))...)
}
// calculate offset if any
offset := 0
offsetReq := isDynamicType(*t.Elem)
if offsetReq {
offset = getTypeSize(*t.Elem) * v.Len()
}
var tail []byte
for i := 0; i < v.Len(); i++ {
val, err := t.Elem.pack(v.Index(i))
if err != nil {
return nil, err
}
if !offsetReq {
ret = append(ret, val...)
continue
}
ret = append(ret, packNum(reflect.ValueOf(offset))...)
offset += len(val)
tail = append(tail, val...)
packed = append(packed, val...)
}
return append(ret, tail...), nil
case TupleTy:
// (T1,...,Tk) for k >= 0 and any types T1, …, Tk
// enc(X) = head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(k))
// where X = (X(1), ..., X(k)) and head and tail are defined for Ti being a static
// type as
// head(X(i)) = enc(X(i)) and tail(X(i)) = "" (the empty string)
// and as
// head(X(i)) = enc(len(head(X(1)) ... head(X(k)) tail(X(1)) ... tail(X(i-1))))
// tail(X(i)) = enc(X(i))
// otherwise, i.e. if Ti is a dynamic type.
fieldmap, err := mapArgNamesToStructFields(t.TupleRawNames, v)
if err != nil {
return nil, err
if t.T == SliceTy {
return packBytesSlice(packed, v.Len()), nil
} else if t.T == ArrayTy {
return packed, nil
}
// Calculate prefix occupied size.
offset := 0
for _, elem := range t.TupleElems {
offset += getTypeSize(*elem)
}
var ret, tail []byte
for i, elem := range t.TupleElems {
field := v.FieldByName(fieldmap[t.TupleRawNames[i]])
if !field.IsValid() {
return nil, fmt.Errorf("field %s for tuple not found in the given struct", t.TupleRawNames[i])
}
val, err := elem.pack(field)
if err != nil {
return nil, err
}
if isDynamicType(*elem) {
ret = append(ret, packNum(reflect.ValueOf(offset))...)
tail = append(tail, val...)
offset += len(val)
} else {
ret = append(ret, val...)
}
}
return append(ret, tail...), nil
default:
return packElement(t, v), nil
}
return packElement(t, v), nil
}
// requireLengthPrefix returns whether the type requires any sort of length
@ -306,47 +202,3 @@ func (t Type) pack(v reflect.Value) ([]byte, error) {
func (t Type) requiresLengthPrefix() bool {
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy
}
// isDynamicType returns true if the type is dynamic.
// The following types are called “dynamic”:
// * bytes
// * string
// * T[] for any T
// * T[k] for any dynamic T and any k >= 0
// * (T1,...,Tk) if Ti is dynamic for some 1 <= i <= k
func isDynamicType(t Type) bool {
if t.T == TupleTy {
for _, elem := range t.TupleElems {
if isDynamicType(*elem) {
return true
}
}
return false
}
return t.T == StringTy || t.T == BytesTy || t.T == SliceTy || (t.T == ArrayTy && isDynamicType(*t.Elem))
}
// getTypeSize returns the size that this type needs to occupy.
// We distinguish static and dynamic types. Static types are encoded in-place
// and dynamic types are encoded at a separately allocated location after the
// current block.
// So for a static variable, the size returned represents the size that the
// variable actually occupies.
// For a dynamic variable, the returned size is fixed 32 bytes, which is used
// to store the location reference for actual value storage.
func getTypeSize(t Type) int {
if t.T == ArrayTy && !isDynamicType(*t.Elem) {
// Recursively calculate type size if it is a nested array
if t.Elem.T == ArrayTy {
return t.Size * getTypeSize(*t.Elem)
}
return t.Size * 32
} else if t.T == TupleTy && !isDynamicType(t) {
total := 0
for _, elem := range t.TupleElems {
total += getTypeSize(*elem)
}
return total
}
return 32
}

View file

@ -21,8 +21,8 @@ import (
"reflect"
"testing"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/davecgh/go-spew/spew"
"github.com/ethereum/go-ethereum/common"
)
// typeWithoutStringer is a alias for the Type type which simply doesn't implement
@ -32,75 +32,72 @@ type typeWithoutStringer Type
// Tests that all allowed types get recognized by the type parser.
func TestTypeRegexp(t *testing.T) {
tests := []struct {
blob string
components []ArgumentMarshaling
kind Type
blob string
kind Type
}{
{"bool", nil, Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}},
{"bool[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool(nil)), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}},
{"bool[2]", nil, Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}},
{"bool[2][]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}},
{"bool[][]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}},
{"bool[][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}},
{"bool[2][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}},
{"bool[2][][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][][2]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}, stringKind: "bool[2][][2]"}},
{"bool[2][2][2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}, stringKind: "bool[2][2][2]"}},
{"bool[][][]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}, stringKind: "bool[][][]"}},
{"bool[][2][]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][2][]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}, stringKind: "bool[][2][]"}},
{"int8", nil, Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}},
{"int16", nil, Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}},
{"int32", nil, Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}},
{"int64", nil, Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}},
{"int256", nil, Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}},
{"int8[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}},
{"int8[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8T, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}},
{"int16[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}},
{"int16[2]", nil, Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16T, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}},
{"int32[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}},
{"int32[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32T, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}},
{"int64[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}},
{"int64[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64T, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}},
{"int256[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}},
{"int256[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}},
{"uint8", nil, Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}},
{"uint16", nil, Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}},
{"uint32", nil, Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}},
{"uint64", nil, Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}},
{"uint256", nil, Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}},
{"uint8[]", nil, Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}},
{"uint8[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8T, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}},
{"uint16[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}},
{"uint16[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16T, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}},
{"uint32[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}},
{"uint32[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32T, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}},
{"uint64[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}},
{"uint64[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64T, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}},
{"uint256[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}},
{"uint256[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]*big.Int{}), Size: 2, Elem: &Type{Kind: reflect.Ptr, Type: bigT, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}},
{"bytes32", nil, Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}},
{"bytes[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]byte{}), Elem: &Type{Kind: reflect.Slice, Type: reflect.TypeOf([]byte{}), T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}},
{"bytes[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]byte{}), Elem: &Type{T: BytesTy, Type: reflect.TypeOf([]byte{}), Kind: reflect.Slice, stringKind: "bytes"}, stringKind: "bytes[2]"}},
{"bytes32[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][32]byte{}), Elem: &Type{Kind: reflect.Array, Type: reflect.TypeOf([32]byte{}), T: FixedBytesTy, Size: 32, stringKind: "bytes32"}, stringKind: "bytes32[]"}},
{"bytes32[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][32]byte{}), Elem: &Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}, stringKind: "bytes32[2]"}},
{"string", nil, Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}},
{"string[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]string{}), Elem: &Type{Kind: reflect.String, Type: reflect.TypeOf(""), T: StringTy, stringKind: "string"}, stringKind: "string[]"}},
{"string[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]string{}), Elem: &Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}, stringKind: "string[2]"}},
{"address", nil, Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}},
{"address[]", nil, Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}},
{"address[2]", nil, Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: addressT, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}},
{"bool", Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}},
{"bool[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool(nil)), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}},
{"bool[2]", Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}},
{"bool[2][]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}},
{"bool[][]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}},
{"bool[][2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}},
{"bool[2][2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}},
{"bool[2][][2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][][2]bool{}), Elem: &Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][]"}, stringKind: "bool[2][][2]"}},
{"bool[2][2][2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][2]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[2]"}, stringKind: "bool[2][2]"}, stringKind: "bool[2][2][2]"}},
{"bool[][][]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][]"}, stringKind: "bool[][][]"}},
{"bool[][2][]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][2][]bool{}), Elem: &Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]bool{}), Elem: &Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]bool{}), Elem: &Type{Kind: reflect.Bool, T: BoolTy, Type: reflect.TypeOf(bool(false)), stringKind: "bool"}, stringKind: "bool[]"}, stringKind: "bool[][2]"}, stringKind: "bool[][2][]"}},
{"int8", Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}},
{"int16", Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}},
{"int32", Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}},
{"int64", Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}},
{"int256", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}},
{"int8[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[]"}},
{"int8[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int8{}), Elem: &Type{Kind: reflect.Int8, Type: int8_t, Size: 8, T: IntTy, stringKind: "int8"}, stringKind: "int8[2]"}},
{"int16[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[]"}},
{"int16[2]", Type{Size: 2, Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]int16{}), Elem: &Type{Kind: reflect.Int16, Type: int16_t, Size: 16, T: IntTy, stringKind: "int16"}, stringKind: "int16[2]"}},
{"int32[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[]"}},
{"int32[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int32{}), Elem: &Type{Kind: reflect.Int32, Type: int32_t, Size: 32, T: IntTy, stringKind: "int32"}, stringKind: "int32[2]"}},
{"int64[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[]"}},
{"int64[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]int64{}), Elem: &Type{Kind: reflect.Int64, Type: int64_t, Size: 64, T: IntTy, stringKind: "int64"}, stringKind: "int64[2]"}},
{"int256[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[]"}},
{"int256[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: IntTy, stringKind: "int256"}, stringKind: "int256[2]"}},
{"uint8", Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}},
{"uint16", Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}},
{"uint32", Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}},
{"uint64", Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}},
{"uint256", Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}},
{"uint8[]", Type{Kind: reflect.Slice, T: SliceTy, Type: reflect.TypeOf([]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[]"}},
{"uint8[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint8{}), Elem: &Type{Kind: reflect.Uint8, Type: uint8_t, Size: 8, T: UintTy, stringKind: "uint8"}, stringKind: "uint8[2]"}},
{"uint16[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[]"}},
{"uint16[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint16{}), Elem: &Type{Kind: reflect.Uint16, Type: uint16_t, Size: 16, T: UintTy, stringKind: "uint16"}, stringKind: "uint16[2]"}},
{"uint32[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[]"}},
{"uint32[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint32{}), Elem: &Type{Kind: reflect.Uint32, Type: uint32_t, Size: 32, T: UintTy, stringKind: "uint32"}, stringKind: "uint32[2]"}},
{"uint64[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[]"}},
{"uint64[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]uint64{}), Elem: &Type{Kind: reflect.Uint64, Type: uint64_t, Size: 64, T: UintTy, stringKind: "uint64"}, stringKind: "uint64[2]"}},
{"uint256[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]*big.Int{}), Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[]"}},
{"uint256[2]", Type{Kind: reflect.Array, T: ArrayTy, Type: reflect.TypeOf([2]*big.Int{}), Size: 2, Elem: &Type{Kind: reflect.Ptr, Type: big_t, Size: 256, T: UintTy, stringKind: "uint256"}, stringKind: "uint256[2]"}},
{"bytes32", Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}},
{"bytes[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][]byte{}), Elem: &Type{Kind: reflect.Slice, Type: reflect.TypeOf([]byte{}), T: BytesTy, stringKind: "bytes"}, stringKind: "bytes[]"}},
{"bytes[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][]byte{}), Elem: &Type{T: BytesTy, Type: reflect.TypeOf([]byte{}), Kind: reflect.Slice, stringKind: "bytes"}, stringKind: "bytes[2]"}},
{"bytes32[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([][32]byte{}), Elem: &Type{Kind: reflect.Array, Type: reflect.TypeOf([32]byte{}), T: FixedBytesTy, Size: 32, stringKind: "bytes32"}, stringKind: "bytes32[]"}},
{"bytes32[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2][32]byte{}), Elem: &Type{Kind: reflect.Array, T: FixedBytesTy, Size: 32, Type: reflect.TypeOf([32]byte{}), stringKind: "bytes32"}, stringKind: "bytes32[2]"}},
{"string", Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}},
{"string[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]string{}), Elem: &Type{Kind: reflect.String, Type: reflect.TypeOf(""), T: StringTy, stringKind: "string"}, stringKind: "string[]"}},
{"string[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]string{}), Elem: &Type{Kind: reflect.String, T: StringTy, Type: reflect.TypeOf(""), stringKind: "string"}, stringKind: "string[2]"}},
{"address", Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}},
{"address[]", Type{T: SliceTy, Kind: reflect.Slice, Type: reflect.TypeOf([]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[]"}},
{"address[2]", Type{Kind: reflect.Array, T: ArrayTy, Size: 2, Type: reflect.TypeOf([2]common.Address{}), Elem: &Type{Kind: reflect.Array, Type: address_t, Size: 20, T: AddressTy, stringKind: "address"}, stringKind: "address[2]"}},
// TODO when fixed types are implemented properly
// {"fixed", nil, Type{}},
// {"fixed128x128", nil, Type{}},
// {"fixed[]", nil, Type{}},
// {"fixed[2]", nil, Type{}},
// {"fixed128x128[]", nil, Type{}},
// {"fixed128x128[2]", nil, Type{}},
{"tuple", []ArgumentMarshaling{{Name: "a", Type: "int64"}}, Type{Kind: reflect.Struct, T: TupleTy, Type: reflect.TypeOf(struct{ A int64 }{}), stringKind: "(int64)",
TupleElems: []*Type{{Kind: reflect.Int64, T: IntTy, Type: reflect.TypeOf(int64(0)), Size: 64, stringKind: "int64"}}, TupleRawNames: []string{"a"}}},
// {"fixed", Type{}},
// {"fixed128x128", Type{}},
// {"fixed[]", Type{}},
// {"fixed[2]", Type{}},
// {"fixed128x128[]", Type{}},
// {"fixed128x128[2]", Type{}},
}
for _, tt := range tests {
typ, err := NewType(tt.blob, tt.components)
typ, err := NewType(tt.blob)
if err != nil {
t.Errorf("type %q: failed to parse type string: %v", tt.blob, err)
}
@ -112,170 +109,151 @@ func TestTypeRegexp(t *testing.T) {
func TestTypeCheck(t *testing.T) {
for i, test := range []struct {
typ string
components []ArgumentMarshaling
input interface{}
err string
typ string
input interface{}
err string
}{
{"uint", nil, big.NewInt(1), "unsupported arg type: uint"},
{"int", nil, big.NewInt(1), "unsupported arg type: int"},
{"uint256", nil, big.NewInt(1), ""},
{"uint256[][3][]", nil, [][3][]*big.Int{{{}}}, ""},
{"uint256[][][3]", nil, [3][][]*big.Int{{{}}}, ""},
{"uint256[3][][]", nil, [][][3]*big.Int{{{}}}, ""},
{"uint256[3][3][3]", nil, [3][3][3]*big.Int{{{}}}, ""},
{"uint8[][]", nil, [][]uint8{}, ""},
{"int256", nil, big.NewInt(1), ""},
{"uint8", nil, uint8(1), ""},
{"uint16", nil, uint16(1), ""},
{"uint32", nil, uint32(1), ""},
{"uint64", nil, uint64(1), ""},
{"int8", nil, int8(1), ""},
{"int16", nil, int16(1), ""},
{"int32", nil, int32(1), ""},
{"int64", nil, int64(1), ""},
{"uint24", nil, big.NewInt(1), ""},
{"uint40", nil, big.NewInt(1), ""},
{"uint48", nil, big.NewInt(1), ""},
{"uint56", nil, big.NewInt(1), ""},
{"uint72", nil, big.NewInt(1), ""},
{"uint80", nil, big.NewInt(1), ""},
{"uint88", nil, big.NewInt(1), ""},
{"uint96", nil, big.NewInt(1), ""},
{"uint104", nil, big.NewInt(1), ""},
{"uint112", nil, big.NewInt(1), ""},
{"uint120", nil, big.NewInt(1), ""},
{"uint128", nil, big.NewInt(1), ""},
{"uint136", nil, big.NewInt(1), ""},
{"uint144", nil, big.NewInt(1), ""},
{"uint152", nil, big.NewInt(1), ""},
{"uint160", nil, big.NewInt(1), ""},
{"uint168", nil, big.NewInt(1), ""},
{"uint176", nil, big.NewInt(1), ""},
{"uint184", nil, big.NewInt(1), ""},
{"uint192", nil, big.NewInt(1), ""},
{"uint200", nil, big.NewInt(1), ""},
{"uint208", nil, big.NewInt(1), ""},
{"uint216", nil, big.NewInt(1), ""},
{"uint224", nil, big.NewInt(1), ""},
{"uint232", nil, big.NewInt(1), ""},
{"uint240", nil, big.NewInt(1), ""},
{"uint248", nil, big.NewInt(1), ""},
{"int24", nil, big.NewInt(1), ""},
{"int40", nil, big.NewInt(1), ""},
{"int48", nil, big.NewInt(1), ""},
{"int56", nil, big.NewInt(1), ""},
{"int72", nil, big.NewInt(1), ""},
{"int80", nil, big.NewInt(1), ""},
{"int88", nil, big.NewInt(1), ""},
{"int96", nil, big.NewInt(1), ""},
{"int104", nil, big.NewInt(1), ""},
{"int112", nil, big.NewInt(1), ""},
{"int120", nil, big.NewInt(1), ""},
{"int128", nil, big.NewInt(1), ""},
{"int136", nil, big.NewInt(1), ""},
{"int144", nil, big.NewInt(1), ""},
{"int152", nil, big.NewInt(1), ""},
{"int160", nil, big.NewInt(1), ""},
{"int168", nil, big.NewInt(1), ""},
{"int176", nil, big.NewInt(1), ""},
{"int184", nil, big.NewInt(1), ""},
{"int192", nil, big.NewInt(1), ""},
{"int200", nil, big.NewInt(1), ""},
{"int208", nil, big.NewInt(1), ""},
{"int216", nil, big.NewInt(1), ""},
{"int224", nil, big.NewInt(1), ""},
{"int232", nil, big.NewInt(1), ""},
{"int240", nil, big.NewInt(1), ""},
{"int248", nil, big.NewInt(1), ""},
{"uint30", nil, uint8(1), "abi: cannot use uint8 as type ptr as argument"},
{"uint8", nil, uint16(1), "abi: cannot use uint16 as type uint8 as argument"},
{"uint8", nil, uint32(1), "abi: cannot use uint32 as type uint8 as argument"},
{"uint8", nil, uint64(1), "abi: cannot use uint64 as type uint8 as argument"},
{"uint8", nil, int8(1), "abi: cannot use int8 as type uint8 as argument"},
{"uint8", nil, int16(1), "abi: cannot use int16 as type uint8 as argument"},
{"uint8", nil, int32(1), "abi: cannot use int32 as type uint8 as argument"},
{"uint8", nil, int64(1), "abi: cannot use int64 as type uint8 as argument"},
{"uint16", nil, uint16(1), ""},
{"uint16", nil, uint8(1), "abi: cannot use uint8 as type uint16 as argument"},
{"uint16[]", nil, []uint16{1, 2, 3}, ""},
{"uint16[]", nil, [3]uint16{1, 2, 3}, ""},
{"uint16[]", nil, []uint32{1, 2, 3}, "abi: cannot use []uint32 as type [0]uint16 as argument"},
{"uint16[3]", nil, [3]uint32{1, 2, 3}, "abi: cannot use [3]uint32 as type [3]uint16 as argument"},
{"uint16[3]", nil, [4]uint16{1, 2, 3}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
{"uint16[3]", nil, []uint16{1, 2, 3}, ""},
{"uint16[3]", nil, []uint16{1, 2, 3, 4}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
{"address[]", nil, []common.Address{{1}}, ""},
{"address[1]", nil, []common.Address{{1}}, ""},
{"address[1]", nil, [1]common.Address{{1}}, ""},
{"address[2]", nil, [1]common.Address{{1}}, "abi: cannot use [1]array as type [2]array as argument"},
{"bytes32", nil, [32]byte{}, ""},
{"bytes31", nil, [31]byte{}, ""},
{"bytes30", nil, [30]byte{}, ""},
{"bytes29", nil, [29]byte{}, ""},
{"bytes28", nil, [28]byte{}, ""},
{"bytes27", nil, [27]byte{}, ""},
{"bytes26", nil, [26]byte{}, ""},
{"bytes25", nil, [25]byte{}, ""},
{"bytes24", nil, [24]byte{}, ""},
{"bytes23", nil, [23]byte{}, ""},
{"bytes22", nil, [22]byte{}, ""},
{"bytes21", nil, [21]byte{}, ""},
{"bytes20", nil, [20]byte{}, ""},
{"bytes19", nil, [19]byte{}, ""},
{"bytes18", nil, [18]byte{}, ""},
{"bytes17", nil, [17]byte{}, ""},
{"bytes16", nil, [16]byte{}, ""},
{"bytes15", nil, [15]byte{}, ""},
{"bytes14", nil, [14]byte{}, ""},
{"bytes13", nil, [13]byte{}, ""},
{"bytes12", nil, [12]byte{}, ""},
{"bytes11", nil, [11]byte{}, ""},
{"bytes10", nil, [10]byte{}, ""},
{"bytes9", nil, [9]byte{}, ""},
{"bytes8", nil, [8]byte{}, ""},
{"bytes7", nil, [7]byte{}, ""},
{"bytes6", nil, [6]byte{}, ""},
{"bytes5", nil, [5]byte{}, ""},
{"bytes4", nil, [4]byte{}, ""},
{"bytes3", nil, [3]byte{}, ""},
{"bytes2", nil, [2]byte{}, ""},
{"bytes1", nil, [1]byte{}, ""},
{"bytes32", nil, [33]byte{}, "abi: cannot use [33]uint8 as type [32]uint8 as argument"},
{"bytes32", nil, common.Hash{1}, ""},
{"bytes31", nil, common.Hash{1}, "abi: cannot use common.Hash as type [31]uint8 as argument"},
{"bytes31", nil, [32]byte{}, "abi: cannot use [32]uint8 as type [31]uint8 as argument"},
{"bytes", nil, []byte{0, 1}, ""},
{"bytes", nil, [2]byte{0, 1}, "abi: cannot use array as type slice as argument"},
{"bytes", nil, common.Hash{1}, "abi: cannot use array as type slice as argument"},
{"string", nil, "hello world", ""},
{"string", nil, string(""), ""},
{"string", nil, []byte{}, "abi: cannot use slice as type string as argument"},
{"bytes32[]", nil, [][32]byte{{}}, ""},
{"function", nil, [24]byte{}, ""},
{"bytes20", nil, common.Address{}, ""},
{"address", nil, [20]byte{}, ""},
{"address", nil, common.Address{}, ""},
{"bytes32[]]", nil, "", "invalid arg type in abi"},
{"invalidType", nil, "", "unsupported arg type: invalidType"},
{"invalidSlice[]", nil, "", "unsupported arg type: invalidSlice"},
// simple tuple
{"tuple", []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256"}}, struct {
A *big.Int
B *big.Int
}{}, ""},
// tuple slice
{"tuple[]", []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256"}}, []struct {
A *big.Int
B *big.Int
}{}, ""},
// tuple array
{"tuple[2]", []ArgumentMarshaling{{Name: "a", Type: "uint256"}, {Name: "b", Type: "uint256"}}, []struct {
A *big.Int
B *big.Int
}{{big.NewInt(0), big.NewInt(0)}, {big.NewInt(0), big.NewInt(0)}}, ""},
{"uint", big.NewInt(1), "unsupported arg type: uint"},
{"int", big.NewInt(1), "unsupported arg type: int"},
{"uint256", big.NewInt(1), ""},
{"uint256[][3][]", [][3][]*big.Int{{{}}}, ""},
{"uint256[][][3]", [3][][]*big.Int{{{}}}, ""},
{"uint256[3][][]", [][][3]*big.Int{{{}}}, ""},
{"uint256[3][3][3]", [3][3][3]*big.Int{{{}}}, ""},
{"uint8[][]", [][]uint8{}, ""},
{"int256", big.NewInt(1), ""},
{"uint8", uint8(1), ""},
{"uint16", uint16(1), ""},
{"uint32", uint32(1), ""},
{"uint64", uint64(1), ""},
{"int8", int8(1), ""},
{"int16", int16(1), ""},
{"int32", int32(1), ""},
{"int64", int64(1), ""},
{"uint24", big.NewInt(1), ""},
{"uint40", big.NewInt(1), ""},
{"uint48", big.NewInt(1), ""},
{"uint56", big.NewInt(1), ""},
{"uint72", big.NewInt(1), ""},
{"uint80", big.NewInt(1), ""},
{"uint88", big.NewInt(1), ""},
{"uint96", big.NewInt(1), ""},
{"uint104", big.NewInt(1), ""},
{"uint112", big.NewInt(1), ""},
{"uint120", big.NewInt(1), ""},
{"uint128", big.NewInt(1), ""},
{"uint136", big.NewInt(1), ""},
{"uint144", big.NewInt(1), ""},
{"uint152", big.NewInt(1), ""},
{"uint160", big.NewInt(1), ""},
{"uint168", big.NewInt(1), ""},
{"uint176", big.NewInt(1), ""},
{"uint184", big.NewInt(1), ""},
{"uint192", big.NewInt(1), ""},
{"uint200", big.NewInt(1), ""},
{"uint208", big.NewInt(1), ""},
{"uint216", big.NewInt(1), ""},
{"uint224", big.NewInt(1), ""},
{"uint232", big.NewInt(1), ""},
{"uint240", big.NewInt(1), ""},
{"uint248", big.NewInt(1), ""},
{"int24", big.NewInt(1), ""},
{"int40", big.NewInt(1), ""},
{"int48", big.NewInt(1), ""},
{"int56", big.NewInt(1), ""},
{"int72", big.NewInt(1), ""},
{"int80", big.NewInt(1), ""},
{"int88", big.NewInt(1), ""},
{"int96", big.NewInt(1), ""},
{"int104", big.NewInt(1), ""},
{"int112", big.NewInt(1), ""},
{"int120", big.NewInt(1), ""},
{"int128", big.NewInt(1), ""},
{"int136", big.NewInt(1), ""},
{"int144", big.NewInt(1), ""},
{"int152", big.NewInt(1), ""},
{"int160", big.NewInt(1), ""},
{"int168", big.NewInt(1), ""},
{"int176", big.NewInt(1), ""},
{"int184", big.NewInt(1), ""},
{"int192", big.NewInt(1), ""},
{"int200", big.NewInt(1), ""},
{"int208", big.NewInt(1), ""},
{"int216", big.NewInt(1), ""},
{"int224", big.NewInt(1), ""},
{"int232", big.NewInt(1), ""},
{"int240", big.NewInt(1), ""},
{"int248", big.NewInt(1), ""},
{"uint30", uint8(1), "abi: cannot use uint8 as type ptr as argument"},
{"uint8", uint16(1), "abi: cannot use uint16 as type uint8 as argument"},
{"uint8", uint32(1), "abi: cannot use uint32 as type uint8 as argument"},
{"uint8", uint64(1), "abi: cannot use uint64 as type uint8 as argument"},
{"uint8", int8(1), "abi: cannot use int8 as type uint8 as argument"},
{"uint8", int16(1), "abi: cannot use int16 as type uint8 as argument"},
{"uint8", int32(1), "abi: cannot use int32 as type uint8 as argument"},
{"uint8", int64(1), "abi: cannot use int64 as type uint8 as argument"},
{"uint16", uint16(1), ""},
{"uint16", uint8(1), "abi: cannot use uint8 as type uint16 as argument"},
{"uint16[]", []uint16{1, 2, 3}, ""},
{"uint16[]", [3]uint16{1, 2, 3}, ""},
{"uint16[]", []uint32{1, 2, 3}, "abi: cannot use []uint32 as type [0]uint16 as argument"},
{"uint16[3]", [3]uint32{1, 2, 3}, "abi: cannot use [3]uint32 as type [3]uint16 as argument"},
{"uint16[3]", [4]uint16{1, 2, 3}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
{"uint16[3]", []uint16{1, 2, 3}, ""},
{"uint16[3]", []uint16{1, 2, 3, 4}, "abi: cannot use [4]uint16 as type [3]uint16 as argument"},
{"address[]", []common.Address{{1}}, ""},
{"address[1]", []common.Address{{1}}, ""},
{"address[1]", [1]common.Address{{1}}, ""},
{"address[2]", [1]common.Address{{1}}, "abi: cannot use [1]array as type [2]array as argument"},
{"bytes32", [32]byte{}, ""},
{"bytes31", [31]byte{}, ""},
{"bytes30", [30]byte{}, ""},
{"bytes29", [29]byte{}, ""},
{"bytes28", [28]byte{}, ""},
{"bytes27", [27]byte{}, ""},
{"bytes26", [26]byte{}, ""},
{"bytes25", [25]byte{}, ""},
{"bytes24", [24]byte{}, ""},
{"bytes23", [23]byte{}, ""},
{"bytes22", [22]byte{}, ""},
{"bytes21", [21]byte{}, ""},
{"bytes20", [20]byte{}, ""},
{"bytes19", [19]byte{}, ""},
{"bytes18", [18]byte{}, ""},
{"bytes17", [17]byte{}, ""},
{"bytes16", [16]byte{}, ""},
{"bytes15", [15]byte{}, ""},
{"bytes14", [14]byte{}, ""},
{"bytes13", [13]byte{}, ""},
{"bytes12", [12]byte{}, ""},
{"bytes11", [11]byte{}, ""},
{"bytes10", [10]byte{}, ""},
{"bytes9", [9]byte{}, ""},
{"bytes8", [8]byte{}, ""},
{"bytes7", [7]byte{}, ""},
{"bytes6", [6]byte{}, ""},
{"bytes5", [5]byte{}, ""},
{"bytes4", [4]byte{}, ""},
{"bytes3", [3]byte{}, ""},
{"bytes2", [2]byte{}, ""},
{"bytes1", [1]byte{}, ""},
{"bytes32", [33]byte{}, "abi: cannot use [33]uint8 as type [32]uint8 as argument"},
{"bytes32", common.Hash{1}, ""},
{"bytes31", common.Hash{1}, "abi: cannot use common.Hash as type [31]uint8 as argument"},
{"bytes31", [32]byte{}, "abi: cannot use [32]uint8 as type [31]uint8 as argument"},
{"bytes", []byte{0, 1}, ""},
{"bytes", [2]byte{0, 1}, "abi: cannot use array as type slice as argument"},
{"bytes", common.Hash{1}, "abi: cannot use array as type slice as argument"},
{"string", "hello world", ""},
{"string", string(""), ""},
{"string", []byte{}, "abi: cannot use slice as type string as argument"},
{"bytes32[]", [][32]byte{{}}, ""},
{"function", [24]byte{}, ""},
{"bytes20", common.Address{}, ""},
{"address", [20]byte{}, ""},
{"address", common.Address{}, ""},
} {
typ, err := NewType(test.typ, test.components)
typ, err := NewType(test.typ)
if err != nil && len(test.err) == 0 {
t.Fatal("unexpected parse error:", err)
} else if err != nil && len(test.err) != 0 {

View file

@ -22,20 +22,11 @@ import (
"math/big"
"reflect"
"github.com/ethereum/go-ethereum/common"
)
var (
maxUint256 = big.NewInt(0).Add(
big.NewInt(0).Exp(big.NewInt(2), big.NewInt(256), nil),
big.NewInt(-1))
maxInt256 = big.NewInt(0).Add(
big.NewInt(0).Exp(big.NewInt(2), big.NewInt(255), nil),
big.NewInt(-1))
"github.com/XinFinOrg/XDPoSChain/common"
)
// reads the integer based on its kind
func readInteger(typ byte, kind reflect.Kind, b []byte) interface{} {
func readInteger(kind reflect.Kind, b []byte) interface{} {
switch kind {
case reflect.Uint8:
return b[len(b)-1]
@ -54,20 +45,7 @@ func readInteger(typ byte, kind reflect.Kind, b []byte) interface{} {
case reflect.Int64:
return int64(binary.BigEndian.Uint64(b[len(b)-8:]))
default:
// the only case lefts for integer is int256/uint256.
// big.SetBytes can't tell if a number is negative, positive on itself.
// On EVM, if the returned number > max int256, it is negative.
ret := new(big.Int).SetBytes(b)
if typ == UintTy {
return ret
}
if ret.Cmp(maxInt256) > 0 {
ret.Add(maxUint256, big.NewInt(0).Neg(ret))
ret.Add(ret, big.NewInt(1))
ret.Neg(ret)
}
return ret
return new(big.Int).SetBytes(b)
}
}
@ -115,6 +93,17 @@ func readFixedBytes(t Type, word []byte) (interface{}, error) {
}
func getFullElemSize(elem *Type) int {
//all other should be counted as 32 (slices have pointers to respective elements)
size := 32
//arrays wrap it, each element being the same size
for elem.T == ArrayTy {
size *= elem.Size
elem = elem.Elem
}
return size
}
// iteratively unpack elements
func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error) {
if size < 0 {
@ -139,9 +128,13 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error)
// Arrays have packed elements, resulting in longer unpack steps.
// Slices have just 32 bytes per element (pointing to the contents).
elemSize := getTypeSize(*t.Elem)
elemSize := 32
if t.T == ArrayTy {
elemSize = getFullElemSize(t.Elem)
}
for i, j := start, 0; j < size; i, j = i+elemSize, j+1 {
inter, err := toGoType(i, *t.Elem, output)
if err != nil {
return nil, err
@ -155,36 +148,6 @@ func forEachUnpack(t Type, output []byte, start, size int) (interface{}, error)
return refSlice.Interface(), nil
}
func forTupleUnpack(t Type, output []byte) (interface{}, error) {
retval := reflect.New(t.Type).Elem()
virtualArgs := 0
for index, elem := range t.TupleElems {
marshalledValue, err := toGoType((index+virtualArgs)*32, *elem, output)
if elem.T == ArrayTy && !isDynamicType(*elem) {
// If we have a static array, like [3]uint256, these are coded as
// just like uint256,uint256,uint256.
// This means that we need to add two 'virtual' arguments when
// we count the index from now on.
//
// Array values nested multiple levels deep are also encoded inline:
// [2][3]uint256: uint256,uint256,uint256,uint256,uint256,uint256
//
// Calculate the full array size to get the correct offset for the next argument.
// Decrement it by 1, as the normal index increment is still applied.
virtualArgs += getTypeSize(*elem)/32 - 1
} else if elem.T == TupleTy && !isDynamicType(*elem) {
// If we have a static tuple, like (uint256, bool, uint256), these are
// coded as just like uint256,bool,uint256
virtualArgs += getTypeSize(*elem)/32 - 1
}
if err != nil {
return nil, err
}
retval.Field(index).Set(reflect.ValueOf(marshalledValue))
}
return retval.Interface(), nil
}
// toGoType parses the output bytes and recursively assigns the value of these bytes
// into a go type with accordance with the ABI spec.
func toGoType(index int, t Type, output []byte) (interface{}, error) {
@ -193,14 +156,14 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
}
var (
returnOutput []byte
begin, length int
err error
returnOutput []byte
begin, end int
err error
)
// if we require a length prefix, find the beginning word and size returned.
if t.requiresLengthPrefix() {
begin, length, err = lengthPrefixPointsTo(index, output)
begin, end, err = lengthPrefixPointsTo(index, output)
if err != nil {
return nil, err
}
@ -209,28 +172,14 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
}
switch t.T {
case TupleTy:
if isDynamicType(t) {
begin, err := tuplePointsTo(index, output)
if err != nil {
return nil, err
}
return forTupleUnpack(t, output[begin:])
} else {
return forTupleUnpack(t, output[index:])
}
case SliceTy:
return forEachUnpack(t, output[begin:], 0, length)
return forEachUnpack(t, output, begin, end)
case ArrayTy:
if isDynamicType(*t.Elem) {
offset := int64(binary.BigEndian.Uint64(returnOutput[len(returnOutput)-8:]))
return forEachUnpack(t, output[offset:], 0, t.Size)
}
return forEachUnpack(t, output[index:], 0, t.Size)
return forEachUnpack(t, output, index, t.Size)
case StringTy: // variable arrays are written at the end of the return bytes
return string(output[begin : begin+length]), nil
return string(output[begin : begin+end]), nil
case IntTy, UintTy:
return readInteger(t.T, t.Kind, returnOutput), nil
return readInteger(t.Kind, returnOutput), nil
case BoolTy:
return readBool(returnOutput)
case AddressTy:
@ -238,7 +187,7 @@ func toGoType(index int, t Type, output []byte) (interface{}, error) {
case HashTy:
return common.BytesToHash(returnOutput), nil
case BytesTy:
return output[begin : begin+length], nil
return output[begin : begin+end], nil
case FixedBytesTy:
return readFixedBytes(t, returnOutput)
case FunctionTy:
@ -279,17 +228,3 @@ func lengthPrefixPointsTo(index int, output []byte) (start int, length int, err
length = int(lengthBig.Uint64())
return
}
// tuplePointsTo resolves the location reference for dynamic tuple.
func tuplePointsTo(index int, output []byte) (start int, err error) {
offset := big.NewInt(0).SetBytes(output[index : index+32])
outputLen := big.NewInt(int64(len(output)))
if offset.Cmp(big.NewInt(int64(len(output)))) > 0 {
return 0, fmt.Errorf("abi: cannot marshal in to go slice: offset %v would go over slice boundary (len=%v)", offset, outputLen)
}
if offset.BitLen() > 63 {
return 0, fmt.Errorf("abi offset larger than int64: %v", offset)
}
return int(offset.Uint64()), nil
}

View file

@ -26,7 +26,7 @@ import (
"strings"
"testing"
"github.com/ethereum/go-ethereum/common"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/stretchr/testify/require"
)
@ -56,23 +56,6 @@ var unpackTests = []unpackTest{
enc: "0000000000000000000000000000000000000000000000000000000000000001",
want: true,
},
{
def: `[{ "type": "bool" }]`,
enc: "0000000000000000000000000000000000000000000000000000000000000000",
want: false,
},
{
def: `[{ "type": "bool" }]`,
enc: "0000000000000000000000000000000000000000000000000001000000000001",
want: false,
err: "abi: improperly encoded boolean value",
},
{
def: `[{ "type": "bool" }]`,
enc: "0000000000000000000000000000000000000000000000000000000000000003",
want: false,
err: "abi: improperly encoded boolean value",
},
{
def: `[{"type": "uint32"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000001",
@ -117,11 +100,6 @@ var unpackTests = []unpackTest{
enc: "0000000000000000000000000000000000000000000000000000000000000001",
want: big.NewInt(1),
},
{
def: `[{"type": "int256"}]`,
enc: "ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff",
want: big.NewInt(-1),
},
{
def: `[{"type": "address"}]`,
enc: "0000000000000000000000000100000000000000000000000000000000000000",
@ -173,14 +151,9 @@ var unpackTests = []unpackTest{
// multi dimensional, if these pass, all types that don't require length prefix should pass
{
def: `[{"type": "uint8[][]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000E0000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: [][]uint8{{1, 2}, {1, 2}},
},
{
def: `[{"type": "uint8[][]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000a00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003",
want: [][]uint8{{1, 2}, {1, 2, 3}},
},
{
def: `[{"type": "uint8[2][2]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
@ -188,7 +161,7 @@ var unpackTests = []unpackTest{
},
{
def: `[{"type": "uint8[][2]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001",
enc: "000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000001",
want: [2][]uint8{{1}, {1}},
},
{
@ -196,11 +169,6 @@ var unpackTests = []unpackTest{
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: [][2]uint8{{1, 2}},
},
{
def: `[{"type": "uint8[2][]"}]`,
enc: "000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: [][2]uint8{{1, 2}, {1, 2}},
},
{
def: `[{"type": "uint16[]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
@ -246,26 +214,6 @@ var unpackTests = []unpackTest{
enc: "000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000003",
want: [3]*big.Int{big.NewInt(1), big.NewInt(2), big.NewInt(3)},
},
{
def: `[{"type": "string[4]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000008000000000000000000000000000000000000000000000000000000000000000c000000000000000000000000000000000000000000000000000000000000001000000000000000000000000000000000000000000000000000000000000000140000000000000000000000000000000000000000000000000000000000000000548656c6c6f0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000005576f726c64000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b476f2d657468657265756d0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000",
want: [4]string{"Hello", "World", "Go-ethereum", "Ethereum"},
},
{
def: `[{"type": "string[]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000b676f2d657468657265756d000000000000000000000000000000000000000000",
want: []string{"Ethereum", "go-ethereum"},
},
{
def: `[{"type": "bytes[]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000003f0f0f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000003f0f0f00000000000000000000000000000000000000000000000000000000000",
want: [][]byte{{0xf0, 0xf0, 0xf0}, {0xf0, 0xf0, 0xf0}},
},
{
def: `[{"type": "uint256[2][][]"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000e00000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000003e80000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000c8000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000003e8",
want: [][][2]*big.Int{{{big.NewInt(1), big.NewInt(200)}, {big.NewInt(1), big.NewInt(1000)}}, {{big.NewInt(1), big.NewInt(200)}, {big.NewInt(1), big.NewInt(1000)}}},
},
{
def: `[{"type": "int8[]"}]`,
enc: "0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000200000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
@ -325,53 +273,6 @@ var unpackTests = []unpackTest{
Int2 *big.Int
}{big.NewInt(1), big.NewInt(2)},
},
{
def: `[{"name":"int_one","type":"int256"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: struct {
IntOne *big.Int
}{big.NewInt(1)},
},
{
def: `[{"name":"int__one","type":"int256"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: struct {
IntOne *big.Int
}{big.NewInt(1)},
},
{
def: `[{"name":"int_one_","type":"int256"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: struct {
IntOne *big.Int
}{big.NewInt(1)},
},
{
def: `[{"name":"int_one","type":"int256"}, {"name":"intone","type":"int256"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: struct {
IntOne *big.Int
Intone *big.Int
}{big.NewInt(1), big.NewInt(2)},
},
{
def: `[{"name":"___","type":"int256"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: struct {
IntOne *big.Int
Intone *big.Int
}{},
err: "abi: purely underscored output cannot unpack to struct",
},
{
def: `[{"name":"int_one","type":"int256"},{"name":"IntOne","type":"int256"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
want: struct {
Int1 *big.Int
Int2 *big.Int
}{},
err: "abi: multiple outputs mapping to the same struct field 'IntOne'",
},
{
def: `[{"name":"int","type":"int256"},{"name":"Int","type":"int256"}]`,
enc: "00000000000000000000000000000000000000000000000000000000000000010000000000000000000000000000000000000000000000000000000000000002",
@ -436,55 +337,6 @@ func TestUnpack(t *testing.T) {
}
}
func TestUnpackSetDynamicArrayOutput(t *testing.T) {
abi, err := JSON(strings.NewReader(`[{"constant":true,"inputs":[],"name":"testDynamicFixedBytes15","outputs":[{"name":"","type":"bytes15[]"}],"payable":false,"stateMutability":"view","type":"function"},{"constant":true,"inputs":[],"name":"testDynamicFixedBytes32","outputs":[{"name":"","type":"bytes32[]"}],"payable":false,"stateMutability":"view","type":"function"}]`))
if err != nil {
t.Fatal(err)
}
var (
marshalledReturn32 = common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000230783132333435363738393000000000000000000000000000000000000000003078303938373635343332310000000000000000000000000000000000000000")
marshalledReturn15 = common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000000000000000000000000000000230783031323334350000000000000000000000000000000000000000000000003078393837363534000000000000000000000000000000000000000000000000")
out32 [][32]byte
out15 [][15]byte
)
// test 32
err = abi.Unpack(&out32, "testDynamicFixedBytes32", marshalledReturn32)
if err != nil {
t.Fatal(err)
}
if len(out32) != 2 {
t.Fatalf("expected array with 2 values, got %d", len(out32))
}
expected := common.Hex2Bytes("3078313233343536373839300000000000000000000000000000000000000000")
if !bytes.Equal(out32[0][:], expected) {
t.Errorf("expected %x, got %x\n", expected, out32[0])
}
expected = common.Hex2Bytes("3078303938373635343332310000000000000000000000000000000000000000")
if !bytes.Equal(out32[1][:], expected) {
t.Errorf("expected %x, got %x\n", expected, out32[1])
}
// test 15
err = abi.Unpack(&out15, "testDynamicFixedBytes32", marshalledReturn15)
if err != nil {
t.Fatal(err)
}
if len(out15) != 2 {
t.Fatalf("expected array with 2 values, got %d", len(out15))
}
expected = common.Hex2Bytes("307830313233343500000000000000")
if !bytes.Equal(out15[0][:], expected) {
t.Errorf("expected %x, got %x\n", expected, out15[0])
}
expected = common.Hex2Bytes("307839383736353400000000000000")
if !bytes.Equal(out15[1][:], expected) {
t.Errorf("expected %x, got %x\n", expected, out15[1])
}
}
type methodMultiOutput struct {
Int *big.Int
String string
@ -588,68 +440,6 @@ func TestMultiReturnWithArray(t *testing.T) {
}
}
func TestMultiReturnWithStringArray(t *testing.T) {
const definition = `[{"name" : "multi", "outputs": [{"name": "","type": "uint256[3]"},{"name": "","type": "address"},{"name": "","type": "string[2]"},{"name": "","type": "bool"}]}]`
abi, err := JSON(strings.NewReader(definition))
if err != nil {
t.Fatal(err)
}
buff := new(bytes.Buffer)
buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000005c1b78ea0000000000000000000000000000000000000000000000000000000000000006000000000000000000000000000000000000000000000001a055690d9db80000000000000000000000000000ab1257528b3782fb40d7ed5f72e624b744dffb2f00000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000008457468657265756d000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001048656c6c6f2c20457468657265756d2100000000000000000000000000000000"))
temp, _ := big.NewInt(0).SetString("30000000000000000000", 10)
ret1, ret1Exp := new([3]*big.Int), [3]*big.Int{big.NewInt(1545304298), big.NewInt(6), temp}
ret2, ret2Exp := new(common.Address), common.HexToAddress("ab1257528b3782fb40d7ed5f72e624b744dffb2f")
ret3, ret3Exp := new([2]string), [2]string{"Ethereum", "Hello, Ethereum!"}
ret4, ret4Exp := new(bool), false
if err := abi.Unpack(&[]interface{}{ret1, ret2, ret3, ret4}, "multi", buff.Bytes()); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(*ret1, ret1Exp) {
t.Error("big.Int array result", *ret1, "!= Expected", ret1Exp)
}
if !reflect.DeepEqual(*ret2, ret2Exp) {
t.Error("address result", *ret2, "!= Expected", ret2Exp)
}
if !reflect.DeepEqual(*ret3, ret3Exp) {
t.Error("string array result", *ret3, "!= Expected", ret3Exp)
}
if !reflect.DeepEqual(*ret4, ret4Exp) {
t.Error("bool result", *ret4, "!= Expected", ret4Exp)
}
}
func TestMultiReturnWithStringSlice(t *testing.T) {
const definition = `[{"name" : "multi", "outputs": [{"name": "","type": "string[]"},{"name": "","type": "uint256[]"}]}]`
abi, err := JSON(strings.NewReader(definition))
if err != nil {
t.Fatal(err)
}
buff := new(bytes.Buffer)
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) // output[0] offset
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000120")) // output[1] offset
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // output[0] length
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000040")) // output[0][0] offset
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000080")) // output[0][1] offset
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000008")) // output[0][0] length
buff.Write(common.Hex2Bytes("657468657265756d000000000000000000000000000000000000000000000000")) // output[0][0] value
buff.Write(common.Hex2Bytes("000000000000000000000000000000000000000000000000000000000000000b")) // output[0][1] length
buff.Write(common.Hex2Bytes("676f2d657468657265756d000000000000000000000000000000000000000000")) // output[0][1] value
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // output[1] length
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000064")) // output[1][0] value
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000065")) // output[1][1] value
ret1, ret1Exp := new([]string), []string{"ethereum", "go-ethereum"}
ret2, ret2Exp := new([]*big.Int), []*big.Int{big.NewInt(100), big.NewInt(101)}
if err := abi.Unpack(&[]interface{}{ret1, ret2}, "multi", buff.Bytes()); err != nil {
t.Fatal(err)
}
if !reflect.DeepEqual(*ret1, ret1Exp) {
t.Error("string slice result", *ret1, "!= Expected", ret1Exp)
}
if !reflect.DeepEqual(*ret2, ret2Exp) {
t.Error("uint256 slice result", *ret2, "!= Expected", ret2Exp)
}
}
func TestMultiReturnWithDeeplyNestedArray(t *testing.T) {
// Similar to TestMultiReturnWithArray, but with a special case in mind:
// values of nested static arrays count towards the size as well, and any element following
@ -939,108 +729,6 @@ func TestUnmarshal(t *testing.T) {
}
}
func TestUnpackTuple(t *testing.T) {
const simpleTuple = `[{"name":"tuple","constant":false,"outputs":[{"type":"tuple","name":"ret","components":[{"type":"int256","name":"a"},{"type":"int256","name":"b"}]}]}]`
abi, err := JSON(strings.NewReader(simpleTuple))
if err != nil {
t.Fatal(err)
}
buff := new(bytes.Buffer)
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // ret[a] = 1
buff.Write(common.Hex2Bytes("ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff")) // ret[b] = -1
v := struct {
Ret struct {
A *big.Int
B *big.Int
}
}{Ret: struct {
A *big.Int
B *big.Int
}{new(big.Int), new(big.Int)}}
err = abi.Unpack(&v, "tuple", buff.Bytes())
if err != nil {
t.Error(err)
} else {
if v.Ret.A.Cmp(big.NewInt(1)) != 0 {
t.Errorf("unexpected value unpacked: want %x, got %x", 1, v.Ret.A)
}
if v.Ret.B.Cmp(big.NewInt(-1)) != 0 {
t.Errorf("unexpected value unpacked: want %x, got %x", v.Ret.B, -1)
}
}
// Test nested tuple
const nestedTuple = `[{"name":"tuple","constant":false,"outputs":[
{"type":"tuple","name":"s","components":[{"type":"uint256","name":"a"},{"type":"uint256[]","name":"b"},{"type":"tuple[]","name":"c","components":[{"name":"x", "type":"uint256"},{"name":"y","type":"uint256"}]}]},
{"type":"tuple","name":"t","components":[{"name":"x", "type":"uint256"},{"name":"y","type":"uint256"}]},
{"type":"uint256","name":"a"}
]}]`
abi, err = JSON(strings.NewReader(nestedTuple))
if err != nil {
t.Fatal(err)
}
buff.Reset()
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000080")) // s offset
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000000")) // t.X = 0
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // t.Y = 1
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // a = 1
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // s.A = 1
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000060")) // s.B offset
buff.Write(common.Hex2Bytes("00000000000000000000000000000000000000000000000000000000000000c0")) // s.C offset
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.B length
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // s.B[0] = 1
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.B[0] = 2
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.C length
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // s.C[0].X
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.C[0].Y
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000002")) // s.C[1].X
buff.Write(common.Hex2Bytes("0000000000000000000000000000000000000000000000000000000000000001")) // s.C[1].Y
type T struct {
X *big.Int `abi:"x"`
Z *big.Int `abi:"y"` // Test whether the abi tag works.
}
type S struct {
A *big.Int
B []*big.Int
C []T
}
type Ret struct {
FieldS S `abi:"s"`
FieldT T `abi:"t"`
A *big.Int
}
var ret Ret
var expected = Ret{
FieldS: S{
A: big.NewInt(1),
B: []*big.Int{big.NewInt(1), big.NewInt(2)},
C: []T{
{big.NewInt(1), big.NewInt(2)},
{big.NewInt(2), big.NewInt(1)},
},
},
FieldT: T{
big.NewInt(0), big.NewInt(1),
},
A: big.NewInt(1),
}
err = abi.Unpack(&ret, "tuple", buff.Bytes())
if err != nil {
t.Error(err)
}
if reflect.DeepEqual(ret, expected) {
t.Error("unexpected unpack value")
}
}
func TestOOMMaliciousInput(t *testing.T) {
oomTests := []unpackTest{
{

View file

@ -20,10 +20,10 @@ package accounts
import (
"math/big"
ethereum "github.com/ethereum/go-ethereum"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/core/types"
"github.com/ethereum/go-ethereum/event"
ethereum "github.com/XinFinOrg/XDPoSChain"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/core/types"
"github.com/XinFinOrg/XDPoSChain/event"
)
// Account represents an Ethereum account located at a specific location defined
@ -106,7 +106,7 @@ type Wallet interface {
// or optionally with the aid of any location metadata from the embedded URL field.
//
// If the wallet requires additional authentication to sign the request (e.g.
// a password to decrypt the account, or a PIN code to verify the transaction),
// a password to decrypt the account, or a PIN code o verify the transaction),
// an AuthNeededError instance will be returned, containing infos for the user
// about which fields or actions are needed. The user may retry by providing
// the needed details via SignTxWithPassphrase, or by other means (e.g. unlock

View file

@ -30,8 +30,8 @@ import (
var DefaultRootDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0}
// DefaultBaseDerivationPath is the base path from which custom derivation endpoints
// are incremented. As such, the first account will be at m/44'/60'/0'/0/0, the second
// at m/44'/60'/0'/0/1, etc.
// are incremented. As such, the first account will be at m/44'/60'/0'/0, the second
// at m/44'/60'/0'/1, etc.
var DefaultBaseDerivationPath = DerivationPath{0x80000000 + 44, 0x80000000 + 60, 0x80000000 + 0, 0, 0}
// DefaultLedgerBaseDerivationPath is the base path from which custom derivation endpoints

View file

@ -27,10 +27,10 @@ import (
"sync"
"time"
"github.com/XinFinOrg/XDPoSChain/accounts"
"github.com/XinFinOrg/XDPoSChain/common"
"github.com/XinFinOrg/XDPoSChain/log"
mapset "github.com/deckarep/golang-set"
"github.com/ethereum/go-ethereum/accounts"
"github.com/ethereum/go-ethereum/common"
"github.com/ethereum/go-ethereum/log"
)
// Minimum amount of time between cache reloads. This limit applies if the platform does
@ -265,10 +265,7 @@ func (ac *accountCache) scanAccounts() error {
case (addr == common.Address{}):
log.Debug("Failed to decode keystore key", "path", path, "err", "missing or zero address")
default:
return &accounts.Account{
Address: addr,
URL: accounts.URL{Scheme: KeyStoreScheme, Path: path},
}
return &accounts.Account{Address: addr, URL: accounts.URL{Scheme: KeyStoreScheme, Path: path}}
}
return nil
}

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