mirror of
https://github.com/ethereum/go-ethereum.git
synced 2026-06-25 16:06:18 +00:00
Merge pull request #138 from hash-laboratories-au/hashlab-master
Hashlab master
This commit is contained in:
commit
2869165165
2856 changed files with 136842 additions and 937206 deletions
1
.gitattributes
vendored
1
.gitattributes
vendored
|
|
@ -1,3 +1,2 @@
|
|||
# Auto detect text files and perform LF normalization
|
||||
* text=auto
|
||||
*.sol linguist-language=Solidity
|
||||
|
|
|
|||
19
.github/CODEOWNERS
vendored
19
.github/CODEOWNERS
vendored
|
|
@ -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
|
||||
|
|
|
|||
46
.github/CONTRIBUTING.md
vendored
46
.github/CONTRIBUTING.md
vendored
|
|
@ -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.
|
||||
|
|
|
|||
6
.github/no-response.yml
vendored
6
.github/no-response.yml
vendored
|
|
@ -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
2
.github/stale.yml
vendored
|
|
@ -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
6
.gitignore
vendored
|
|
@ -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
|
||||
|
|
@ -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
123
.mailmap
|
|
@ -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>
|
||||
224
.travis.yml
224
.travis.yml
|
|
@ -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
174
AUTHORS
|
|
@ -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>
|
||||
14
Dockerfile
14
Dockerfile
|
|
@ -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
|
||||
|
||||
|
|
|
|||
|
|
@ -1,19 +1,19 @@
|
|||
FROM golang:1.10-alpine as builder
|
||||
FROM golang:1.11-alpine as builder
|
||||
|
||||
RUN apk add --no-cache make gcc musl-dev linux-headers
|
||||
|
||||
ADD . /XDCchain
|
||||
RUN cd /XDCchain && make bootnode
|
||||
ADD . /XDPoSChain
|
||||
RUN cd /XDPoSChain && make bootnode
|
||||
|
||||
RUN chmod +x /XDCchain/build/bin/bootnode
|
||||
RUN chmod +x /XDPoSChain/build/bin/bootnode
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
LABEL maintainer="anil@xinfin.org"
|
||||
LABEL maintainer="etienne@XDPoSChain.com"
|
||||
|
||||
WORKDIR /XDCchain
|
||||
WORKDIR /XDPoSChain
|
||||
|
||||
COPY --from=builder /XDCchain/build/bin/bootnode /usr/local/bin/bootnode
|
||||
COPY --from=builder /XDPoSChain/build/bin/bootnode /usr/local/bin/bootnode
|
||||
|
||||
COPY docker/bootnode ./
|
||||
|
||||
|
|
|
|||
|
|
@ -1,20 +1,18 @@
|
|||
FROM golang:1.10-alpine as builder
|
||||
FROM golang:1.12-alpine as builder
|
||||
|
||||
RUN apk add --no-cache make gcc musl-dev linux-headers
|
||||
RUN apk add --no-cache make gcc musl-dev linux-headers git
|
||||
|
||||
ADD . /XDCchain
|
||||
ADD . /XDPoSChain
|
||||
|
||||
RUN cd /XDCchain \
|
||||
RUN cd /XDPoSChain \
|
||||
&& make XDC \
|
||||
&& chmod +x /XDCchain/build/bin/XDC
|
||||
&& chmod +x /XDPoSChain/build/bin/XDC
|
||||
|
||||
FROM alpine:latest
|
||||
|
||||
LABEL maintainer="anil@xinfin.org"
|
||||
WORKDIR /XDPoSChain
|
||||
|
||||
WORKDIR /XDCchain
|
||||
|
||||
COPY --from=builder /XDCchain/build/bin/XDC /usr/local/bin/XDC
|
||||
COPY --from=builder /XDPoSChain/build/bin/XDC /usr/local/bin/XDC
|
||||
|
||||
ENV IDENTITY ''
|
||||
ENV PASSWORD ''
|
||||
|
|
@ -23,7 +21,7 @@ ENV BOOTNODES ''
|
|||
ENV EXTIP ''
|
||||
ENV VERBOSITY 3
|
||||
ENV SYNC_MODE 'full'
|
||||
ENV NETWORK_ID '89'
|
||||
ENV NETWORK_ID '88'
|
||||
ENV WS_SECRET ''
|
||||
ENV NETSTATS_HOST 'netstats-server'
|
||||
ENV NETSTATS_PORT '3000'
|
||||
|
|
@ -31,7 +29,7 @@ ENV ANNOUNCE_TXS ''
|
|||
|
||||
RUN apk add --no-cache ca-certificates
|
||||
|
||||
COPY docker/XDCchain ./
|
||||
COPY docker/XDPoSChain ./
|
||||
COPY genesis/ ./
|
||||
|
||||
EXPOSE 8545 8546 30303 30303/udp
|
||||
|
|
|
|||
40
Makefile
40
Makefile
|
|
@ -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
|
||||
|
|
|
|||
10
README.md
10
README.md
|
|
@ -1,9 +1 @@
|
|||
## XinFin
|
||||
|
||||
Blockchain for decentralized applications, token issuance and integration
|
||||
|
||||
Website Resource : https://xinFin.org
|
||||
|
||||
XinFin Mainet URL: http://XinFin.network/
|
||||
|
||||
XinFin TestNet/Apothem URL: http://apothem.network/
|
||||
# XDPoSChain
|
||||
|
|
|
|||
663
XDCx/XDCx.go
Normal file
663
XDCx/XDCx.go
Normal file
|
|
@ -0,0 +1,663 @@
|
|||
package XDCx
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
|
||||
"github.com/XinFinOrg/XDPoSChain/XDCxDAO"
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/p2p"
|
||||
"gopkg.in/karalabe/cookiejar.v2/collections/prque"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/state"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/rpc"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"golang.org/x/sync/syncmap"
|
||||
)
|
||||
|
||||
const (
|
||||
ProtocolName = "XDCx"
|
||||
ProtocolVersion = uint64(1)
|
||||
ProtocolVersionStr = "1.0"
|
||||
overflowIdx // Indicator of message queue overflow
|
||||
defaultCacheLimit = 1024
|
||||
MaximumTxMatchSize = 1000
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNonceTooHigh = errors.New("nonce too high")
|
||||
ErrNonceTooLow = errors.New("nonce too low")
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
DataDir string `toml:",omitempty"`
|
||||
DBEngine string `toml:",omitempty"`
|
||||
DBName string `toml:",omitempty"`
|
||||
ConnectionUrl string `toml:",omitempty"`
|
||||
ReplicaSetName string `toml:",omitempty"`
|
||||
}
|
||||
|
||||
// DefaultConfig represents (shocker!) the default configuration.
|
||||
var DefaultConfig = Config{
|
||||
DataDir: "",
|
||||
}
|
||||
|
||||
type XDCX struct {
|
||||
// Order related
|
||||
db XDCxDAO.XDCXDAO
|
||||
mongodb XDCxDAO.XDCXDAO
|
||||
Triegc *prque.Prque // Priority queue mapping block numbers to tries to gc
|
||||
StateCache tradingstate.Database // State database to reuse between imports (contains state cache) *XDCx_state.TradingStateDB
|
||||
|
||||
orderNonce map[common.Address]*big.Int
|
||||
|
||||
sdkNode bool
|
||||
settings syncmap.Map // holds configuration settings that can be dynamically changed
|
||||
tokenDecimalCache *lru.Cache
|
||||
orderCache *lru.Cache
|
||||
}
|
||||
|
||||
func (XDCx *XDCX) Protocols() []p2p.Protocol {
|
||||
return []p2p.Protocol{}
|
||||
}
|
||||
|
||||
func (XDCx *XDCX) Start(server *p2p.Server) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (XDCx *XDCX) SaveData() {
|
||||
}
|
||||
func (XDCx *XDCX) Stop() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func NewLDBEngine(cfg *Config) *XDCxDAO.BatchDatabase {
|
||||
datadir := cfg.DataDir
|
||||
batchDB := XDCxDAO.NewBatchDatabaseWithEncode(datadir, 0)
|
||||
return batchDB
|
||||
}
|
||||
|
||||
func NewMongoDBEngine(cfg *Config) *XDCxDAO.MongoDatabase {
|
||||
mongoDB, err := XDCxDAO.NewMongoDatabase(nil, cfg.DBName, cfg.ConnectionUrl, cfg.ReplicaSetName, 0)
|
||||
|
||||
if err != nil {
|
||||
log.Crit("Failed to init mongodb engine", "err", err)
|
||||
}
|
||||
|
||||
return mongoDB
|
||||
}
|
||||
|
||||
func New(cfg *Config) *XDCX {
|
||||
tokenDecimalCache, _ := lru.New(defaultCacheLimit)
|
||||
orderCache, _ := lru.New(tradingstate.OrderCacheLimit)
|
||||
XDCX := &XDCX{
|
||||
orderNonce: make(map[common.Address]*big.Int),
|
||||
Triegc: prque.New(),
|
||||
tokenDecimalCache: tokenDecimalCache,
|
||||
orderCache: orderCache,
|
||||
}
|
||||
|
||||
// default DBEngine: levelDB
|
||||
XDCX.db = NewLDBEngine(cfg)
|
||||
XDCX.sdkNode = false
|
||||
|
||||
if cfg.DBEngine == "mongodb" { // this is an add-on DBEngine for SDK nodes
|
||||
XDCX.mongodb = NewMongoDBEngine(cfg)
|
||||
XDCX.sdkNode = true
|
||||
}
|
||||
|
||||
XDCX.StateCache = tradingstate.NewDatabase(XDCX.db)
|
||||
XDCX.settings.Store(overflowIdx, false)
|
||||
|
||||
return XDCX
|
||||
}
|
||||
|
||||
// Overflow returns an indication if the message queue is full.
|
||||
func (XDCx *XDCX) Overflow() bool {
|
||||
val, _ := XDCx.settings.Load(overflowIdx)
|
||||
return val.(bool)
|
||||
}
|
||||
|
||||
func (XDCx *XDCX) IsSDKNode() bool {
|
||||
return XDCx.sdkNode
|
||||
}
|
||||
|
||||
func (XDCx *XDCX) GetLevelDB() XDCxDAO.XDCXDAO {
|
||||
return XDCx.db
|
||||
}
|
||||
|
||||
func (XDCx *XDCX) GetMongoDB() XDCxDAO.XDCXDAO {
|
||||
return XDCx.mongodb
|
||||
}
|
||||
|
||||
// APIs returns the RPC descriptors the XDCX implementation offers
|
||||
func (XDCx *XDCX) APIs() []rpc.API {
|
||||
return []rpc.API{
|
||||
{
|
||||
Namespace: ProtocolName,
|
||||
Version: ProtocolVersionStr,
|
||||
Service: NewPublicXDCXAPI(XDCx),
|
||||
Public: true,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// Version returns the XDCX sub-protocols version number.
|
||||
func (XDCx *XDCX) Version() uint64 {
|
||||
return ProtocolVersion
|
||||
}
|
||||
|
||||
func (XDCx *XDCX) ProcessOrderPending(header *types.Header, coinbase common.Address, chain consensus.ChainContext, pending map[common.Address]types.OrderTransactions, statedb *state.StateDB, XDCXstatedb *tradingstate.TradingStateDB) ([]tradingstate.TxDataMatch, map[common.Hash]tradingstate.MatchingResult) {
|
||||
txMatches := []tradingstate.TxDataMatch{}
|
||||
matchingResults := map[common.Hash]tradingstate.MatchingResult{}
|
||||
|
||||
txs := types.NewOrderTransactionByNonce(types.OrderTxSigner{}, pending)
|
||||
numberTx := 0
|
||||
for {
|
||||
tx := txs.Peek()
|
||||
if tx == nil {
|
||||
break
|
||||
}
|
||||
if numberTx > MaximumTxMatchSize {
|
||||
break
|
||||
}
|
||||
numberTx++
|
||||
log.Debug("ProcessOrderPending start", "len", len(pending))
|
||||
log.Debug("Get pending orders to process", "address", tx.UserAddress(), "nonce", tx.Nonce())
|
||||
V, R, S := tx.Signature()
|
||||
|
||||
bigstr := V.String()
|
||||
n, e := strconv.ParseInt(bigstr, 10, 8)
|
||||
if e != nil {
|
||||
continue
|
||||
}
|
||||
|
||||
order := &tradingstate.OrderItem{
|
||||
Nonce: big.NewInt(int64(tx.Nonce())),
|
||||
Quantity: tx.Quantity(),
|
||||
Price: tx.Price(),
|
||||
ExchangeAddress: tx.ExchangeAddress(),
|
||||
UserAddress: tx.UserAddress(),
|
||||
BaseToken: tx.BaseToken(),
|
||||
QuoteToken: tx.QuoteToken(),
|
||||
Status: tx.Status(),
|
||||
Side: tx.Side(),
|
||||
Type: tx.Type(),
|
||||
Hash: tx.OrderHash(),
|
||||
OrderID: tx.OrderID(),
|
||||
Signature: &tradingstate.Signature{
|
||||
V: byte(n),
|
||||
R: common.BigToHash(R),
|
||||
S: common.BigToHash(S),
|
||||
},
|
||||
}
|
||||
cancel := false
|
||||
if order.Status == tradingstate.OrderStatusCancelled {
|
||||
cancel = true
|
||||
}
|
||||
|
||||
log.Info("Process order pending", "orderPending", order, "BaseToken", order.BaseToken.Hex(), "QuoteToken", order.QuoteToken)
|
||||
originalOrder := &tradingstate.OrderItem{}
|
||||
*originalOrder = *order
|
||||
originalOrder.Quantity = tradingstate.CloneBigInt(order.Quantity)
|
||||
|
||||
if cancel {
|
||||
order.Status = tradingstate.OrderStatusCancelled
|
||||
}
|
||||
|
||||
newTrades, newRejectedOrders, err := XDCx.CommitOrder(header, coinbase, chain, statedb, XDCXstatedb, tradingstate.GetTradingOrderBookHash(order.BaseToken, order.QuoteToken), order)
|
||||
|
||||
for _, reject := range newRejectedOrders {
|
||||
log.Debug("Reject order", "reject", *reject)
|
||||
}
|
||||
|
||||
switch err {
|
||||
case ErrNonceTooLow:
|
||||
// New head notification data race between the transaction pool and miner, shift
|
||||
log.Debug("Skipping order with low nonce", "sender", tx.UserAddress(), "nonce", tx.Nonce())
|
||||
txs.Shift()
|
||||
continue
|
||||
|
||||
case ErrNonceTooHigh:
|
||||
// Reorg notification data race between the transaction pool and miner, skip account =
|
||||
log.Debug("Skipping order account with high nonce", "sender", tx.UserAddress(), "nonce", tx.Nonce())
|
||||
txs.Pop()
|
||||
continue
|
||||
|
||||
case nil:
|
||||
// everything ok
|
||||
txs.Shift()
|
||||
|
||||
default:
|
||||
// Strange error, discard the transaction and get the next in line (note, the
|
||||
// nonce-too-high clause will prevent us from executing in vain).
|
||||
log.Debug("Transaction failed, account skipped", "hash", tx.Hash(), "err", err)
|
||||
txs.Shift()
|
||||
continue
|
||||
}
|
||||
|
||||
// orderID has been updated
|
||||
originalOrder.OrderID = order.OrderID
|
||||
originalOrder.ExtraData = order.ExtraData
|
||||
originalOrderValue, err := tradingstate.EncodeBytesItem(originalOrder)
|
||||
if err != nil {
|
||||
log.Error("Can't encode", "order", originalOrder, "err", err)
|
||||
continue
|
||||
}
|
||||
txMatch := tradingstate.TxDataMatch{
|
||||
Order: originalOrderValue,
|
||||
}
|
||||
txMatches = append(txMatches, txMatch)
|
||||
matchingResults[tradingstate.GetMatchingResultCacheKey(order)] = tradingstate.MatchingResult{
|
||||
Trades: newTrades,
|
||||
Rejects: newRejectedOrders,
|
||||
}
|
||||
}
|
||||
return txMatches, matchingResults
|
||||
}
|
||||
|
||||
// return average price of the given pair in the last epoch
|
||||
func (XDCx *XDCX) GetAveragePriceLastEpoch(chain consensus.ChainContext, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, baseToken common.Address, quoteToken common.Address) (*big.Int, error) {
|
||||
price := tradingStateDb.GetMediumPriceBeforeEpoch(tradingstate.GetTradingOrderBookHash(baseToken, quoteToken))
|
||||
if price != nil && price.Sign() > 0 {
|
||||
log.Debug("GetAveragePriceLastEpoch", "baseToken", baseToken.Hex(), "quoteToken", quoteToken.Hex(), "price", price)
|
||||
return price, nil
|
||||
} else {
|
||||
inversePrice := tradingStateDb.GetMediumPriceBeforeEpoch(tradingstate.GetTradingOrderBookHash(quoteToken, baseToken))
|
||||
log.Debug("GetAveragePriceLastEpoch", "baseToken", baseToken.Hex(), "quoteToken", quoteToken.Hex(), "inversePrice", inversePrice)
|
||||
if inversePrice != nil && inversePrice.Sign() > 0 {
|
||||
quoteTokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, quoteToken)
|
||||
if err != nil || quoteTokenDecimal.Sign() == 0 {
|
||||
return nil, fmt.Errorf("fail to get tokenDecimal. Token: %v . Err: %v", quoteToken.String(), err)
|
||||
}
|
||||
baseTokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, baseToken)
|
||||
if err != nil || baseTokenDecimal.Sign() == 0 {
|
||||
return nil, fmt.Errorf("fail to get tokenDecimal. Token: %v . Err: %v", baseToken.String(), err)
|
||||
}
|
||||
price = new(big.Int).Mul(baseTokenDecimal, quoteTokenDecimal)
|
||||
price = new(big.Int).Div(price, inversePrice)
|
||||
log.Debug("GetAveragePriceLastEpoch", "baseToken", baseToken.Hex(), "quoteToken", quoteToken.Hex(), "baseTokenDecimal", baseTokenDecimal, "quoteTokenDecimal", quoteTokenDecimal, "inversePrice", inversePrice)
|
||||
return price, nil
|
||||
}
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// return tokenQuantity (after convert from XDC to token), tokenPriceInXDC, error
|
||||
func (XDCx *XDCX) ConvertXDCToToken(chain consensus.ChainContext, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, token common.Address, quantity *big.Int) (*big.Int, *big.Int, error) {
|
||||
if token.String() == common.XDCNativeAddress {
|
||||
return quantity, common.BasePrice, nil
|
||||
}
|
||||
tokenPriceInXDC, err := XDCx.GetAveragePriceLastEpoch(chain, statedb, tradingStateDb, token, common.HexToAddress(common.XDCNativeAddress))
|
||||
if err != nil || tokenPriceInXDC == nil || tokenPriceInXDC.Sign() <= 0 {
|
||||
return common.Big0, common.Big0, err
|
||||
}
|
||||
|
||||
tokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, token)
|
||||
if err != nil || tokenDecimal.Sign() == 0 {
|
||||
return common.Big0, common.Big0, fmt.Errorf("fail to get tokenDecimal. Token: %v . Err: %v", token.String(), err)
|
||||
}
|
||||
tokenQuantity := new(big.Int).Mul(quantity, tokenDecimal)
|
||||
tokenQuantity = new(big.Int).Div(tokenQuantity, tokenPriceInXDC)
|
||||
return tokenQuantity, tokenPriceInXDC, nil
|
||||
}
|
||||
|
||||
// there are 3 tasks need to complete to update data in SDK nodes after matching
|
||||
// 1. txMatchData.Order: order has been processed. This order should be put to `orders` collection with status sdktypes.OrderStatusOpen
|
||||
// 2. txMatchData.Trades: includes information of matched orders.
|
||||
// a. PutObject them to `trades` collection
|
||||
// b. Update status of regrading orders to sdktypes.OrderStatusFilled
|
||||
func (XDCx *XDCX) SyncDataToSDKNode(takerOrderInTx *tradingstate.OrderItem, txHash common.Hash, txMatchTime time.Time, statedb *state.StateDB, trades []map[string]string, rejectedOrders []*tradingstate.OrderItem, dirtyOrderCount *uint64) error {
|
||||
var (
|
||||
// originTakerOrder: order get from db, nil if it doesn't exist
|
||||
// takerOrderInTx: order decoded from txdata
|
||||
// updatedTakerOrder: order with new status, filledAmount, CreatedAt, UpdatedAt. This will be inserted to db
|
||||
originTakerOrder, updatedTakerOrder *tradingstate.OrderItem
|
||||
makerDirtyHashes []string
|
||||
makerDirtyFilledAmount map[string]*big.Int
|
||||
err error
|
||||
)
|
||||
db := XDCx.GetMongoDB()
|
||||
db.InitBulk()
|
||||
if takerOrderInTx.Status == tradingstate.OrderStatusCancelled && len(rejectedOrders) > 0 {
|
||||
// cancel order is rejected -> nothing change
|
||||
log.Debug("Cancel order is rejected", "order", tradingstate.ToJSON(takerOrderInTx))
|
||||
return nil
|
||||
}
|
||||
// 1. put processed takerOrderInTx to db
|
||||
lastState := tradingstate.OrderHistoryItem{}
|
||||
val, err := db.GetObject(takerOrderInTx.Hash, &tradingstate.OrderItem{})
|
||||
if err == nil && val != nil {
|
||||
originTakerOrder = val.(*tradingstate.OrderItem)
|
||||
lastState = tradingstate.OrderHistoryItem{
|
||||
TxHash: originTakerOrder.TxHash,
|
||||
FilledAmount: tradingstate.CloneBigInt(originTakerOrder.FilledAmount),
|
||||
Status: originTakerOrder.Status,
|
||||
UpdatedAt: originTakerOrder.UpdatedAt,
|
||||
}
|
||||
}
|
||||
if originTakerOrder != nil {
|
||||
updatedTakerOrder = originTakerOrder
|
||||
} else {
|
||||
updatedTakerOrder = takerOrderInTx
|
||||
updatedTakerOrder.FilledAmount = new(big.Int)
|
||||
}
|
||||
|
||||
if takerOrderInTx.Status != tradingstate.OrderStatusCancelled {
|
||||
updatedTakerOrder.Status = tradingstate.OrderStatusOpen
|
||||
} else {
|
||||
updatedTakerOrder.Status = tradingstate.OrderStatusCancelled
|
||||
updatedTakerOrder.ExtraData = takerOrderInTx.ExtraData
|
||||
}
|
||||
updatedTakerOrder.TxHash = txHash
|
||||
if updatedTakerOrder.CreatedAt.IsZero() {
|
||||
updatedTakerOrder.CreatedAt = txMatchTime
|
||||
}
|
||||
if txMatchTime.Before(updatedTakerOrder.UpdatedAt) || (txMatchTime.Equal(updatedTakerOrder.UpdatedAt) && *dirtyOrderCount == 0) {
|
||||
log.Debug("Ignore old orders/trades taker", "txHash", txHash.Hex(), "txTime", txMatchTime.UnixNano(), "updatedAt", updatedTakerOrder.UpdatedAt.UnixNano())
|
||||
return nil
|
||||
}
|
||||
*dirtyOrderCount++
|
||||
|
||||
XDCx.UpdateOrderCache(updatedTakerOrder.BaseToken, updatedTakerOrder.QuoteToken, updatedTakerOrder.Hash, txHash, lastState)
|
||||
updatedTakerOrder.UpdatedAt = txMatchTime
|
||||
|
||||
// 2. put trades to db and update status to FILLED
|
||||
log.Debug("Got trades", "number", len(trades), "txhash", txHash.Hex())
|
||||
makerDirtyFilledAmount = make(map[string]*big.Int)
|
||||
for _, trade := range trades {
|
||||
// 2.a. put to trades
|
||||
if trade == nil {
|
||||
continue
|
||||
}
|
||||
tradeRecord := &tradingstate.Trade{}
|
||||
quantity := tradingstate.ToBigInt(trade[tradingstate.TradeQuantity])
|
||||
price := tradingstate.ToBigInt(trade[tradingstate.TradePrice])
|
||||
if price.Cmp(big.NewInt(0)) <= 0 || quantity.Cmp(big.NewInt(0)) <= 0 {
|
||||
return fmt.Errorf("trade misses important information. tradedPrice %v, tradedQuantity %v", price, quantity)
|
||||
}
|
||||
tradeRecord.Amount = quantity
|
||||
tradeRecord.PricePoint = price
|
||||
tradeRecord.BaseToken = updatedTakerOrder.BaseToken
|
||||
tradeRecord.QuoteToken = updatedTakerOrder.QuoteToken
|
||||
tradeRecord.Status = tradingstate.TradeStatusSuccess
|
||||
tradeRecord.Taker = updatedTakerOrder.UserAddress
|
||||
tradeRecord.Maker = common.HexToAddress(trade[tradingstate.TradeMaker])
|
||||
tradeRecord.TakerOrderHash = updatedTakerOrder.Hash
|
||||
tradeRecord.MakerOrderHash = common.HexToHash(trade[tradingstate.TradeMakerOrderHash])
|
||||
tradeRecord.TxHash = txHash
|
||||
tradeRecord.TakerOrderSide = updatedTakerOrder.Side
|
||||
tradeRecord.TakerExchange = updatedTakerOrder.ExchangeAddress
|
||||
tradeRecord.MakerExchange = common.HexToAddress(trade[tradingstate.TradeMakerExchange])
|
||||
|
||||
tradeRecord.MakeFee, _ = new(big.Int).SetString(trade[tradingstate.MakerFee], 10)
|
||||
tradeRecord.TakeFee, _ = new(big.Int).SetString(trade[tradingstate.TakerFee], 10)
|
||||
|
||||
// set makerOrderType, takerOrderType
|
||||
tradeRecord.MakerOrderType = trade[tradingstate.MakerOrderType]
|
||||
tradeRecord.TakerOrderType = updatedTakerOrder.Type
|
||||
|
||||
if tradeRecord.CreatedAt.IsZero() {
|
||||
tradeRecord.CreatedAt = txMatchTime
|
||||
}
|
||||
tradeRecord.UpdatedAt = txMatchTime
|
||||
tradeRecord.Hash = tradeRecord.ComputeHash()
|
||||
|
||||
log.Debug("TRADE history", "amount", tradeRecord.Amount, "pricepoint", tradeRecord.PricePoint,
|
||||
"taker", tradeRecord.Taker.Hex(), "maker", tradeRecord.Maker.Hex(), "takerOrder", tradeRecord.TakerOrderHash.Hex(), "makerOrder", tradeRecord.MakerOrderHash.Hex(),
|
||||
"takerFee", tradeRecord.TakeFee, "makerFee", tradeRecord.MakeFee)
|
||||
if err := db.PutObject(tradeRecord.Hash, tradeRecord); err != nil {
|
||||
return fmt.Errorf("SDKNode: failed to store tradeRecord %s", err.Error())
|
||||
}
|
||||
|
||||
// 2.b. update status and filledAmount
|
||||
filledAmount := quantity
|
||||
// maker dirty order
|
||||
makerFilledAmount := big.NewInt(0)
|
||||
if amount, ok := makerDirtyFilledAmount[trade[tradingstate.TradeMakerOrderHash]]; ok {
|
||||
makerFilledAmount = tradingstate.CloneBigInt(amount)
|
||||
}
|
||||
makerFilledAmount = new(big.Int).Add(makerFilledAmount, filledAmount)
|
||||
makerDirtyFilledAmount[trade[tradingstate.TradeMakerOrderHash]] = makerFilledAmount
|
||||
makerDirtyHashes = append(makerDirtyHashes, trade[tradingstate.TradeMakerOrderHash])
|
||||
|
||||
//updatedTakerOrder = XDCx.updateMatchedOrder(updatedTakerOrder, filledAmount, txMatchTime, txHash)
|
||||
// update filledAmount, status of takerOrder
|
||||
updatedTakerOrder.FilledAmount = new(big.Int).Add(updatedTakerOrder.FilledAmount, filledAmount)
|
||||
if updatedTakerOrder.FilledAmount.Cmp(updatedTakerOrder.Quantity) < 0 && updatedTakerOrder.Type == tradingstate.Limit {
|
||||
updatedTakerOrder.Status = tradingstate.OrderStatusPartialFilled
|
||||
} else {
|
||||
updatedTakerOrder.Status = tradingstate.OrderStatusFilled
|
||||
}
|
||||
}
|
||||
|
||||
// for Market orders
|
||||
// filledAmount > 0 : FILLED
|
||||
// otherwise: REJECTED
|
||||
if updatedTakerOrder.Type == tradingstate.Market {
|
||||
if updatedTakerOrder.FilledAmount.Sign() > 0 {
|
||||
updatedTakerOrder.Status = tradingstate.OrderStatusFilled
|
||||
} else {
|
||||
updatedTakerOrder.Status = tradingstate.OrderStatusRejected
|
||||
}
|
||||
}
|
||||
log.Debug("PutObject processed takerOrder",
|
||||
"userAddr", updatedTakerOrder.UserAddress.Hex(), "side", updatedTakerOrder.Side,
|
||||
"price", updatedTakerOrder.Price, "quantity", updatedTakerOrder.Quantity, "filledAmount", updatedTakerOrder.FilledAmount, "status", updatedTakerOrder.Status,
|
||||
"hash", updatedTakerOrder.Hash.Hex(), "txHash", updatedTakerOrder.TxHash.Hex())
|
||||
if err := db.PutObject(updatedTakerOrder.Hash, updatedTakerOrder); err != nil {
|
||||
return fmt.Errorf("SDKNode: failed to put processed takerOrder. Hash: %s Error: %s", updatedTakerOrder.Hash.Hex(), err.Error())
|
||||
}
|
||||
items := db.GetListItemByHashes(makerDirtyHashes, &tradingstate.OrderItem{})
|
||||
if items != nil {
|
||||
makerOrders := items.([]*tradingstate.OrderItem)
|
||||
log.Debug("Maker dirty orders", "len", len(makerOrders), "txhash", txHash.Hex())
|
||||
for _, o := range makerOrders {
|
||||
if txMatchTime.Before(o.UpdatedAt) {
|
||||
log.Debug("Ignore old orders/trades maker", "txHash", txHash.Hex(), "txTime", txMatchTime.UnixNano(), "updatedAt", updatedTakerOrder.UpdatedAt.UnixNano())
|
||||
continue
|
||||
}
|
||||
lastState = tradingstate.OrderHistoryItem{
|
||||
TxHash: o.TxHash,
|
||||
FilledAmount: tradingstate.CloneBigInt(o.FilledAmount),
|
||||
Status: o.Status,
|
||||
UpdatedAt: o.UpdatedAt,
|
||||
}
|
||||
XDCx.UpdateOrderCache(o.BaseToken, o.QuoteToken, o.Hash, txHash, lastState)
|
||||
o.TxHash = txHash
|
||||
o.UpdatedAt = txMatchTime
|
||||
o.FilledAmount = new(big.Int).Add(o.FilledAmount, makerDirtyFilledAmount[o.Hash.Hex()])
|
||||
if o.FilledAmount.Cmp(o.Quantity) < 0 {
|
||||
o.Status = tradingstate.OrderStatusPartialFilled
|
||||
} else {
|
||||
o.Status = tradingstate.OrderStatusFilled
|
||||
}
|
||||
log.Debug("PutObject processed makerOrder",
|
||||
"userAddr", o.UserAddress.Hex(), "side", o.Side,
|
||||
"price", o.Price, "quantity", o.Quantity, "filledAmount", o.FilledAmount, "status", o.Status,
|
||||
"hash", o.Hash.Hex(), "txHash", o.TxHash.Hex())
|
||||
if err := db.PutObject(o.Hash, o); err != nil {
|
||||
return fmt.Errorf("SDKNode: failed to put processed makerOrder. Hash: %s Error: %s", o.Hash.Hex(), err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 3. put rejected orders to db and update status REJECTED
|
||||
log.Debug("Got rejected orders", "number", len(rejectedOrders), "rejectedOrders", rejectedOrders)
|
||||
|
||||
if len(rejectedOrders) > 0 {
|
||||
var rejectedHashes []string
|
||||
// updateRejectedOrders
|
||||
for _, rejectedOrder := range rejectedOrders {
|
||||
rejectedHashes = append(rejectedHashes, rejectedOrder.Hash.Hex())
|
||||
if updatedTakerOrder.Hash == rejectedOrder.Hash && !txMatchTime.Before(updatedTakerOrder.UpdatedAt) {
|
||||
// cache order history for handling reorg
|
||||
orderHistoryRecord := tradingstate.OrderHistoryItem{
|
||||
TxHash: updatedTakerOrder.TxHash,
|
||||
FilledAmount: tradingstate.CloneBigInt(updatedTakerOrder.FilledAmount),
|
||||
Status: updatedTakerOrder.Status,
|
||||
UpdatedAt: updatedTakerOrder.UpdatedAt,
|
||||
}
|
||||
XDCx.UpdateOrderCache(updatedTakerOrder.BaseToken, updatedTakerOrder.QuoteToken, updatedTakerOrder.Hash, txHash, orderHistoryRecord)
|
||||
// if whole order is rejected, status = REJECTED
|
||||
// otherwise, status = FILLED
|
||||
if updatedTakerOrder.FilledAmount.Sign() > 0 {
|
||||
updatedTakerOrder.Status = tradingstate.OrderStatusFilled
|
||||
} else {
|
||||
updatedTakerOrder.Status = tradingstate.OrderStatusRejected
|
||||
}
|
||||
updatedTakerOrder.TxHash = txHash
|
||||
updatedTakerOrder.UpdatedAt = txMatchTime
|
||||
if err := db.PutObject(updatedTakerOrder.Hash, updatedTakerOrder); err != nil {
|
||||
return fmt.Errorf("SDKNode: failed to reject takerOrder. Hash: %s Error: %s", updatedTakerOrder.Hash.Hex(), err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
items := db.GetListItemByHashes(rejectedHashes, &tradingstate.OrderItem{})
|
||||
if items != nil {
|
||||
dirtyRejectedOrders := items.([]*tradingstate.OrderItem)
|
||||
for _, order := range dirtyRejectedOrders {
|
||||
if txMatchTime.Before(order.UpdatedAt) {
|
||||
log.Debug("Ignore old orders/trades reject", "txHash", txHash.Hex(), "txTime", txMatchTime.UnixNano(), "updatedAt", updatedTakerOrder.UpdatedAt.UnixNano())
|
||||
continue
|
||||
}
|
||||
// cache order history for handling reorg
|
||||
orderHistoryRecord := tradingstate.OrderHistoryItem{
|
||||
TxHash: order.TxHash,
|
||||
FilledAmount: tradingstate.CloneBigInt(order.FilledAmount),
|
||||
Status: order.Status,
|
||||
UpdatedAt: order.UpdatedAt,
|
||||
}
|
||||
XDCx.UpdateOrderCache(order.BaseToken, order.QuoteToken, order.Hash, txHash, orderHistoryRecord)
|
||||
dirtyFilledAmount, ok := makerDirtyFilledAmount[order.Hash.Hex()]
|
||||
if ok && dirtyFilledAmount != nil {
|
||||
order.FilledAmount = new(big.Int).Add(order.FilledAmount, dirtyFilledAmount)
|
||||
}
|
||||
// if whole order is rejected, status = REJECTED
|
||||
// otherwise, status = FILLED
|
||||
if order.FilledAmount.Sign() > 0 {
|
||||
order.Status = tradingstate.OrderStatusFilled
|
||||
} else {
|
||||
order.Status = tradingstate.OrderStatusRejected
|
||||
}
|
||||
order.TxHash = txHash
|
||||
order.UpdatedAt = txMatchTime
|
||||
if err = db.PutObject(order.Hash, order); err != nil {
|
||||
return fmt.Errorf("SDKNode: failed to update rejectedOder to sdkNode %s", err.Error())
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if err := db.CommitBulk(); err != nil {
|
||||
return fmt.Errorf("SDKNode fail to commit bulk update orders, trades at txhash %s . Error: %s", txHash.Hex(), err.Error())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (XDCx *XDCX) GetTradingState(block *types.Block, author common.Address) (*tradingstate.TradingStateDB, error) {
|
||||
root, err := XDCx.GetTradingStateRoot(block, author)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if XDCx.StateCache == nil {
|
||||
return nil, errors.New("Not initialized XDCx")
|
||||
}
|
||||
return tradingstate.New(root, XDCx.StateCache)
|
||||
}
|
||||
|
||||
func (XDCx *XDCX) GetStateCache() tradingstate.Database {
|
||||
return XDCx.StateCache
|
||||
}
|
||||
func (XDCx *XDCX) HasTradingState(block *types.Block, author common.Address) bool {
|
||||
root, err := XDCx.GetTradingStateRoot(block, author)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
_, err = XDCx.StateCache.OpenTrie(root)
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
func (XDCx *XDCX) GetTriegc() *prque.Prque {
|
||||
return XDCx.Triegc
|
||||
}
|
||||
|
||||
func (XDCx *XDCX) GetTradingStateRoot(block *types.Block, author common.Address) (common.Hash, error) {
|
||||
for _, tx := range block.Transactions() {
|
||||
from := *(tx.From())
|
||||
if tx.To() != nil && tx.To().Hex() == common.TradingStateAddr && from.String() == author.String() {
|
||||
if len(tx.Data()) >= 32 {
|
||||
return common.BytesToHash(tx.Data()[:32]), nil
|
||||
}
|
||||
}
|
||||
}
|
||||
return tradingstate.EmptyRoot, nil
|
||||
}
|
||||
|
||||
func (XDCx *XDCX) UpdateOrderCache(baseToken, quoteToken common.Address, orderHash common.Hash, txhash common.Hash, lastState tradingstate.OrderHistoryItem) {
|
||||
var orderCacheAtTxHash map[common.Hash]tradingstate.OrderHistoryItem
|
||||
c, ok := XDCx.orderCache.Get(txhash)
|
||||
if !ok || c == nil {
|
||||
orderCacheAtTxHash = make(map[common.Hash]tradingstate.OrderHistoryItem)
|
||||
} else {
|
||||
orderCacheAtTxHash = c.(map[common.Hash]tradingstate.OrderHistoryItem)
|
||||
}
|
||||
orderKey := tradingstate.GetOrderHistoryKey(baseToken, quoteToken, orderHash)
|
||||
_, ok = orderCacheAtTxHash[orderKey]
|
||||
if !ok {
|
||||
orderCacheAtTxHash[orderKey] = lastState
|
||||
}
|
||||
XDCx.orderCache.Add(txhash, orderCacheAtTxHash)
|
||||
}
|
||||
|
||||
func (XDCx *XDCX) RollbackReorgTxMatch(txhash common.Hash) error {
|
||||
db := XDCx.GetMongoDB()
|
||||
db.InitBulk()
|
||||
|
||||
items := db.GetListItemByTxHash(txhash, &tradingstate.OrderItem{})
|
||||
if items != nil {
|
||||
for _, order := range items.([]*tradingstate.OrderItem) {
|
||||
c, ok := XDCx.orderCache.Get(txhash)
|
||||
log.Debug("XDCx reorg: rollback order", "txhash", txhash.Hex(), "order", tradingstate.ToJSON(order), "orderHistoryItem", c)
|
||||
if !ok {
|
||||
log.Debug("XDCx reorg: remove order due to no orderCache", "order", tradingstate.ToJSON(order))
|
||||
if err := db.DeleteObject(order.Hash, &tradingstate.OrderItem{}); err != nil {
|
||||
log.Crit("SDKNode: failed to remove reorg order", "err", err.Error(), "order", tradingstate.ToJSON(order))
|
||||
}
|
||||
continue
|
||||
}
|
||||
orderCacheAtTxHash := c.(map[common.Hash]tradingstate.OrderHistoryItem)
|
||||
orderHistoryItem, _ := orderCacheAtTxHash[tradingstate.GetOrderHistoryKey(order.BaseToken, order.QuoteToken, order.Hash)]
|
||||
if (orderHistoryItem == tradingstate.OrderHistoryItem{}) {
|
||||
log.Debug("XDCx reorg: remove order due to empty orderHistory", "order", tradingstate.ToJSON(order))
|
||||
if err := db.DeleteObject(order.Hash, &tradingstate.OrderItem{}); err != nil {
|
||||
log.Crit("SDKNode: failed to remove reorg order", "err", err.Error(), "order", tradingstate.ToJSON(order))
|
||||
}
|
||||
continue
|
||||
}
|
||||
order.TxHash = orderHistoryItem.TxHash
|
||||
order.Status = orderHistoryItem.Status
|
||||
order.FilledAmount = tradingstate.CloneBigInt(orderHistoryItem.FilledAmount)
|
||||
order.UpdatedAt = orderHistoryItem.UpdatedAt
|
||||
log.Debug("XDCx reorg: update order to the last orderHistoryItem", "order", tradingstate.ToJSON(order), "orderHistoryItem", orderHistoryItem)
|
||||
if err := db.PutObject(order.Hash, order); err != nil {
|
||||
log.Crit("SDKNode: failed to update reorg order", "err", err.Error(), "order", tradingstate.ToJSON(order))
|
||||
}
|
||||
}
|
||||
}
|
||||
log.Debug("XDCx reorg: DeleteTradeByTxHash", "txhash", txhash.Hex())
|
||||
db.DeleteItemByTxHash(txhash, &tradingstate.Trade{})
|
||||
if err := db.CommitBulk(); err != nil {
|
||||
return fmt.Errorf("failed to RollbackTradingData. %v", err)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
42
XDCx/api.go
Normal file
42
XDCx/api.go
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
package XDCx
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
LimitThresholdOrderNonceInQueue = 100
|
||||
)
|
||||
|
||||
// List of errors
|
||||
var (
|
||||
ErrNoTopics = errors.New("missing topic(s)")
|
||||
ErrOrderNonceTooLow = errors.New("OrderNonce too low")
|
||||
ErrOrderNonceTooHigh = errors.New("OrderNonce too high")
|
||||
)
|
||||
|
||||
// PublicXDCXAPI provides the XDCX RPC service that can be
|
||||
// use publicly without security implications.
|
||||
type PublicXDCXAPI struct {
|
||||
t *XDCX
|
||||
mu sync.Mutex
|
||||
lastUsed map[string]time.Time // keeps track when a filter was polled for the last time.
|
||||
|
||||
}
|
||||
|
||||
// NewPublicXDCXAPI create a new RPC XDCX service.
|
||||
func NewPublicXDCXAPI(t *XDCX) *PublicXDCXAPI {
|
||||
api := &PublicXDCXAPI{
|
||||
t: t,
|
||||
lastUsed: make(map[string]time.Time),
|
||||
}
|
||||
return api
|
||||
}
|
||||
|
||||
// Version returns the XDCX sub-protocol version.
|
||||
func (api *PublicXDCXAPI) Version(ctx context.Context) string {
|
||||
return ProtocolVersionStr
|
||||
}
|
||||
783
XDCx/order_processor.go
Normal file
783
XDCx/order_processor.go
Normal file
|
|
@ -0,0 +1,783 @@
|
|||
package XDCx
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/consensus"
|
||||
|
||||
"fmt"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/state"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
func (XDCx *XDCX) CommitOrder(header *types.Header, coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, tradingStateDB *tradingstate.TradingStateDB, orderBook common.Hash, order *tradingstate.OrderItem) ([]map[string]string, []*tradingstate.OrderItem, error) {
|
||||
XDCxSnap := tradingStateDB.Snapshot()
|
||||
dbSnap := statedb.Snapshot()
|
||||
trades, rejects, err := XDCx.ApplyOrder(header, coinbase, chain, statedb, tradingStateDB, orderBook, order)
|
||||
if err != nil {
|
||||
tradingStateDB.RevertToSnapshot(XDCxSnap)
|
||||
statedb.RevertToSnapshot(dbSnap)
|
||||
return nil, nil, err
|
||||
}
|
||||
return trades, rejects, err
|
||||
}
|
||||
|
||||
func (XDCx *XDCX) ApplyOrder(header *types.Header, coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, tradingStateDB *tradingstate.TradingStateDB, orderBook common.Hash, order *tradingstate.OrderItem) ([]map[string]string, []*tradingstate.OrderItem, error) {
|
||||
var (
|
||||
rejects []*tradingstate.OrderItem
|
||||
trades []map[string]string
|
||||
err error
|
||||
)
|
||||
nonce := tradingStateDB.GetNonce(order.UserAddress.Hash())
|
||||
log.Debug("ApplyOrder", "addr", order.UserAddress, "statenonce", nonce, "ordernonce", order.Nonce)
|
||||
if big.NewInt(int64(nonce)).Cmp(order.Nonce) == -1 {
|
||||
log.Debug("ApplyOrder ErrNonceTooHigh", "nonce", order.Nonce)
|
||||
return nil, nil, ErrNonceTooHigh
|
||||
} else if big.NewInt(int64(nonce)).Cmp(order.Nonce) == 1 {
|
||||
log.Debug("ApplyOrder ErrNonceTooLow", "nonce", order.Nonce)
|
||||
return nil, nil, ErrNonceTooLow
|
||||
}
|
||||
// increase nonce
|
||||
log.Debug("ApplyOrder set nonce", "nonce", nonce+1, "addr", order.UserAddress.Hex(), "status", order.Status, "oldnonce", nonce)
|
||||
tradingStateDB.SetNonce(order.UserAddress.Hash(), nonce+1)
|
||||
XDCxSnap := tradingStateDB.Snapshot()
|
||||
dbSnap := statedb.Snapshot()
|
||||
defer func() {
|
||||
if err != nil {
|
||||
tradingStateDB.RevertToSnapshot(XDCxSnap)
|
||||
statedb.RevertToSnapshot(dbSnap)
|
||||
}
|
||||
}()
|
||||
|
||||
if err := order.VerifyOrder(statedb); err != nil {
|
||||
rejects = append(rejects, order)
|
||||
return trades, rejects, nil
|
||||
}
|
||||
if order.Status == tradingstate.OrderStatusCancelled {
|
||||
err, reject := XDCx.ProcessCancelOrder(header, tradingStateDB, statedb, chain, coinbase, orderBook, order)
|
||||
if err != nil || reject {
|
||||
log.Debug("Reject cancelled order", "err", err)
|
||||
rejects = append(rejects, order)
|
||||
}
|
||||
return trades, rejects, nil
|
||||
}
|
||||
if order.Type != tradingstate.Market {
|
||||
if order.Price.Sign() == 0 || common.BigToHash(order.Price).Big().Cmp(order.Price) != 0 {
|
||||
log.Debug("Reject order price invalid", "price", order.Price)
|
||||
rejects = append(rejects, order)
|
||||
return trades, rejects, nil
|
||||
}
|
||||
}
|
||||
if order.Quantity.Sign() == 0 || common.BigToHash(order.Quantity).Big().Cmp(order.Quantity) != 0 {
|
||||
log.Debug("Reject order quantity invalid", "quantity", order.Quantity)
|
||||
rejects = append(rejects, order)
|
||||
return trades, rejects, nil
|
||||
}
|
||||
orderType := order.Type
|
||||
// if we do not use auto-increment orderid, we must set price slot to avoid conflict
|
||||
if orderType == tradingstate.Market {
|
||||
log.Debug("Process maket order", "side", order.Side, "quantity", order.Quantity, "price", order.Price)
|
||||
trades, rejects, err = XDCx.processMarketOrder(coinbase, chain, statedb, tradingStateDB, orderBook, order)
|
||||
if err != nil {
|
||||
log.Debug("Reject market order", "err", err, "order", tradingstate.ToJSON(order))
|
||||
trades = []map[string]string{}
|
||||
rejects = append(rejects, order)
|
||||
}
|
||||
} else {
|
||||
log.Debug("Process limit order", "side", order.Side, "quantity", order.Quantity, "price", order.Price)
|
||||
trades, rejects, err = XDCx.processLimitOrder(coinbase, chain, statedb, tradingStateDB, orderBook, order)
|
||||
if err != nil {
|
||||
log.Debug("Reject limit order", "err", err, "order", tradingstate.ToJSON(order))
|
||||
trades = []map[string]string{}
|
||||
rejects = append(rejects, order)
|
||||
}
|
||||
}
|
||||
|
||||
return trades, rejects, nil
|
||||
}
|
||||
|
||||
// processMarketOrder : process the market order
|
||||
func (XDCx *XDCX) processMarketOrder(coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, tradingStateDB *tradingstate.TradingStateDB, orderBook common.Hash, order *tradingstate.OrderItem) ([]map[string]string, []*tradingstate.OrderItem, error) {
|
||||
var (
|
||||
trades []map[string]string
|
||||
newTrades []map[string]string
|
||||
rejects []*tradingstate.OrderItem
|
||||
newRejects []*tradingstate.OrderItem
|
||||
err error
|
||||
)
|
||||
quantityToTrade := order.Quantity
|
||||
side := order.Side
|
||||
// speedup the comparison, do not assign because it is pointer
|
||||
zero := tradingstate.Zero
|
||||
if side == tradingstate.Bid {
|
||||
bestPrice, volume := tradingStateDB.GetBestAskPrice(orderBook)
|
||||
log.Debug("processMarketOrder ", "side", side, "bestPrice", bestPrice, "quantityToTrade", quantityToTrade, "volume", volume)
|
||||
for quantityToTrade.Cmp(zero) > 0 && bestPrice.Cmp(zero) > 0 {
|
||||
quantityToTrade, newTrades, newRejects, err = XDCx.processOrderList(coinbase, chain, statedb, tradingStateDB, tradingstate.Ask, orderBook, bestPrice, quantityToTrade, order)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
trades = append(trades, newTrades...)
|
||||
rejects = append(rejects, newRejects...)
|
||||
bestPrice, volume = tradingStateDB.GetBestAskPrice(orderBook)
|
||||
log.Debug("processMarketOrder ", "side", side, "bestPrice", bestPrice, "quantityToTrade", quantityToTrade, "volume", volume)
|
||||
}
|
||||
} else {
|
||||
bestPrice, volume := tradingStateDB.GetBestBidPrice(orderBook)
|
||||
log.Debug("processMarketOrder ", "side", side, "bestPrice", bestPrice, "quantityToTrade", quantityToTrade, "volume", volume)
|
||||
for quantityToTrade.Cmp(zero) > 0 && bestPrice.Cmp(zero) > 0 {
|
||||
quantityToTrade, newTrades, newRejects, err = XDCx.processOrderList(coinbase, chain, statedb, tradingStateDB, tradingstate.Bid, orderBook, bestPrice, quantityToTrade, order)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
trades = append(trades, newTrades...)
|
||||
rejects = append(rejects, newRejects...)
|
||||
bestPrice, volume = tradingStateDB.GetBestBidPrice(orderBook)
|
||||
log.Debug("processMarketOrder ", "side", side, "bestPrice", bestPrice, "quantityToTrade", quantityToTrade, "volume", volume)
|
||||
}
|
||||
}
|
||||
return trades, rejects, nil
|
||||
}
|
||||
|
||||
// processLimitOrder : process the limit order, can change the quote
|
||||
// If not care for performance, we should make a copy of quote to prevent further reference problem
|
||||
func (XDCx *XDCX) processLimitOrder(coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, tradingStateDB *tradingstate.TradingStateDB, orderBook common.Hash, order *tradingstate.OrderItem) ([]map[string]string, []*tradingstate.OrderItem, error) {
|
||||
var (
|
||||
trades []map[string]string
|
||||
newTrades []map[string]string
|
||||
rejects []*tradingstate.OrderItem
|
||||
newRejects []*tradingstate.OrderItem
|
||||
err error
|
||||
)
|
||||
quantityToTrade := order.Quantity
|
||||
side := order.Side
|
||||
price := order.Price
|
||||
|
||||
// speedup the comparison, do not assign because it is pointer
|
||||
zero := tradingstate.Zero
|
||||
|
||||
if side == tradingstate.Bid {
|
||||
minPrice, volume := tradingStateDB.GetBestAskPrice(orderBook)
|
||||
log.Debug("processLimitOrder ", "side", side, "minPrice", minPrice, "orderPrice", price, "volume", volume)
|
||||
for quantityToTrade.Cmp(zero) > 0 && price.Cmp(minPrice) >= 0 && minPrice.Cmp(zero) > 0 {
|
||||
log.Debug("Min price in asks tree", "price", minPrice.String())
|
||||
quantityToTrade, newTrades, newRejects, err = XDCx.processOrderList(coinbase, chain, statedb, tradingStateDB, tradingstate.Ask, orderBook, minPrice, quantityToTrade, order)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
trades = append(trades, newTrades...)
|
||||
rejects = append(rejects, newRejects...)
|
||||
log.Debug("New trade found", "newTrades", newTrades, "quantityToTrade", quantityToTrade)
|
||||
minPrice, volume = tradingStateDB.GetBestAskPrice(orderBook)
|
||||
log.Debug("processLimitOrder ", "side", side, "minPrice", minPrice, "orderPrice", price, "volume", volume)
|
||||
}
|
||||
} else {
|
||||
maxPrice, volume := tradingStateDB.GetBestBidPrice(orderBook)
|
||||
log.Debug("processLimitOrder ", "side", side, "maxPrice", maxPrice, "orderPrice", price, "volume", volume)
|
||||
for quantityToTrade.Cmp(zero) > 0 && price.Cmp(maxPrice) <= 0 && maxPrice.Cmp(zero) > 0 {
|
||||
log.Debug("Max price in bids tree", "price", maxPrice.String())
|
||||
quantityToTrade, newTrades, newRejects, err = XDCx.processOrderList(coinbase, chain, statedb, tradingStateDB, tradingstate.Bid, orderBook, maxPrice, quantityToTrade, order)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
trades = append(trades, newTrades...)
|
||||
rejects = append(rejects, newRejects...)
|
||||
log.Debug("New trade found", "newTrades", newTrades, "quantityToTrade", quantityToTrade)
|
||||
maxPrice, volume = tradingStateDB.GetBestBidPrice(orderBook)
|
||||
log.Debug("processLimitOrder ", "side", side, "maxPrice", maxPrice, "orderPrice", price, "volume", volume)
|
||||
}
|
||||
}
|
||||
if quantityToTrade.Cmp(zero) > 0 {
|
||||
orderId := tradingStateDB.GetNonce(orderBook)
|
||||
order.OrderID = orderId + 1
|
||||
order.Quantity = quantityToTrade
|
||||
tradingStateDB.SetNonce(orderBook, orderId+1)
|
||||
orderIdHash := common.BigToHash(new(big.Int).SetUint64(order.OrderID))
|
||||
tradingStateDB.InsertOrderItem(orderBook, orderIdHash, *order)
|
||||
log.Debug("After matching, order (unmatched part) is now added to tree", "side", order.Side, "order", order)
|
||||
}
|
||||
return trades, rejects, nil
|
||||
}
|
||||
|
||||
// processOrderList : process the order list
|
||||
func (XDCx *XDCX) processOrderList(coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, tradingStateDB *tradingstate.TradingStateDB, side string, orderBook common.Hash, price *big.Int, quantityStillToTrade *big.Int, order *tradingstate.OrderItem) (*big.Int, []map[string]string, []*tradingstate.OrderItem, error) {
|
||||
quantityToTrade := tradingstate.CloneBigInt(quantityStillToTrade)
|
||||
log.Debug("Process matching between order and orderlist", "quantityToTrade", quantityToTrade)
|
||||
var (
|
||||
trades []map[string]string
|
||||
|
||||
rejects []*tradingstate.OrderItem
|
||||
)
|
||||
for quantityToTrade.Sign() > 0 {
|
||||
orderId, amount, _ := tradingStateDB.GetBestOrderIdAndAmount(orderBook, price, side)
|
||||
var oldestOrder tradingstate.OrderItem
|
||||
if amount.Sign() > 0 {
|
||||
oldestOrder = tradingStateDB.GetOrder(orderBook, orderId)
|
||||
}
|
||||
log.Debug("found order ", "orderId ", orderId, "side", oldestOrder.Side, "amount", amount)
|
||||
if oldestOrder.Quantity == nil || oldestOrder.Quantity.Sign() == 0 && amount.Sign() == 0 {
|
||||
break
|
||||
}
|
||||
var (
|
||||
tradedQuantity *big.Int
|
||||
maxTradedQuantity *big.Int
|
||||
)
|
||||
if quantityToTrade.Cmp(amount) <= 0 {
|
||||
maxTradedQuantity = tradingstate.CloneBigInt(quantityToTrade)
|
||||
} else {
|
||||
maxTradedQuantity = tradingstate.CloneBigInt(amount)
|
||||
}
|
||||
var quotePrice *big.Int
|
||||
if oldestOrder.QuoteToken.String() != common.XDCNativeAddress {
|
||||
quotePrice = tradingStateDB.GetLastPrice(tradingstate.GetTradingOrderBookHash(oldestOrder.QuoteToken, common.HexToAddress(common.XDCNativeAddress)))
|
||||
log.Debug("TryGet quotePrice QuoteToken/XDC", "quotePrice", quotePrice)
|
||||
if quotePrice == nil || quotePrice.Sign() == 0 {
|
||||
inversePrice := tradingStateDB.GetLastPrice(tradingstate.GetTradingOrderBookHash(common.HexToAddress(common.XDCNativeAddress), oldestOrder.QuoteToken))
|
||||
quoteTokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, oldestOrder.QuoteToken)
|
||||
if err != nil || quoteTokenDecimal.Sign() == 0 {
|
||||
return nil, nil, nil, fmt.Errorf("Fail to get tokenDecimal. Token: %v . Err: %v", oldestOrder.QuoteToken.String(), err)
|
||||
}
|
||||
log.Debug("TryGet inversePrice XDC/QuoteToken", "inversePrice", inversePrice)
|
||||
if inversePrice != nil && inversePrice.Sign() > 0 {
|
||||
quotePrice = new(big.Int).Mul(common.BasePrice, quoteTokenDecimal)
|
||||
quotePrice = new(big.Int).Div(quotePrice, inversePrice)
|
||||
log.Debug("TryGet quotePrice after get inversePrice XDC/QuoteToken", "quotePrice", quotePrice, "quoteTokenDecimal", quoteTokenDecimal)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quotePrice = common.BasePrice
|
||||
}
|
||||
tradedQuantity, rejectMaker, settleBalanceResult, err := XDCx.getTradeQuantity(quotePrice, coinbase, chain, statedb, order, &oldestOrder, maxTradedQuantity)
|
||||
if err != nil && err == tradingstate.ErrQuantityTradeTooSmall {
|
||||
if tradedQuantity.Cmp(maxTradedQuantity) == 0 {
|
||||
if quantityToTrade.Cmp(amount) == 0 { // reject Taker & maker
|
||||
rejects = append(rejects, order)
|
||||
quantityToTrade = tradingstate.Zero
|
||||
rejects = append(rejects, &oldestOrder)
|
||||
err = tradingStateDB.CancelOrder(orderBook, &oldestOrder)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
break
|
||||
} else if quantityToTrade.Cmp(amount) < 0 { // reject Taker
|
||||
rejects = append(rejects, order)
|
||||
quantityToTrade = tradingstate.Zero
|
||||
break
|
||||
} else { // reject maker
|
||||
rejects = append(rejects, &oldestOrder)
|
||||
err = tradingStateDB.CancelOrder(orderBook, &oldestOrder)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
continue
|
||||
}
|
||||
} else {
|
||||
if rejectMaker { // reject maker
|
||||
rejects = append(rejects, &oldestOrder)
|
||||
err = tradingStateDB.CancelOrder(orderBook, &oldestOrder)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
continue
|
||||
} else { // reject Taker
|
||||
rejects = append(rejects, order)
|
||||
quantityToTrade = tradingstate.Zero
|
||||
break
|
||||
}
|
||||
}
|
||||
} else if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
if tradedQuantity.Sign() == 0 && !rejectMaker {
|
||||
log.Debug("Reject order Taker ", "tradedQuantity", tradedQuantity, "rejectMaker", rejectMaker)
|
||||
rejects = append(rejects, order)
|
||||
quantityToTrade = tradingstate.Zero
|
||||
break
|
||||
}
|
||||
if tradedQuantity.Sign() > 0 {
|
||||
quantityToTrade = tradingstate.Sub(quantityToTrade, tradedQuantity)
|
||||
tradingStateDB.SubAmountOrderItem(orderBook, orderId, price, tradedQuantity, side)
|
||||
tradingStateDB.SetLastPrice(orderBook, price)
|
||||
log.Debug("Update quantity for orderId", "orderId", orderId.Hex())
|
||||
log.Debug("TRADE", "orderBook", orderBook, "Taker price", price, "maker price", order.Price, "Amount", tradedQuantity, "orderId", orderId, "side", side)
|
||||
|
||||
tradeRecord := make(map[string]string)
|
||||
tradeRecord[tradingstate.TradeTakerOrderHash] = order.Hash.Hex()
|
||||
tradeRecord[tradingstate.TradeMakerOrderHash] = oldestOrder.Hash.Hex()
|
||||
tradeRecord[tradingstate.TradeTimestamp] = strconv.FormatInt(time.Now().Unix(), 10)
|
||||
tradeRecord[tradingstate.TradeQuantity] = tradedQuantity.String()
|
||||
tradeRecord[tradingstate.TradeMakerExchange] = oldestOrder.ExchangeAddress.String()
|
||||
tradeRecord[tradingstate.TradeMaker] = oldestOrder.UserAddress.String()
|
||||
tradeRecord[tradingstate.TradeBaseToken] = oldestOrder.BaseToken.String()
|
||||
tradeRecord[tradingstate.TradeQuoteToken] = oldestOrder.QuoteToken.String()
|
||||
if settleBalanceResult != nil {
|
||||
tradeRecord[tradingstate.MakerFee] = settleBalanceResult.Maker.Fee.Text(10)
|
||||
tradeRecord[tradingstate.TakerFee] = settleBalanceResult.Taker.Fee.Text(10)
|
||||
}
|
||||
// maker price is actual price
|
||||
// Taker price is offer price
|
||||
// tradedPrice is always actual price
|
||||
tradeRecord[tradingstate.TradePrice] = oldestOrder.Price.String()
|
||||
tradeRecord[tradingstate.MakerOrderType] = oldestOrder.Type
|
||||
trades = append(trades, tradeRecord)
|
||||
|
||||
oldAveragePrice, oldTotalQuantity := tradingStateDB.GetMediumPriceAndTotalAmount(orderBook)
|
||||
|
||||
var newAveragePrice, newTotalQuantity *big.Int
|
||||
if oldAveragePrice == nil || oldAveragePrice.Sign() <= 0 || oldTotalQuantity == nil || oldTotalQuantity.Sign() <= 0 {
|
||||
newAveragePrice = price
|
||||
newTotalQuantity = tradedQuantity
|
||||
} else {
|
||||
//volume = price * quantity
|
||||
//=> price = volume /quantity
|
||||
// averagePrice = totalVolume / totalQuantity
|
||||
// averagePrice = (oldVolume + newTradeVolume) / (oldQuantity + newTradeQuantity)
|
||||
// FIXME: average price formula
|
||||
// https://user-images.githubusercontent.com/17243442/72722447-ecb83700-3bb0-11ea-9273-1c1028dbade0.jpg
|
||||
|
||||
oldVolume := new(big.Int).Mul(oldAveragePrice, oldTotalQuantity)
|
||||
newTradeVolume := new(big.Int).Mul(price, tradedQuantity)
|
||||
newTotalQuantity = new(big.Int).Add(oldTotalQuantity, tradedQuantity)
|
||||
newAveragePrice = new(big.Int).Div(new(big.Int).Add(oldVolume, newTradeVolume), newTotalQuantity)
|
||||
}
|
||||
|
||||
tradingStateDB.SetMediumPrice(orderBook, newAveragePrice, newTotalQuantity)
|
||||
}
|
||||
if rejectMaker {
|
||||
rejects = append(rejects, &oldestOrder)
|
||||
err := tradingStateDB.CancelOrder(orderBook, &oldestOrder)
|
||||
if err != nil {
|
||||
return nil, nil, nil, err
|
||||
}
|
||||
}
|
||||
}
|
||||
return quantityToTrade, trades, rejects, nil
|
||||
}
|
||||
|
||||
func (XDCx *XDCX) getTradeQuantity(quotePrice *big.Int, coinbase common.Address, chain consensus.ChainContext, statedb *state.StateDB, takerOrder *tradingstate.OrderItem, makerOrder *tradingstate.OrderItem, quantityToTrade *big.Int) (*big.Int, bool, *tradingstate.SettleBalance, error) {
|
||||
baseTokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, makerOrder.BaseToken)
|
||||
if err != nil || baseTokenDecimal.Sign() == 0 {
|
||||
return tradingstate.Zero, false, nil, fmt.Errorf("Fail to get tokenDecimal. Token: %v . Err: %v", makerOrder.BaseToken.String(), err)
|
||||
}
|
||||
quoteTokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, makerOrder.QuoteToken)
|
||||
if err != nil || quoteTokenDecimal.Sign() == 0 {
|
||||
return tradingstate.Zero, false, nil, fmt.Errorf("Fail to get tokenDecimal. Token: %v . Err: %v", makerOrder.QuoteToken.String(), err)
|
||||
}
|
||||
if makerOrder.QuoteToken.String() == common.XDCNativeAddress {
|
||||
quotePrice = quoteTokenDecimal
|
||||
}
|
||||
if takerOrder.ExchangeAddress.String() == makerOrder.ExchangeAddress.String() {
|
||||
if err := tradingstate.CheckRelayerFee(takerOrder.ExchangeAddress, new(big.Int).Mul(common.RelayerFee, big.NewInt(2)), statedb); err != nil {
|
||||
log.Debug("Reject order Taker Exchnage = Maker Exchange , relayer not enough fee ", "err", err)
|
||||
return tradingstate.Zero, false, nil, nil
|
||||
}
|
||||
} else {
|
||||
if err := tradingstate.CheckRelayerFee(takerOrder.ExchangeAddress, common.RelayerFee, statedb); err != nil {
|
||||
log.Debug("Reject order Taker , relayer not enough fee ", "err", err)
|
||||
return tradingstate.Zero, false, nil, nil
|
||||
}
|
||||
if err := tradingstate.CheckRelayerFee(makerOrder.ExchangeAddress, common.RelayerFee, statedb); err != nil {
|
||||
log.Debug("Reject order maker , relayer not enough fee ", "err", err)
|
||||
return tradingstate.Zero, true, nil, nil
|
||||
}
|
||||
}
|
||||
takerFeeRate := tradingstate.GetExRelayerFee(takerOrder.ExchangeAddress, statedb)
|
||||
makerFeeRate := tradingstate.GetExRelayerFee(makerOrder.ExchangeAddress, statedb)
|
||||
var takerBalance, makerBalance *big.Int
|
||||
switch takerOrder.Side {
|
||||
case tradingstate.Bid:
|
||||
takerBalance = tradingstate.GetTokenBalance(takerOrder.UserAddress, makerOrder.QuoteToken, statedb)
|
||||
makerBalance = tradingstate.GetTokenBalance(makerOrder.UserAddress, makerOrder.BaseToken, statedb)
|
||||
case tradingstate.Ask:
|
||||
takerBalance = tradingstate.GetTokenBalance(takerOrder.UserAddress, makerOrder.BaseToken, statedb)
|
||||
makerBalance = tradingstate.GetTokenBalance(makerOrder.UserAddress, makerOrder.QuoteToken, statedb)
|
||||
default:
|
||||
takerBalance = big.NewInt(0)
|
||||
makerBalance = big.NewInt(0)
|
||||
}
|
||||
quantity, rejectMaker := GetTradeQuantity(takerOrder.Side, takerFeeRate, takerBalance, makerOrder.Price, makerFeeRate, makerBalance, baseTokenDecimal, quantityToTrade)
|
||||
log.Debug("GetTradeQuantity", "side", takerOrder.Side, "takerBalance", takerBalance, "makerBalance", makerBalance, "BaseToken", makerOrder.BaseToken, "QuoteToken", makerOrder.QuoteToken, "quantity", quantity, "rejectMaker", rejectMaker, "quotePrice", quotePrice)
|
||||
var settleBalanceResult *tradingstate.SettleBalance
|
||||
if quantity.Sign() > 0 {
|
||||
// Apply Match Order
|
||||
settleBalanceResult, err = tradingstate.GetSettleBalance(quotePrice, takerOrder.Side, takerFeeRate, makerOrder.BaseToken, makerOrder.QuoteToken, makerOrder.Price, makerFeeRate, baseTokenDecimal, quoteTokenDecimal, quantity)
|
||||
log.Debug("GetSettleBalance", "settleBalanceResult", settleBalanceResult, "err", err)
|
||||
if err == nil {
|
||||
err = DoSettleBalance(coinbase, takerOrder, makerOrder, settleBalanceResult, statedb)
|
||||
}
|
||||
return quantity, rejectMaker, settleBalanceResult, err
|
||||
}
|
||||
return quantity, rejectMaker, settleBalanceResult, nil
|
||||
}
|
||||
|
||||
func GetTradeQuantity(takerSide string, takerFeeRate *big.Int, takerBalance *big.Int, makerPrice *big.Int, makerFeeRate *big.Int, makerBalance *big.Int, baseTokenDecimal *big.Int, quantityToTrade *big.Int) (*big.Int, bool) {
|
||||
if takerSide == tradingstate.Bid {
|
||||
// maker InQuantity quoteTokenQuantity=(quantityToTrade*maker.Price/baseTokenDecimal)
|
||||
quoteTokenQuantity := new(big.Int).Mul(quantityToTrade, makerPrice)
|
||||
quoteTokenQuantity = new(big.Int).Div(quoteTokenQuantity, baseTokenDecimal)
|
||||
// Fee
|
||||
// charge on the token he/she has before the trade, in this case: quoteToken
|
||||
// charge on the token he/she has before the trade, in this case: baseToken
|
||||
// takerFee = quoteTokenQuantity*takerFeeRate/baseFee=(quantityToTrade*maker.Price/baseTokenDecimal) * makerFeeRate/baseFee
|
||||
takerFee := big.NewInt(0).Mul(quoteTokenQuantity, takerFeeRate)
|
||||
takerFee = big.NewInt(0).Div(takerFee, common.XDCXBaseFee)
|
||||
//takerOutTotal= quoteTokenQuantity + takerFee = quantityToTrade*maker.Price/baseTokenDecimal + quantityToTrade*maker.Price/baseTokenDecimal * takerFeeRate/baseFee
|
||||
// = quantityToTrade * maker.Price/baseTokenDecimal ( 1 + takerFeeRate/baseFee)
|
||||
// = quantityToTrade * maker.Price * (baseFee + takerFeeRate ) / ( baseTokenDecimal * baseFee)
|
||||
takerOutTotal := new(big.Int).Add(quoteTokenQuantity, takerFee)
|
||||
makerOutTotal := quantityToTrade
|
||||
if takerBalance.Cmp(takerOutTotal) >= 0 && makerBalance.Cmp(makerOutTotal) >= 0 {
|
||||
return quantityToTrade, false
|
||||
} else if takerBalance.Cmp(takerOutTotal) < 0 && makerBalance.Cmp(makerOutTotal) >= 0 {
|
||||
newQuantityTrade := new(big.Int).Mul(takerBalance, baseTokenDecimal)
|
||||
newQuantityTrade = new(big.Int).Mul(newQuantityTrade, common.XDCXBaseFee)
|
||||
newQuantityTrade = new(big.Int).Div(newQuantityTrade, new(big.Int).Add(common.XDCXBaseFee, takerFeeRate))
|
||||
newQuantityTrade = new(big.Int).Div(newQuantityTrade, makerPrice)
|
||||
if newQuantityTrade.Sign() == 0 {
|
||||
log.Debug("Reject order Taker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "takerOutTotal", takerOutTotal)
|
||||
}
|
||||
return newQuantityTrade, false
|
||||
} else if takerBalance.Cmp(takerOutTotal) >= 0 && makerBalance.Cmp(makerOutTotal) < 0 {
|
||||
log.Debug("Reject order maker , not enough balance ", "makerBalance", makerBalance, " makerOutTotal", makerOutTotal)
|
||||
return makerBalance, true
|
||||
} else {
|
||||
// takerBalance.Cmp(takerOutTotal) < 0 && makerBalance.Cmp(makerOutTotal) < 0
|
||||
newQuantityTrade := new(big.Int).Mul(takerBalance, baseTokenDecimal)
|
||||
newQuantityTrade = new(big.Int).Mul(newQuantityTrade, common.XDCXBaseFee)
|
||||
newQuantityTrade = new(big.Int).Div(newQuantityTrade, new(big.Int).Add(common.XDCXBaseFee, takerFeeRate))
|
||||
newQuantityTrade = new(big.Int).Div(newQuantityTrade, makerPrice)
|
||||
if newQuantityTrade.Cmp(makerBalance) <= 0 {
|
||||
if newQuantityTrade.Sign() == 0 {
|
||||
log.Debug("Reject order Taker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "makerBalance", makerBalance, " newQuantityTrade ", newQuantityTrade)
|
||||
}
|
||||
return newQuantityTrade, false
|
||||
}
|
||||
log.Debug("Reject order maker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "makerBalance", makerBalance, " newQuantityTrade ", newQuantityTrade)
|
||||
return makerBalance, true
|
||||
}
|
||||
} else {
|
||||
// Taker InQuantity
|
||||
// quoteTokenQuantity = quantityToTrade * makerPrice / baseTokenDecimal
|
||||
quoteTokenQuantity := new(big.Int).Mul(quantityToTrade, makerPrice)
|
||||
quoteTokenQuantity = new(big.Int).Div(quoteTokenQuantity, baseTokenDecimal)
|
||||
// maker InQuantity
|
||||
|
||||
// Fee
|
||||
// charge on the token he/she has before the trade, in this case: baseToken
|
||||
// makerFee = quoteTokenQuantity * makerFeeRate / baseFee = quantityToTrade * makerPrice / baseTokenDecimal * makerFeeRate / baseFee
|
||||
// charge on the token he/she has before the trade, in this case: quoteToken
|
||||
makerFee := new(big.Int).Mul(quoteTokenQuantity, makerFeeRate)
|
||||
makerFee = new(big.Int).Div(makerFee, common.XDCXBaseFee)
|
||||
|
||||
takerOutTotal := quantityToTrade
|
||||
// makerOutTotal = quoteTokenQuantity + makerFee = quantityToTrade * makerPrice / baseTokenDecimal + quantityToTrade * makerPrice / baseTokenDecimal * makerFeeRate / baseFee
|
||||
// = quantityToTrade * makerPrice / baseTokenDecimal * (1+makerFeeRate / baseFee)
|
||||
// = quantityToTrade * makerPrice * (baseFee + makerFeeRate) / ( baseTokenDecimal * baseFee )
|
||||
makerOutTotal := new(big.Int).Add(quoteTokenQuantity, makerFee)
|
||||
if takerBalance.Cmp(takerOutTotal) >= 0 && makerBalance.Cmp(makerOutTotal) >= 0 {
|
||||
return quantityToTrade, false
|
||||
} else if takerBalance.Cmp(takerOutTotal) < 0 && makerBalance.Cmp(makerOutTotal) >= 0 {
|
||||
if takerBalance.Sign() == 0 {
|
||||
log.Debug("Reject order Taker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "takerOutTotal", takerOutTotal)
|
||||
}
|
||||
return takerBalance, false
|
||||
} else if takerBalance.Cmp(takerOutTotal) >= 0 && makerBalance.Cmp(makerOutTotal) < 0 {
|
||||
newQuantityTrade := new(big.Int).Mul(makerBalance, baseTokenDecimal)
|
||||
newQuantityTrade = new(big.Int).Mul(newQuantityTrade, common.XDCXBaseFee)
|
||||
newQuantityTrade = new(big.Int).Div(newQuantityTrade, new(big.Int).Add(common.XDCXBaseFee, makerFeeRate))
|
||||
newQuantityTrade = new(big.Int).Div(newQuantityTrade, makerPrice)
|
||||
log.Debug("Reject order maker , not enough balance ", "makerBalance", makerBalance, " makerOutTotal", makerOutTotal)
|
||||
return newQuantityTrade, true
|
||||
} else {
|
||||
// takerBalance.Cmp(takerOutTotal) < 0 && makerBalance.Cmp(makerOutTotal) < 0
|
||||
newQuantityTrade := new(big.Int).Mul(makerBalance, baseTokenDecimal)
|
||||
newQuantityTrade = new(big.Int).Mul(newQuantityTrade, common.XDCXBaseFee)
|
||||
newQuantityTrade = new(big.Int).Div(newQuantityTrade, new(big.Int).Add(common.XDCXBaseFee, makerFeeRate))
|
||||
newQuantityTrade = new(big.Int).Div(newQuantityTrade, makerPrice)
|
||||
if newQuantityTrade.Cmp(takerBalance) <= 0 {
|
||||
log.Debug("Reject order maker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "makerBalance", makerBalance, " newQuantityTrade ", newQuantityTrade)
|
||||
return newQuantityTrade, true
|
||||
}
|
||||
if takerBalance.Sign() == 0 {
|
||||
log.Debug("Reject order Taker , not enough balance ", "takerSide", takerSide, "takerBalance", takerBalance, "makerBalance", makerBalance, " newQuantityTrade ", newQuantityTrade)
|
||||
}
|
||||
return takerBalance, false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func DoSettleBalance(coinbase common.Address, takerOrder, makerOrder *tradingstate.OrderItem, settleBalance *tradingstate.SettleBalance, statedb *state.StateDB) error {
|
||||
takerExOwner := tradingstate.GetRelayerOwner(takerOrder.ExchangeAddress, statedb)
|
||||
makerExOwner := tradingstate.GetRelayerOwner(makerOrder.ExchangeAddress, statedb)
|
||||
matchingFee := big.NewInt(0)
|
||||
// masternodes charges fee of both 2 relayers. If maker and Taker are on same relayer, that relayer is charged fee twice
|
||||
matchingFee = new(big.Int).Add(matchingFee, common.RelayerFee)
|
||||
matchingFee = new(big.Int).Add(matchingFee, common.RelayerFee)
|
||||
|
||||
if common.EmptyHash(takerExOwner.Hash()) || common.EmptyHash(makerExOwner.Hash()) {
|
||||
return fmt.Errorf("Echange owner empty , Taker: %v , maker : %v ", takerExOwner, makerExOwner)
|
||||
}
|
||||
mapBalances := map[common.Address]map[common.Address]*big.Int{}
|
||||
//Checking balance
|
||||
newTakerInTotal, err := tradingstate.CheckAddTokenBalance(takerOrder.UserAddress, settleBalance.Taker.InTotal, settleBalance.Taker.InToken, statedb, mapBalances)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mapBalances[settleBalance.Taker.InToken] == nil {
|
||||
mapBalances[settleBalance.Taker.InToken] = map[common.Address]*big.Int{}
|
||||
}
|
||||
mapBalances[settleBalance.Taker.InToken][takerOrder.UserAddress] = newTakerInTotal
|
||||
newTakerOutTotal, err := tradingstate.CheckSubTokenBalance(takerOrder.UserAddress, settleBalance.Taker.OutTotal, settleBalance.Taker.OutToken, statedb, mapBalances)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mapBalances[settleBalance.Taker.OutToken] == nil {
|
||||
mapBalances[settleBalance.Taker.OutToken] = map[common.Address]*big.Int{}
|
||||
}
|
||||
mapBalances[settleBalance.Taker.OutToken][takerOrder.UserAddress] = newTakerOutTotal
|
||||
newMakerInTotal, err := tradingstate.CheckAddTokenBalance(makerOrder.UserAddress, settleBalance.Maker.InTotal, settleBalance.Maker.InToken, statedb, mapBalances)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mapBalances[settleBalance.Maker.InToken] == nil {
|
||||
mapBalances[settleBalance.Maker.InToken] = map[common.Address]*big.Int{}
|
||||
}
|
||||
mapBalances[settleBalance.Maker.InToken][makerOrder.UserAddress] = newMakerInTotal
|
||||
newMakerOutTotal, err := tradingstate.CheckSubTokenBalance(makerOrder.UserAddress, settleBalance.Maker.OutTotal, settleBalance.Maker.OutToken, statedb, mapBalances)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mapBalances[settleBalance.Maker.OutToken] == nil {
|
||||
mapBalances[settleBalance.Maker.OutToken] = map[common.Address]*big.Int{}
|
||||
}
|
||||
mapBalances[settleBalance.Maker.OutToken][makerOrder.UserAddress] = newMakerOutTotal
|
||||
newTakerFee, err := tradingstate.CheckAddTokenBalance(takerExOwner, settleBalance.Taker.Fee, makerOrder.QuoteToken, statedb, mapBalances)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mapBalances[makerOrder.QuoteToken] == nil {
|
||||
mapBalances[makerOrder.QuoteToken] = map[common.Address]*big.Int{}
|
||||
}
|
||||
mapBalances[makerOrder.QuoteToken][takerExOwner] = newTakerFee
|
||||
newMakerFee, err := tradingstate.CheckAddTokenBalance(makerExOwner, settleBalance.Maker.Fee, makerOrder.QuoteToken, statedb, mapBalances)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mapBalances[makerOrder.QuoteToken][makerExOwner] = newMakerFee
|
||||
|
||||
mapRelayerFee := map[common.Address]*big.Int{}
|
||||
newRelayerTakerFee, err := tradingstate.CheckSubRelayerFee(takerOrder.ExchangeAddress, common.RelayerFee, statedb, mapRelayerFee)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mapRelayerFee[takerOrder.ExchangeAddress] = newRelayerTakerFee
|
||||
newRelayerMakerFee, err := tradingstate.CheckSubRelayerFee(makerOrder.ExchangeAddress, common.RelayerFee, statedb, mapRelayerFee)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
mapRelayerFee[makerOrder.ExchangeAddress] = newRelayerMakerFee
|
||||
tradingstate.SetSubRelayerFee(takerOrder.ExchangeAddress, newRelayerTakerFee, common.RelayerFee, statedb)
|
||||
tradingstate.SetSubRelayerFee(makerOrder.ExchangeAddress, newRelayerMakerFee, common.RelayerFee, statedb)
|
||||
|
||||
masternodeOwner := statedb.GetOwner(coinbase)
|
||||
statedb.AddBalance(masternodeOwner, matchingFee)
|
||||
|
||||
tradingstate.SetTokenBalance(takerOrder.UserAddress, newTakerInTotal, settleBalance.Taker.InToken, statedb)
|
||||
tradingstate.SetTokenBalance(takerOrder.UserAddress, newTakerOutTotal, settleBalance.Taker.OutToken, statedb)
|
||||
|
||||
tradingstate.SetTokenBalance(makerOrder.UserAddress, newMakerInTotal, settleBalance.Maker.InToken, statedb)
|
||||
tradingstate.SetTokenBalance(makerOrder.UserAddress, newMakerOutTotal, settleBalance.Maker.OutToken, statedb)
|
||||
|
||||
// add balance for relayers
|
||||
//log.Debug("ApplyXDCXMatchedTransaction settle fee for relayers",
|
||||
// "takerRelayerOwner", takerExOwner,
|
||||
// "takerFeeToken", quoteToken, "takerFee", settleBalanceResult[takerAddr][XDCx.Fee].(*big.Int),
|
||||
// "makerRelayerOwner", makerExOwner,
|
||||
// "makerFeeToken", quoteToken, "makerFee", settleBalanceResult[makerAddr][XDCx.Fee].(*big.Int))
|
||||
// takerFee
|
||||
tradingstate.SetTokenBalance(takerExOwner, newTakerFee, makerOrder.QuoteToken, statedb)
|
||||
tradingstate.SetTokenBalance(makerExOwner, newMakerFee, makerOrder.QuoteToken, statedb)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (XDCx *XDCX) ProcessCancelOrder(header *types.Header, tradingStateDB *tradingstate.TradingStateDB, statedb *state.StateDB, chain consensus.ChainContext, coinbase common.Address, orderBook common.Hash, order *tradingstate.OrderItem) (error, bool) {
|
||||
if err := tradingstate.CheckRelayerFee(order.ExchangeAddress, common.RelayerCancelFee, statedb); err != nil {
|
||||
log.Debug("Relayer not enough fee when cancel order", "err", err)
|
||||
return nil, true
|
||||
}
|
||||
baseTokenDecimal, err := XDCx.GetTokenDecimal(chain, statedb, order.BaseToken)
|
||||
if err != nil || baseTokenDecimal.Sign() == 0 {
|
||||
log.Debug("Fail to get tokenDecimal ", "Token", order.BaseToken.String(), "err", err)
|
||||
return err, false
|
||||
}
|
||||
// order: basic order information (includes orderId, orderHash, baseToken, quoteToken) which user send to XDCx to cancel order
|
||||
// originOrder: full order information getting from order trie
|
||||
originOrder := tradingStateDB.GetOrder(orderBook, common.BigToHash(new(big.Int).SetUint64(order.OrderID)))
|
||||
if originOrder == tradingstate.EmptyOrder {
|
||||
return fmt.Errorf("order not found. OrderId: %v. Base: %s. Quote: %s", order.OrderID, order.BaseToken.Hex(), order.QuoteToken.Hex()), false
|
||||
}
|
||||
var tokenBalance *big.Int
|
||||
switch originOrder.Side {
|
||||
case tradingstate.Ask:
|
||||
tokenBalance = tradingstate.GetTokenBalance(originOrder.UserAddress, originOrder.BaseToken, statedb)
|
||||
case tradingstate.Bid:
|
||||
tokenBalance = tradingstate.GetTokenBalance(originOrder.UserAddress, originOrder.QuoteToken, statedb)
|
||||
default:
|
||||
log.Debug("Not found order side", "Side", originOrder.Side)
|
||||
return nil, false
|
||||
}
|
||||
log.Debug("ProcessCancelOrder", "baseToken", originOrder.BaseToken, "quoteToken", originOrder.QuoteToken)
|
||||
feeRate := tradingstate.GetExRelayerFee(originOrder.ExchangeAddress, statedb)
|
||||
tokenCancelFee, tokenPriceInXDC := common.Big0, common.Big0
|
||||
if !chain.Config().IsTIPXDCXCancellationFee(header.Number) {
|
||||
tokenCancelFee = getCancelFeeV1(baseTokenDecimal, feeRate, &originOrder)
|
||||
} else {
|
||||
tokenCancelFee, tokenPriceInXDC = XDCx.getCancelFee(chain, statedb, tradingStateDB, &originOrder, feeRate)
|
||||
}
|
||||
if tokenBalance.Cmp(tokenCancelFee) < 0 {
|
||||
log.Debug("User not enough balance when cancel order", "Side", originOrder.Side, "balance", tokenBalance, "fee", tokenCancelFee)
|
||||
return nil, true
|
||||
}
|
||||
|
||||
err = tradingStateDB.CancelOrder(orderBook, order)
|
||||
if err != nil {
|
||||
log.Debug("Error when cancel order", "order", order)
|
||||
return err, false
|
||||
}
|
||||
// relayers pay XDC for masternode
|
||||
tradingstate.SubRelayerFee(originOrder.ExchangeAddress, common.RelayerCancelFee, statedb)
|
||||
masternodeOwner := statedb.GetOwner(coinbase)
|
||||
// relayers pay XDC for masternode
|
||||
statedb.AddBalance(masternodeOwner, common.RelayerCancelFee)
|
||||
|
||||
relayerOwner := tradingstate.GetRelayerOwner(originOrder.ExchangeAddress, statedb)
|
||||
switch originOrder.Side {
|
||||
case tradingstate.Ask:
|
||||
// users pay token (which they have) for relayer
|
||||
tradingstate.SubTokenBalance(originOrder.UserAddress, tokenCancelFee, originOrder.BaseToken, statedb)
|
||||
tradingstate.AddTokenBalance(relayerOwner, tokenCancelFee, originOrder.BaseToken, statedb)
|
||||
case tradingstate.Bid:
|
||||
// users pay token (which they have) for relayer
|
||||
tradingstate.SubTokenBalance(originOrder.UserAddress, tokenCancelFee, originOrder.QuoteToken, statedb)
|
||||
tradingstate.AddTokenBalance(relayerOwner, tokenCancelFee, originOrder.QuoteToken, statedb)
|
||||
default:
|
||||
}
|
||||
// update cancel fee
|
||||
extraData, _ := json.Marshal(struct {
|
||||
CancelFee string
|
||||
TokenPriceInXDC string
|
||||
}{
|
||||
CancelFee: tokenCancelFee.Text(10),
|
||||
TokenPriceInXDC: tokenPriceInXDC.Text(10),
|
||||
})
|
||||
order.ExtraData = string(extraData)
|
||||
|
||||
return nil, false
|
||||
}
|
||||
|
||||
// cancellation fee = 1/10 trading fee
|
||||
// deprecated after hardfork at TIPXDCXCancellationFee
|
||||
func getCancelFeeV1(baseTokenDecimal *big.Int, feeRate *big.Int, order *tradingstate.OrderItem) *big.Int {
|
||||
cancelFee := big.NewInt(0)
|
||||
if order.Side == tradingstate.Ask {
|
||||
// SELL 1 BTC => XDC ,,
|
||||
// order.Quantity =1 && fee rate =2
|
||||
// ==> cancel fee = 2/10000
|
||||
// order.Quantity already included baseToken decimal
|
||||
cancelFee = new(big.Int).Mul(order.Quantity, feeRate)
|
||||
cancelFee = new(big.Int).Div(cancelFee, common.XDCXBaseCancelFee)
|
||||
} else {
|
||||
// BUY 1 BTC => XDC with Price : 10000
|
||||
// quoteTokenQuantity = 10000 && fee rate =2
|
||||
// => cancel fee =2
|
||||
quoteTokenQuantity := new(big.Int).Mul(order.Quantity, order.Price)
|
||||
quoteTokenQuantity = new(big.Int).Div(quoteTokenQuantity, baseTokenDecimal)
|
||||
// Fee
|
||||
// makerFee = quoteTokenQuantity * feeRate / baseFee = quantityToTrade * makerPrice / baseTokenDecimal * feeRate / baseFee
|
||||
cancelFee = new(big.Int).Mul(quoteTokenQuantity, feeRate)
|
||||
cancelFee = new(big.Int).Div(cancelFee, common.XDCXBaseCancelFee)
|
||||
}
|
||||
return cancelFee
|
||||
}
|
||||
|
||||
// return tokenQuantity, tokenPriceInXDC
|
||||
func (XDCx *XDCX) getCancelFee(chain consensus.ChainContext, statedb *state.StateDB, tradingStateDb *tradingstate.TradingStateDB, order *tradingstate.OrderItem, feeRate *big.Int) (*big.Int, *big.Int) {
|
||||
if feeRate == nil || feeRate.Sign() == 0 {
|
||||
return common.Big0, common.Big0
|
||||
}
|
||||
cancelFee := big.NewInt(0)
|
||||
tokenPriceInXDC := big.NewInt(0)
|
||||
var err error
|
||||
if order.Side == tradingstate.Ask {
|
||||
cancelFee, tokenPriceInXDC, err = XDCx.ConvertXDCToToken(chain, statedb, tradingStateDb, order.BaseToken, common.RelayerCancelFee)
|
||||
} else {
|
||||
cancelFee, tokenPriceInXDC, err = XDCx.ConvertXDCToToken(chain, statedb, tradingStateDb, order.QuoteToken, common.RelayerCancelFee)
|
||||
}
|
||||
if err != nil {
|
||||
return common.Big0, common.Big0
|
||||
}
|
||||
return cancelFee, tokenPriceInXDC
|
||||
}
|
||||
|
||||
func (XDCx *XDCX) UpdateMediumPriceBeforeEpoch(epochNumber uint64, tradingStateDB *tradingstate.TradingStateDB, statedb *state.StateDB) error {
|
||||
mapPairs, err := tradingstate.GetAllTradingPairs(statedb)
|
||||
log.Debug("UpdateMediumPriceBeforeEpoch", "len(mapPairs)", len(mapPairs))
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
epochPriceResult := map[common.Hash]*big.Int{}
|
||||
for orderbook := range mapPairs {
|
||||
oldMediumPriceBeforeEpoch := tradingStateDB.GetMediumPriceBeforeEpoch(orderbook)
|
||||
mediumPriceCurrent, _ := tradingStateDB.GetMediumPriceAndTotalAmount(orderbook)
|
||||
|
||||
// if there is no trade in this epoch, use average price of last epoch
|
||||
epochPriceResult[orderbook] = oldMediumPriceBeforeEpoch
|
||||
if mediumPriceCurrent.Sign() > 0 && mediumPriceCurrent.Cmp(oldMediumPriceBeforeEpoch) != 0 {
|
||||
log.Debug("UpdateMediumPriceBeforeEpoch", "mediumPriceCurrent", mediumPriceCurrent)
|
||||
tradingStateDB.SetMediumPriceBeforeEpoch(orderbook, mediumPriceCurrent)
|
||||
epochPriceResult[orderbook] = mediumPriceCurrent
|
||||
}
|
||||
tradingStateDB.SetMediumPrice(orderbook, tradingstate.Zero, tradingstate.Zero)
|
||||
}
|
||||
if XDCx.IsSDKNode() {
|
||||
if err := XDCx.LogEpochPrice(epochNumber, epochPriceResult); err != nil {
|
||||
log.Error("failed to update epochPrice", "err", err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// put average price of epoch to mongodb for tracking liquidation trades
|
||||
// epochPriceResult: a map of epoch average price, key is orderbook hash , value is epoch average price
|
||||
// orderbook hash genereted from baseToken, quoteToken at XDPoSChain/XDCx/tradingstate/common.go:214
|
||||
func (XDCx *XDCX) LogEpochPrice(epochNumber uint64, epochPriceResult map[common.Hash]*big.Int) error {
|
||||
db := XDCx.GetMongoDB()
|
||||
db.InitBulk()
|
||||
|
||||
for orderbook, price := range epochPriceResult {
|
||||
if price.Sign() <= 0 {
|
||||
continue
|
||||
}
|
||||
epochPriceItem := &tradingstate.EpochPriceItem{
|
||||
Epoch: epochNumber,
|
||||
Orderbook: orderbook,
|
||||
Price: price,
|
||||
}
|
||||
epochPriceItem.Hash = epochPriceItem.ComputeHash()
|
||||
if err := db.PutObject(epochPriceItem.Hash, epochPriceItem); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := db.CommitBulk(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
574
XDCx/order_processor_test.go
Normal file
574
XDCx/order_processor_test.go
Normal file
|
|
@ -0,0 +1,574 @@
|
|||
package XDCx
|
||||
|
||||
import (
|
||||
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_getCancelFeeV1(t *testing.T) {
|
||||
type CancelFeeArg struct {
|
||||
baseTokenDecimal *big.Int
|
||||
feeRate *big.Int
|
||||
order *tradingstate.OrderItem
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args CancelFeeArg
|
||||
want *big.Int
|
||||
}{
|
||||
// zero fee test: SELL
|
||||
{
|
||||
"zero fee getCancelFeeV1: SELL",
|
||||
CancelFeeArg{
|
||||
baseTokenDecimal: common.Big1,
|
||||
feeRate: common.Big0,
|
||||
order: &tradingstate.OrderItem{
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: tradingstate.Ask,
|
||||
},
|
||||
},
|
||||
common.Big0,
|
||||
},
|
||||
|
||||
// zero fee test: BUY
|
||||
{
|
||||
"zero fee getCancelFeeV1: BUY",
|
||||
CancelFeeArg{
|
||||
baseTokenDecimal: common.Big1,
|
||||
feeRate: common.Big0,
|
||||
order: &tradingstate.OrderItem{
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Price: new(big.Int).SetUint64(1),
|
||||
Side: tradingstate.Bid,
|
||||
},
|
||||
},
|
||||
common.Big0,
|
||||
},
|
||||
|
||||
// test getCancelFee: SELL
|
||||
{
|
||||
"test getCancelFeeV1:: SELL",
|
||||
CancelFeeArg{
|
||||
baseTokenDecimal: common.Big1,
|
||||
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
|
||||
order: &tradingstate.OrderItem{
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: tradingstate.Ask,
|
||||
},
|
||||
},
|
||||
common.Big1,
|
||||
},
|
||||
|
||||
// test getCancelFee:: BUY
|
||||
{
|
||||
"test getCancelFeeV1:: BUY",
|
||||
CancelFeeArg{
|
||||
baseTokenDecimal: common.Big1,
|
||||
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
|
||||
order: &tradingstate.OrderItem{
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Price: new(big.Int).SetUint64(1),
|
||||
Side: tradingstate.Bid,
|
||||
},
|
||||
},
|
||||
common.Big1,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := getCancelFeeV1(tt.args.baseTokenDecimal, tt.args.feeRate, tt.args.order); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("getCancelFeeV1() = %v, quantity %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getCancelFee(t *testing.T) {
|
||||
XDCx := New(&DefaultConfig)
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
stateCache := tradingstate.NewDatabase(db)
|
||||
tradingStateDb, _ := tradingstate.New(common.Hash{}, stateCache)
|
||||
|
||||
testTokenA := common.HexToAddress("0x1000000000000000000000000000000000000002")
|
||||
testTokenB := common.HexToAddress("0x1100000000000000000000000000000000000003")
|
||||
// set decimal
|
||||
// tokenA has decimal 10^18
|
||||
XDCx.SetTokenDecimal(testTokenA, common.BasePrice)
|
||||
// tokenB has decimal 10^8
|
||||
tokenBDecimal := new(big.Int).Exp(big.NewInt(10), big.NewInt(8), nil)
|
||||
XDCx.SetTokenDecimal(testTokenB, tokenBDecimal)
|
||||
|
||||
// set tokenAPrice = 1 XDC
|
||||
tradingStateDb.SetMediumPriceBeforeEpoch(tradingstate.GetTradingOrderBookHash(testTokenA, common.HexToAddress(common.XDCNativeAddress)), common.BasePrice)
|
||||
// set tokenBPrice = 1 XDC
|
||||
tradingStateDb.SetMediumPriceBeforeEpoch(tradingstate.GetTradingOrderBookHash(common.HexToAddress(common.XDCNativeAddress), testTokenB), tokenBDecimal)
|
||||
|
||||
type CancelFeeArg struct {
|
||||
feeRate *big.Int
|
||||
order *tradingstate.OrderItem
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args CancelFeeArg
|
||||
want *big.Int
|
||||
}{
|
||||
|
||||
// BASE: testTokenA,
|
||||
// QUOTE: XDC
|
||||
|
||||
// zero fee test: SELL
|
||||
{
|
||||
"TokenA/XDC zero fee test: SELL",
|
||||
CancelFeeArg{
|
||||
feeRate: common.Big0,
|
||||
order: &tradingstate.OrderItem{
|
||||
BaseToken: testTokenA,
|
||||
QuoteToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: tradingstate.Ask,
|
||||
},
|
||||
},
|
||||
common.Big0,
|
||||
},
|
||||
|
||||
// zero fee test: BUY
|
||||
{
|
||||
"TokenA/XDC zero fee test: BUY",
|
||||
CancelFeeArg{
|
||||
feeRate: common.Big0,
|
||||
order: &tradingstate.OrderItem{
|
||||
BaseToken: testTokenA,
|
||||
QuoteToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: tradingstate.Bid,
|
||||
},
|
||||
},
|
||||
common.Big0,
|
||||
},
|
||||
|
||||
// test getCancelFee: SELL
|
||||
{
|
||||
"TokenA/XDC test getCancelFee:: SELL",
|
||||
CancelFeeArg{
|
||||
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
|
||||
order: &tradingstate.OrderItem{
|
||||
BaseToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
QuoteToken: testTokenA,
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: tradingstate.Ask,
|
||||
},
|
||||
},
|
||||
common.RelayerCancelFee,
|
||||
},
|
||||
|
||||
// test getCancelFee:: BUY
|
||||
{
|
||||
"TokenA/XDC test getCancelFee:: BUY",
|
||||
CancelFeeArg{
|
||||
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
|
||||
order: &tradingstate.OrderItem{
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
BaseToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
QuoteToken: testTokenA,
|
||||
Side: tradingstate.Bid,
|
||||
},
|
||||
},
|
||||
common.RelayerCancelFee,
|
||||
},
|
||||
|
||||
// BASE: XDC
|
||||
// QUOTE: testTokenA
|
||||
// zero fee test: SELL
|
||||
{
|
||||
"XDC/TokenA zero fee test: SELL",
|
||||
CancelFeeArg{
|
||||
feeRate: common.Big0,
|
||||
order: &tradingstate.OrderItem{
|
||||
BaseToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
QuoteToken: testTokenA,
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: tradingstate.Ask,
|
||||
},
|
||||
},
|
||||
common.Big0,
|
||||
},
|
||||
|
||||
// zero fee test: BUY
|
||||
{
|
||||
"XDC/TokenA zero fee test: BUY",
|
||||
CancelFeeArg{
|
||||
feeRate: common.Big0,
|
||||
order: &tradingstate.OrderItem{
|
||||
BaseToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
QuoteToken: testTokenA,
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: tradingstate.Bid,
|
||||
},
|
||||
},
|
||||
common.Big0,
|
||||
},
|
||||
|
||||
// test getCancelFee: SELL
|
||||
{
|
||||
"XDC/TokenA test getCancelFee:: SELL",
|
||||
CancelFeeArg{
|
||||
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
|
||||
order: &tradingstate.OrderItem{
|
||||
BaseToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
QuoteToken: testTokenA,
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: tradingstate.Ask,
|
||||
},
|
||||
},
|
||||
common.RelayerCancelFee,
|
||||
},
|
||||
|
||||
// test getCancelFee:: BUY
|
||||
{
|
||||
"XDC/TokenA test getCancelFee:: BUY",
|
||||
CancelFeeArg{
|
||||
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
|
||||
order: &tradingstate.OrderItem{
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
BaseToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
QuoteToken: testTokenA,
|
||||
Side: tradingstate.Bid,
|
||||
},
|
||||
},
|
||||
common.RelayerCancelFee,
|
||||
},
|
||||
|
||||
// BASE: testTokenB
|
||||
// QUOTE: testTokenA
|
||||
// zero fee test: SELL
|
||||
{
|
||||
"TokenB/TokenA zero fee test: SELL",
|
||||
CancelFeeArg{
|
||||
feeRate: common.Big0,
|
||||
order: &tradingstate.OrderItem{
|
||||
BaseToken: testTokenB,
|
||||
QuoteToken: testTokenA,
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: tradingstate.Ask,
|
||||
},
|
||||
},
|
||||
common.Big0,
|
||||
},
|
||||
|
||||
// zero fee test: BUY
|
||||
{
|
||||
"TokenB/TokenA zero fee test: BUY",
|
||||
CancelFeeArg{
|
||||
feeRate: common.Big0,
|
||||
order: &tradingstate.OrderItem{
|
||||
BaseToken: testTokenB,
|
||||
QuoteToken: testTokenA,
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: tradingstate.Bid,
|
||||
},
|
||||
},
|
||||
common.Big0,
|
||||
},
|
||||
|
||||
// test getCancelFee: SELL
|
||||
{
|
||||
"TokenB/TokenA test getCancelFee:: SELL",
|
||||
CancelFeeArg{
|
||||
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
|
||||
order: &tradingstate.OrderItem{
|
||||
BaseToken: testTokenB,
|
||||
QuoteToken: testTokenA,
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: tradingstate.Ask,
|
||||
},
|
||||
},
|
||||
new(big.Int).Exp(big.NewInt(10), big.NewInt(4), nil),
|
||||
},
|
||||
|
||||
// test getCancelFee:: BUY
|
||||
{
|
||||
"TokenB/TokenA test getCancelFee:: BUY",
|
||||
CancelFeeArg{
|
||||
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
|
||||
order: &tradingstate.OrderItem{
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
BaseToken: testTokenB,
|
||||
QuoteToken: testTokenA,
|
||||
Side: tradingstate.Bid,
|
||||
},
|
||||
},
|
||||
common.RelayerCancelFee,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got, _ := XDCx.getCancelFee(nil, nil, tradingStateDb, tt.args.order, tt.args.feeRate); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("getCancelFee() = %v, quantity %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// testcase: can't get price of token in XDC
|
||||
testTokenC := common.HexToAddress("0x1200000000000000000000000000000000000004")
|
||||
XDCx.SetTokenDecimal(testTokenC, big.NewInt(1))
|
||||
tokenCOrder := CancelFeeArg{
|
||||
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
|
||||
order: &tradingstate.OrderItem{
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
BaseToken: testTokenC,
|
||||
QuoteToken: testTokenA,
|
||||
Side: tradingstate.Ask,
|
||||
},
|
||||
}
|
||||
if fee, _ := XDCx.getCancelFee(nil, nil, tradingStateDb, tokenCOrder.order, tokenCOrder.feeRate); fee != nil && fee.Sign() != 0 {
|
||||
t.Errorf("getCancelFee() = %v, want %v", fee, common.Big0)
|
||||
}
|
||||
|
||||
// testcase: invalid token decimal
|
||||
testTokenD := common.HexToAddress("0x1300000000000000000000000000000000000005")
|
||||
XDCx.SetTokenDecimal(testTokenD, big.NewInt(0))
|
||||
tokenDOrder := CancelFeeArg{
|
||||
feeRate: new(big.Int).SetUint64(10), // 10/10000= 0.1%
|
||||
order: &tradingstate.OrderItem{
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
BaseToken: testTokenD,
|
||||
QuoteToken: testTokenA,
|
||||
Side: tradingstate.Ask,
|
||||
},
|
||||
}
|
||||
if fee, _ := XDCx.getCancelFee(nil, nil, tradingStateDb, tokenDOrder.order, tokenDOrder.feeRate); fee != nil && fee.Sign() != 0 {
|
||||
t.Errorf("getCancelFee() = %v, want %v", fee, common.Big0)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetTradeQuantity(t *testing.T) {
|
||||
type GetTradeQuantityArg struct {
|
||||
takerSide string
|
||||
takerFeeRate *big.Int
|
||||
takerBalance *big.Int
|
||||
makerPrice *big.Int
|
||||
makerFeeRate *big.Int
|
||||
makerBalance *big.Int
|
||||
baseTokenDecimal *big.Int
|
||||
quantityToTrade *big.Int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args GetTradeQuantityArg
|
||||
quantity *big.Int
|
||||
rejectMaker bool
|
||||
}{
|
||||
{
|
||||
"BUY: feeRate = 0, price 1, quantity 1000, taker balance 1000, maker balance 1000",
|
||||
GetTradeQuantityArg{
|
||||
takerSide: tradingstate.Bid,
|
||||
takerFeeRate: common.Big0,
|
||||
takerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: common.Big0,
|
||||
makerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
},
|
||||
new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"BUY: feeRate = 0, price 1, quantity 1000, taker balance 1000, maker balance 900 -> reject maker",
|
||||
GetTradeQuantityArg{
|
||||
takerSide: tradingstate.Bid,
|
||||
takerFeeRate: common.Big0,
|
||||
takerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: common.Big0,
|
||||
makerBalance: new(big.Int).Mul(big.NewInt(900), common.BasePrice),
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
},
|
||||
new(big.Int).Mul(big.NewInt(900), common.BasePrice),
|
||||
true,
|
||||
},
|
||||
{
|
||||
"BUY: feeRate = 0, price 1, quantity 1000, taker balance 900, maker balance 1000 -> reject taker",
|
||||
GetTradeQuantityArg{
|
||||
takerSide: tradingstate.Bid,
|
||||
takerFeeRate: common.Big0,
|
||||
takerBalance: new(big.Int).Mul(big.NewInt(900), common.BasePrice),
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: common.Big0,
|
||||
makerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
},
|
||||
new(big.Int).Mul(big.NewInt(900), common.BasePrice),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"BUY: feeRate = 0, price 1, quantity 1000, taker balance 0, maker balance 1000 -> reject taker",
|
||||
GetTradeQuantityArg{
|
||||
takerSide: tradingstate.Bid,
|
||||
takerFeeRate: common.Big0,
|
||||
takerBalance: common.Big0,
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: common.Big0,
|
||||
makerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
},
|
||||
common.Big0,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"BUY: feeRate = 0, price 1, quantity 1000, taker balance 0, maker balance 0 -> reject both taker",
|
||||
GetTradeQuantityArg{
|
||||
takerSide: tradingstate.Bid,
|
||||
takerFeeRate: common.Big0,
|
||||
takerBalance: common.Big0,
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: common.Big0,
|
||||
makerBalance: common.Big0,
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
},
|
||||
common.Big0,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"BUY: feeRate = 0, price 1, quantity 1000, taker balance 500, maker balance 100 -> reject both taker, maker",
|
||||
GetTradeQuantityArg{
|
||||
takerSide: tradingstate.Bid,
|
||||
takerFeeRate: common.Big0,
|
||||
takerBalance: new(big.Int).Mul(big.NewInt(500), common.BasePrice),
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: common.Big0,
|
||||
makerBalance: new(big.Int).Mul(big.NewInt(100), common.BasePrice),
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
},
|
||||
new(big.Int).Mul(big.NewInt(100), common.BasePrice),
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"SELL: feeRate = 0, price 1, quantity 1000, taker balance 1000, maker balance 1000",
|
||||
GetTradeQuantityArg{
|
||||
takerSide: tradingstate.Ask,
|
||||
takerFeeRate: common.Big0,
|
||||
takerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: common.Big0,
|
||||
makerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
},
|
||||
new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"SELL: feeRate = 0, price 1, quantity 1000, taker balance 1000, maker balance 900 -> reject maker",
|
||||
GetTradeQuantityArg{
|
||||
takerSide: tradingstate.Ask,
|
||||
takerFeeRate: common.Big0,
|
||||
takerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: common.Big0,
|
||||
makerBalance: new(big.Int).Mul(big.NewInt(900), common.BasePrice),
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
},
|
||||
new(big.Int).Mul(big.NewInt(900), common.BasePrice),
|
||||
true,
|
||||
},
|
||||
{
|
||||
"SELL: feeRate = 0, price 1, quantity 1000, taker balance 900, maker balance 1000 -> reject taker",
|
||||
GetTradeQuantityArg{
|
||||
takerSide: tradingstate.Ask,
|
||||
takerFeeRate: common.Big0,
|
||||
takerBalance: new(big.Int).Mul(big.NewInt(900), common.BasePrice),
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: common.Big0,
|
||||
makerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
},
|
||||
new(big.Int).Mul(big.NewInt(900), common.BasePrice),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"SELL: feeRate = 0, price 1, quantity 1000, taker balance 0, maker balance 1000 -> reject taker",
|
||||
GetTradeQuantityArg{
|
||||
takerSide: tradingstate.Ask,
|
||||
takerFeeRate: common.Big0,
|
||||
takerBalance: common.Big0,
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: common.Big0,
|
||||
makerBalance: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
},
|
||||
common.Big0,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"SELL: feeRate = 0, price 1, quantity 1000, taker balance 0, maker balance 0 -> reject maker",
|
||||
GetTradeQuantityArg{
|
||||
takerSide: tradingstate.Ask,
|
||||
takerFeeRate: common.Big0,
|
||||
takerBalance: common.Big0,
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: common.Big0,
|
||||
makerBalance: common.Big0,
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
},
|
||||
common.Big0,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"SELL: feeRate = 0, price 1, quantity 1000, taker balance 500, maker balance 100 -> reject both taker, maker",
|
||||
GetTradeQuantityArg{
|
||||
takerSide: tradingstate.Ask,
|
||||
takerFeeRate: common.Big0,
|
||||
takerBalance: new(big.Int).Mul(big.NewInt(500), common.BasePrice),
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: common.Big0,
|
||||
makerBalance: new(big.Int).Mul(big.NewInt(100), common.BasePrice),
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
},
|
||||
new(big.Int).Mul(big.NewInt(100), common.BasePrice),
|
||||
true,
|
||||
},
|
||||
{
|
||||
"SELL: feeRate = 0, price 1, quantity 1000, taker balance 0, maker balance 100 -> reject both taker, maker",
|
||||
GetTradeQuantityArg{
|
||||
takerSide: tradingstate.Ask,
|
||||
takerFeeRate: common.Big0,
|
||||
takerBalance: common.Big0,
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: common.Big0,
|
||||
makerBalance: new(big.Int).Mul(big.NewInt(100), common.BasePrice),
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
},
|
||||
common.Big0,
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, got1 := GetTradeQuantity(tt.args.takerSide, tt.args.takerFeeRate, tt.args.takerBalance, tt.args.makerPrice, tt.args.makerFeeRate, tt.args.makerBalance, tt.args.baseTokenDecimal, tt.args.quantityToTrade)
|
||||
if !reflect.DeepEqual(got, tt.quantity) {
|
||||
t.Errorf("GetTradeQuantity() got = %v, quantity %v", got, tt.quantity)
|
||||
}
|
||||
if got1 != tt.rejectMaker {
|
||||
t.Errorf("GetTradeQuantity() got1 = %v, quantity %v", got1, tt.rejectMaker)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
78
XDCx/token.go
Normal file
78
XDCx/token.go
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
package XDCx
|
||||
|
||||
import (
|
||||
"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)
|
||||
}
|
||||
221
XDCx/tradingstate/XDCx_trie.go
Normal file
221
XDCx/tradingstate/XDCx_trie.go
Normal file
|
|
@ -0,0 +1,221 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package tradingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||
"github.com/XinFinOrg/XDPoSChain/trie"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
// XDCXTrie wraps a trie with key hashing. In a secure trie, all
|
||||
// access operations hash the key using keccak256. This prevents
|
||||
// calling code from creating long chains of nodes that
|
||||
// increase the access time.
|
||||
//
|
||||
// Contrary to a regular trie, a XDCXTrie can only be created with
|
||||
// New and must have an attached database. The database also stores
|
||||
// the preimage of each key.
|
||||
//
|
||||
// XDCXTrie is not safe for concurrent use.
|
||||
type XDCXTrie struct {
|
||||
trie trie.Trie
|
||||
hashKeyBuf [common.HashLength]byte
|
||||
secKeyCache map[string][]byte
|
||||
secKeyCacheOwner *XDCXTrie // Pointer to self, replace the key cache on mismatch
|
||||
}
|
||||
|
||||
// NewXDCXTrie creates a trie with an existing root node from a backing database
|
||||
// and optional intermediate in-memory node pool.
|
||||
//
|
||||
// If root is the zero hash or the sha3 hash of an empty string, the
|
||||
// trie is initially empty. Otherwise, New will panic if db is nil
|
||||
// and returns MissingNodeError if the root node cannot be found.
|
||||
//
|
||||
// Accessing the trie loads nodes from the database or node pool on demand.
|
||||
// Loaded nodes are kept around until their 'cache generation' expires.
|
||||
// A new cache generation is created by each call to Commit.
|
||||
// cachelimit sets the number of past cache generations to keep.
|
||||
func NewXDCXTrie(root common.Hash, db *trie.Database) (*XDCXTrie, error) {
|
||||
if db == nil {
|
||||
panic("trie.NewXDCXTrie called without a database")
|
||||
}
|
||||
trie, err := trie.New(root, db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &XDCXTrie{trie: *trie}, nil
|
||||
}
|
||||
|
||||
// Get returns the value for key stored in the trie.
|
||||
// The value bytes must not be modified by the caller.
|
||||
func (t *XDCXTrie) Get(key []byte) []byte {
|
||||
res, err := t.TryGet(key)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// TryGet returns the value for key stored in the trie.
|
||||
// The value bytes must not be modified by the caller.
|
||||
// If a node was not found in the database, a MissingNodeError is returned.
|
||||
func (t *XDCXTrie) TryGet(key []byte) ([]byte, error) {
|
||||
return t.trie.TryGet(key)
|
||||
}
|
||||
|
||||
// TryGetBestLeftKey returns the value of max left leaf
|
||||
// If a node was not found in the database, a MissingNodeError is returned.
|
||||
func (t *XDCXTrie) TryGetBestLeftKeyAndValue() ([]byte, []byte, error) {
|
||||
return t.trie.TryGetBestLeftKeyAndValue()
|
||||
}
|
||||
|
||||
func (t *XDCXTrie) TryGetAllLeftKeyAndValue(limit []byte) ([][]byte, [][]byte, error) {
|
||||
return t.trie.TryGetAllLeftKeyAndValue(limit)
|
||||
}
|
||||
|
||||
// TryGetBestRightKey returns the value of max left leaf
|
||||
// If a node was not found in the database, a MissingNodeError is returned.
|
||||
func (t *XDCXTrie) TryGetBestRightKeyAndValue() ([]byte, []byte, error) {
|
||||
return t.trie.TryGetBestRightKeyAndValue()
|
||||
}
|
||||
|
||||
// Update associates key with value in the trie. Subsequent calls to
|
||||
// Get will return value. If value has length zero, any existing value
|
||||
// is deleted from the trie and calls to Get will return nil.
|
||||
//
|
||||
// The value bytes must not be modified by the caller while they are
|
||||
// stored in the trie.
|
||||
func (t *XDCXTrie) Update(key, value []byte) {
|
||||
if err := t.TryUpdate(key, value); err != nil {
|
||||
log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// TryUpdate associates key with value in the trie. Subsequent calls to
|
||||
// Get will return value. If value has length zero, any existing value
|
||||
// is deleted from the trie and calls to Get will return nil.
|
||||
//
|
||||
// The value bytes must not be modified by the caller while they are
|
||||
// stored in the trie.
|
||||
//
|
||||
// If a node was not found in the database, a MissingNodeError is returned.
|
||||
func (t *XDCXTrie) TryUpdate(key, value []byte) error {
|
||||
err := t.trie.TryUpdate(key, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.getSecKeyCache()[string(key)] = common.CopyBytes(key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes any existing value for key from the trie.
|
||||
func (t *XDCXTrie) Delete(key []byte) {
|
||||
if err := t.TryDelete(key); err != nil {
|
||||
log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// TryDelete removes any existing value for key from the trie.
|
||||
// If a node was not found in the database, a MissingNodeError is returned.
|
||||
func (t *XDCXTrie) TryDelete(key []byte) error {
|
||||
delete(t.getSecKeyCache(), string(key))
|
||||
return t.trie.TryDelete(key)
|
||||
}
|
||||
|
||||
// GetKey returns the sha3 preimage of a hashed key that was
|
||||
// previously used to store a value.
|
||||
func (t *XDCXTrie) GetKey(shaKey []byte) []byte {
|
||||
if key, ok := t.getSecKeyCache()[string(shaKey)]; ok {
|
||||
return key
|
||||
}
|
||||
key, _ := t.trie.Db.Preimage(common.BytesToHash(shaKey))
|
||||
return key
|
||||
}
|
||||
|
||||
// Commit writes all nodes and the secure hash pre-images to the trie's database.
|
||||
// Nodes are stored with their sha3 hash as the key.
|
||||
//
|
||||
// Committing flushes nodes from memory. Subsequent Get calls will load nodes
|
||||
// from the database.
|
||||
func (t *XDCXTrie) Commit(onleaf trie.LeafCallback) (root common.Hash, err error) {
|
||||
// Write all the pre-images to the actual disk database
|
||||
if len(t.getSecKeyCache()) > 0 {
|
||||
t.trie.Db.Lock.Lock()
|
||||
for hk, key := range t.secKeyCache {
|
||||
t.trie.Db.InsertPreimage(common.BytesToHash([]byte(hk)), key)
|
||||
}
|
||||
t.trie.Db.Lock.Unlock()
|
||||
|
||||
t.secKeyCache = make(map[string][]byte)
|
||||
}
|
||||
// Commit the trie to its intermediate node database
|
||||
return t.trie.Commit(onleaf)
|
||||
}
|
||||
|
||||
func (t *XDCXTrie) Hash() common.Hash {
|
||||
return t.trie.Hash()
|
||||
}
|
||||
|
||||
func (t *XDCXTrie) Copy() *XDCXTrie {
|
||||
cpy := *t
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// NodeIterator returns an iterator that returns nodes of the underlying trie. Iteration
|
||||
// starts at the key after the given start key.
|
||||
func (t *XDCXTrie) NodeIterator(start []byte) trie.NodeIterator {
|
||||
return t.trie.NodeIterator(start)
|
||||
}
|
||||
|
||||
// hashKey returns the hash of key as an ephemeral buffer.
|
||||
// The caller must not hold onto the return value because it will become
|
||||
// invalid on the next call to hashKey or secKey.
|
||||
//func (t *XDCXTrie) hashKey(key []byte) []byte {
|
||||
// h := newHasher(0, 0, nil)
|
||||
// h.sha.Reset()
|
||||
// h.sha.Write(key)
|
||||
// buf := h.sha.Sum(t.hashKeyBuf[:0])
|
||||
// returnHasherToPool(h)
|
||||
// return buf
|
||||
//}
|
||||
|
||||
// getSecKeyCache returns the current secure key cache, creating a new one if
|
||||
// ownership changed (i.e. the current secure trie is a copy of another owning
|
||||
// the actual cache).
|
||||
func (t *XDCXTrie) getSecKeyCache() map[string][]byte {
|
||||
if t != t.secKeyCacheOwner {
|
||||
t.secKeyCacheOwner = t
|
||||
t.secKeyCache = make(map[string][]byte)
|
||||
}
|
||||
return t.secKeyCache
|
||||
}
|
||||
|
||||
// Prove constructs a merkle proof for key. The result contains all encoded nodes
|
||||
// on the path to the value at key. The value itself is also included in the last
|
||||
// node and can be retrieved by verifying the proof.
|
||||
//
|
||||
// If the trie does not contain a value for key, the returned proof contains all
|
||||
// nodes of the longest existing prefix of the key (at least the root node), ending
|
||||
// with the node that proves the absence of the key.
|
||||
func (t *XDCXTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error {
|
||||
return t.trie.Prove(key, fromLevel, proofDb)
|
||||
}
|
||||
47
XDCx/tradingstate/XDCx_trie_test.go
Normal file
47
XDCx/tradingstate/XDCx_trie_test.go
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
package tradingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestXDCxTrieTest(t *testing.T) {
|
||||
t.SkipNow()
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
stateCache := NewDatabase(db)
|
||||
trie, _ := stateCache.OpenStorageTrie(EmptyHash, EmptyHash)
|
||||
max := 1000000
|
||||
for i := 1; i < max; i++ {
|
||||
k := common.BigToHash(big.NewInt(int64(i))).Bytes()
|
||||
trie.TryUpdate(k, k)
|
||||
}
|
||||
left, _, _ := trie.TryGetBestLeftKeyAndValue()
|
||||
right, _, _ := trie.TryGetBestRightKeyAndValue()
|
||||
fmt.Println(left, right)
|
||||
for i := 0; i < 100; i++ {
|
||||
limit := big.NewInt(rand.Int63n(int64(max / 10)))
|
||||
fmt.Println(time.Now().Format("2006-01-02 15:04:05"), "test", i, limit)
|
||||
allKeyLefts, allValues, err := trie.TryGetAllLeftKeyAndValue(common.BigToHash(limit).Bytes())
|
||||
if err != nil {
|
||||
t.Fatal("err", err)
|
||||
}
|
||||
if len(allKeyLefts) != int(limit.Int64())-1 {
|
||||
t.Fatal("err when length", len(allKeyLefts), "limit", limit)
|
||||
}
|
||||
for j := 0; j < len(allKeyLefts); j++ {
|
||||
key := new(big.Int).SetBytes(allKeyLefts[j])
|
||||
value := new(big.Int).SetBytes(allValues[j])
|
||||
if key.Cmp(value) != 0 {
|
||||
t.Fatal("err when compare key", key, "value", value)
|
||||
}
|
||||
if key.Cmp(limit) >= 0 {
|
||||
t.Fatal("err when compare key", key, "limit", limit)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
220
XDCx/tradingstate/common.go
Normal file
220
XDCx/tradingstate/common.go
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
package tradingstate
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
)
|
||||
|
||||
const (
|
||||
OrderCacheLimit = 10000
|
||||
)
|
||||
|
||||
var (
|
||||
EmptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
|
||||
Ask = "SELL"
|
||||
Bid = "BUY"
|
||||
Market = "MO"
|
||||
Limit = "LO"
|
||||
Cancel = "CANCELLED"
|
||||
OrderNew = "NEW"
|
||||
)
|
||||
|
||||
var EmptyHash = common.Hash{}
|
||||
var Zero = big.NewInt(0)
|
||||
var One = big.NewInt(1)
|
||||
|
||||
var EmptyOrderList = orderList{
|
||||
Volume: nil,
|
||||
Root: EmptyHash,
|
||||
}
|
||||
var EmptyExchangeOnject = tradingExchangeObject{
|
||||
Nonce: 0,
|
||||
AskRoot: EmptyHash,
|
||||
BidRoot: EmptyHash,
|
||||
}
|
||||
var EmptyOrder = OrderItem{
|
||||
Quantity: Zero,
|
||||
}
|
||||
|
||||
var (
|
||||
ErrInvalidSignature = errors.New("verify order: invalid signature")
|
||||
ErrInvalidPrice = errors.New("verify order: invalid price")
|
||||
ErrInvalidQuantity = errors.New("verify order: invalid quantity")
|
||||
ErrInvalidRelayer = errors.New("verify order: invalid relayer")
|
||||
ErrInvalidOrderType = errors.New("verify order: unsupported order type")
|
||||
ErrInvalidOrderSide = errors.New("verify order: invalid order side")
|
||||
ErrInvalidStatus = errors.New("verify order: invalid status")
|
||||
|
||||
// supported order types
|
||||
MatchingOrderType = map[string]bool{
|
||||
Market: true,
|
||||
Limit: true,
|
||||
}
|
||||
)
|
||||
|
||||
// tradingExchangeObject is the Ethereum consensus representation of exchanges.
|
||||
// These objects are stored in the main orderId trie.
|
||||
type orderList struct {
|
||||
Volume *big.Int
|
||||
Root common.Hash // merkle root of the storage trie
|
||||
}
|
||||
|
||||
// tradingExchangeObject is the Ethereum consensus representation of exchanges.
|
||||
// These objects are stored in the main orderId trie.
|
||||
type tradingExchangeObject struct {
|
||||
Nonce uint64
|
||||
LastPrice *big.Int
|
||||
MediumPriceBeforeEpoch *big.Int
|
||||
MediumPrice *big.Int
|
||||
TotalQuantity *big.Int
|
||||
LendingCount *big.Int
|
||||
AskRoot common.Hash // merkle root of the storage trie
|
||||
BidRoot common.Hash // merkle root of the storage trie
|
||||
OrderRoot common.Hash
|
||||
LiquidationPriceRoot common.Hash
|
||||
}
|
||||
|
||||
var (
|
||||
TokenMappingSlot = map[string]uint64{
|
||||
"balances": 0,
|
||||
}
|
||||
RelayerMappingSlot = map[string]uint64{
|
||||
"CONTRACT_OWNER": 0,
|
||||
"MaximumRelayers": 1,
|
||||
"MaximumTokenList": 2,
|
||||
"RELAYER_LIST": 3,
|
||||
"RELAYER_COINBASES": 4,
|
||||
"RESIGN_REQUESTS": 5,
|
||||
"RELAYER_ON_SALE_LIST": 6,
|
||||
"RelayerCount": 7,
|
||||
"MinimumDeposit": 8,
|
||||
}
|
||||
RelayerStructMappingSlot = map[string]*big.Int{
|
||||
"_deposit": big.NewInt(0),
|
||||
"_fee": big.NewInt(1),
|
||||
"_fromTokens": big.NewInt(2),
|
||||
"_toTokens": big.NewInt(3),
|
||||
"_index": big.NewInt(4),
|
||||
"_owner": big.NewInt(5),
|
||||
}
|
||||
)
|
||||
|
||||
type TxDataMatch struct {
|
||||
Order []byte // serialized data of order has been processed in this tx
|
||||
}
|
||||
|
||||
type TxMatchBatch struct {
|
||||
Data []TxDataMatch
|
||||
Timestamp int64
|
||||
TxHash common.Hash
|
||||
}
|
||||
|
||||
type MatchingResult struct {
|
||||
Trades []map[string]string
|
||||
Rejects []*OrderItem
|
||||
}
|
||||
|
||||
func EncodeTxMatchesBatch(txMatchBatch TxMatchBatch) ([]byte, error) {
|
||||
data, err := json.Marshal(txMatchBatch)
|
||||
if err != nil || data == nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func DecodeTxMatchesBatch(data []byte) (TxMatchBatch, error) {
|
||||
txMatchResult := TxMatchBatch{}
|
||||
if err := json.Unmarshal(data, &txMatchResult); err != nil {
|
||||
return TxMatchBatch{}, err
|
||||
}
|
||||
return txMatchResult, nil
|
||||
}
|
||||
|
||||
// use orderHash instead of orderId
|
||||
// because both takerOrders don't have orderId
|
||||
func GetOrderHistoryKey(baseToken, quoteToken common.Address, orderHash common.Hash) common.Hash {
|
||||
return crypto.Keccak256Hash(baseToken.Bytes(), quoteToken.Bytes(), orderHash.Bytes())
|
||||
}
|
||||
|
||||
func (tx TxDataMatch) DecodeOrder() (*OrderItem, error) {
|
||||
order := &OrderItem{}
|
||||
if err := DecodeBytesItem(tx.Order, order); err != nil {
|
||||
return order, err
|
||||
}
|
||||
return order, nil
|
||||
}
|
||||
|
||||
type OrderHistoryItem struct {
|
||||
TxHash common.Hash
|
||||
FilledAmount *big.Int
|
||||
Status string
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
// ToJSON : log json string
|
||||
func ToJSON(object interface{}, args ...string) string {
|
||||
var str []byte
|
||||
if len(args) == 2 {
|
||||
str, _ = json.MarshalIndent(object, args[0], args[1])
|
||||
} else {
|
||||
str, _ = json.Marshal(object)
|
||||
}
|
||||
return string(str)
|
||||
}
|
||||
|
||||
func Mul(x, y *big.Int) *big.Int {
|
||||
return big.NewInt(0).Mul(x, y)
|
||||
}
|
||||
|
||||
func Div(x, y *big.Int) *big.Int {
|
||||
return big.NewInt(0).Div(x, y)
|
||||
}
|
||||
|
||||
func Add(x, y *big.Int) *big.Int {
|
||||
return big.NewInt(0).Add(x, y)
|
||||
}
|
||||
|
||||
func Sub(x, y *big.Int) *big.Int {
|
||||
return big.NewInt(0).Sub(x, y)
|
||||
}
|
||||
|
||||
func Neg(x *big.Int) *big.Int {
|
||||
return big.NewInt(0).Neg(x)
|
||||
}
|
||||
|
||||
func ToBigInt(s string) *big.Int {
|
||||
res := big.NewInt(0)
|
||||
res.SetString(s, 10)
|
||||
return res
|
||||
}
|
||||
|
||||
func CloneBigInt(bigInt *big.Int) *big.Int {
|
||||
res := new(big.Int).SetBytes(bigInt.Bytes())
|
||||
return res
|
||||
}
|
||||
|
||||
func Exp(x, y *big.Int) *big.Int {
|
||||
return big.NewInt(0).Exp(x, y, nil)
|
||||
}
|
||||
|
||||
func Max(a, b *big.Int) *big.Int {
|
||||
if a.Cmp(b) == 1 {
|
||||
return a
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
func GetTradingOrderBookHash(baseToken common.Address, quoteToken common.Address) common.Hash {
|
||||
return common.BytesToHash(append(baseToken[:16], quoteToken[4:]...))
|
||||
}
|
||||
|
||||
func GetMatchingResultCacheKey(order *OrderItem) common.Hash {
|
||||
return crypto.Keccak256Hash(order.UserAddress.Bytes(), order.Nonce.Bytes())
|
||||
}
|
||||
43
XDCx/tradingstate/common_test.go
Normal file
43
XDCx/tradingstate/common_test.go
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
package tradingstate
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
// Testing scenario:
|
||||
// encode originalTxMatchesBatch -> byteData
|
||||
// decode byteData -> txMatchesBatch
|
||||
// compare originalTxMatchesBatch and txMatchesBatch
|
||||
func TestTxMatchesBatch(t *testing.T) {
|
||||
originalTxMatchesBatch := []TxDataMatch{
|
||||
{
|
||||
Order: []byte("order1"),
|
||||
},
|
||||
{
|
||||
Order: []byte("order2"),
|
||||
},
|
||||
{
|
||||
Order: []byte("order3"),
|
||||
},
|
||||
}
|
||||
|
||||
encodedData, err := EncodeTxMatchesBatch(TxMatchBatch{
|
||||
Data: originalTxMatchesBatch,
|
||||
})
|
||||
if err != nil {
|
||||
t.Error("Failed to encode", err.Error())
|
||||
}
|
||||
|
||||
txMatchesBatch, err := DecodeTxMatchesBatch(encodedData)
|
||||
if err != nil {
|
||||
t.Error("Failed to decode", err.Error())
|
||||
}
|
||||
|
||||
eq := reflect.DeepEqual(originalTxMatchesBatch, txMatchesBatch.Data)
|
||||
if eq {
|
||||
t.Log("Awesome, encode and decode txMatchesBatch are correct")
|
||||
} else {
|
||||
t.Error("txMatchesBatch is different from originalTxMatchesBatch", "txMatchesBatch", txMatchesBatch, "originalTxMatchesBatch", originalTxMatchesBatch)
|
||||
}
|
||||
}
|
||||
140
XDCx/tradingstate/database.go
Normal file
140
XDCx/tradingstate/database.go
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package tradingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||
"github.com/XinFinOrg/XDPoSChain/trie"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
// Trie cache generation limit after which to evic trie nodes from memory.
|
||||
var MaxTrieCacheGen = uint16(120)
|
||||
|
||||
const (
|
||||
// Number of past tries to keep. This value is chosen such that
|
||||
// reasonable chain reorg depths will hit an existing trie.
|
||||
maxPastTries = 12
|
||||
|
||||
// Number of codehash->size associations to keep.
|
||||
codeSizeCacheSize = 100000
|
||||
)
|
||||
|
||||
// Database wraps access to tries and contract code.
|
||||
type Database interface {
|
||||
// OpenTrie opens the main account trie.
|
||||
OpenTrie(root common.Hash) (Trie, error)
|
||||
|
||||
// OpenStorageTrie opens the storage trie of an account.
|
||||
OpenStorageTrie(addrHash, root common.Hash) (Trie, error)
|
||||
|
||||
// CopyTrie returns an independent copy of the given trie.
|
||||
CopyTrie(Trie) Trie
|
||||
|
||||
// ContractCode retrieves a particular contract's code.
|
||||
ContractCode(addrHash, codeHash common.Hash) ([]byte, error)
|
||||
|
||||
// ContractCodeSize retrieves a particular contracts code's size.
|
||||
ContractCodeSize(addrHash, codeHash common.Hash) (int, error)
|
||||
|
||||
// TrieDB retrieves the low level trie database used for data storage.
|
||||
TrieDB() *trie.Database
|
||||
}
|
||||
|
||||
// Trie is a Ethereum Merkle Trie.
|
||||
type Trie interface {
|
||||
TryGet(key []byte) ([]byte, error)
|
||||
TryGetBestLeftKeyAndValue() ([]byte, []byte, error)
|
||||
TryGetAllLeftKeyAndValue(limit []byte) ([][]byte, [][]byte, error)
|
||||
TryGetBestRightKeyAndValue() ([]byte, []byte, error)
|
||||
TryUpdate(key, value []byte) error
|
||||
TryDelete(key []byte) error
|
||||
Commit(onleaf trie.LeafCallback) (common.Hash, error)
|
||||
Hash() common.Hash
|
||||
NodeIterator(startKey []byte) trie.NodeIterator
|
||||
GetKey([]byte) []byte // TODO(fjl): remove this when XDCXTrie is removed
|
||||
Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error
|
||||
}
|
||||
|
||||
// NewDatabase creates a backing store for state. The returned database is safe for
|
||||
// concurrent use and retains cached trie nodes in memory. The pool is an optional
|
||||
// intermediate trie-node memory pool between the low level storage layer and the
|
||||
// high level trie abstraction.
|
||||
func NewDatabase(db ethdb.Database) Database {
|
||||
csc, _ := lru.New(codeSizeCacheSize)
|
||||
return &cachingDB{
|
||||
db: trie.NewDatabase(db),
|
||||
codeSizeCache: csc,
|
||||
}
|
||||
}
|
||||
|
||||
type cachingDB struct {
|
||||
db *trie.Database
|
||||
mu sync.Mutex
|
||||
pastTries []*XDCXTrie
|
||||
codeSizeCache *lru.Cache
|
||||
}
|
||||
|
||||
// OpenTrie opens the main account trie.
|
||||
func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
|
||||
return NewXDCXTrie(root, db.db)
|
||||
}
|
||||
|
||||
func (db *cachingDB) pushTrie(t *XDCXTrie) {
|
||||
db.mu.Lock()
|
||||
defer db.mu.Unlock()
|
||||
if len(db.pastTries) >= maxPastTries {
|
||||
copy(db.pastTries, db.pastTries[1:])
|
||||
db.pastTries[len(db.pastTries)-1] = t
|
||||
} else {
|
||||
db.pastTries = append(db.pastTries, t)
|
||||
}
|
||||
}
|
||||
|
||||
// OpenStorageTrie opens the storage trie of an account.
|
||||
func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) {
|
||||
return NewXDCXTrie(root, db.db)
|
||||
}
|
||||
|
||||
// CopyTrie returns an independent copy of the given trie.
|
||||
func (db *cachingDB) CopyTrie(t Trie) Trie {
|
||||
switch t := t.(type) {
|
||||
case *XDCXTrie:
|
||||
return t.Copy()
|
||||
default:
|
||||
panic(fmt.Errorf("unknown trie type %T", t))
|
||||
}
|
||||
}
|
||||
|
||||
// ContractCode retrieves a particular contract's code.
|
||||
func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ContractCodeSize retrieves a particular contracts code's size.
|
||||
func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// TrieDB retrieves any intermediate trie-node caching layer.
|
||||
func (db *cachingDB) TrieDB() *trie.Database {
|
||||
return db.db
|
||||
}
|
||||
384
XDCx/tradingstate/dump.go
Normal file
384
XDCx/tradingstate/dump.go
Normal file
|
|
@ -0,0 +1,384 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package tradingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
"github.com/XinFinOrg/XDPoSChain/trie"
|
||||
)
|
||||
|
||||
type DumpOrderList struct {
|
||||
Volume *big.Int
|
||||
Orders map[*big.Int]*big.Int
|
||||
}
|
||||
type DumpLendingBook struct {
|
||||
Volume *big.Int
|
||||
LendingBooks map[common.Hash]DumpOrderList
|
||||
}
|
||||
|
||||
type DumpOrderBookInfo struct {
|
||||
LastPrice *big.Int
|
||||
LendingCount *big.Int
|
||||
MediumPrice *big.Int
|
||||
MediumPriceBeforeEpoch *big.Int
|
||||
Nonce uint64
|
||||
TotalQuantity *big.Int
|
||||
BestAsk *big.Int
|
||||
BestBid *big.Int
|
||||
LowestLiquidationPrice *big.Int
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) DumpAskTrie(orderBook common.Hash) (map[*big.Int]DumpOrderList, error) {
|
||||
exhangeObject := self.getStateExchangeObject(orderBook)
|
||||
if exhangeObject == nil {
|
||||
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
|
||||
}
|
||||
mapResult := map[*big.Int]DumpOrderList{}
|
||||
it := trie.NewIterator(exhangeObject.getAsksTrie(self.db).NodeIterator(nil))
|
||||
for it.Next() {
|
||||
priceHash := common.BytesToHash(it.Key)
|
||||
if common.EmptyHash(priceHash) {
|
||||
continue
|
||||
}
|
||||
price := new(big.Int).SetBytes(priceHash.Bytes())
|
||||
if _, exist := exhangeObject.stateAskObjects[priceHash]; exist {
|
||||
continue
|
||||
} else {
|
||||
var data orderList
|
||||
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
|
||||
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,price :%v ", orderBook.Hex(), price)
|
||||
}
|
||||
stateOrderList := newStateOrderList(self, Ask, orderBook, priceHash, data, nil)
|
||||
mapResult[price] = stateOrderList.DumpOrderList(self.db)
|
||||
}
|
||||
}
|
||||
for priceHash, stateOrderList := range exhangeObject.stateAskObjects {
|
||||
if stateOrderList.Volume().Sign() > 0 {
|
||||
mapResult[new(big.Int).SetBytes(priceHash.Bytes())] = stateOrderList.DumpOrderList(self.db)
|
||||
}
|
||||
}
|
||||
listPrice := []*big.Int{}
|
||||
for price := range mapResult {
|
||||
listPrice = append(listPrice, price)
|
||||
}
|
||||
sort.Slice(listPrice, func(i, j int) bool {
|
||||
return listPrice[i].Cmp(listPrice[j]) < 0
|
||||
})
|
||||
result := map[*big.Int]DumpOrderList{}
|
||||
for _, price := range listPrice {
|
||||
result[price] = mapResult[price]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) DumpBidTrie(orderBook common.Hash) (map[*big.Int]DumpOrderList, error) {
|
||||
exhangeObject := self.getStateExchangeObject(orderBook)
|
||||
if exhangeObject == nil {
|
||||
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
|
||||
}
|
||||
mapResult := map[*big.Int]DumpOrderList{}
|
||||
it := trie.NewIterator(exhangeObject.getBidsTrie(self.db).NodeIterator(nil))
|
||||
for it.Next() {
|
||||
priceHash := common.BytesToHash(it.Key)
|
||||
if common.EmptyHash(priceHash) {
|
||||
continue
|
||||
}
|
||||
price := new(big.Int).SetBytes(priceHash.Bytes())
|
||||
if _, exist := exhangeObject.stateBidObjects[priceHash]; exist {
|
||||
continue
|
||||
} else {
|
||||
var data orderList
|
||||
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
|
||||
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,price :%v ", orderBook.Hex(), price)
|
||||
}
|
||||
stateOrderList := newStateOrderList(self, Bid, orderBook, priceHash, data, nil)
|
||||
mapResult[price] = stateOrderList.DumpOrderList(self.db)
|
||||
}
|
||||
}
|
||||
for priceHash, stateOrderList := range exhangeObject.stateBidObjects {
|
||||
if stateOrderList.Volume().Sign() > 0 {
|
||||
mapResult[new(big.Int).SetBytes(priceHash.Bytes())] = stateOrderList.DumpOrderList(self.db)
|
||||
}
|
||||
}
|
||||
listPrice := []*big.Int{}
|
||||
for price := range mapResult {
|
||||
listPrice = append(listPrice, price)
|
||||
}
|
||||
sort.Slice(listPrice, func(i, j int) bool {
|
||||
return listPrice[i].Cmp(listPrice[j]) < 0
|
||||
})
|
||||
result := map[*big.Int]DumpOrderList{}
|
||||
for _, price := range listPrice {
|
||||
result[price] = mapResult[price]
|
||||
}
|
||||
return mapResult, nil
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) GetBids(orderBook common.Hash) (map[*big.Int]*big.Int, error) {
|
||||
exhangeObject := self.getStateExchangeObject(orderBook)
|
||||
if exhangeObject == nil {
|
||||
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
|
||||
}
|
||||
mapResult := map[*big.Int]*big.Int{}
|
||||
it := trie.NewIterator(exhangeObject.getBidsTrie(self.db).NodeIterator(nil))
|
||||
for it.Next() {
|
||||
priceHash := common.BytesToHash(it.Key)
|
||||
if common.EmptyHash(priceHash) {
|
||||
continue
|
||||
}
|
||||
price := new(big.Int).SetBytes(priceHash.Bytes())
|
||||
if _, exist := exhangeObject.stateBidObjects[priceHash]; exist {
|
||||
continue
|
||||
} else {
|
||||
var data orderList
|
||||
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
|
||||
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,price :%v ", orderBook.Hex(), price)
|
||||
}
|
||||
stateOrderList := newStateOrderList(self, Bid, orderBook, priceHash, data, nil)
|
||||
mapResult[price] = stateOrderList.data.Volume
|
||||
}
|
||||
}
|
||||
for priceHash, stateOrderList := range exhangeObject.stateBidObjects {
|
||||
if stateOrderList.Volume().Sign() > 0 {
|
||||
mapResult[new(big.Int).SetBytes(priceHash.Bytes())] = stateOrderList.data.Volume
|
||||
}
|
||||
}
|
||||
listPrice := []*big.Int{}
|
||||
for price := range mapResult {
|
||||
listPrice = append(listPrice, price)
|
||||
}
|
||||
sort.Slice(listPrice, func(i, j int) bool {
|
||||
return listPrice[i].Cmp(listPrice[j]) < 0
|
||||
})
|
||||
result := map[*big.Int]*big.Int{}
|
||||
for _, price := range listPrice {
|
||||
result[price] = mapResult[price]
|
||||
}
|
||||
return mapResult, nil
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) GetAsks(orderBook common.Hash) (map[*big.Int]*big.Int, error) {
|
||||
exhangeObject := self.getStateExchangeObject(orderBook)
|
||||
if exhangeObject == nil {
|
||||
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
|
||||
}
|
||||
mapResult := map[*big.Int]*big.Int{}
|
||||
it := trie.NewIterator(exhangeObject.getAsksTrie(self.db).NodeIterator(nil))
|
||||
for it.Next() {
|
||||
priceHash := common.BytesToHash(it.Key)
|
||||
if common.EmptyHash(priceHash) {
|
||||
continue
|
||||
}
|
||||
price := new(big.Int).SetBytes(priceHash.Bytes())
|
||||
if _, exist := exhangeObject.stateAskObjects[priceHash]; exist {
|
||||
continue
|
||||
} else {
|
||||
var data orderList
|
||||
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
|
||||
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,price :%v ", orderBook.Hex(), price)
|
||||
}
|
||||
stateOrderList := newStateOrderList(self, Ask, orderBook, priceHash, data, nil)
|
||||
mapResult[price] = stateOrderList.data.Volume
|
||||
}
|
||||
}
|
||||
for priceHash, stateOrderList := range exhangeObject.stateAskObjects {
|
||||
if stateOrderList.Volume().Sign() > 0 {
|
||||
mapResult[new(big.Int).SetBytes(priceHash.Bytes())] = stateOrderList.data.Volume
|
||||
}
|
||||
}
|
||||
listPrice := []*big.Int{}
|
||||
for price := range mapResult {
|
||||
listPrice = append(listPrice, price)
|
||||
}
|
||||
sort.Slice(listPrice, func(i, j int) bool {
|
||||
return listPrice[i].Cmp(listPrice[j]) < 0
|
||||
})
|
||||
result := map[*big.Int]*big.Int{}
|
||||
for _, price := range listPrice {
|
||||
result[price] = mapResult[price]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
func (self *stateOrderList) DumpOrderList(db Database) DumpOrderList {
|
||||
mapResult := DumpOrderList{Volume: self.Volume(), Orders: map[*big.Int]*big.Int{}}
|
||||
orderListIt := trie.NewIterator(self.getTrie(db).NodeIterator(nil))
|
||||
for orderListIt.Next() {
|
||||
keyHash := common.BytesToHash(orderListIt.Key)
|
||||
if common.EmptyHash(keyHash) {
|
||||
continue
|
||||
}
|
||||
if _, exist := self.cachedStorage[keyHash]; exist {
|
||||
continue
|
||||
} else {
|
||||
_, content, _, _ := rlp.Split(orderListIt.Value)
|
||||
mapResult.Orders[new(big.Int).SetBytes(keyHash.Bytes())] = new(big.Int).SetBytes(content)
|
||||
}
|
||||
}
|
||||
for key, value := range self.cachedStorage {
|
||||
if !common.EmptyHash(value) {
|
||||
mapResult.Orders[new(big.Int).SetBytes(key.Bytes())] = new(big.Int).SetBytes(value.Bytes())
|
||||
}
|
||||
}
|
||||
listIds := []*big.Int{}
|
||||
for id := range mapResult.Orders {
|
||||
listIds = append(listIds, id)
|
||||
}
|
||||
sort.Slice(listIds, func(i, j int) bool {
|
||||
return listIds[i].Cmp(listIds[j]) < 0
|
||||
})
|
||||
result := DumpOrderList{Volume: self.Volume(), Orders: map[*big.Int]*big.Int{}}
|
||||
for _, id := range listIds {
|
||||
result.Orders[id] = mapResult.Orders[id]
|
||||
}
|
||||
return mapResult
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) DumpOrderBookInfo(orderBook common.Hash) (*DumpOrderBookInfo, error) {
|
||||
exhangeObject := self.getStateExchangeObject(orderBook)
|
||||
if exhangeObject == nil {
|
||||
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
|
||||
}
|
||||
result := &DumpOrderBookInfo{}
|
||||
result.LastPrice = exhangeObject.data.LastPrice
|
||||
result.LendingCount = exhangeObject.data.LendingCount
|
||||
result.MediumPrice = exhangeObject.data.MediumPrice
|
||||
result.MediumPriceBeforeEpoch = exhangeObject.data.MediumPriceBeforeEpoch
|
||||
result.Nonce = exhangeObject.data.Nonce
|
||||
result.TotalQuantity = exhangeObject.data.TotalQuantity
|
||||
result.BestAsk = new(big.Int).SetBytes(exhangeObject.getBestPriceAsksTrie(self.db).Bytes())
|
||||
result.BestBid = new(big.Int).SetBytes(exhangeObject.getBestBidsTrie(self.db).Bytes())
|
||||
lowestPrice, _ := exhangeObject.getLowestLiquidationPrice(self.db)
|
||||
result.LowestLiquidationPrice = new(big.Int).SetBytes(lowestPrice.Bytes())
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (self *stateLendingBook) DumpOrderList(db Database) DumpOrderList {
|
||||
mapResult := DumpOrderList{Volume: self.Volume(), Orders: map[*big.Int]*big.Int{}}
|
||||
orderListIt := trie.NewIterator(self.getTrie(db).NodeIterator(nil))
|
||||
for orderListIt.Next() {
|
||||
keyHash := common.BytesToHash(orderListIt.Key)
|
||||
if common.EmptyHash(keyHash) {
|
||||
continue
|
||||
}
|
||||
if _, exist := self.cachedStorage[keyHash]; exist {
|
||||
continue
|
||||
} else {
|
||||
_, content, _, _ := rlp.Split(orderListIt.Value)
|
||||
mapResult.Orders[new(big.Int).SetBytes(keyHash.Bytes())] = new(big.Int).SetBytes(content)
|
||||
}
|
||||
}
|
||||
for key, value := range self.cachedStorage {
|
||||
if !common.EmptyHash(value) {
|
||||
mapResult.Orders[new(big.Int).SetBytes(key.Bytes())] = new(big.Int).SetBytes(value.Bytes())
|
||||
}
|
||||
}
|
||||
listIds := []*big.Int{}
|
||||
for id := range mapResult.Orders {
|
||||
listIds = append(listIds, id)
|
||||
}
|
||||
sort.Slice(listIds, func(i, j int) bool {
|
||||
return listIds[i].Cmp(listIds[j]) < 0
|
||||
})
|
||||
result := DumpOrderList{Volume: self.Volume(), Orders: map[*big.Int]*big.Int{}}
|
||||
for _, id := range listIds {
|
||||
result.Orders[id] = mapResult.Orders[id]
|
||||
}
|
||||
return mapResult
|
||||
}
|
||||
|
||||
func (self *liquidationPriceState) DumpLendingBook(db Database) (DumpLendingBook, error) {
|
||||
result := DumpLendingBook{Volume: self.Volume(), LendingBooks: map[common.Hash]DumpOrderList{}}
|
||||
it := trie.NewIterator(self.getTrie(db).NodeIterator(nil))
|
||||
for it.Next() {
|
||||
lendingBook := common.BytesToHash(it.Key)
|
||||
if common.EmptyHash(lendingBook) {
|
||||
continue
|
||||
}
|
||||
if _, exist := self.stateLendingBooks[lendingBook]; exist {
|
||||
continue
|
||||
} else {
|
||||
var data orderList
|
||||
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
|
||||
return result, fmt.Errorf("Failed to decode state lending book orderbook : %s ,liquidation price :%s , lendingBook : %s ,err : %v", self.orderBook, self.liquidationPrice, lendingBook, err)
|
||||
}
|
||||
stateLendingBook := newStateLendingBook(self.orderBook, self.liquidationPrice, lendingBook, data, nil)
|
||||
result.LendingBooks[lendingBook] = stateLendingBook.DumpOrderList(db)
|
||||
}
|
||||
}
|
||||
for lendingBook, stateLendingBook := range self.stateLendingBooks {
|
||||
if !common.EmptyHash(lendingBook) {
|
||||
result.LendingBooks[lendingBook] = stateLendingBook.DumpOrderList(db)
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) DumpLiquidationPriceTrie(orderBook common.Hash) (map[*big.Int]DumpLendingBook, error) {
|
||||
exhangeObject := self.getStateExchangeObject(orderBook)
|
||||
if exhangeObject == nil {
|
||||
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
|
||||
}
|
||||
mapResult := map[*big.Int]DumpLendingBook{}
|
||||
it := trie.NewIterator(exhangeObject.getLiquidationPriceTrie(self.db).NodeIterator(nil))
|
||||
for it.Next() {
|
||||
priceHash := common.BytesToHash(it.Key)
|
||||
if common.EmptyHash(priceHash) {
|
||||
continue
|
||||
}
|
||||
price := new(big.Int).SetBytes(priceHash.Bytes())
|
||||
if _, exist := exhangeObject.liquidationPriceStates[priceHash]; exist {
|
||||
continue
|
||||
} else {
|
||||
var data orderList
|
||||
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
|
||||
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,price :%v ", orderBook.Hex(), price)
|
||||
}
|
||||
liquidationPriceState := newLiquidationPriceState(self, orderBook, priceHash, data, nil)
|
||||
dumpLendingBook, err := liquidationPriceState.DumpLendingBook(self.db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mapResult[price] = dumpLendingBook
|
||||
}
|
||||
}
|
||||
for priceHash, liquidationPriceState := range exhangeObject.liquidationPriceStates {
|
||||
if liquidationPriceState.Volume().Sign() > 0 {
|
||||
dumpLendingBook, err := liquidationPriceState.DumpLendingBook(self.db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
mapResult[new(big.Int).SetBytes(priceHash.Bytes())] = dumpLendingBook
|
||||
}
|
||||
}
|
||||
listPrice := []*big.Int{}
|
||||
for price := range mapResult {
|
||||
listPrice = append(listPrice, price)
|
||||
}
|
||||
sort.Slice(listPrice, func(i, j int) bool {
|
||||
return listPrice[i].Cmp(listPrice[j]) < 0
|
||||
})
|
||||
result := map[*big.Int]DumpLendingBook{}
|
||||
for _, price := range listPrice {
|
||||
result[price] = mapResult[price]
|
||||
}
|
||||
return mapResult, nil
|
||||
}
|
||||
14
XDCx/tradingstate/encoding.go
Normal file
14
XDCx/tradingstate/encoding.go
Normal file
|
|
@ -0,0 +1,14 @@
|
|||
package tradingstate
|
||||
|
||||
import (
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
)
|
||||
|
||||
func EncodeBytesItem(val interface{}) ([]byte, error) {
|
||||
return rlp.EncodeToBytes(val)
|
||||
}
|
||||
|
||||
func DecodeBytesItem(bytes []byte, val interface{}) error {
|
||||
return rlp.DecodeBytes(bytes, val)
|
||||
|
||||
}
|
||||
58
XDCx/tradingstate/epochpriceitem.go
Normal file
58
XDCx/tradingstate/epochpriceitem.go
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
package tradingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
type EpochPriceItem struct {
|
||||
Epoch uint64 `bson:"epoch" json:"epoch"`
|
||||
Orderbook common.Hash `bson:"orderbook" json:"orderbook"`
|
||||
Hash common.Hash `bson:"hash" json:"hash"`
|
||||
Price *big.Int `bson:"price" json:"price"`
|
||||
}
|
||||
|
||||
type EpochPriceItemBSON struct {
|
||||
Epoch string `bson:"epoch" json:"epoch"`
|
||||
Orderbook string `bson:"orderbook" json:"orderbook"`
|
||||
Hash string `bson:"hash" json:"hash"` // Keccak256Hash of Epoch and orderbook, used as an index of this collection
|
||||
Price string `bson:"price" json:"price"`
|
||||
}
|
||||
|
||||
func (item *EpochPriceItem) GetBSON() (interface{}, error) {
|
||||
return EpochPriceItemBSON{
|
||||
Epoch: strconv.FormatUint(item.Epoch, 10),
|
||||
Orderbook: item.Orderbook.Hex(),
|
||||
Price: item.Price.String(),
|
||||
Hash: item.Hash.Hex(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (item *EpochPriceItem) SetBSON(raw bson.Raw) error {
|
||||
decoded := new(EpochPriceItemBSON)
|
||||
|
||||
err := raw.Unmarshal(decoded)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to decode EpochPriceItem. Err: %v", err)
|
||||
}
|
||||
epochNumber, err := strconv.ParseUint(decoded.Epoch, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse EpochPriceItem.Epoch. Err: %v", err)
|
||||
}
|
||||
item.Epoch = epochNumber
|
||||
item.Orderbook = common.HexToHash(decoded.Orderbook)
|
||||
item.Hash = common.HexToHash(decoded.Hash)
|
||||
if decoded.Price != "" {
|
||||
item.Price = ToBigInt(decoded.Price)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (item *EpochPriceItem) ComputeHash() common.Hash {
|
||||
return crypto.Keccak256Hash(new(big.Int).SetUint64(item.Epoch).Bytes(), item.Orderbook.Bytes())
|
||||
}
|
||||
121
XDCx/tradingstate/journal.go
Normal file
121
XDCx/tradingstate/journal.go
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package tradingstate
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
)
|
||||
|
||||
type journalEntry interface {
|
||||
undo(db *TradingStateDB)
|
||||
}
|
||||
|
||||
type journal []journalEntry
|
||||
|
||||
type (
|
||||
// Changes to the account trie.
|
||||
insertOrder struct {
|
||||
orderBook common.Hash
|
||||
orderId common.Hash
|
||||
order *OrderItem
|
||||
}
|
||||
cancelOrder struct {
|
||||
orderBook common.Hash
|
||||
orderId common.Hash
|
||||
order OrderItem
|
||||
}
|
||||
subAmountOrder struct {
|
||||
orderBook common.Hash
|
||||
orderId common.Hash
|
||||
order OrderItem
|
||||
amount *big.Int
|
||||
}
|
||||
nonceChange struct {
|
||||
hash common.Hash
|
||||
prev uint64
|
||||
}
|
||||
lastPriceChange struct {
|
||||
hash common.Hash
|
||||
prev *big.Int
|
||||
}
|
||||
mediumPriceChange struct {
|
||||
hash common.Hash
|
||||
prevPrice *big.Int
|
||||
prevQuantity *big.Int
|
||||
}
|
||||
mediumPriceBeforeEpochChange struct {
|
||||
hash common.Hash
|
||||
prevPrice *big.Int
|
||||
}
|
||||
insertLiquidationPrice struct {
|
||||
orderBook common.Hash
|
||||
price *big.Int
|
||||
lendingBook common.Hash
|
||||
tradeId uint64
|
||||
}
|
||||
removeLiquidationPrice struct {
|
||||
orderBook common.Hash
|
||||
price *big.Int
|
||||
lendingBook common.Hash
|
||||
tradeId uint64
|
||||
}
|
||||
)
|
||||
|
||||
func (ch insertOrder) undo(s *TradingStateDB) {
|
||||
s.CancelOrder(ch.orderBook, ch.order)
|
||||
}
|
||||
func (ch cancelOrder) undo(s *TradingStateDB) {
|
||||
s.InsertOrderItem(ch.orderBook, ch.orderId, ch.order)
|
||||
}
|
||||
func (ch insertLiquidationPrice) undo(s *TradingStateDB) {
|
||||
s.RemoveLiquidationPrice(ch.orderBook, ch.price, ch.lendingBook, ch.tradeId)
|
||||
}
|
||||
func (ch removeLiquidationPrice) undo(s *TradingStateDB) {
|
||||
s.InsertLiquidationPrice(ch.orderBook, ch.price, ch.lendingBook, ch.tradeId)
|
||||
}
|
||||
func (ch subAmountOrder) undo(s *TradingStateDB) {
|
||||
priceHash := common.BigToHash(ch.order.Price)
|
||||
stateOrderBook := s.getStateExchangeObject(ch.orderBook)
|
||||
var stateOrderList *stateOrderList
|
||||
switch ch.order.Side {
|
||||
case Ask:
|
||||
stateOrderList = stateOrderBook.getStateOrderListAskObject(s.db, priceHash)
|
||||
case Bid:
|
||||
stateOrderList = stateOrderBook.getStateBidOrderListObject(s.db, priceHash)
|
||||
default:
|
||||
return
|
||||
}
|
||||
stateOrderItem := stateOrderBook.getStateOrderObject(s.db, ch.orderId)
|
||||
newAmount := new(big.Int).Add(stateOrderItem.Quantity(), ch.amount)
|
||||
stateOrderItem.setVolume(newAmount)
|
||||
stateOrderList.insertOrderItem(s.db, ch.orderId, common.BigToHash(newAmount))
|
||||
stateOrderList.AddVolume(ch.amount)
|
||||
}
|
||||
func (ch nonceChange) undo(s *TradingStateDB) {
|
||||
s.SetNonce(ch.hash, ch.prev)
|
||||
}
|
||||
func (ch lastPriceChange) undo(s *TradingStateDB) {
|
||||
s.SetLastPrice(ch.hash, ch.prev)
|
||||
}
|
||||
func (ch mediumPriceChange) undo(s *TradingStateDB) {
|
||||
s.SetMediumPrice(ch.hash, ch.prevPrice, ch.prevQuantity)
|
||||
}
|
||||
func (ch mediumPriceBeforeEpochChange) undo(s *TradingStateDB) {
|
||||
s.SetMediumPriceBeforeEpoch(ch.hash, ch.prevPrice)
|
||||
}
|
||||
140
XDCx/tradingstate/managed_state.go
Normal file
140
XDCx/tradingstate/managed_state.go
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package tradingstate
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
)
|
||||
|
||||
type exchanges struct {
|
||||
stateObject *tradingExchanges
|
||||
nstart uint64
|
||||
nonces []bool
|
||||
}
|
||||
|
||||
type XDCXManagedState struct {
|
||||
*TradingStateDB
|
||||
mu sync.RWMutex
|
||||
exchanges map[common.Hash]*exchanges
|
||||
}
|
||||
|
||||
// XDCXManagedState returns a new managed state with the statedb as it's backing layer
|
||||
func ManageState(statedb *TradingStateDB) *XDCXManagedState {
|
||||
return &XDCXManagedState{
|
||||
TradingStateDB: statedb.Copy(),
|
||||
exchanges: make(map[common.Hash]*exchanges),
|
||||
}
|
||||
}
|
||||
|
||||
// SetState sets the backing layer of the managed state
|
||||
func (ms *XDCXManagedState) SetState(statedb *TradingStateDB) {
|
||||
ms.mu.Lock()
|
||||
defer ms.mu.Unlock()
|
||||
ms.TradingStateDB = statedb
|
||||
}
|
||||
|
||||
// RemoveNonce removed the nonce from the managed state and all future pending nonces
|
||||
func (ms *XDCXManagedState) RemoveNonce(addr common.Hash, n uint64) {
|
||||
if ms.hasAccount(addr) {
|
||||
ms.mu.Lock()
|
||||
defer ms.mu.Unlock()
|
||||
|
||||
account := ms.getAccount(addr)
|
||||
if n-account.nstart <= uint64(len(account.nonces)) {
|
||||
reslice := make([]bool, n-account.nstart)
|
||||
copy(reslice, account.nonces[:n-account.nstart])
|
||||
account.nonces = reslice
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewNonce returns the new canonical nonce for the managed orderId
|
||||
func (ms *XDCXManagedState) NewNonce(addr common.Hash) uint64 {
|
||||
ms.mu.Lock()
|
||||
defer ms.mu.Unlock()
|
||||
|
||||
account := ms.getAccount(addr)
|
||||
for i, nonce := range account.nonces {
|
||||
if !nonce {
|
||||
return account.nstart + uint64(i)
|
||||
}
|
||||
}
|
||||
account.nonces = append(account.nonces, true)
|
||||
|
||||
return uint64(len(account.nonces)-1) + account.nstart
|
||||
}
|
||||
|
||||
// GetNonce returns the canonical nonce for the managed or unmanaged orderId.
|
||||
//
|
||||
// Because GetNonce mutates the DB, we must take a write lock.
|
||||
func (ms *XDCXManagedState) GetNonce(addr common.Hash) uint64 {
|
||||
ms.mu.Lock()
|
||||
defer ms.mu.Unlock()
|
||||
if ms.hasAccount(addr) {
|
||||
account := ms.getAccount(addr)
|
||||
return uint64(len(account.nonces)) + account.nstart
|
||||
} else {
|
||||
return ms.TradingStateDB.GetNonce(addr)
|
||||
}
|
||||
}
|
||||
|
||||
// SetNonce sets the new canonical nonce for the managed state
|
||||
func (ms *XDCXManagedState) SetNonce(addr common.Hash, nonce uint64) {
|
||||
ms.mu.Lock()
|
||||
defer ms.mu.Unlock()
|
||||
|
||||
so := ms.GetOrNewStateExchangeObject(addr)
|
||||
so.SetNonce(nonce)
|
||||
|
||||
ms.exchanges[addr] = newAccount(so)
|
||||
}
|
||||
|
||||
// HasAccount returns whether the given address is managed or not
|
||||
func (ms *XDCXManagedState) HasAccount(addr common.Hash) bool {
|
||||
ms.mu.RLock()
|
||||
defer ms.mu.RUnlock()
|
||||
return ms.hasAccount(addr)
|
||||
}
|
||||
|
||||
func (ms *XDCXManagedState) hasAccount(addr common.Hash) bool {
|
||||
_, ok := ms.exchanges[addr]
|
||||
return ok
|
||||
}
|
||||
|
||||
// populate the managed state
|
||||
func (ms *XDCXManagedState) getAccount(addr common.Hash) *exchanges {
|
||||
if account, ok := ms.exchanges[addr]; !ok {
|
||||
so := ms.GetOrNewStateExchangeObject(addr)
|
||||
ms.exchanges[addr] = newAccount(so)
|
||||
} else {
|
||||
// Always make sure the state orderId nonce isn't actually higher
|
||||
// than the tracked one.
|
||||
so := ms.TradingStateDB.getStateExchangeObject(addr)
|
||||
if so != nil && uint64(len(account.nonces))+account.nstart < so.Nonce() {
|
||||
ms.exchanges[addr] = newAccount(so)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ms.exchanges[addr]
|
||||
}
|
||||
|
||||
func newAccount(so *tradingExchanges) *exchanges {
|
||||
return &exchanges{so, so.Nonce(), nil}
|
||||
}
|
||||
415
XDCx/tradingstate/orderitem.go
Normal file
415
XDCx/tradingstate/orderitem.go
Normal file
|
|
@ -0,0 +1,415 @@
|
|||
package tradingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/state"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
const (
|
||||
OrderStatusNew = "NEW"
|
||||
OrderStatusOpen = "OPEN"
|
||||
OrderStatusPartialFilled = "PARTIAL_FILLED"
|
||||
OrderStatusFilled = "FILLED"
|
||||
OrderStatusCancelled = "CANCELLED"
|
||||
OrderStatusRejected = "REJECTED"
|
||||
)
|
||||
|
||||
// OrderItem : info that will be store in database
|
||||
type OrderItem struct {
|
||||
Quantity *big.Int `json:"quantity,omitempty"`
|
||||
Price *big.Int `json:"price,omitempty"`
|
||||
ExchangeAddress common.Address `json:"exchangeAddress,omitempty"`
|
||||
UserAddress common.Address `json:"userAddress,omitempty"`
|
||||
BaseToken common.Address `json:"baseToken,omitempty"`
|
||||
QuoteToken common.Address `json:"quoteToken,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Side string `json:"side,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
Hash common.Hash `json:"hash,omitempty"`
|
||||
TxHash common.Hash `json:"txHash,omitempty"`
|
||||
Signature *Signature `json:"signature,omitempty"`
|
||||
FilledAmount *big.Int `json:"filledAmount,omitempty"`
|
||||
Nonce *big.Int `json:"nonce,omitempty"`
|
||||
CreatedAt time.Time `json:"createdAt,omitempty"`
|
||||
UpdatedAt time.Time `json:"updatedAt,omitempty"`
|
||||
OrderID uint64 `json:"orderID,omitempty"`
|
||||
ExtraData string `json:"extraData,omitempty"`
|
||||
}
|
||||
|
||||
// Signature struct
|
||||
type Signature struct {
|
||||
V byte
|
||||
R common.Hash
|
||||
S common.Hash
|
||||
}
|
||||
|
||||
type SignatureRecord struct {
|
||||
V byte `json:"V" bson:"V"`
|
||||
R string `json:"R" bson:"R"`
|
||||
S string `json:"S" bson:"S"`
|
||||
}
|
||||
|
||||
type OrderItemBSON struct {
|
||||
Quantity string `json:"quantity,omitempty" bson:"quantity"`
|
||||
Price string `json:"price,omitempty" bson:"price"`
|
||||
ExchangeAddress string `json:"exchangeAddress,omitempty" bson:"exchangeAddress"`
|
||||
UserAddress string `json:"userAddress,omitempty" bson:"userAddress"`
|
||||
BaseToken string `json:"baseToken,omitempty" bson:"baseToken"`
|
||||
QuoteToken string `json:"quoteToken,omitempty" bson:"quoteToken"`
|
||||
Status string `json:"status,omitempty" bson:"status"`
|
||||
Side string `json:"side,omitempty" bson:"side"`
|
||||
Type string `json:"type,omitempty" bson:"type"`
|
||||
Hash string `json:"hash,omitempty" bson:"hash"`
|
||||
TxHash string `json:"txHash,omitempty" bson:"txHash"`
|
||||
Signature *SignatureRecord `json:"signature,omitempty" bson:"signature"`
|
||||
FilledAmount string `json:"filledAmount,omitempty" bson:"filledAmount"`
|
||||
Nonce string `json:"nonce,omitempty" bson:"nonce"`
|
||||
CreatedAt time.Time `json:"createdAt,omitempty" bson:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt,omitempty" bson:"updatedAt"`
|
||||
OrderID string `json:"orderID,omitempty" bson:"orderID"`
|
||||
ExtraData string `json:"extraData,omitempty" bson:"extraData"`
|
||||
}
|
||||
|
||||
func (o *OrderItem) GetBSON() (interface{}, error) {
|
||||
or := OrderItemBSON{
|
||||
ExchangeAddress: o.ExchangeAddress.Hex(),
|
||||
UserAddress: o.UserAddress.Hex(),
|
||||
BaseToken: o.BaseToken.Hex(),
|
||||
QuoteToken: o.QuoteToken.Hex(),
|
||||
Status: o.Status,
|
||||
Side: o.Side,
|
||||
Type: o.Type,
|
||||
Hash: o.Hash.Hex(),
|
||||
TxHash: o.TxHash.Hex(),
|
||||
Quantity: o.Quantity.String(),
|
||||
Price: o.Price.String(),
|
||||
Nonce: o.Nonce.String(),
|
||||
CreatedAt: o.CreatedAt,
|
||||
UpdatedAt: o.UpdatedAt,
|
||||
OrderID: strconv.FormatUint(o.OrderID, 10),
|
||||
ExtraData: o.ExtraData,
|
||||
}
|
||||
|
||||
if o.FilledAmount != nil {
|
||||
or.FilledAmount = o.FilledAmount.String()
|
||||
}
|
||||
|
||||
if o.Signature != nil {
|
||||
or.Signature = &SignatureRecord{
|
||||
V: o.Signature.V,
|
||||
R: o.Signature.R.Hex(),
|
||||
S: o.Signature.S.Hex(),
|
||||
}
|
||||
}
|
||||
|
||||
return or, nil
|
||||
}
|
||||
|
||||
func (o *OrderItem) SetBSON(raw bson.Raw) error {
|
||||
decoded := new(struct {
|
||||
ID bson.ObjectId `json:"id,omitempty" bson:"_id"`
|
||||
ExchangeAddress string `json:"exchangeAddress" bson:"exchangeAddress"`
|
||||
UserAddress string `json:"userAddress" bson:"userAddress"`
|
||||
BaseToken string `json:"baseToken" bson:"baseToken"`
|
||||
QuoteToken string `json:"quoteToken" bson:"quoteToken"`
|
||||
Status string `json:"status" bson:"status"`
|
||||
Side string `json:"side" bson:"side"`
|
||||
Type string `json:"type" bson:"type"`
|
||||
Hash string `json:"hash" bson:"hash"`
|
||||
TxHash string `json:"txHash,omitempty" bson:"txHash"`
|
||||
Price string `json:"price" bson:"price"`
|
||||
Quantity string `json:"quantity" bson:"quantity"`
|
||||
FilledAmount string `json:"filledAmount" bson:"filledAmount"`
|
||||
Nonce string `json:"nonce" bson:"nonce"`
|
||||
MakeFee string `json:"makeFee" bson:"makeFee"`
|
||||
TakeFee string `json:"takeFee" bson:"takeFee"`
|
||||
Signature *SignatureRecord `json:"signature" bson:"signature"`
|
||||
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
|
||||
OrderID string `json:"orderID" bson:"orderID"`
|
||||
ExtraData string `json:"extraData,omitempty" bson:"extraData"`
|
||||
})
|
||||
|
||||
err := raw.Unmarshal(decoded)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
o.ExchangeAddress = common.HexToAddress(decoded.ExchangeAddress)
|
||||
o.UserAddress = common.HexToAddress(decoded.UserAddress)
|
||||
o.BaseToken = common.HexToAddress(decoded.BaseToken)
|
||||
o.QuoteToken = common.HexToAddress(decoded.QuoteToken)
|
||||
o.FilledAmount = ToBigInt(decoded.FilledAmount)
|
||||
o.Nonce = ToBigInt(decoded.Nonce)
|
||||
o.Status = decoded.Status
|
||||
o.Side = decoded.Side
|
||||
o.Type = decoded.Type
|
||||
o.Hash = common.HexToHash(decoded.Hash)
|
||||
o.TxHash = common.HexToHash(decoded.TxHash)
|
||||
|
||||
if decoded.Quantity != "" {
|
||||
o.Quantity = ToBigInt(decoded.Quantity)
|
||||
}
|
||||
|
||||
if decoded.FilledAmount != "" {
|
||||
o.FilledAmount = ToBigInt(decoded.FilledAmount)
|
||||
}
|
||||
|
||||
if decoded.Price != "" {
|
||||
o.Price = ToBigInt(decoded.Price)
|
||||
}
|
||||
|
||||
if decoded.Signature != nil {
|
||||
o.Signature = &Signature{
|
||||
V: byte(decoded.Signature.V),
|
||||
R: common.HexToHash(decoded.Signature.R),
|
||||
S: common.HexToHash(decoded.Signature.S),
|
||||
}
|
||||
}
|
||||
|
||||
o.CreatedAt = decoded.CreatedAt
|
||||
o.UpdatedAt = decoded.UpdatedAt
|
||||
orderID, err := strconv.ParseInt(decoded.OrderID, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
o.OrderID = uint64(orderID)
|
||||
o.ExtraData = decoded.ExtraData
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyOrder verify orderItem
|
||||
func (o *OrderItem) VerifyOrder(state *state.StateDB) error {
|
||||
if err := o.VerifyBasicOrderInfo(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.verifyRelayer(state); err != nil {
|
||||
return err
|
||||
}
|
||||
if o.Status == OrderNew {
|
||||
if err := VerifyPair(state, o.ExchangeAddress, o.BaseToken, o.QuoteToken); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// VerifyBasicOrderInfo verify basic info
|
||||
func (o *OrderItem) VerifyBasicOrderInfo() error {
|
||||
|
||||
if o.Status == OrderNew {
|
||||
if o.Type == Limit {
|
||||
if err := o.verifyPrice(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := o.verifyQuantity(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.verifyOrderSide(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.verifyOrderType(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if err := o.verifyStatus(); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := o.verifySignature(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// verify whether the exchange applies to become relayer
|
||||
func (o *OrderItem) verifyRelayer(state *state.StateDB) error {
|
||||
if !IsValidRelayer(state, o.ExchangeAddress) {
|
||||
return ErrInvalidRelayer
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//verify signatures
|
||||
func (o *OrderItem) verifySignature() error {
|
||||
bigstr := o.Nonce.String()
|
||||
n, err := strconv.ParseInt(bigstr, 10, 64)
|
||||
if err != nil {
|
||||
return ErrInvalidSignature
|
||||
}
|
||||
V := big.NewInt(int64(o.Signature.V))
|
||||
R := o.Signature.R.Big()
|
||||
S := o.Signature.S.Big()
|
||||
|
||||
tx := types.NewOrderTransaction(uint64(n), o.Quantity, o.Price, o.ExchangeAddress, o.UserAddress,
|
||||
o.BaseToken, o.QuoteToken, o.Status, o.Side, o.Type, o.Hash, o.OrderID)
|
||||
tx.ImportSignature(V, R, S)
|
||||
from, _ := types.OrderSender(types.OrderTxSigner{}, tx)
|
||||
if from != tx.UserAddress() {
|
||||
return ErrInvalidSignature
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// verify order type
|
||||
func (o *OrderItem) verifyOrderType() error {
|
||||
if _, ok := MatchingOrderType[o.Type]; !ok {
|
||||
log.Debug("Invalid order type", "type", o.Type)
|
||||
return ErrInvalidOrderType
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
//verify order side
|
||||
func (o *OrderItem) verifyOrderSide() error {
|
||||
|
||||
if o.Side != Bid && o.Side != Ask {
|
||||
log.Debug("Invalid orderSide", "side", o.Side)
|
||||
return ErrInvalidOrderSide
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (o *OrderItem) encodedSide() *big.Int {
|
||||
if o.Side == Bid {
|
||||
return big.NewInt(0)
|
||||
}
|
||||
return big.NewInt(1)
|
||||
}
|
||||
|
||||
// verifyPrice make sure price is a positive number
|
||||
func (o *OrderItem) verifyPrice() error {
|
||||
if o.Price == nil || o.Price.Cmp(big.NewInt(0)) <= 0 {
|
||||
log.Debug("Invalid price", "price", o.Price.String())
|
||||
return ErrInvalidPrice
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyQuantity make sure quantity is a positive number
|
||||
func (o *OrderItem) verifyQuantity() error {
|
||||
if o.Quantity == nil || o.Quantity.Cmp(big.NewInt(0)) <= 0 {
|
||||
log.Debug("Invalid quantity", "quantity", o.Quantity.String())
|
||||
return ErrInvalidQuantity
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// verifyStatus make sure status is NEW OR CANCELLED
|
||||
func (o *OrderItem) verifyStatus() error {
|
||||
if o.Status != Cancel && o.Status != OrderNew {
|
||||
log.Debug("Invalid status", "status", o.Status)
|
||||
return ErrInvalidStatus
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func IsValidRelayer(statedb *state.StateDB, address common.Address) bool {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locRelayerState := GetLocMappingAtKey(address.Hash(), slot)
|
||||
|
||||
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locRelayerState, RelayerStructMappingSlot["_deposit"])
|
||||
locHashDeposit := common.BigToHash(locBigDeposit)
|
||||
balance := statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit).Big()
|
||||
if balance.Cmp(new(big.Int).Mul(common.BasePrice, common.RelayerLockedFund)) <= 0 {
|
||||
log.Debug("Relayer is not in relayer list", "relayer", address.String(), "balance", balance)
|
||||
return false
|
||||
}
|
||||
if IsResignedRelayer(address, statedb) {
|
||||
log.Debug("Relayer has resigned", "relayer", address.String())
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func VerifyPair(statedb *state.StateDB, exchangeAddress, baseToken, quoteToken common.Address) error {
|
||||
baseTokenLength := GetBaseTokenLength(exchangeAddress, statedb)
|
||||
quoteTokenLength := GetQuoteTokenLength(exchangeAddress, statedb)
|
||||
if baseTokenLength != quoteTokenLength {
|
||||
return fmt.Errorf("invalid length of baseTokenList: %d . QuoteTokenList: %d", baseTokenLength, quoteTokenLength)
|
||||
}
|
||||
var baseIndexes []uint64
|
||||
for i := uint64(0); i < baseTokenLength; i++ {
|
||||
if baseToken == GetBaseTokenAtIndex(exchangeAddress, statedb, i) {
|
||||
baseIndexes = append(baseIndexes, i)
|
||||
}
|
||||
}
|
||||
if len(baseIndexes) == 0 {
|
||||
return fmt.Errorf("basetoken not found in relayer registration. BaseToken: %s. Exchange: %s", baseToken.Hex(), exchangeAddress.Hex())
|
||||
}
|
||||
for _, index := range baseIndexes {
|
||||
if quoteToken == GetQuoteTokenAtIndex(exchangeAddress, statedb, index) {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
return fmt.Errorf("invalid exchange pair. Base: %s. Quote: %s. Exchange: %s", baseToken.Hex(), quoteToken.Hex(), exchangeAddress.Hex())
|
||||
}
|
||||
|
||||
func VerifyBalance(statedb *state.StateDB, XDCxStateDb *TradingStateDB, order *types.OrderTransaction, baseDecimal, quoteDecimal *big.Int) error {
|
||||
var quotePrice *big.Int
|
||||
if order.QuoteToken().String() != common.XDCNativeAddress {
|
||||
quotePrice = XDCxStateDb.GetLastPrice(GetTradingOrderBookHash(order.QuoteToken(), common.HexToAddress(common.XDCNativeAddress)))
|
||||
log.Debug("TryGet quotePrice QuoteToken/XDC", "quotePrice", quotePrice)
|
||||
if quotePrice == nil || quotePrice.Sign() == 0 {
|
||||
inversePrice := XDCxStateDb.GetLastPrice(GetTradingOrderBookHash(common.HexToAddress(common.XDCNativeAddress), order.QuoteToken()))
|
||||
log.Debug("TryGet inversePrice XDC/QuoteToken", "inversePrice", inversePrice)
|
||||
if inversePrice != nil && inversePrice.Sign() > 0 {
|
||||
quotePrice = new(big.Int).Mul(common.BasePrice, quoteDecimal)
|
||||
quotePrice = new(big.Int).Div(quotePrice, inversePrice)
|
||||
log.Debug("TryGet quotePrice after get inversePrice XDC/QuoteToken", "quotePrice", quotePrice, "quoteTokenDecimal", quoteDecimal)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
quotePrice = common.BasePrice
|
||||
}
|
||||
feeRate := GetExRelayerFee(order.ExchangeAddress(), statedb)
|
||||
balanceResult, err := GetSettleBalance(quotePrice, order.Side(), feeRate, order.BaseToken(), order.QuoteToken(), order.Price(), feeRate, baseDecimal, quoteDecimal, order.Quantity())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
expectedBalance := balanceResult.Taker.OutTotal
|
||||
actualBalance := GetTokenBalance(order.UserAddress(), balanceResult.Taker.OutToken, statedb)
|
||||
if actualBalance.Cmp(expectedBalance) < 0 {
|
||||
return fmt.Errorf("token: %s . ExpectedBalance: %s . ActualBalance: %s", balanceResult.Taker.OutToken.Hex(), expectedBalance.String(), actualBalance.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// MarshalSignature marshals the signature struct to []byte
|
||||
func (s *Signature) MarshalSignature() ([]byte, error) {
|
||||
sigBytes1 := s.R.Bytes()
|
||||
sigBytes2 := s.S.Bytes()
|
||||
sigBytes3 := s.V - 27
|
||||
|
||||
sigBytes := append([]byte{}, sigBytes1...)
|
||||
sigBytes = append(sigBytes, sigBytes2...)
|
||||
sigBytes = append(sigBytes, sigBytes3)
|
||||
|
||||
return sigBytes, nil
|
||||
}
|
||||
|
||||
// Verify returns the address that corresponds to the given signature and signed message
|
||||
func (s *Signature) Verify(hash common.Hash) (common.Address, error) {
|
||||
|
||||
hashBytes := hash.Bytes()
|
||||
sigBytes, err := s.MarshalSignature()
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
|
||||
pubKey, err := crypto.SigToPub(hashBytes, sigBytes)
|
||||
if err != nil {
|
||||
return common.Address{}, err
|
||||
}
|
||||
address := crypto.PubkeyToAddress(*pubKey)
|
||||
return address, nil
|
||||
}
|
||||
352
XDCx/tradingstate/relayer_state.go
Normal file
352
XDCx/tradingstate/relayer_state.go
Normal file
|
|
@ -0,0 +1,352 @@
|
|||
package tradingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/state"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func GetLocMappingAtKey(key common.Hash, slot uint64) *big.Int {
|
||||
slotHash := common.BigToHash(new(big.Int).SetUint64(slot))
|
||||
retByte := crypto.Keccak256(key.Bytes(), slotHash.Bytes())
|
||||
ret := new(big.Int)
|
||||
ret.SetBytes(retByte)
|
||||
return ret
|
||||
}
|
||||
|
||||
func GetExRelayerFee(relayer common.Address, statedb *state.StateDB) *big.Int {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_fee"])
|
||||
locHash := common.BigToHash(locBig)
|
||||
return statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash).Big()
|
||||
}
|
||||
|
||||
func GetRelayerOwner(relayer common.Address, statedb *state.StateDB) common.Address {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
log.Debug("GetRelayerOwner", "relayer", relayer.Hex(), "slot", slot, "locBig", locBig)
|
||||
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_owner"])
|
||||
locHash := common.BigToHash(locBig)
|
||||
return common.BytesToAddress(statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash).Bytes())
|
||||
}
|
||||
|
||||
// return true if relayer request to resign and have not withdraw locked fund
|
||||
func IsResignedRelayer(relayer common.Address, statedb *state.StateDB) bool {
|
||||
slot := RelayerMappingSlot["RESIGN_REQUESTS"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
locHash := common.BigToHash(locBig)
|
||||
if statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash) != (common.Hash{}) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetBaseTokenLength(relayer common.Address, statedb *state.StateDB) uint64 {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_fromTokens"])
|
||||
locHash := common.BigToHash(locBig)
|
||||
return statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash).Big().Uint64()
|
||||
}
|
||||
|
||||
func GetBaseTokenAtIndex(relayer common.Address, statedb *state.StateDB, index uint64) common.Address {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_fromTokens"])
|
||||
locHash := common.BigToHash(locBig)
|
||||
loc := state.GetLocDynamicArrAtElement(locHash, index, 1)
|
||||
return common.BytesToAddress(statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), loc).Bytes())
|
||||
}
|
||||
|
||||
func GetQuoteTokenLength(relayer common.Address, statedb *state.StateDB) uint64 {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_toTokens"])
|
||||
locHash := common.BigToHash(locBig)
|
||||
return statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash).Big().Uint64()
|
||||
}
|
||||
|
||||
func GetQuoteTokenAtIndex(relayer common.Address, statedb *state.StateDB, index uint64) common.Address {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_toTokens"])
|
||||
locHash := common.BigToHash(locBig)
|
||||
loc := state.GetLocDynamicArrAtElement(locHash, index, 1)
|
||||
return common.BytesToAddress(statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), loc).Bytes())
|
||||
}
|
||||
|
||||
func GetRelayerCount(statedb *state.StateDB) uint64 {
|
||||
slot := RelayerMappingSlot["RelayerCount"]
|
||||
slotHash := common.BigToHash(new(big.Int).SetUint64(slot))
|
||||
valueHash := statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), slotHash)
|
||||
return new(big.Int).SetBytes(valueHash.Bytes()).Uint64()
|
||||
}
|
||||
|
||||
func GetAllCoinbases(statedb *state.StateDB) []common.Address {
|
||||
relayerCount := GetRelayerCount(statedb)
|
||||
slot := RelayerMappingSlot["RELAYER_COINBASES"]
|
||||
coinbases := []common.Address{}
|
||||
for i := uint64(0); i < relayerCount; i++ {
|
||||
valueHash := statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), common.BytesToHash(state.GetLocMappingAtKey(common.BigToHash(big.NewInt(int64(i))), slot).Bytes()))
|
||||
coinbases = append(coinbases, common.BytesToAddress(valueHash.Bytes()))
|
||||
}
|
||||
return coinbases
|
||||
}
|
||||
func GetAllTradingPairs(statedb *state.StateDB) (map[common.Hash]bool, error) {
|
||||
coinbases := GetAllCoinbases(statedb)
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
allPairs := map[common.Hash]bool{}
|
||||
for _, coinbase := range coinbases {
|
||||
locBig := GetLocMappingAtKey(coinbase.Hash(), slot)
|
||||
fromTokenSlot := new(big.Int).Add(locBig, RelayerStructMappingSlot["_fromTokens"])
|
||||
fromTokenLength := statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), common.BigToHash(fromTokenSlot)).Big().Uint64()
|
||||
toTokenSlot := new(big.Int).Add(locBig, RelayerStructMappingSlot["_toTokens"])
|
||||
toTokenLength := statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), common.BigToHash(toTokenSlot)).Big().Uint64()
|
||||
if toTokenLength != fromTokenLength {
|
||||
return map[common.Hash]bool{}, fmt.Errorf("Invalid length from token & to toke : from :%d , to :%d ", fromTokenLength, toTokenLength)
|
||||
}
|
||||
fromTokens := []common.Address{}
|
||||
fromTokenSlotHash := common.BytesToHash(fromTokenSlot.Bytes())
|
||||
for i := uint64(0); i < fromTokenLength; i++ {
|
||||
fromToken := common.BytesToAddress(statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), state.GetLocDynamicArrAtElement(fromTokenSlotHash, i, uint64(1))).Bytes())
|
||||
fromTokens = append(fromTokens, fromToken)
|
||||
}
|
||||
toTokenSlotHash := common.BytesToHash(toTokenSlot.Bytes())
|
||||
for i := uint64(0); i < toTokenLength; i++ {
|
||||
toToken := common.BytesToAddress(statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), state.GetLocDynamicArrAtElement(toTokenSlotHash, i, uint64(1))).Bytes())
|
||||
|
||||
log.Debug("GetAllTradingPairs all pair info", "from", fromTokens[i].Hex(), "toToken", toToken.Hex())
|
||||
allPairs[GetTradingOrderBookHash(fromTokens[i], toToken)] = true
|
||||
}
|
||||
}
|
||||
log.Debug("GetAllTradingPairs", "coinbase", len(coinbases), "allPairs", len(allPairs))
|
||||
return allPairs, nil
|
||||
}
|
||||
|
||||
func SubRelayerFee(relayer common.Address, fee *big.Int, statedb *state.StateDB) error {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
|
||||
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"])
|
||||
locHashDeposit := common.BigToHash(locBigDeposit)
|
||||
balance := statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit).Big()
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: SubRelayerFee BEFORE", "relayer", relayer.String(), "balance", balance)
|
||||
if balance.Cmp(fee) < 0 {
|
||||
return errors.Errorf("relayer %s isn't enough XDC fee", relayer.String())
|
||||
} else {
|
||||
balance = new(big.Int).Sub(balance, fee)
|
||||
statedb.SetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit, common.BigToHash(balance))
|
||||
statedb.SubBalance(common.HexToAddress(common.RelayerRegistrationSMC), fee)
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: SubRelayerFee AFTER", "relayer", relayer.String(), "balance", balance)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func CheckRelayerFee(relayer common.Address, fee *big.Int, statedb *state.StateDB) error {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
|
||||
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"])
|
||||
locHashDeposit := common.BigToHash(locBigDeposit)
|
||||
balance := statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit).Big()
|
||||
if new(big.Int).Sub(balance, fee).Cmp(new(big.Int).Mul(common.BasePrice, common.RelayerLockedFund)) < 0 {
|
||||
return errors.Errorf("relayer %s isn't enough XDC fee : balance %d , fee : %d ", relayer.Hex(), balance.Uint64(), fee.Uint64())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func AddTokenBalance(addr common.Address, value *big.Int, token common.Address, statedb *state.StateDB) error {
|
||||
// XDC native
|
||||
if token.String() == common.XDCNativeAddress {
|
||||
balance := statedb.GetBalance(addr)
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD TOKEN XDC NATIVE BEFORE", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
|
||||
statedb.AddBalance(addr, value)
|
||||
balance = statedb.GetBalance(addr)
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD XDC NATIVE BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TRC tokens
|
||||
if statedb.Exist(token) {
|
||||
slot := TokenMappingSlot["balances"]
|
||||
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
|
||||
balance := statedb.GetState(token, locHash).Big()
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD TOKEN BALANCE BEFORE", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
|
||||
balance = new(big.Int).Add(balance, value)
|
||||
statedb.SetState(token, locHash, common.BigToHash(balance))
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD TOKEN BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
|
||||
return nil
|
||||
} else {
|
||||
return errors.Errorf("token %s isn't exist", token.String())
|
||||
}
|
||||
}
|
||||
|
||||
func SubTokenBalance(addr common.Address, value *big.Int, token common.Address, statedb *state.StateDB) error {
|
||||
// XDC native
|
||||
if token.String() == common.XDCNativeAddress {
|
||||
|
||||
balance := statedb.GetBalance(addr)
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB XDC NATIVE BALANCE BEFORE", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
|
||||
if balance.Cmp(value) < 0 {
|
||||
return errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), token.String(), balance, value)
|
||||
}
|
||||
statedb.SubBalance(addr, value)
|
||||
balance = statedb.GetBalance(addr)
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB XDC NATIVE BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TRC tokens
|
||||
if statedb.Exist(token) {
|
||||
slot := TokenMappingSlot["balances"]
|
||||
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
|
||||
balance := statedb.GetState(token, locHash).Big()
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB TOKEN BALANCE BEFORE", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
|
||||
if balance.Cmp(value) < 0 {
|
||||
return errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), token.String(), balance, value)
|
||||
}
|
||||
balance = new(big.Int).Sub(balance, value)
|
||||
statedb.SetState(token, locHash, common.BigToHash(balance))
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB TOKEN BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
|
||||
return nil
|
||||
} else {
|
||||
return errors.Errorf("token %s isn't exist", token.String())
|
||||
}
|
||||
}
|
||||
|
||||
func CheckSubTokenBalance(addr common.Address, value *big.Int, token common.Address, statedb *state.StateDB, mapBalances map[common.Address]map[common.Address]*big.Int) (*big.Int, error) {
|
||||
// XDC native
|
||||
if token.String() == common.XDCNativeAddress {
|
||||
var balance *big.Int
|
||||
if value := mapBalances[token][addr]; value != nil {
|
||||
balance = value
|
||||
} else {
|
||||
balance = statedb.GetBalance(addr)
|
||||
}
|
||||
if balance.Cmp(value) < 0 {
|
||||
return nil, errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), token.String(), balance, value)
|
||||
}
|
||||
newBalance := new(big.Int).Sub(balance, value)
|
||||
log.Debug("CheckSubTokenBalance settle balance: SUB XDC NATIVE BALANCE ", "token", token.String(), "address", addr.String(), "balance", balance, "value", value, "newBalance", newBalance)
|
||||
return newBalance, nil
|
||||
}
|
||||
// TRC tokens
|
||||
if statedb.Exist(token) {
|
||||
var balance *big.Int
|
||||
if value := mapBalances[token][addr]; value != nil {
|
||||
balance = value
|
||||
} else {
|
||||
slot := TokenMappingSlot["balances"]
|
||||
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
|
||||
balance = statedb.GetState(token, locHash).Big()
|
||||
}
|
||||
if balance.Cmp(value) < 0 {
|
||||
return nil, errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), token.String(), balance, value)
|
||||
}
|
||||
newBalance := new(big.Int).Sub(balance, value)
|
||||
log.Debug("CheckSubTokenBalance settle balance: SUB TOKEN BALANCE ", "token", token.String(), "address", addr.String(), "balance", balance, "value", value, "newBalance", newBalance)
|
||||
return newBalance, nil
|
||||
} else {
|
||||
return nil, errors.Errorf("token %s isn't exist", token.String())
|
||||
}
|
||||
}
|
||||
|
||||
func CheckAddTokenBalance(addr common.Address, value *big.Int, token common.Address, statedb *state.StateDB, mapBalances map[common.Address]map[common.Address]*big.Int) (*big.Int, error) {
|
||||
// XDC native
|
||||
if token.String() == common.XDCNativeAddress {
|
||||
var balance *big.Int
|
||||
if value := mapBalances[token][addr]; value != nil {
|
||||
balance = value
|
||||
} else {
|
||||
balance = statedb.GetBalance(addr)
|
||||
}
|
||||
newBalance := new(big.Int).Add(balance, value)
|
||||
log.Debug("CheckAddTokenBalance settle balance: ADD XDC NATIVE BALANCE ", "token", token.String(), "address", addr.String(), "balance", balance, "value", value, "newBalance", newBalance)
|
||||
return newBalance, nil
|
||||
}
|
||||
// TRC tokens
|
||||
if statedb.Exist(token) {
|
||||
var balance *big.Int
|
||||
if value := mapBalances[token][addr]; value != nil {
|
||||
balance = value
|
||||
} else {
|
||||
slot := TokenMappingSlot["balances"]
|
||||
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
|
||||
balance = statedb.GetState(token, locHash).Big()
|
||||
}
|
||||
newBalance := new(big.Int).Add(balance, value)
|
||||
log.Debug("CheckAddTokenBalance settle balance: ADD TOKEN BALANCE ", "token", token.String(), "address", addr.String(), "balance", balance, "value", value, "newBalance", newBalance)
|
||||
if common.BigToHash(newBalance).Big().Cmp(newBalance) != 0 {
|
||||
return nil, fmt.Errorf("Overflow when try add token balance , max is 2^256 , balance : %v , value:%v ", balance, value)
|
||||
} else {
|
||||
return newBalance, nil
|
||||
}
|
||||
} else {
|
||||
return nil, errors.Errorf("token %s isn't exist", token.String())
|
||||
}
|
||||
}
|
||||
|
||||
func CheckSubRelayerFee(relayer common.Address, fee *big.Int, statedb *state.StateDB, mapBalances map[common.Address]*big.Int) (*big.Int, error) {
|
||||
balance := mapBalances[relayer]
|
||||
if balance == nil {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"])
|
||||
locHashDeposit := common.BigToHash(locBigDeposit)
|
||||
balance = statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit).Big()
|
||||
}
|
||||
log.Debug("CheckSubRelayerFee settle balance: SubRelayerFee ", "relayer", relayer.String(), "balance", balance, "fee", fee)
|
||||
if balance.Cmp(fee) < 0 {
|
||||
return nil, errors.Errorf("relayer %s isn't enough XDC fee", relayer.String())
|
||||
} else {
|
||||
return new(big.Int).Sub(balance, fee), nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetTokenBalance(addr common.Address, token common.Address, statedb *state.StateDB) *big.Int {
|
||||
// XDC native
|
||||
if token.String() == common.XDCNativeAddress {
|
||||
return statedb.GetBalance(addr)
|
||||
}
|
||||
// TRC tokens
|
||||
if statedb.Exist(token) {
|
||||
slot := TokenMappingSlot["balances"]
|
||||
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
|
||||
return statedb.GetState(token, locHash).Big()
|
||||
} else {
|
||||
return common.Big0
|
||||
}
|
||||
}
|
||||
|
||||
func SetTokenBalance(addr common.Address, balance *big.Int, token common.Address, statedb *state.StateDB) error {
|
||||
// XDC native
|
||||
if token.String() == common.XDCNativeAddress {
|
||||
statedb.SetBalance(addr, balance)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TRC tokens
|
||||
if statedb.Exist(token) {
|
||||
slot := TokenMappingSlot["balances"]
|
||||
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
|
||||
statedb.SetState(token, locHash, common.BigToHash(balance))
|
||||
return nil
|
||||
} else {
|
||||
return errors.Errorf("token %s isn't exist", token.String())
|
||||
}
|
||||
}
|
||||
|
||||
func SetSubRelayerFee(relayer common.Address, balance *big.Int, fee *big.Int, statedb *state.StateDB) {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"])
|
||||
locHashDeposit := common.BigToHash(locBigDeposit)
|
||||
statedb.SetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit, common.BigToHash(balance))
|
||||
statedb.SubBalance(common.HexToAddress(common.RelayerRegistrationSMC), fee)
|
||||
}
|
||||
165
XDCx/tradingstate/settle_balance.go
Normal file
165
XDCx/tradingstate/settle_balance.go
Normal file
|
|
@ -0,0 +1,165 @@
|
|||
package tradingstate
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
const DefaultFeeRate = 10 // 10 / XDCXBaseFee = 10 / 10000 = 0.1%
|
||||
var ErrQuantityTradeTooSmall = errors.New("quantity trade too small")
|
||||
|
||||
type TradeResult struct {
|
||||
Fee *big.Int
|
||||
InToken common.Address
|
||||
InTotal *big.Int
|
||||
OutToken common.Address
|
||||
OutTotal *big.Int
|
||||
}
|
||||
type SettleBalance struct {
|
||||
Taker TradeResult
|
||||
Maker TradeResult
|
||||
}
|
||||
|
||||
func (settleBalance *SettleBalance) String() string {
|
||||
jsonData, _ := json.Marshal(settleBalance)
|
||||
return string(jsonData)
|
||||
}
|
||||
|
||||
func GetSettleBalance(quotePrice *big.Int, takerSide string, takerFeeRate *big.Int, baseToken, quoteToken common.Address, makerPrice *big.Int, makerFeeRate *big.Int, baseTokenDecimal *big.Int, quoteTokenDecimal *big.Int, quantityToTrade *big.Int) (*SettleBalance, error) {
|
||||
log.Debug("GetSettleBalance", "takerSide", takerSide, "takerFeeRate", takerFeeRate, "baseToken", baseToken, "quoteToken", quoteToken, "makerPrice", makerPrice, "makerFeeRate", makerFeeRate, "baseTokenDecimal", baseTokenDecimal, "quantityToTrade", quantityToTrade, "quotePrice", quotePrice)
|
||||
var result *SettleBalance
|
||||
//result = map[common.Address]map[string]interface{}{}
|
||||
|
||||
// quoteTokenQuantity = quantityToTrade * makerPrice / baseTokenDecimal
|
||||
quoteTokenQuantity := new(big.Int).Mul(quantityToTrade, makerPrice)
|
||||
quoteTokenQuantity = new(big.Int).Div(quoteTokenQuantity, baseTokenDecimal)
|
||||
|
||||
makerFee := new(big.Int).Mul(quoteTokenQuantity, makerFeeRate)
|
||||
makerFee = new(big.Int).Div(makerFee, common.XDCXBaseFee)
|
||||
takerFee := new(big.Int).Mul(quoteTokenQuantity, takerFeeRate)
|
||||
takerFee = new(big.Int).Div(takerFee, common.XDCXBaseFee)
|
||||
|
||||
// use the defaultFee to validate small orders
|
||||
defaultFee := new(big.Int).Mul(quoteTokenQuantity, new(big.Int).SetUint64(DefaultFeeRate))
|
||||
defaultFee = new(big.Int).Div(defaultFee, common.XDCXBaseFee)
|
||||
|
||||
if takerSide == Bid {
|
||||
if quoteTokenQuantity.Cmp(makerFee) <= 0 || quoteTokenQuantity.Cmp(defaultFee) <= 0 {
|
||||
log.Debug("quantity trade too small", "quoteTokenQuantity", quoteTokenQuantity, "makerFee", makerFee, "defaultFee", defaultFee)
|
||||
return result, ErrQuantityTradeTooSmall
|
||||
}
|
||||
if quoteToken.String() != common.XDCNativeAddress && quotePrice != nil && quotePrice.Cmp(common.Big0) > 0 {
|
||||
// defaultFeeInXDC
|
||||
defaultFeeInXDC := new(big.Int).Mul(defaultFee, quotePrice)
|
||||
defaultFeeInXDC = new(big.Int).Div(defaultFeeInXDC, quoteTokenDecimal)
|
||||
|
||||
exMakerReceivedFee := new(big.Int).Mul(makerFee, quotePrice)
|
||||
exMakerReceivedFee = new(big.Int).Div(exMakerReceivedFee, quoteTokenDecimal)
|
||||
if (exMakerReceivedFee.Cmp(common.RelayerFee) <= 0 && exMakerReceivedFee.Sign() > 0) || defaultFeeInXDC.Cmp(common.RelayerFee) <= 0 {
|
||||
log.Debug("makerFee too small", "quoteTokenQuantity", quoteTokenQuantity, "makerFee", makerFee, "exMakerReceivedFee", exMakerReceivedFee, "quotePrice", quotePrice, "defaultFeeInXDC", defaultFeeInXDC)
|
||||
return result, ErrQuantityTradeTooSmall
|
||||
}
|
||||
exTakerReceivedFee := new(big.Int).Mul(takerFee, quotePrice)
|
||||
exTakerReceivedFee = new(big.Int).Div(exTakerReceivedFee, quoteTokenDecimal)
|
||||
if (exTakerReceivedFee.Cmp(common.RelayerFee) <= 0 && exTakerReceivedFee.Sign() > 0) || defaultFeeInXDC.Cmp(common.RelayerFee) <= 0 {
|
||||
log.Debug("takerFee too small", "quoteTokenQuantity", quoteTokenQuantity, "takerFee", takerFee, "exTakerReceivedFee", exTakerReceivedFee, "quotePrice", quotePrice, "defaultFeeInXDC", defaultFeeInXDC)
|
||||
return result, ErrQuantityTradeTooSmall
|
||||
}
|
||||
} else if quoteToken.String() == common.XDCNativeAddress {
|
||||
exMakerReceivedFee := makerFee
|
||||
if (exMakerReceivedFee.Cmp(common.RelayerFee) <= 0 && exMakerReceivedFee.Sign() > 0) || defaultFee.Cmp(common.RelayerFee) <= 0 {
|
||||
log.Debug("makerFee too small", "quantityToTrade", quantityToTrade, "makerFee", makerFee, "exMakerReceivedFee", exMakerReceivedFee, "makerFeeRate", makerFeeRate, "defaultFee", defaultFee)
|
||||
return result, ErrQuantityTradeTooSmall
|
||||
}
|
||||
exTakerReceivedFee := takerFee
|
||||
if (exTakerReceivedFee.Cmp(common.RelayerFee) <= 0 && exTakerReceivedFee.Sign() > 0) || defaultFee.Cmp(common.RelayerFee) <= 0 {
|
||||
log.Debug("takerFee too small", "quantityToTrade", quantityToTrade, "takerFee", takerFee, "exTakerReceivedFee", exTakerReceivedFee, "takerFeeRate", takerFeeRate, "defaultFee", defaultFee)
|
||||
return result, ErrQuantityTradeTooSmall
|
||||
}
|
||||
}
|
||||
inTotal := new(big.Int).Sub(quoteTokenQuantity, makerFee)
|
||||
//takerOutTotal= quoteTokenQuantity + takerFee = quantityToTrade*maker.Price/baseTokenDecimal + quantityToTrade*maker.Price/baseTokenDecimal * takerFeeRate/baseFee
|
||||
// = quantityToTrade * maker.Price/baseTokenDecimal ( 1 + takerFeeRate/baseFee)
|
||||
// = quantityToTrade * maker.Price * (baseFee + takerFeeRate ) / ( baseTokenDecimal * baseFee)
|
||||
takerOutTotal := new(big.Int).Add(quoteTokenQuantity, takerFee)
|
||||
|
||||
result = &SettleBalance{
|
||||
Taker: TradeResult{
|
||||
Fee: takerFee,
|
||||
InToken: baseToken,
|
||||
InTotal: quantityToTrade,
|
||||
OutToken: quoteToken,
|
||||
OutTotal: takerOutTotal,
|
||||
},
|
||||
Maker: TradeResult{
|
||||
Fee: makerFee,
|
||||
InToken: quoteToken,
|
||||
InTotal: inTotal,
|
||||
OutToken: baseToken,
|
||||
OutTotal: quantityToTrade,
|
||||
},
|
||||
}
|
||||
} else {
|
||||
if quoteTokenQuantity.Cmp(takerFee) <= 0 || quoteTokenQuantity.Cmp(defaultFee) <= 0 {
|
||||
log.Debug("quantity trade too small", "quoteTokenQuantity", quoteTokenQuantity, "takerFee", takerFee)
|
||||
return result, ErrQuantityTradeTooSmall
|
||||
}
|
||||
if quoteToken.String() != common.XDCNativeAddress && quotePrice != nil && quotePrice.Cmp(common.Big0) > 0 {
|
||||
// defaultFeeInXDC
|
||||
defaultFeeInXDC := new(big.Int).Mul(defaultFee, quotePrice)
|
||||
defaultFeeInXDC = new(big.Int).Div(defaultFeeInXDC, quoteTokenDecimal)
|
||||
|
||||
exMakerReceivedFee := new(big.Int).Mul(makerFee, quotePrice)
|
||||
exMakerReceivedFee = new(big.Int).Div(exMakerReceivedFee, quoteTokenDecimal)
|
||||
log.Debug("exMakerReceivedFee", "quoteTokenQuantity", quoteTokenQuantity, "makerFee", makerFee, "exMakerReceivedFee", exMakerReceivedFee, "quotePrice", quotePrice)
|
||||
if (exMakerReceivedFee.Cmp(common.RelayerFee) <= 0 && exMakerReceivedFee.Sign() > 0) || defaultFeeInXDC.Cmp(common.RelayerFee) <= 0 {
|
||||
log.Debug("makerFee too small", "quoteTokenQuantity", quoteTokenQuantity, "makerFee", makerFee, "exMakerReceivedFee", exMakerReceivedFee, "quotePrice", quotePrice, "defaultMakerFeeInXDC", defaultFeeInXDC)
|
||||
return result, ErrQuantityTradeTooSmall
|
||||
}
|
||||
exTakerReceivedFee := new(big.Int).Mul(takerFee, quotePrice)
|
||||
exTakerReceivedFee = new(big.Int).Div(exTakerReceivedFee, quoteTokenDecimal)
|
||||
if (exTakerReceivedFee.Cmp(common.RelayerFee) <= 0 && exTakerReceivedFee.Sign() > 0) || defaultFeeInXDC.Cmp(common.RelayerFee) <= 0 {
|
||||
log.Debug("takerFee too small", "quoteTokenQuantity", quoteTokenQuantity, "takerFee", takerFee, "exTakerReceivedFee", exTakerReceivedFee, "quotePrice", quotePrice, "defaultFeeInXDC", defaultFeeInXDC)
|
||||
return result, ErrQuantityTradeTooSmall
|
||||
}
|
||||
} else if quoteToken.String() == common.XDCNativeAddress {
|
||||
exMakerReceivedFee := makerFee
|
||||
if (exMakerReceivedFee.Cmp(common.RelayerFee) <= 0 && exMakerReceivedFee.Sign() > 0) || defaultFee.Cmp(common.RelayerFee) <= 0 {
|
||||
log.Debug("makerFee too small", "quantityToTrade", quantityToTrade, "makerFee", makerFee, "exMakerReceivedFee", exMakerReceivedFee, "makerFeeRate", makerFeeRate, "defaultFee", defaultFee)
|
||||
return result, ErrQuantityTradeTooSmall
|
||||
}
|
||||
exTakerReceivedFee := takerFee
|
||||
if (exTakerReceivedFee.Cmp(common.RelayerFee) <= 0 && exTakerReceivedFee.Sign() > 0) || defaultFee.Cmp(common.RelayerFee) <= 0 {
|
||||
log.Debug("takerFee too small", "quantityToTrade", quantityToTrade, "takerFee", takerFee, "exTakerReceivedFee", exTakerReceivedFee, "takerFeeRate", takerFeeRate, "defaultFee", defaultFee)
|
||||
return result, ErrQuantityTradeTooSmall
|
||||
}
|
||||
}
|
||||
inTotal := new(big.Int).Sub(quoteTokenQuantity, takerFee)
|
||||
// makerOutTotal = quoteTokenQuantity + makerFee = quantityToTrade * makerPrice / baseTokenDecimal + quantityToTrade * makerPrice / baseTokenDecimal * makerFeeRate / baseFee
|
||||
// = quantityToTrade * makerPrice / baseTokenDecimal * (1+makerFeeRate / baseFee)
|
||||
// = quantityToTrade * makerPrice * (baseFee + makerFeeRate) / ( baseTokenDecimal * baseFee )
|
||||
makerOutTotal := new(big.Int).Add(quoteTokenQuantity, makerFee)
|
||||
// Fee
|
||||
result = &SettleBalance{
|
||||
Taker: TradeResult{
|
||||
Fee: takerFee,
|
||||
InToken: quoteToken,
|
||||
InTotal: inTotal,
|
||||
OutToken: baseToken,
|
||||
OutTotal: quantityToTrade,
|
||||
},
|
||||
Maker: TradeResult{
|
||||
Fee: makerFee,
|
||||
InToken: baseToken,
|
||||
InTotal: quantityToTrade,
|
||||
OutToken: quoteToken,
|
||||
OutTotal: makerOutTotal,
|
||||
},
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
263
XDCx/tradingstate/settle_balance_test.go
Normal file
263
XDCx/tradingstate/settle_balance_test.go
Normal file
|
|
@ -0,0 +1,263 @@
|
|||
package tradingstate
|
||||
|
||||
import (
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestGetSettleBalance(t *testing.T) {
|
||||
testToken := common.HexToAddress("0x0000000000000000000000000000000000000022")
|
||||
testFee, _ := new(big.Int).SetString("1000000000000000000", 10)
|
||||
tradeQuantity, _ := new(big.Int).SetString("1000000000000000000000", 10)
|
||||
tradeQuantityIncludedFee, _ := new(big.Int).SetString("1001000000000000000000", 10)
|
||||
tradeQuantityExcludedFee, _ := new(big.Int).SetString("999000000000000000000", 10)
|
||||
type GetSettleBalanceArg struct {
|
||||
quotePrice *big.Int
|
||||
takerSide string
|
||||
takerFeeRate *big.Int
|
||||
baseToken common.Address
|
||||
quoteToken common.Address
|
||||
makerPrice *big.Int
|
||||
makerFeeRate *big.Int
|
||||
baseTokenDecimal *big.Int
|
||||
quoteTokenDecimal *big.Int
|
||||
quantityToTrade *big.Int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args GetSettleBalanceArg
|
||||
want *SettleBalance
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"BUY tradeQuantity == fee",
|
||||
GetSettleBalanceArg{
|
||||
quotePrice: common.BasePrice,
|
||||
takerSide: Bid,
|
||||
takerFeeRate: big.NewInt(10000), // feeRate 100%
|
||||
baseToken: common.Address{},
|
||||
quoteToken: common.Address{},
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: big.NewInt(10000), // feeRate 100%
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quoteTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"BUY, quote is not XDC, makerFee <= 0.001 XDC",
|
||||
GetSettleBalanceArg{
|
||||
quotePrice: common.BasePrice,
|
||||
takerSide: Bid,
|
||||
takerFeeRate: big.NewInt(10), // feeRate 0.1%
|
||||
baseToken: testToken,
|
||||
quoteToken: common.HexToAddress("0x0000000000000000000000000000000000000002"),
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: big.NewInt(10), // feeRate 0.1%
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quoteTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1), common.BasePrice),
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"BUY, quote is not XDC, takerFee <= 0.001 XDC",
|
||||
GetSettleBalanceArg{
|
||||
quotePrice: common.BasePrice,
|
||||
takerSide: Bid,
|
||||
takerFeeRate: big.NewInt(5), // feeRate 0.05%
|
||||
baseToken: testToken,
|
||||
quoteToken: common.HexToAddress("0x0000000000000000000000000000000000000002"),
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: big.NewInt(10), // feeRate 0.1%
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quoteTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(2), common.BasePrice),
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"BUY, quote is XDC, makerFee <= 0.001 XDC",
|
||||
GetSettleBalanceArg{
|
||||
quotePrice: common.BasePrice,
|
||||
takerSide: Bid,
|
||||
takerFeeRate: big.NewInt(10), // feeRate 0.1%
|
||||
baseToken: testToken,
|
||||
quoteToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: big.NewInt(10), // feeRate 0.1%
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quoteTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1), common.BasePrice),
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"BUY, quote is XDC, takerFee <= 0.001 XDC",
|
||||
GetSettleBalanceArg{
|
||||
quotePrice: common.BasePrice,
|
||||
takerSide: Bid,
|
||||
takerFeeRate: big.NewInt(5), // feeRate 0.05%
|
||||
baseToken: testToken,
|
||||
quoteToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: big.NewInt(10), // feeRate 0.1%
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quoteTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(2), common.BasePrice),
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"BUY, no error",
|
||||
GetSettleBalanceArg{
|
||||
quotePrice: common.BasePrice,
|
||||
takerSide: Bid,
|
||||
takerFeeRate: big.NewInt(10), // feeRate 0.1%
|
||||
baseToken: testToken,
|
||||
quoteToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: big.NewInt(10), // feeRate 0.1%
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quoteTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
},
|
||||
&SettleBalance{
|
||||
Taker: TradeResult{Fee: testFee, InToken: testToken, InTotal: tradeQuantity, OutToken: common.HexToAddress(common.XDCNativeAddress), OutTotal: tradeQuantityIncludedFee},
|
||||
Maker: TradeResult{Fee: testFee, InToken: common.HexToAddress(common.XDCNativeAddress), InTotal: tradeQuantityExcludedFee, OutToken: testToken, OutTotal: tradeQuantity},
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"SELL tradeQuantity == fee",
|
||||
GetSettleBalanceArg{
|
||||
quotePrice: common.BasePrice,
|
||||
takerSide: Ask,
|
||||
takerFeeRate: big.NewInt(10000), // feeRate 100%
|
||||
baseToken: testToken,
|
||||
quoteToken: common.Address{},
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: big.NewInt(10000), // feeRate 100%
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quoteTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"SELL, quote is not XDC, makerFee <= 0.001 XDC",
|
||||
GetSettleBalanceArg{
|
||||
quotePrice: common.BasePrice,
|
||||
takerSide: Ask,
|
||||
takerFeeRate: big.NewInt(10), // feeRate 0.1%
|
||||
baseToken: testToken,
|
||||
quoteToken: common.HexToAddress("0x0000000000000000000000000000000000000002"),
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: big.NewInt(10), // feeRate 0.1%
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quoteTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1), common.BasePrice),
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"SELL, quote is not XDC, takerFee <= 0.001 XDC",
|
||||
GetSettleBalanceArg{
|
||||
quotePrice: common.BasePrice,
|
||||
takerSide: Ask,
|
||||
takerFeeRate: big.NewInt(5), // feeRate 0.05%
|
||||
baseToken: testToken,
|
||||
quoteToken: common.HexToAddress("0x0000000000000000000000000000000000000002"),
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: big.NewInt(10), // feeRate 0.1%
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quoteTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(2), common.BasePrice),
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"SELL, quote is XDC, makerFee <= 0.001 XDC",
|
||||
GetSettleBalanceArg{
|
||||
quotePrice: common.BasePrice,
|
||||
takerSide: Ask,
|
||||
takerFeeRate: big.NewInt(10), // feeRate 0.1%
|
||||
baseToken: testToken,
|
||||
quoteToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: big.NewInt(10), // feeRate 0.1%
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quoteTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1), common.BasePrice),
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"SELL, quote is XDC, takerFee <= 0.001 XDC",
|
||||
GetSettleBalanceArg{
|
||||
quotePrice: common.BasePrice,
|
||||
takerSide: Ask,
|
||||
takerFeeRate: big.NewInt(5), // feeRate 0.05%
|
||||
baseToken: testToken,
|
||||
quoteToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: big.NewInt(10), // feeRate 0.1%
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quoteTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(2), common.BasePrice),
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"SELL, no error",
|
||||
GetSettleBalanceArg{
|
||||
quotePrice: common.BasePrice,
|
||||
takerSide: Ask,
|
||||
takerFeeRate: big.NewInt(10), // feeRate 15%
|
||||
baseToken: testToken,
|
||||
quoteToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
makerPrice: common.BasePrice,
|
||||
makerFeeRate: big.NewInt(10), // feeRate 0.1%
|
||||
baseTokenDecimal: common.BasePrice,
|
||||
quoteTokenDecimal: common.BasePrice,
|
||||
quantityToTrade: new(big.Int).Mul(big.NewInt(1000), common.BasePrice),
|
||||
},
|
||||
&SettleBalance{
|
||||
Maker: TradeResult{Fee: testFee, InToken: testToken, InTotal: tradeQuantity, OutToken: common.HexToAddress(common.XDCNativeAddress), OutTotal: tradeQuantityIncludedFee},
|
||||
Taker: TradeResult{Fee: testFee, InToken: common.HexToAddress(common.XDCNativeAddress), InTotal: tradeQuantityExcludedFee, OutToken: testToken, OutTotal: tradeQuantity},
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := GetSettleBalance(tt.args.quotePrice, tt.args.takerSide, tt.args.takerFeeRate, tt.args.baseToken, tt.args.quoteToken, tt.args.makerPrice, tt.args.makerFeeRate, tt.args.baseTokenDecimal, tt.args.quoteTokenDecimal, tt.args.quantityToTrade)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetSettleBalance() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if tt.want != nil {
|
||||
t.Log(tt.want.String())
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("GetSettleBalance() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
215
XDCx/tradingstate/state_lendingbook.go
Normal file
215
XDCx/tradingstate/state_lendingbook.go
Normal file
|
|
@ -0,0 +1,215 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package tradingstate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
"github.com/XinFinOrg/XDPoSChain/trie"
|
||||
)
|
||||
|
||||
type stateLendingBook struct {
|
||||
price common.Hash
|
||||
orderBook common.Hash
|
||||
lendingBook common.Hash
|
||||
data orderList
|
||||
|
||||
// DB error.
|
||||
// State objects are used by the consensus core and VM which are
|
||||
// unable to deal with database-level errors. Any error that occurs
|
||||
// during a database read is memoized here and will eventually be returned
|
||||
// by TradingStateDB.Commit.
|
||||
dbErr error
|
||||
|
||||
// Write caches.
|
||||
trie Trie // storage trie, which becomes non-nil on first access
|
||||
|
||||
cachedStorage map[common.Hash]common.Hash
|
||||
dirtyStorage map[common.Hash]common.Hash
|
||||
|
||||
onDirty func(price common.Hash) // Callback method to mark a state object newly dirty
|
||||
}
|
||||
|
||||
func (s *stateLendingBook) empty() bool {
|
||||
return s.data.Volume == nil || s.data.Volume.Sign() == 0
|
||||
}
|
||||
|
||||
func newStateLendingBook(orderBook common.Hash, price common.Hash, lendingBook common.Hash, data orderList, onDirty func(price common.Hash)) *stateLendingBook {
|
||||
return &stateLendingBook{
|
||||
lendingBook: lendingBook,
|
||||
orderBook: orderBook,
|
||||
price: price,
|
||||
data: data,
|
||||
cachedStorage: make(map[common.Hash]common.Hash),
|
||||
dirtyStorage: make(map[common.Hash]common.Hash),
|
||||
onDirty: onDirty,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *stateLendingBook) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, self.data)
|
||||
}
|
||||
|
||||
func (self *stateLendingBook) setError(err error) {
|
||||
if self.dbErr == nil {
|
||||
self.dbErr = err
|
||||
}
|
||||
}
|
||||
|
||||
func (self *stateLendingBook) getTrie(db Database) Trie {
|
||||
if self.trie == nil {
|
||||
var err error
|
||||
self.trie, err = db.OpenStorageTrie(self.lendingBook, self.data.Root)
|
||||
if err != nil {
|
||||
self.trie, _ = db.OpenStorageTrie(self.price, EmptyHash)
|
||||
self.setError(fmt.Errorf("can't create storage trie: %v", err))
|
||||
}
|
||||
}
|
||||
return self.trie
|
||||
}
|
||||
|
||||
func (self *stateLendingBook) Exist(db Database, lendingId common.Hash) bool {
|
||||
amount, exists := self.cachedStorage[lendingId]
|
||||
if exists {
|
||||
return true
|
||||
}
|
||||
// Load from DB in case it is missing.
|
||||
enc, err := self.getTrie(db).TryGet(lendingId[:])
|
||||
if err != nil {
|
||||
self.setError(err)
|
||||
return false
|
||||
}
|
||||
if len(enc) > 0 {
|
||||
_, content, _, err := rlp.Split(enc)
|
||||
if err != nil {
|
||||
self.setError(err)
|
||||
}
|
||||
amount.SetBytes(content)
|
||||
}
|
||||
if (amount != common.Hash{}) {
|
||||
self.cachedStorage[lendingId] = amount
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *stateLendingBook) getAllTradeIds(db Database) []common.Hash {
|
||||
tradeIds := []common.Hash{}
|
||||
lendingBookTrie := self.getTrie(db)
|
||||
if lendingBookTrie == nil {
|
||||
return tradeIds
|
||||
}
|
||||
for id, value := range self.cachedStorage {
|
||||
if !common.EmptyHash(value) {
|
||||
tradeIds = append(tradeIds, id)
|
||||
}
|
||||
}
|
||||
orderListIt := trie.NewIterator(lendingBookTrie.NodeIterator(nil))
|
||||
for orderListIt.Next() {
|
||||
id := common.BytesToHash(orderListIt.Key)
|
||||
if _, exist := self.cachedStorage[id]; exist {
|
||||
continue
|
||||
}
|
||||
tradeIds = append(tradeIds, id)
|
||||
}
|
||||
return tradeIds
|
||||
}
|
||||
|
||||
func (self *stateLendingBook) insertTradingId(db Database, tradeId common.Hash) {
|
||||
self.setTradingId(tradeId, tradeId)
|
||||
self.setError(self.getTrie(db).TryUpdate(tradeId[:], tradeId[:]))
|
||||
}
|
||||
|
||||
func (self *stateLendingBook) removeTradingId(db Database, tradeId common.Hash) {
|
||||
tr := self.getTrie(db)
|
||||
self.setError(tr.TryDelete(tradeId[:]))
|
||||
self.setTradingId(tradeId, EmptyHash)
|
||||
}
|
||||
|
||||
func (self *stateLendingBook) setTradingId(tradeId common.Hash, value common.Hash) {
|
||||
self.cachedStorage[tradeId] = value
|
||||
self.dirtyStorage[tradeId] = value
|
||||
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.lendingBook)
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *stateLendingBook) updateTrie(db Database) Trie {
|
||||
tr := self.getTrie(db)
|
||||
for key, value := range self.dirtyStorage {
|
||||
delete(self.dirtyStorage, key)
|
||||
if value == EmptyHash {
|
||||
self.setError(tr.TryDelete(key[:]))
|
||||
continue
|
||||
}
|
||||
v, _ := rlp.EncodeToBytes(bytes.TrimLeft(value[:], "\x00"))
|
||||
self.setError(tr.TryUpdate(key[:], v))
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
func (self *stateLendingBook) updateRoot(db Database) error {
|
||||
self.updateTrie(db)
|
||||
if self.dbErr != nil {
|
||||
return self.dbErr
|
||||
}
|
||||
root, err := self.trie.Commit(nil)
|
||||
if err == nil {
|
||||
self.data.Root = root
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *stateLendingBook) deepCopy(db *TradingStateDB, onDirty func(price common.Hash)) *stateLendingBook {
|
||||
stateLendingBook := newStateLendingBook(self.lendingBook, self.orderBook, self.price, self.data, onDirty)
|
||||
if self.trie != nil {
|
||||
stateLendingBook.trie = db.db.CopyTrie(self.trie)
|
||||
}
|
||||
for key, value := range self.dirtyStorage {
|
||||
stateLendingBook.dirtyStorage[key] = value
|
||||
}
|
||||
for key, value := range self.cachedStorage {
|
||||
stateLendingBook.cachedStorage[key] = value
|
||||
}
|
||||
return stateLendingBook
|
||||
}
|
||||
|
||||
func (c *stateLendingBook) AddVolume(amount *big.Int) {
|
||||
c.setVolume(new(big.Int).Add(c.data.Volume, amount))
|
||||
}
|
||||
|
||||
func (c *stateLendingBook) subVolume(amount *big.Int) {
|
||||
c.setVolume(new(big.Int).Sub(c.data.Volume, amount))
|
||||
}
|
||||
|
||||
func (self *stateLendingBook) setVolume(volume *big.Int) {
|
||||
self.data.Volume = volume
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.lendingBook)
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *stateLendingBook) Volume() *big.Int {
|
||||
return self.data.Volume
|
||||
}
|
||||
235
XDCx/tradingstate/state_liquidationprice.go
Normal file
235
XDCx/tradingstate/state_liquidationprice.go
Normal file
|
|
@ -0,0 +1,235 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package tradingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
"github.com/XinFinOrg/XDPoSChain/trie"
|
||||
)
|
||||
|
||||
type liquidationPriceState struct {
|
||||
liquidationPrice common.Hash
|
||||
orderBook common.Hash
|
||||
data orderList
|
||||
db *TradingStateDB
|
||||
|
||||
// DB error.
|
||||
// State objects are used by the consensus core and VM which are
|
||||
// unable to deal with database-level errors. Any error that occurs
|
||||
// during a database read is memoized here and will eventually be returned
|
||||
// by TradingStateDB.Commit.
|
||||
dbErr error
|
||||
|
||||
// Write caches.
|
||||
trie Trie // storage trie, which becomes non-nil on first access
|
||||
|
||||
stateLendingBooks map[common.Hash]*stateLendingBook
|
||||
stateLendingBooksDirty map[common.Hash]struct{}
|
||||
|
||||
onDirty func(price common.Hash) // Callback method to mark a state object newly dirty
|
||||
}
|
||||
|
||||
// empty returns whether the orderId is considered empty.
|
||||
func (s *liquidationPriceState) empty() bool {
|
||||
return s.data.Volume == nil || s.data.Volume.Sign() == 0
|
||||
}
|
||||
|
||||
// newObject creates a state object.
|
||||
func newLiquidationPriceState(db *TradingStateDB, orderBook common.Hash, price common.Hash, data orderList, onDirty func(price common.Hash)) *liquidationPriceState {
|
||||
return &liquidationPriceState{
|
||||
db: db,
|
||||
orderBook: orderBook,
|
||||
liquidationPrice: price,
|
||||
data: data,
|
||||
stateLendingBooks: make(map[common.Hash]*stateLendingBook),
|
||||
stateLendingBooksDirty: make(map[common.Hash]struct{}),
|
||||
onDirty: onDirty,
|
||||
}
|
||||
}
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (c *liquidationPriceState) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, c.data)
|
||||
}
|
||||
|
||||
// setError remembers the first non-nil error it is called with.
|
||||
func (self *liquidationPriceState) setError(err error) {
|
||||
if self.dbErr == nil {
|
||||
self.dbErr = err
|
||||
}
|
||||
}
|
||||
|
||||
func (self *liquidationPriceState) MarkStateLendingBookDirty(price common.Hash) {
|
||||
self.stateLendingBooksDirty[price] = struct{}{}
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.liquidationPrice)
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *liquidationPriceState) createLendingBook(db Database, lendingBook common.Hash) (newobj *stateLendingBook) {
|
||||
newobj = newStateLendingBook(self.orderBook, self.liquidationPrice, lendingBook, orderList{Volume: Zero}, self.MarkStateLendingBookDirty)
|
||||
self.stateLendingBooks[lendingBook] = newobj
|
||||
self.stateLendingBooksDirty[lendingBook] = struct{}{}
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.liquidationPrice)
|
||||
self.onDirty = nil
|
||||
}
|
||||
return newobj
|
||||
}
|
||||
|
||||
func (self *liquidationPriceState) getTrie(db Database) Trie {
|
||||
if self.trie == nil {
|
||||
var err error
|
||||
self.trie, err = db.OpenStorageTrie(self.liquidationPrice, self.data.Root)
|
||||
if err != nil {
|
||||
self.trie, _ = db.OpenStorageTrie(self.liquidationPrice, EmptyHash)
|
||||
self.setError(fmt.Errorf("can't create storage trie: %v", err))
|
||||
}
|
||||
}
|
||||
return self.trie
|
||||
}
|
||||
|
||||
func (self *liquidationPriceState) updateTrie(db Database) Trie {
|
||||
tr := self.getTrie(db)
|
||||
for lendingId, stateObject := range self.stateLendingBooks {
|
||||
delete(self.stateLendingBooksDirty, lendingId)
|
||||
if stateObject.empty() {
|
||||
self.setError(tr.TryDelete(lendingId[:]))
|
||||
continue
|
||||
}
|
||||
stateObject.updateRoot(db)
|
||||
// Encoding []byte cannot fail, ok to ignore the error.
|
||||
v, _ := rlp.EncodeToBytes(stateObject)
|
||||
self.setError(tr.TryUpdate(lendingId[:], v))
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
func (self *liquidationPriceState) updateRoot(db Database) error {
|
||||
self.updateTrie(db)
|
||||
if self.dbErr != nil {
|
||||
return self.dbErr
|
||||
}
|
||||
root, err := self.trie.Commit(func(leaf []byte, parent common.Hash) error {
|
||||
var orderList orderList
|
||||
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
|
||||
return nil
|
||||
}
|
||||
if orderList.Root != EmptyRoot {
|
||||
db.TrieDB().Reference(orderList.Root, parent)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err == nil {
|
||||
self.data.Root = root
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *liquidationPriceState) deepCopy(db *TradingStateDB, onDirty func(liquidationPrice common.Hash)) *liquidationPriceState {
|
||||
stateOrderList := newLiquidationPriceState(db, self.orderBook, self.liquidationPrice, self.data, onDirty)
|
||||
if self.trie != nil {
|
||||
stateOrderList.trie = db.db.CopyTrie(self.trie)
|
||||
}
|
||||
for key, value := range self.stateLendingBooks {
|
||||
stateOrderList.stateLendingBooks[key] = value.deepCopy(db, self.MarkStateLendingBookDirty)
|
||||
}
|
||||
for key, value := range self.stateLendingBooksDirty {
|
||||
stateOrderList.stateLendingBooksDirty[key] = value
|
||||
}
|
||||
return stateOrderList
|
||||
}
|
||||
|
||||
// Retrieve a state object given my the address. Returns nil if not found.
|
||||
func (self *liquidationPriceState) getStateLendingBook(db Database, lendingBook common.Hash) (stateObject *stateLendingBook) {
|
||||
// Prefer 'live' objects.
|
||||
if obj := self.stateLendingBooks[lendingBook]; obj != nil {
|
||||
return obj
|
||||
}
|
||||
|
||||
// Load the object from the database.
|
||||
enc, err := self.getTrie(db).TryGet(lendingBook[:])
|
||||
if len(enc) == 0 {
|
||||
self.setError(err)
|
||||
return nil
|
||||
}
|
||||
var data orderList
|
||||
if err := rlp.DecodeBytes(enc, &data); err != nil {
|
||||
log.Error("Failed to decode state lending book ", "orderbook", self.orderBook, "liquidation price", self.liquidationPrice, "lendingBook", lendingBook, "err", err)
|
||||
return nil
|
||||
}
|
||||
// Insert into the live set.
|
||||
obj := newStateLendingBook(self.orderBook, self.liquidationPrice, lendingBook, data, self.MarkStateLendingBookDirty)
|
||||
self.stateLendingBooks[lendingBook] = obj
|
||||
return obj
|
||||
}
|
||||
|
||||
func (self *liquidationPriceState) getAllLiquidationData(db Database) map[common.Hash][]common.Hash {
|
||||
liquidationData := map[common.Hash][]common.Hash{}
|
||||
lendingBookTrie := self.getTrie(db)
|
||||
if lendingBookTrie == nil {
|
||||
return liquidationData
|
||||
}
|
||||
lendingBooks := []common.Hash{}
|
||||
for id, stateLendingBook := range self.stateLendingBooks {
|
||||
if !stateLendingBook.empty() {
|
||||
lendingBooks = append(lendingBooks, id)
|
||||
}
|
||||
}
|
||||
lendingBookListIt := trie.NewIterator(lendingBookTrie.NodeIterator(nil))
|
||||
for lendingBookListIt.Next() {
|
||||
id := common.BytesToHash(lendingBookListIt.Key)
|
||||
if _, exist := self.stateLendingBooks[id]; exist {
|
||||
continue
|
||||
}
|
||||
lendingBooks = append(lendingBooks, id)
|
||||
}
|
||||
for _, lendingBook := range lendingBooks {
|
||||
stateLendingBook := self.getStateLendingBook(db, lendingBook)
|
||||
if stateLendingBook != nil {
|
||||
liquidationData[lendingBook] = stateLendingBook.getAllTradeIds(db)
|
||||
}
|
||||
}
|
||||
return liquidationData
|
||||
}
|
||||
|
||||
func (c *liquidationPriceState) AddVolume(amount *big.Int) {
|
||||
c.setVolume(new(big.Int).Add(c.data.Volume, amount))
|
||||
}
|
||||
|
||||
func (c *liquidationPriceState) subVolume(amount *big.Int) {
|
||||
c.setVolume(new(big.Int).Sub(c.data.Volume, amount))
|
||||
}
|
||||
|
||||
func (self *liquidationPriceState) setVolume(volume *big.Int) {
|
||||
self.data.Volume = volume
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.liquidationPrice)
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *liquidationPriceState) Volume() *big.Int {
|
||||
return self.data.Volume
|
||||
}
|
||||
75
XDCx/tradingstate/state_orderItem.go
Normal file
75
XDCx/tradingstate/state_orderItem.go
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package tradingstate
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
)
|
||||
|
||||
// stateObject represents an Ethereum orderId which is being modified.
|
||||
//
|
||||
// The usage pattern is as follows:
|
||||
// First you need to obtain a state object.
|
||||
// tradingExchangeObject values can be accessed and modified through the object.
|
||||
// Finally, call CommitAskTrie to write the modified storage trie into a database.
|
||||
type stateOrderItem struct {
|
||||
orderBook common.Hash
|
||||
orderId common.Hash
|
||||
data OrderItem
|
||||
onDirty func(orderId common.Hash) // Callback method to mark a state object newly dirty
|
||||
}
|
||||
|
||||
// empty returns whether the orderId is considered empty.
|
||||
func (s *stateOrderItem) empty() bool {
|
||||
return s.data.Quantity == nil || s.data.Quantity.Cmp(Zero) == 0
|
||||
}
|
||||
|
||||
// newObject creates a state object.
|
||||
func newStateOrderItem(orderBook common.Hash, orderId common.Hash, data OrderItem, onDirty func(orderId common.Hash)) *stateOrderItem {
|
||||
return &stateOrderItem{
|
||||
orderBook: orderBook,
|
||||
orderId: orderId,
|
||||
data: data,
|
||||
onDirty: onDirty,
|
||||
}
|
||||
}
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (c *stateOrderItem) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, c.data)
|
||||
}
|
||||
|
||||
func (self *stateOrderItem) deepCopy(onDirty func(orderId common.Hash)) *stateOrderItem {
|
||||
stateOrderList := newStateOrderItem(self.orderBook, self.orderId, self.data, onDirty)
|
||||
return stateOrderList
|
||||
}
|
||||
|
||||
func (self *stateOrderItem) setVolume(volume *big.Int) {
|
||||
self.data.Quantity = volume
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.orderId)
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *stateOrderItem) Quantity() *big.Int {
|
||||
return self.data.Quantity
|
||||
}
|
||||
218
XDCx/tradingstate/state_orderList.go
Normal file
218
XDCx/tradingstate/state_orderList.go
Normal file
|
|
@ -0,0 +1,218 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package tradingstate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
)
|
||||
|
||||
// stateObject represents an Ethereum orderId which is being modified.
|
||||
//
|
||||
// The usage pattern is as follows:
|
||||
// First you need to obtain a state object.
|
||||
// tradingExchangeObject values can be accessed and modified through the object.
|
||||
// Finally, call CommitAskTrie to write the modified storage trie into a database.
|
||||
type stateOrderList struct {
|
||||
price common.Hash
|
||||
orderBook common.Hash
|
||||
orderType string
|
||||
data orderList
|
||||
db *TradingStateDB
|
||||
|
||||
// DB error.
|
||||
// State objects are used by the consensus core and VM which are
|
||||
// unable to deal with database-level errors. Any error that occurs
|
||||
// during a database read is memoized here and will eventually be returned
|
||||
// by TradingStateDB.Commit.
|
||||
dbErr error
|
||||
|
||||
// Write caches.
|
||||
trie Trie // storage trie, which becomes non-nil on first access
|
||||
|
||||
cachedStorage map[common.Hash]common.Hash // Storage entry cache to avoid duplicate reads
|
||||
dirtyStorage map[common.Hash]common.Hash // Storage entries that need to be flushed to disk
|
||||
|
||||
onDirty func(price common.Hash) // Callback method to mark a state object newly dirty
|
||||
}
|
||||
|
||||
// empty returns whether the orderId is considered empty.
|
||||
func (s *stateOrderList) empty() bool {
|
||||
return s.data.Volume == nil || s.data.Volume.Cmp(Zero) == 0
|
||||
}
|
||||
|
||||
// newObject creates a state object.
|
||||
func newStateOrderList(db *TradingStateDB, orderType string, orderBook common.Hash, price common.Hash, data orderList, onDirty func(price common.Hash)) *stateOrderList {
|
||||
return &stateOrderList{
|
||||
db: db,
|
||||
orderType: orderType,
|
||||
orderBook: orderBook,
|
||||
price: price,
|
||||
data: data,
|
||||
cachedStorage: make(map[common.Hash]common.Hash),
|
||||
dirtyStorage: make(map[common.Hash]common.Hash),
|
||||
onDirty: onDirty,
|
||||
}
|
||||
}
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (c *stateOrderList) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, c.data)
|
||||
}
|
||||
|
||||
// setError remembers the first non-nil error it is called with.
|
||||
func (self *stateOrderList) setError(err error) {
|
||||
if self.dbErr == nil {
|
||||
self.dbErr = err
|
||||
}
|
||||
}
|
||||
|
||||
func (c *stateOrderList) getTrie(db Database) Trie {
|
||||
if c.trie == nil {
|
||||
var err error
|
||||
c.trie, err = db.OpenStorageTrie(c.price, c.data.Root)
|
||||
if err != nil {
|
||||
c.trie, _ = db.OpenStorageTrie(c.price, EmptyHash)
|
||||
c.setError(fmt.Errorf("can't create storage trie: %v", err))
|
||||
}
|
||||
}
|
||||
return c.trie
|
||||
}
|
||||
|
||||
// GetState returns a value in orderId storage.
|
||||
func (self *stateOrderList) GetOrderAmount(db Database, orderId common.Hash) common.Hash {
|
||||
amount, exists := self.cachedStorage[orderId]
|
||||
if exists {
|
||||
return amount
|
||||
}
|
||||
// Load from DB in case it is missing.
|
||||
enc, err := self.getTrie(db).TryGet(orderId[:])
|
||||
if err != nil {
|
||||
self.setError(err)
|
||||
return EmptyHash
|
||||
}
|
||||
if len(enc) > 0 {
|
||||
_, content, _, err := rlp.Split(enc)
|
||||
if err != nil {
|
||||
self.setError(err)
|
||||
}
|
||||
amount.SetBytes(content)
|
||||
}
|
||||
if (amount != common.Hash{}) {
|
||||
self.cachedStorage[orderId] = amount
|
||||
}
|
||||
return amount
|
||||
}
|
||||
|
||||
// SetState updates a value in orderId storage.
|
||||
func (self *stateOrderList) insertOrderItem(db Database, orderId common.Hash, amount common.Hash) {
|
||||
self.setOrderItem(orderId, amount)
|
||||
self.setError(self.getTrie(db).TryUpdate(orderId[:], amount[:]))
|
||||
}
|
||||
|
||||
// SetState updates a value in orderId storage.
|
||||
func (self *stateOrderList) removeOrderItem(db Database, orderId common.Hash) {
|
||||
tr := self.getTrie(db)
|
||||
self.setError(tr.TryDelete(orderId[:]))
|
||||
self.setOrderItem(orderId, EmptyHash)
|
||||
}
|
||||
|
||||
func (self *stateOrderList) setOrderItem(orderId common.Hash, amount common.Hash) {
|
||||
self.cachedStorage[orderId] = amount
|
||||
self.dirtyStorage[orderId] = amount
|
||||
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Price())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
// updateAskTrie writes cached storage modifications into the object's storage trie.
|
||||
func (self *stateOrderList) updateTrie(db Database) Trie {
|
||||
tr := self.getTrie(db)
|
||||
for orderId, amount := range self.dirtyStorage {
|
||||
delete(self.dirtyStorage, orderId)
|
||||
if amount == EmptyHash {
|
||||
self.setError(tr.TryDelete(orderId[:]))
|
||||
continue
|
||||
}
|
||||
v, _ := rlp.EncodeToBytes(bytes.TrimLeft(amount[:], "\x00"))
|
||||
self.setError(tr.TryUpdate(orderId[:], v))
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
// UpdateRoot sets the trie root to the current root orderId of
|
||||
func (self *stateOrderList) updateRoot(db Database) error {
|
||||
self.updateTrie(db)
|
||||
if self.dbErr != nil {
|
||||
return self.dbErr
|
||||
}
|
||||
root, err := self.trie.Commit(nil)
|
||||
if err == nil {
|
||||
self.data.Root = root
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *stateOrderList) deepCopy(db *TradingStateDB, onDirty func(price common.Hash)) *stateOrderList {
|
||||
stateOrderList := newStateOrderList(db, self.orderType, self.orderBook, self.price, self.data, onDirty)
|
||||
if self.trie != nil {
|
||||
stateOrderList.trie = db.db.CopyTrie(self.trie)
|
||||
}
|
||||
for orderId, amount := range self.dirtyStorage {
|
||||
stateOrderList.dirtyStorage[orderId] = amount
|
||||
}
|
||||
for orderId, amount := range self.cachedStorage {
|
||||
stateOrderList.cachedStorage[orderId] = amount
|
||||
}
|
||||
return stateOrderList
|
||||
}
|
||||
|
||||
// AddVolume removes amount from c's balance.
|
||||
// It is used to add funds to the destination exchanges of a transfer.
|
||||
func (c *stateOrderList) AddVolume(amount *big.Int) {
|
||||
c.setVolume(new(big.Int).Add(c.data.Volume, amount))
|
||||
}
|
||||
|
||||
// AddVolume removes amount from c's balance.
|
||||
// It is used to add funds to the destination exchanges of a transfer.
|
||||
func (c *stateOrderList) subVolume(amount *big.Int) {
|
||||
c.setVolume(new(big.Int).Sub(c.data.Volume, amount))
|
||||
}
|
||||
|
||||
func (self *stateOrderList) setVolume(volume *big.Int) {
|
||||
self.data.Volume = volume
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.price)
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
// Returns the address of the contract/orderId
|
||||
func (c *stateOrderList) Price() common.Hash {
|
||||
return c.price
|
||||
}
|
||||
|
||||
func (self *stateOrderList) Volume() *big.Int {
|
||||
return self.data.Volume
|
||||
}
|
||||
805
XDCx/tradingstate/state_orderbook.go
Normal file
805
XDCx/tradingstate/state_orderbook.go
Normal file
|
|
@ -0,0 +1,805 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package tradingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
)
|
||||
|
||||
// stateObject represents an Ethereum orderId which is being modified.
|
||||
//
|
||||
// The usage pattern is as follows:
|
||||
// First you need to obtain a state object.
|
||||
// tradingExchangeObject values can be accessed and modified through the object.
|
||||
// Finally, call CommitAskTrie to write the modified storage trie into a database.
|
||||
type tradingExchanges struct {
|
||||
orderBookHash common.Hash
|
||||
data tradingExchangeObject
|
||||
db *TradingStateDB
|
||||
|
||||
// DB error.
|
||||
// State objects are used by the consensus core and VM which are
|
||||
// unable to deal with database-level errors. Any error that occurs
|
||||
// during a database read is memoized here and will eventually be returned
|
||||
// by TradingStateDB.Commit.
|
||||
dbErr error
|
||||
|
||||
// Write caches.
|
||||
asksTrie Trie // storage trie, which becomes non-nil on first access
|
||||
bidsTrie Trie // storage trie, which becomes non-nil on first access
|
||||
ordersTrie Trie // storage trie, which becomes non-nil on first access
|
||||
liquidationPriceTrie Trie
|
||||
|
||||
stateAskObjects map[common.Hash]*stateOrderList
|
||||
stateAskObjectsDirty map[common.Hash]struct{}
|
||||
|
||||
stateBidObjects map[common.Hash]*stateOrderList
|
||||
stateBidObjectsDirty map[common.Hash]struct{}
|
||||
|
||||
stateOrderObjects map[common.Hash]*stateOrderItem
|
||||
stateOrderObjectsDirty map[common.Hash]struct{}
|
||||
|
||||
liquidationPriceStates map[common.Hash]*liquidationPriceState
|
||||
liquidationPriceStatesDirty map[common.Hash]struct{}
|
||||
|
||||
onDirty func(hash common.Hash) // Callback method to mark a state object newly dirty
|
||||
}
|
||||
|
||||
// empty returns whether the orderId is considered empty.
|
||||
func (s *tradingExchanges) empty() bool {
|
||||
if s.data.Nonce != 0 {
|
||||
return false
|
||||
}
|
||||
if s.data.LendingCount != nil && s.data.LendingCount.Sign() > 0 {
|
||||
return false
|
||||
}
|
||||
if s.data.LastPrice != nil && s.data.LastPrice.Sign() > 0 {
|
||||
return false
|
||||
}
|
||||
if s.data.MediumPrice != nil && s.data.MediumPrice.Sign() > 0 {
|
||||
return false
|
||||
}
|
||||
if s.data.MediumPriceBeforeEpoch != nil && s.data.MediumPriceBeforeEpoch.Sign() > 0 {
|
||||
return false
|
||||
}
|
||||
if s.data.TotalQuantity != nil && s.data.TotalQuantity.Sign() > 0 {
|
||||
return false
|
||||
}
|
||||
if !common.EmptyHash(s.data.AskRoot) {
|
||||
return false
|
||||
}
|
||||
if !common.EmptyHash(s.data.BidRoot) {
|
||||
return false
|
||||
}
|
||||
if !common.EmptyHash(s.data.OrderRoot) {
|
||||
return false
|
||||
}
|
||||
if !common.EmptyHash(s.data.LiquidationPriceRoot) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// newObject creates a state object.
|
||||
func newStateExchanges(db *TradingStateDB, hash common.Hash, data tradingExchangeObject, onDirty func(addr common.Hash)) *tradingExchanges {
|
||||
return &tradingExchanges{
|
||||
db: db,
|
||||
orderBookHash: hash,
|
||||
data: data,
|
||||
stateAskObjects: make(map[common.Hash]*stateOrderList),
|
||||
stateBidObjects: make(map[common.Hash]*stateOrderList),
|
||||
stateOrderObjects: make(map[common.Hash]*stateOrderItem),
|
||||
liquidationPriceStates: make(map[common.Hash]*liquidationPriceState),
|
||||
stateAskObjectsDirty: make(map[common.Hash]struct{}),
|
||||
stateBidObjectsDirty: make(map[common.Hash]struct{}),
|
||||
stateOrderObjectsDirty: make(map[common.Hash]struct{}),
|
||||
liquidationPriceStatesDirty: make(map[common.Hash]struct{}),
|
||||
onDirty: onDirty,
|
||||
}
|
||||
}
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (c *tradingExchanges) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, c.data)
|
||||
}
|
||||
|
||||
// setError remembers the first non-nil error it is called with.
|
||||
func (self *tradingExchanges) setError(err error) {
|
||||
if self.dbErr == nil {
|
||||
self.dbErr = err
|
||||
}
|
||||
}
|
||||
|
||||
func (c *tradingExchanges) getAsksTrie(db Database) Trie {
|
||||
if c.asksTrie == nil {
|
||||
var err error
|
||||
c.asksTrie, err = db.OpenStorageTrie(c.orderBookHash, c.data.AskRoot)
|
||||
if err != nil {
|
||||
c.asksTrie, _ = db.OpenStorageTrie(c.orderBookHash, EmptyHash)
|
||||
c.setError(fmt.Errorf("can't create asks trie: %v", err))
|
||||
}
|
||||
}
|
||||
return c.asksTrie
|
||||
}
|
||||
|
||||
func (c *tradingExchanges) getOrdersTrie(db Database) Trie {
|
||||
if c.ordersTrie == nil {
|
||||
var err error
|
||||
c.ordersTrie, err = db.OpenStorageTrie(c.orderBookHash, c.data.OrderRoot)
|
||||
if err != nil {
|
||||
c.ordersTrie, _ = db.OpenStorageTrie(c.orderBookHash, EmptyHash)
|
||||
c.setError(fmt.Errorf("can't create asks trie: %v", err))
|
||||
}
|
||||
}
|
||||
return c.ordersTrie
|
||||
}
|
||||
|
||||
func (c *tradingExchanges) getBestPriceAsksTrie(db Database) common.Hash {
|
||||
trie := c.getAsksTrie(db)
|
||||
encKey, encValue, err := trie.TryGetBestLeftKeyAndValue()
|
||||
if err != nil {
|
||||
log.Error("Failed find best price ask trie ", "orderbook", c.orderBookHash.Hex())
|
||||
return EmptyHash
|
||||
}
|
||||
if len(encKey) == 0 || len(encValue) == 0 {
|
||||
log.Debug("Not found get best ask trie", "encKey", encKey, "encValue", encValue)
|
||||
return EmptyHash
|
||||
}
|
||||
price := common.BytesToHash(encKey)
|
||||
if _, exit := c.stateAskObjects[price]; !exit {
|
||||
var data orderList
|
||||
if err := rlp.DecodeBytes(encValue, &data); err != nil {
|
||||
log.Error("Failed to decode state get best ask trie", "err", err)
|
||||
return EmptyHash
|
||||
}
|
||||
obj := newStateOrderList(c.db, Bid, c.orderBookHash, price, data, c.MarkStateAskObjectDirty)
|
||||
c.stateAskObjects[price] = obj
|
||||
}
|
||||
return common.BytesToHash(encKey)
|
||||
}
|
||||
|
||||
func (c *tradingExchanges) getBestBidsTrie(db Database) common.Hash {
|
||||
trie := c.getBidsTrie(db)
|
||||
encKey, encValue, err := trie.TryGetBestRightKeyAndValue()
|
||||
if err != nil {
|
||||
log.Error("Failed find best price bid trie ", "orderbook", c.orderBookHash.Hex())
|
||||
return EmptyHash
|
||||
}
|
||||
if len(encKey) == 0 || len(encValue) == 0 {
|
||||
log.Debug("Not found get best bid trie", "encKey", encKey, "encValue", encValue)
|
||||
return EmptyHash
|
||||
}
|
||||
price := common.BytesToHash(encKey)
|
||||
if _, exit := c.stateBidObjects[price]; !exit {
|
||||
var data orderList
|
||||
if err := rlp.DecodeBytes(encValue, &data); err != nil {
|
||||
log.Error("Failed to decode state get best bid trie", "err", err)
|
||||
return EmptyHash
|
||||
}
|
||||
// Insert into the live set.
|
||||
obj := newStateOrderList(c.db, Bid, c.orderBookHash, price, data, c.MarkStateBidObjectDirty)
|
||||
c.stateBidObjects[price] = obj
|
||||
}
|
||||
return common.BytesToHash(encKey)
|
||||
}
|
||||
|
||||
// updateAskTrie writes cached storage modifications into the object's storage trie.
|
||||
func (self *tradingExchanges) updateAsksTrie(db Database) Trie {
|
||||
tr := self.getAsksTrie(db)
|
||||
for price, orderList := range self.stateAskObjects {
|
||||
if _, isDirty := self.stateAskObjectsDirty[price]; isDirty {
|
||||
delete(self.stateAskObjectsDirty, price)
|
||||
if orderList.empty() {
|
||||
self.setError(tr.TryDelete(price[:]))
|
||||
continue
|
||||
}
|
||||
orderList.updateRoot(db)
|
||||
// Encoding []byte cannot fail, ok to ignore the error.
|
||||
v, _ := rlp.EncodeToBytes(orderList)
|
||||
self.setError(tr.TryUpdate(price[:], v))
|
||||
}
|
||||
}
|
||||
|
||||
return tr
|
||||
}
|
||||
|
||||
// CommitAskTrie the storage trie of the object to dwb.
|
||||
// This updates the trie root.
|
||||
func (self *tradingExchanges) updateAsksRoot(db Database) error {
|
||||
self.updateAsksTrie(db)
|
||||
if self.dbErr != nil {
|
||||
return self.dbErr
|
||||
}
|
||||
self.data.AskRoot = self.asksTrie.Hash()
|
||||
return nil
|
||||
}
|
||||
|
||||
// CommitAskTrie the storage trie of the object to dwb.
|
||||
// This updates the trie root.
|
||||
func (self *tradingExchanges) CommitAsksTrie(db Database) error {
|
||||
self.updateAsksTrie(db)
|
||||
if self.dbErr != nil {
|
||||
return self.dbErr
|
||||
}
|
||||
root, err := self.asksTrie.Commit(func(leaf []byte, parent common.Hash) error {
|
||||
var orderList orderList
|
||||
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
|
||||
return nil
|
||||
}
|
||||
if orderList.Root != EmptyRoot {
|
||||
db.TrieDB().Reference(orderList.Root, parent)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err == nil {
|
||||
self.data.AskRoot = root
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *tradingExchanges) getBidsTrie(db Database) Trie {
|
||||
if c.bidsTrie == nil {
|
||||
var err error
|
||||
c.bidsTrie, err = db.OpenStorageTrie(c.orderBookHash, c.data.BidRoot)
|
||||
if err != nil {
|
||||
c.bidsTrie, _ = db.OpenStorageTrie(c.orderBookHash, EmptyHash)
|
||||
c.setError(fmt.Errorf("can't create bids trie: %v", err))
|
||||
}
|
||||
}
|
||||
return c.bidsTrie
|
||||
}
|
||||
|
||||
// updateAskTrie writes cached storage modifications into the object's storage trie.
|
||||
func (self *tradingExchanges) updateBidsTrie(db Database) Trie {
|
||||
tr := self.getBidsTrie(db)
|
||||
for price, orderList := range self.stateBidObjects {
|
||||
if _, isDirty := self.stateBidObjectsDirty[price]; isDirty {
|
||||
delete(self.stateBidObjectsDirty, price)
|
||||
if orderList.empty() {
|
||||
self.setError(tr.TryDelete(price[:]))
|
||||
continue
|
||||
}
|
||||
orderList.updateRoot(db)
|
||||
// Encoding []byte cannot fail, ok to ignore the error.
|
||||
v, _ := rlp.EncodeToBytes(orderList)
|
||||
self.setError(tr.TryUpdate(price[:], v))
|
||||
}
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
func (self *tradingExchanges) updateBidsRoot(db Database) {
|
||||
self.updateBidsTrie(db)
|
||||
self.data.BidRoot = self.bidsTrie.Hash()
|
||||
}
|
||||
|
||||
// CommitAskTrie the storage trie of the object to dwb.
|
||||
// This updates the trie root.
|
||||
func (self *tradingExchanges) CommitBidsTrie(db Database) error {
|
||||
self.updateBidsTrie(db)
|
||||
if self.dbErr != nil {
|
||||
return self.dbErr
|
||||
}
|
||||
root, err := self.bidsTrie.Commit(func(leaf []byte, parent common.Hash) error {
|
||||
var orderList orderList
|
||||
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
|
||||
return nil
|
||||
}
|
||||
if orderList.Root != EmptyRoot {
|
||||
db.TrieDB().Reference(orderList.Root, parent)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err == nil {
|
||||
self.data.BidRoot = root
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *tradingExchanges) deepCopy(db *TradingStateDB, onDirty func(hash common.Hash)) *tradingExchanges {
|
||||
stateExchanges := newStateExchanges(db, self.orderBookHash, self.data, onDirty)
|
||||
if self.asksTrie != nil {
|
||||
stateExchanges.asksTrie = db.db.CopyTrie(self.asksTrie)
|
||||
}
|
||||
if self.bidsTrie != nil {
|
||||
stateExchanges.bidsTrie = db.db.CopyTrie(self.bidsTrie)
|
||||
}
|
||||
if self.ordersTrie != nil {
|
||||
stateExchanges.ordersTrie = db.db.CopyTrie(self.ordersTrie)
|
||||
}
|
||||
for price, bidObject := range self.stateBidObjects {
|
||||
stateExchanges.stateBidObjects[price] = bidObject.deepCopy(db, self.MarkStateBidObjectDirty)
|
||||
}
|
||||
for price := range self.stateBidObjectsDirty {
|
||||
stateExchanges.stateBidObjectsDirty[price] = struct{}{}
|
||||
}
|
||||
for price, askObject := range self.stateAskObjects {
|
||||
stateExchanges.stateAskObjects[price] = askObject.deepCopy(db, self.MarkStateAskObjectDirty)
|
||||
}
|
||||
for price := range self.stateAskObjectsDirty {
|
||||
stateExchanges.stateAskObjectsDirty[price] = struct{}{}
|
||||
}
|
||||
for orderId, orderItem := range self.stateOrderObjects {
|
||||
stateExchanges.stateOrderObjects[orderId] = orderItem.deepCopy(self.MarkStateOrderObjectDirty)
|
||||
}
|
||||
for orderId := range self.stateOrderObjectsDirty {
|
||||
stateExchanges.stateOrderObjectsDirty[orderId] = struct{}{}
|
||||
}
|
||||
for price, liquidationPrice := range self.liquidationPriceStates {
|
||||
stateExchanges.liquidationPriceStates[price] = liquidationPrice.deepCopy(db, self.MarkStateLiquidationPriceDirty)
|
||||
}
|
||||
for price := range self.liquidationPriceStatesDirty {
|
||||
stateExchanges.liquidationPriceStatesDirty[price] = struct{}{}
|
||||
}
|
||||
return stateExchanges
|
||||
}
|
||||
|
||||
// Returns the address of the contract/orderId
|
||||
func (c *tradingExchanges) Hash() common.Hash {
|
||||
return c.orderBookHash
|
||||
}
|
||||
|
||||
func (self *tradingExchanges) SetNonce(nonce uint64) {
|
||||
self.setNonce(nonce)
|
||||
}
|
||||
|
||||
func (self *tradingExchanges) setNonce(nonce uint64) {
|
||||
self.data.Nonce = nonce
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *tradingExchanges) Nonce() uint64 {
|
||||
return self.data.Nonce
|
||||
}
|
||||
|
||||
func (self *tradingExchanges) setLastPrice(price *big.Int) {
|
||||
self.data.LastPrice = price
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *tradingExchanges) setMediumPriceBeforeEpoch(price *big.Int) {
|
||||
self.data.MediumPriceBeforeEpoch = price
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *tradingExchanges) setMediumPrice(price *big.Int, quantity *big.Int) {
|
||||
self.data.MediumPrice = price
|
||||
self.data.TotalQuantity = quantity
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
// updateStateExchangeObject writes the given object to the trie.
|
||||
func (self *tradingExchanges) removeStateOrderListAskObject(db Database, stateOrderList *stateOrderList) {
|
||||
self.setError(self.asksTrie.TryDelete(stateOrderList.price[:]))
|
||||
}
|
||||
|
||||
// updateStateExchangeObject writes the given object to the trie.
|
||||
func (self *tradingExchanges) removeStateOrderListBidObject(db Database, stateOrderList *stateOrderList) {
|
||||
self.setError(self.bidsTrie.TryDelete(stateOrderList.price[:]))
|
||||
}
|
||||
|
||||
// Retrieve a state object given my the address. Returns nil if not found.
|
||||
func (self *tradingExchanges) getStateOrderListAskObject(db Database, price common.Hash) (stateOrderList *stateOrderList) {
|
||||
// Prefer 'live' objects.
|
||||
if obj := self.stateAskObjects[price]; obj != nil {
|
||||
return obj
|
||||
}
|
||||
|
||||
// Load the object from the database.
|
||||
enc, err := self.getAsksTrie(db).TryGet(price[:])
|
||||
if len(enc) == 0 {
|
||||
self.setError(err)
|
||||
return nil
|
||||
}
|
||||
var data orderList
|
||||
if err := rlp.DecodeBytes(enc, &data); err != nil {
|
||||
log.Error("Failed to decode state order list object", "orderId", price, "err", err)
|
||||
return nil
|
||||
}
|
||||
// Insert into the live set.
|
||||
obj := newStateOrderList(self.db, Bid, self.orderBookHash, price, data, self.MarkStateAskObjectDirty)
|
||||
self.stateAskObjects[price] = obj
|
||||
return obj
|
||||
}
|
||||
|
||||
// MarkStateAskObjectDirty adds the specified object to the dirty map to avoid costly
|
||||
// state object cache iteration to find a handful of modified ones.
|
||||
func (self *tradingExchanges) MarkStateAskObjectDirty(price common.Hash) {
|
||||
self.stateAskObjectsDirty[price] = struct{}{}
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
// createStateOrderListObject creates a new state object. If there is an existing orderId with
|
||||
// the given address, it is overwritten and returned as the second return value.
|
||||
func (self *tradingExchanges) createStateOrderListAskObject(db Database, price common.Hash) (newobj *stateOrderList) {
|
||||
newobj = newStateOrderList(self.db, Ask, self.orderBookHash, price, orderList{Volume: Zero}, self.MarkStateAskObjectDirty)
|
||||
self.stateAskObjects[price] = newobj
|
||||
self.stateAskObjectsDirty[price] = struct{}{}
|
||||
data, err := rlp.EncodeToBytes(newobj)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("can't encode order list object at %x: %v", price[:], err))
|
||||
}
|
||||
self.setError(self.asksTrie.TryUpdate(price[:], data))
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
return newobj
|
||||
}
|
||||
|
||||
// Retrieve a state object given my the address. Returns nil if not found.
|
||||
func (self *tradingExchanges) getStateBidOrderListObject(db Database, price common.Hash) (stateOrderList *stateOrderList) {
|
||||
// Prefer 'live' objects.
|
||||
if obj := self.stateBidObjects[price]; obj != nil {
|
||||
return obj
|
||||
}
|
||||
|
||||
// Load the object from the database.
|
||||
enc, err := self.getBidsTrie(db).TryGet(price[:])
|
||||
if len(enc) == 0 {
|
||||
self.setError(err)
|
||||
return nil
|
||||
}
|
||||
var data orderList
|
||||
if err := rlp.DecodeBytes(enc, &data); err != nil {
|
||||
log.Error("Failed to decode state order list object", "orderId", price, "err", err)
|
||||
return nil
|
||||
}
|
||||
// Insert into the live set.
|
||||
obj := newStateOrderList(self.db, Bid, self.orderBookHash, price, data, self.MarkStateBidObjectDirty)
|
||||
self.stateBidObjects[price] = obj
|
||||
return obj
|
||||
}
|
||||
|
||||
// MarkStateAskObjectDirty adds the specified object to the dirty map to avoid costly
|
||||
// state object cache iteration to find a handful of modified ones.
|
||||
func (self *tradingExchanges) MarkStateBidObjectDirty(price common.Hash) {
|
||||
self.stateBidObjectsDirty[price] = struct{}{}
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
// createStateOrderListObject creates a new state object. If there is an existing orderId with
|
||||
// the given address, it is overwritten and returned as the second return value.
|
||||
func (self *tradingExchanges) createStateBidOrderListObject(db Database, price common.Hash) (newobj *stateOrderList) {
|
||||
newobj = newStateOrderList(self.db, Bid, self.orderBookHash, price, orderList{Volume: Zero}, self.MarkStateBidObjectDirty)
|
||||
self.stateBidObjects[price] = newobj
|
||||
self.stateBidObjectsDirty[price] = struct{}{}
|
||||
data, err := rlp.EncodeToBytes(newobj)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("can't encode order list object at %x: %v", price[:], err))
|
||||
}
|
||||
self.setError(self.bidsTrie.TryUpdate(price[:], data))
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
return newobj
|
||||
}
|
||||
|
||||
// Retrieve a state object given my the address. Returns nil if not found.
|
||||
func (self *tradingExchanges) getStateOrderObject(db Database, orderId common.Hash) (stateOrderItem *stateOrderItem) {
|
||||
// Prefer 'live' objects.
|
||||
if obj := self.stateOrderObjects[orderId]; obj != nil {
|
||||
return obj
|
||||
}
|
||||
|
||||
// Load the object from the database.
|
||||
enc, err := self.getOrdersTrie(db).TryGet(orderId[:])
|
||||
if len(enc) == 0 {
|
||||
self.setError(err)
|
||||
return nil
|
||||
}
|
||||
var data OrderItem
|
||||
if err := rlp.DecodeBytes(enc, &data); err != nil {
|
||||
log.Error("Failed to decode state order object", "orderId", orderId, "err", err)
|
||||
return nil
|
||||
}
|
||||
// Insert into the live set.
|
||||
obj := newStateOrderItem(self.orderBookHash, orderId, data, self.MarkStateOrderObjectDirty)
|
||||
self.stateOrderObjects[orderId] = obj
|
||||
return obj
|
||||
}
|
||||
|
||||
// MarkStateAskObjectDirty adds the specified object to the dirty map to avoid costly
|
||||
// state object cache iteration to find a handful of modified ones.
|
||||
func (self *tradingExchanges) MarkStateOrderObjectDirty(orderId common.Hash) {
|
||||
self.stateOrderObjectsDirty[orderId] = struct{}{}
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
// createStateOrderListObject creates a new state object. If there is an existing orderId with
|
||||
// the given address, it is overwritten and returned as the second return value.
|
||||
func (self *tradingExchanges) createStateOrderObject(db Database, orderId common.Hash, order OrderItem) (newobj *stateOrderItem) {
|
||||
newobj = newStateOrderItem(self.orderBookHash, orderId, order, self.MarkStateOrderObjectDirty)
|
||||
orderIdHash := common.BigToHash(new(big.Int).SetUint64(order.OrderID))
|
||||
self.stateOrderObjects[orderIdHash] = newobj
|
||||
self.stateOrderObjectsDirty[orderIdHash] = struct{}{}
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.orderBookHash)
|
||||
self.onDirty = nil
|
||||
}
|
||||
return newobj
|
||||
}
|
||||
|
||||
// updateAskTrie writes cached storage modifications into the object's storage trie.
|
||||
func (self *tradingExchanges) updateOrdersTrie(db Database) Trie {
|
||||
tr := self.getOrdersTrie(db)
|
||||
for orderId, orderItem := range self.stateOrderObjects {
|
||||
if _, isDirty := self.stateOrderObjectsDirty[orderId]; isDirty {
|
||||
delete(self.stateOrderObjectsDirty, orderId)
|
||||
if orderItem.empty() {
|
||||
self.setError(tr.TryDelete(orderId[:]))
|
||||
continue
|
||||
}
|
||||
// Encoding []byte cannot fail, ok to ignore the error.
|
||||
v, _ := rlp.EncodeToBytes(orderItem)
|
||||
self.setError(tr.TryUpdate(orderId[:], v))
|
||||
}
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
// CommitAskTrie the storage trie of the object to dwb.
|
||||
// This updates the trie root.
|
||||
func (self *tradingExchanges) updateOrdersRoot(db Database) {
|
||||
self.updateOrdersTrie(db)
|
||||
self.data.OrderRoot = self.ordersTrie.Hash()
|
||||
}
|
||||
|
||||
// CommitAskTrie the storage trie of the object to dwb.
|
||||
// This updates the trie root.
|
||||
func (self *tradingExchanges) CommitOrdersTrie(db Database) error {
|
||||
self.updateOrdersTrie(db)
|
||||
if self.dbErr != nil {
|
||||
return self.dbErr
|
||||
}
|
||||
root, err := self.ordersTrie.Commit(nil)
|
||||
if err == nil {
|
||||
self.data.OrderRoot = root
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *tradingExchanges) MarkStateLiquidationPriceDirty(price common.Hash) {
|
||||
self.liquidationPriceStatesDirty[price] = struct{}{}
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *tradingExchanges) createStateLiquidationPrice(db Database, liquidationPrice common.Hash) (newobj *liquidationPriceState) {
|
||||
newobj = newLiquidationPriceState(self.db, self.orderBookHash, liquidationPrice, orderList{Volume: Zero}, self.MarkStateLiquidationPriceDirty)
|
||||
self.liquidationPriceStates[liquidationPrice] = newobj
|
||||
self.liquidationPriceStatesDirty[liquidationPrice] = struct{}{}
|
||||
data, err := rlp.EncodeToBytes(newobj)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("can't encode liquidation price object at %x: %v", liquidationPrice[:], err))
|
||||
}
|
||||
self.setError(self.getLiquidationPriceTrie(db).TryUpdate(liquidationPrice[:], data))
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
return newobj
|
||||
}
|
||||
|
||||
func (self *tradingExchanges) getLiquidationPriceTrie(db Database) Trie {
|
||||
if self.liquidationPriceTrie == nil {
|
||||
var err error
|
||||
self.liquidationPriceTrie, err = db.OpenStorageTrie(self.orderBookHash, self.data.LiquidationPriceRoot)
|
||||
if err != nil {
|
||||
self.liquidationPriceTrie, _ = db.OpenStorageTrie(self.orderBookHash, EmptyHash)
|
||||
self.setError(fmt.Errorf("can't create liquidation liquidationPrice trie: %v", err))
|
||||
}
|
||||
}
|
||||
return self.liquidationPriceTrie
|
||||
}
|
||||
|
||||
func (self *tradingExchanges) getStateLiquidationPrice(db Database, price common.Hash) (stateObject *liquidationPriceState) {
|
||||
// Prefer 'live' objects.
|
||||
if obj := self.liquidationPriceStates[price]; obj != nil {
|
||||
return obj
|
||||
}
|
||||
|
||||
// Load the object from the database.
|
||||
enc, err := self.getLiquidationPriceTrie(db).TryGet(price[:])
|
||||
if len(enc) == 0 {
|
||||
self.setError(err)
|
||||
return nil
|
||||
}
|
||||
var data orderList
|
||||
if err := rlp.DecodeBytes(enc, &data); err != nil {
|
||||
log.Error("Failed to decode state liquidation liquidationPrice", "liquidationPrice", price, "err", err)
|
||||
return nil
|
||||
}
|
||||
// Insert into the live set.
|
||||
obj := newLiquidationPriceState(self.db, self.orderBookHash, price, data, self.MarkStateLiquidationPriceDirty)
|
||||
self.liquidationPriceStates[price] = obj
|
||||
return obj
|
||||
}
|
||||
|
||||
func (self *tradingExchanges) getLowestLiquidationPrice(db Database) (common.Hash, *liquidationPriceState) {
|
||||
trie := self.getLiquidationPriceTrie(db)
|
||||
encKey, encValue, err := trie.TryGetBestLeftKeyAndValue()
|
||||
if err != nil {
|
||||
log.Error("Failed find best liquidationPrice ask trie ", "orderbook", self.orderBookHash.Hex())
|
||||
return EmptyHash, nil
|
||||
}
|
||||
if len(encKey) == 0 || len(encValue) == 0 {
|
||||
log.Debug("Not found get best ask trie", "encKey", encKey, "encValue", encValue)
|
||||
return EmptyHash, nil
|
||||
}
|
||||
price := common.BytesToHash(encKey)
|
||||
obj := self.liquidationPriceStates[price]
|
||||
if obj == nil {
|
||||
var data orderList
|
||||
if err := rlp.DecodeBytes(encValue, &data); err != nil {
|
||||
log.Error("Failed to decode state get best ask trie", "err", err)
|
||||
return EmptyHash, nil
|
||||
}
|
||||
obj = newLiquidationPriceState(self.db, self.orderBookHash, price, data, self.MarkStateLiquidationPriceDirty)
|
||||
self.liquidationPriceStates[price] = obj
|
||||
}
|
||||
return price, obj
|
||||
}
|
||||
|
||||
func (self *tradingExchanges) getAllLowerLiquidationPrice(db Database, limit common.Hash) map[common.Hash]*liquidationPriceState {
|
||||
trie := self.getLiquidationPriceTrie(db)
|
||||
encKeys, encValues, err := trie.TryGetAllLeftKeyAndValue(limit.Bytes())
|
||||
result := map[common.Hash]*liquidationPriceState{}
|
||||
if err != nil || len(encKeys) != len(encValues) {
|
||||
log.Error("Failed get lower liquidation price trie ", "orderbook", self.orderBookHash.Hex(), "encKeys", len(encKeys), "encValues", len(encValues))
|
||||
return result
|
||||
}
|
||||
if len(encKeys) == 0 || len(encValues) == 0 {
|
||||
log.Debug("Not found get lower liquidation price trie ", "limit", limit)
|
||||
return result
|
||||
}
|
||||
for i := range encKeys {
|
||||
price := common.BytesToHash(encKeys[i])
|
||||
obj := self.liquidationPriceStates[price]
|
||||
if obj == nil {
|
||||
var data orderList
|
||||
if err := rlp.DecodeBytes(encValues[i], &data); err != nil {
|
||||
log.Error("Failed to decode state get all lower liquidation price trie", "price", price, "encValues", encValues[i], "err", err)
|
||||
return result
|
||||
}
|
||||
obj = newLiquidationPriceState(self.db, self.orderBookHash, price, data, self.MarkStateLiquidationPriceDirty)
|
||||
self.liquidationPriceStates[price] = obj
|
||||
}
|
||||
if obj.empty() {
|
||||
continue
|
||||
}
|
||||
result[price] = obj
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (self *tradingExchanges) getHighestLiquidationPrice(db Database) (common.Hash, *liquidationPriceState) {
|
||||
trie := self.getLiquidationPriceTrie(db)
|
||||
encKey, encValue, err := trie.TryGetBestRightKeyAndValue()
|
||||
if err != nil {
|
||||
log.Error("Failed find best liquidationPrice ask trie ", "orderbook", self.orderBookHash.Hex())
|
||||
return EmptyHash, nil
|
||||
}
|
||||
if len(encKey) == 0 || len(encValue) == 0 {
|
||||
log.Debug("Not found get best ask trie", "encKey", encKey, "encValue", encValue)
|
||||
return EmptyHash, nil
|
||||
}
|
||||
price := common.BytesToHash(encKey)
|
||||
obj := self.liquidationPriceStates[price]
|
||||
if obj == nil {
|
||||
var data orderList
|
||||
if err := rlp.DecodeBytes(encValue, &data); err != nil {
|
||||
log.Error("Failed to decode state get best ask trie", "err", err)
|
||||
return EmptyHash, nil
|
||||
}
|
||||
obj = newLiquidationPriceState(self.db, self.orderBookHash, price, data, self.MarkStateLiquidationPriceDirty)
|
||||
self.liquidationPriceStates[price] = obj
|
||||
}
|
||||
if obj.empty() {
|
||||
return EmptyHash, nil
|
||||
}
|
||||
return price, obj
|
||||
}
|
||||
func (self *tradingExchanges) updateLiquidationPriceTrie(db Database) Trie {
|
||||
tr := self.getLiquidationPriceTrie(db)
|
||||
for price, stateObject := range self.liquidationPriceStates {
|
||||
if _, isDirty := self.liquidationPriceStatesDirty[price]; isDirty {
|
||||
delete(self.liquidationPriceStatesDirty, price)
|
||||
if stateObject.empty() {
|
||||
self.setError(tr.TryDelete(price[:]))
|
||||
continue
|
||||
}
|
||||
stateObject.updateRoot(db)
|
||||
// Encoding []byte cannot fail, ok to ignore the error.
|
||||
v, _ := rlp.EncodeToBytes(stateObject)
|
||||
self.setError(tr.TryUpdate(price[:], v))
|
||||
}
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
func (self *tradingExchanges) updateLiquidationPriceRoot(db Database) {
|
||||
self.updateLiquidationPriceTrie(db)
|
||||
self.data.LiquidationPriceRoot = self.liquidationPriceTrie.Hash()
|
||||
}
|
||||
|
||||
func (self *tradingExchanges) CommitLiquidationPriceTrie(db Database) error {
|
||||
self.updateLiquidationPriceTrie(db)
|
||||
if self.dbErr != nil {
|
||||
return self.dbErr
|
||||
}
|
||||
root, err := self.liquidationPriceTrie.Commit(func(leaf []byte, parent common.Hash) error {
|
||||
var orderList orderList
|
||||
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
|
||||
return nil
|
||||
}
|
||||
if orderList.Root != EmptyRoot {
|
||||
db.TrieDB().Reference(orderList.Root, parent)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err == nil {
|
||||
self.data.LiquidationPriceRoot = root
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (c *tradingExchanges) addLendingCount(amount *big.Int) {
|
||||
c.setLendingCount(new(big.Int).Add(c.data.LendingCount, amount))
|
||||
}
|
||||
|
||||
func (c *tradingExchanges) subLendingCount(amount *big.Int) {
|
||||
c.setLendingCount(new(big.Int).Sub(c.data.LendingCount, amount))
|
||||
}
|
||||
|
||||
func (self *tradingExchanges) setLendingCount(volume *big.Int) {
|
||||
self.data.LendingCount = volume
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.orderBookHash)
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
726
XDCx/tradingstate/statedb.go
Normal file
726
XDCx/tradingstate/statedb.go
Normal file
|
|
@ -0,0 +1,726 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package state provides a caching layer atop the Ethereum state trie.
|
||||
package tradingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
)
|
||||
|
||||
type revision struct {
|
||||
id int
|
||||
journalIndex int
|
||||
}
|
||||
|
||||
// StateDBs within the ethereum protocol are used to store anything
|
||||
// within the merkle trie. StateDBs take care of caching and storing
|
||||
// nested states. It's the general query interface to retrieve:
|
||||
// * Contracts
|
||||
// * Accounts
|
||||
type TradingStateDB struct {
|
||||
db Database
|
||||
trie Trie
|
||||
|
||||
// This map holds 'live' objects, which will get modified while processing a state transition.
|
||||
stateExhangeObjects map[common.Hash]*tradingExchanges
|
||||
stateExhangeObjectsDirty map[common.Hash]struct{}
|
||||
|
||||
// DB error.
|
||||
// State objects are used by the consensus core and VM which are
|
||||
// unable to deal with database-level errors. Any error that occurs
|
||||
// during a database read is memoized here and will eventually be returned
|
||||
// by TradingStateDB.Commit.
|
||||
dbErr error
|
||||
|
||||
// Journal of state modifications. This is the backbone of
|
||||
// Snapshot and RevertToSnapshot.
|
||||
journal journal
|
||||
validRevisions []revision
|
||||
nextRevisionId int
|
||||
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// Create a new state from a given trie.
|
||||
func New(root common.Hash, db Database) (*TradingStateDB, error) {
|
||||
tr, err := db.OpenTrie(root)
|
||||
if err != nil {
|
||||
log.Error("Error when init new trading state trie ", "root", root.Hex(), "err", err)
|
||||
return nil, err
|
||||
}
|
||||
return &TradingStateDB{
|
||||
db: db,
|
||||
trie: tr,
|
||||
stateExhangeObjects: make(map[common.Hash]*tradingExchanges),
|
||||
stateExhangeObjectsDirty: make(map[common.Hash]struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// setError remembers the first non-nil error it is called with.
|
||||
func (self *TradingStateDB) setError(err error) {
|
||||
if self.dbErr == nil {
|
||||
self.dbErr = err
|
||||
}
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) Error() error {
|
||||
return self.dbErr
|
||||
}
|
||||
|
||||
// Exist reports whether the given orderId address exists in the state.
|
||||
// Notably this also returns true for suicided exchanges.
|
||||
func (self *TradingStateDB) Exist(addr common.Hash) bool {
|
||||
return self.getStateExchangeObject(addr) != nil
|
||||
}
|
||||
|
||||
// Empty returns whether the state object is either non-existent
|
||||
// or empty according to the EIP161 specification (balance = nonce = code = 0)
|
||||
func (self *TradingStateDB) Empty(addr common.Hash) bool {
|
||||
so := self.getStateExchangeObject(addr)
|
||||
return so == nil || so.empty()
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) GetNonce(addr common.Hash) uint64 {
|
||||
stateObject := self.getStateExchangeObject(addr)
|
||||
if stateObject != nil {
|
||||
return stateObject.Nonce()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) GetLastPrice(addr common.Hash) *big.Int {
|
||||
stateObject := self.getStateExchangeObject(addr)
|
||||
if stateObject != nil {
|
||||
return stateObject.data.LastPrice
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) GetMediumPriceBeforeEpoch(addr common.Hash) *big.Int {
|
||||
stateObject := self.getStateExchangeObject(addr)
|
||||
if stateObject != nil {
|
||||
return stateObject.data.MediumPriceBeforeEpoch
|
||||
}
|
||||
return Zero
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) GetMediumPriceAndTotalAmount(addr common.Hash) (*big.Int, *big.Int) {
|
||||
stateObject := self.getStateExchangeObject(addr)
|
||||
if stateObject != nil {
|
||||
return stateObject.data.MediumPrice, stateObject.data.TotalQuantity
|
||||
}
|
||||
return Zero, Zero
|
||||
}
|
||||
|
||||
// Database retrieves the low level database supporting the lower level trie ops.
|
||||
func (self *TradingStateDB) Database() Database {
|
||||
return self.db
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) SetNonce(addr common.Hash, nonce uint64) {
|
||||
stateObject := self.GetOrNewStateExchangeObject(addr)
|
||||
if stateObject != nil {
|
||||
self.journal = append(self.journal, nonceChange{
|
||||
hash: addr,
|
||||
prev: self.GetNonce(addr),
|
||||
})
|
||||
stateObject.SetNonce(nonce)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) SetLastPrice(addr common.Hash, price *big.Int) {
|
||||
stateObject := self.GetOrNewStateExchangeObject(addr)
|
||||
if stateObject != nil {
|
||||
self.journal = append(self.journal, lastPriceChange{
|
||||
hash: addr,
|
||||
prev: stateObject.data.LastPrice,
|
||||
})
|
||||
stateObject.setLastPrice(price)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) SetMediumPrice(addr common.Hash, price *big.Int, quantity *big.Int) {
|
||||
stateObject := self.GetOrNewStateExchangeObject(addr)
|
||||
if stateObject != nil {
|
||||
self.journal = append(self.journal, mediumPriceChange{
|
||||
hash: addr,
|
||||
prevPrice: stateObject.data.MediumPrice,
|
||||
prevQuantity: stateObject.data.TotalQuantity,
|
||||
})
|
||||
stateObject.setMediumPrice(price, quantity)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) SetMediumPriceBeforeEpoch(addr common.Hash, price *big.Int) {
|
||||
stateObject := self.GetOrNewStateExchangeObject(addr)
|
||||
if stateObject != nil {
|
||||
self.journal = append(self.journal, mediumPriceBeforeEpochChange{
|
||||
hash: addr,
|
||||
prevPrice: stateObject.data.MediumPriceBeforeEpoch,
|
||||
})
|
||||
stateObject.setMediumPriceBeforeEpoch(price)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) InsertOrderItem(orderBook common.Hash, orderId common.Hash, order OrderItem) {
|
||||
priceHash := common.BigToHash(order.Price)
|
||||
stateExchange := self.getStateExchangeObject(orderBook)
|
||||
if stateExchange == nil {
|
||||
stateExchange = self.createExchangeObject(orderBook)
|
||||
}
|
||||
var stateOrderList *stateOrderList
|
||||
switch order.Side {
|
||||
case Ask:
|
||||
stateOrderList = stateExchange.getStateOrderListAskObject(self.db, priceHash)
|
||||
if stateOrderList == nil {
|
||||
stateOrderList = stateExchange.createStateOrderListAskObject(self.db, priceHash)
|
||||
}
|
||||
case Bid:
|
||||
stateOrderList = stateExchange.getStateBidOrderListObject(self.db, priceHash)
|
||||
if stateOrderList == nil {
|
||||
stateOrderList = stateExchange.createStateBidOrderListObject(self.db, priceHash)
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
self.journal = append(self.journal, insertOrder{
|
||||
orderBook: orderBook,
|
||||
orderId: orderId,
|
||||
order: &order,
|
||||
})
|
||||
stateExchange.createStateOrderObject(self.db, orderId, order)
|
||||
stateOrderList.insertOrderItem(self.db, orderId, common.BigToHash(order.Quantity))
|
||||
stateOrderList.AddVolume(order.Quantity)
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) GetOrder(orderBook common.Hash, orderId common.Hash) OrderItem {
|
||||
stateObject := self.GetOrNewStateExchangeObject(orderBook)
|
||||
if stateObject == nil {
|
||||
return EmptyOrder
|
||||
}
|
||||
stateOrderItem := stateObject.getStateOrderObject(self.db, orderId)
|
||||
if stateOrderItem == nil {
|
||||
return EmptyOrder
|
||||
}
|
||||
return stateOrderItem.data
|
||||
}
|
||||
func (self *TradingStateDB) SubAmountOrderItem(orderBook common.Hash, orderId common.Hash, price *big.Int, amount *big.Int, side string) error {
|
||||
priceHash := common.BigToHash(price)
|
||||
stateObject := self.GetOrNewStateExchangeObject(orderBook)
|
||||
if stateObject == nil {
|
||||
return fmt.Errorf("Order book not found : %s ", orderBook.Hex())
|
||||
}
|
||||
var stateOrderList *stateOrderList
|
||||
switch side {
|
||||
case Ask:
|
||||
stateOrderList = stateObject.getStateOrderListAskObject(self.db, priceHash)
|
||||
case Bid:
|
||||
stateOrderList = stateObject.getStateBidOrderListObject(self.db, priceHash)
|
||||
default:
|
||||
return fmt.Errorf("Order type not found : %s ", side)
|
||||
}
|
||||
if stateOrderList == nil || stateOrderList.empty() {
|
||||
return fmt.Errorf("Order list empty order book : %s , order id : %s , price : %s ", orderBook, orderId.Hex(), priceHash.Hex())
|
||||
}
|
||||
stateOrderItem := stateObject.getStateOrderObject(self.db, orderId)
|
||||
if stateOrderItem == nil || stateOrderItem.empty() {
|
||||
return fmt.Errorf("Order item empty order book : %s , order id : %s , price : %s ", orderBook, orderId.Hex(), priceHash.Hex())
|
||||
}
|
||||
currentAmount := new(big.Int).SetBytes(stateOrderList.GetOrderAmount(self.db, orderId).Bytes()[:])
|
||||
if currentAmount.Cmp(amount) < 0 {
|
||||
return fmt.Errorf("Order amount not enough : %s , have : %d , want : %d ", orderId.Hex(), currentAmount, amount)
|
||||
}
|
||||
self.journal = append(self.journal, subAmountOrder{
|
||||
orderBook: orderBook,
|
||||
orderId: orderId,
|
||||
order: self.GetOrder(orderBook, orderId),
|
||||
amount: amount,
|
||||
})
|
||||
newAmount := new(big.Int).Sub(currentAmount, amount)
|
||||
log.Debug("SubAmountOrderItem", "orderId", orderId.Hex(), "side", side, "price", price.Uint64(), "amount", amount.Uint64(), "new amount", newAmount.Uint64())
|
||||
stateOrderList.subVolume(amount)
|
||||
stateOrderItem.setVolume(newAmount)
|
||||
if newAmount.Sign() == 0 {
|
||||
stateOrderList.removeOrderItem(self.db, orderId)
|
||||
} else {
|
||||
stateOrderList.setOrderItem(orderId, common.BigToHash(newAmount))
|
||||
}
|
||||
if stateOrderList.empty() {
|
||||
switch side {
|
||||
case Ask:
|
||||
stateObject.removeStateOrderListAskObject(self.db, stateOrderList)
|
||||
case Bid:
|
||||
stateObject.removeStateOrderListBidObject(self.db, stateOrderList)
|
||||
default:
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) CancelOrder(orderBook common.Hash, order *OrderItem) error {
|
||||
orderIdHash := common.BigToHash(new(big.Int).SetUint64(order.OrderID))
|
||||
stateObject := self.GetOrNewStateExchangeObject(orderBook)
|
||||
if stateObject == nil {
|
||||
return fmt.Errorf("Order book not found : %s ", orderBook.Hex())
|
||||
}
|
||||
stateOrderItem := stateObject.getStateOrderObject(self.db, orderIdHash)
|
||||
if stateOrderItem == nil || stateOrderItem.empty() {
|
||||
return fmt.Errorf("Order item empty order book : %s , order id : %s ", orderBook, orderIdHash.Hex())
|
||||
}
|
||||
priceHash := common.BigToHash(stateOrderItem.data.Price)
|
||||
var stateOrderList *stateOrderList
|
||||
switch stateOrderItem.data.Side {
|
||||
case Ask:
|
||||
stateOrderList = stateObject.getStateOrderListAskObject(self.db, priceHash)
|
||||
case Bid:
|
||||
stateOrderList = stateObject.getStateBidOrderListObject(self.db, priceHash)
|
||||
default:
|
||||
return fmt.Errorf("Order side not found : %s ", order.Side)
|
||||
}
|
||||
if stateOrderList == nil || stateOrderList.empty() {
|
||||
return fmt.Errorf("Order list empty order book : %s , order id : %s , price : %s ", orderBook, orderIdHash.Hex(), priceHash.Hex())
|
||||
}
|
||||
|
||||
if stateOrderItem.data.UserAddress != order.UserAddress {
|
||||
return fmt.Errorf("Error Order User Address mismatch when cancel order book : %s , order id : %s , got : %s , expect : %s ", orderBook, orderIdHash.Hex(), stateOrderItem.data.UserAddress.Hex(), order.UserAddress.Hex())
|
||||
}
|
||||
if stateOrderItem.data.Hash != order.Hash {
|
||||
return fmt.Errorf("Invalid order hash : got : %s , expect : %s ", order.Hash.Hex(), stateOrderItem.data.Hash.Hex())
|
||||
}
|
||||
if stateOrderItem.data.ExchangeAddress != order.ExchangeAddress {
|
||||
return fmt.Errorf("Exchange Address mismatch when cancel. order book : %s , order id : %s , got : %s , expect : %s ", orderBook, orderIdHash.Hex(), order.ExchangeAddress.Hex(), stateOrderItem.data.ExchangeAddress.Hex())
|
||||
}
|
||||
self.journal = append(self.journal, cancelOrder{
|
||||
orderBook: orderBook,
|
||||
orderId: orderIdHash,
|
||||
order: stateOrderItem.data,
|
||||
})
|
||||
currentAmount := new(big.Int).SetBytes(stateOrderList.GetOrderAmount(self.db, orderIdHash).Bytes()[:])
|
||||
stateOrderItem.setVolume(big.NewInt(0))
|
||||
stateOrderList.subVolume(currentAmount)
|
||||
stateOrderList.removeOrderItem(self.db, orderIdHash)
|
||||
if stateOrderList.empty() {
|
||||
switch stateOrderItem.data.Side {
|
||||
case Ask:
|
||||
stateObject.removeStateOrderListAskObject(self.db, stateOrderList)
|
||||
case Bid:
|
||||
stateObject.removeStateOrderListBidObject(self.db, stateOrderList)
|
||||
default:
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) GetVolume(orderBook common.Hash, price *big.Int, orderType string) *big.Int {
|
||||
stateObject := self.GetOrNewStateExchangeObject(orderBook)
|
||||
var volume *big.Int = nil
|
||||
if stateObject != nil {
|
||||
var stateOrderList *stateOrderList
|
||||
switch orderType {
|
||||
case Ask:
|
||||
stateOrderList = stateObject.getStateOrderListAskObject(self.db, common.BigToHash(price))
|
||||
case Bid:
|
||||
stateOrderList = stateObject.getStateBidOrderListObject(self.db, common.BigToHash(price))
|
||||
default:
|
||||
return Zero
|
||||
}
|
||||
if stateOrderList == nil || stateOrderList.empty() {
|
||||
return Zero
|
||||
}
|
||||
volume = stateOrderList.Volume()
|
||||
}
|
||||
return volume
|
||||
}
|
||||
func (self *TradingStateDB) GetBestAskPrice(orderBook common.Hash) (*big.Int, *big.Int) {
|
||||
stateObject := self.getStateExchangeObject(orderBook)
|
||||
if stateObject != nil {
|
||||
priceHash := stateObject.getBestPriceAsksTrie(self.db)
|
||||
if common.EmptyHash(priceHash) {
|
||||
return Zero, Zero
|
||||
}
|
||||
orderList := stateObject.getStateOrderListAskObject(self.db, priceHash)
|
||||
if orderList == nil {
|
||||
log.Error("order list ask not found", "price", priceHash.Hex())
|
||||
return Zero, Zero
|
||||
}
|
||||
return new(big.Int).SetBytes(priceHash.Bytes()), orderList.Volume()
|
||||
}
|
||||
return Zero, Zero
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) GetBestBidPrice(orderBook common.Hash) (*big.Int, *big.Int) {
|
||||
stateObject := self.getStateExchangeObject(orderBook)
|
||||
if stateObject != nil {
|
||||
priceHash := stateObject.getBestBidsTrie(self.db)
|
||||
if common.EmptyHash(priceHash) {
|
||||
return Zero, Zero
|
||||
}
|
||||
orderList := stateObject.getStateBidOrderListObject(self.db, priceHash)
|
||||
if orderList == nil {
|
||||
log.Error("order list bid not found", "price", priceHash.Hex())
|
||||
return Zero, Zero
|
||||
}
|
||||
return new(big.Int).SetBytes(priceHash.Bytes()), orderList.Volume()
|
||||
}
|
||||
return Zero, Zero
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) GetBestOrderIdAndAmount(orderBook common.Hash, price *big.Int, side string) (common.Hash, *big.Int, error) {
|
||||
stateObject := self.GetOrNewStateExchangeObject(orderBook)
|
||||
if stateObject != nil {
|
||||
var stateOrderList *stateOrderList
|
||||
switch side {
|
||||
case Ask:
|
||||
stateOrderList = stateObject.getStateOrderListAskObject(self.db, common.BigToHash(price))
|
||||
case Bid:
|
||||
stateOrderList = stateObject.getStateBidOrderListObject(self.db, common.BigToHash(price))
|
||||
default:
|
||||
return EmptyHash, Zero, fmt.Errorf("not found side :%s ", side)
|
||||
}
|
||||
if stateOrderList != nil {
|
||||
key, _, err := stateOrderList.getTrie(self.db).TryGetBestLeftKeyAndValue()
|
||||
if err != nil {
|
||||
return EmptyHash, Zero, err
|
||||
}
|
||||
orderId := common.BytesToHash(key)
|
||||
amount := stateOrderList.GetOrderAmount(self.db, orderId)
|
||||
return orderId, new(big.Int).SetBytes(amount.Bytes()), nil
|
||||
}
|
||||
return EmptyHash, Zero, fmt.Errorf("not found order list with orderBook : %s , price : %d , side :%s ", orderBook.Hex(), price, side)
|
||||
}
|
||||
return EmptyHash, Zero, fmt.Errorf("not found orderBook : %s ", orderBook.Hex())
|
||||
}
|
||||
|
||||
// updateStateExchangeObject writes the given object to the trie.
|
||||
func (self *TradingStateDB) updateStateExchangeObject(stateObject *tradingExchanges) {
|
||||
addr := stateObject.Hash()
|
||||
data, err := rlp.EncodeToBytes(stateObject)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("can't encode object at %x: %v", addr[:], err))
|
||||
}
|
||||
self.setError(self.trie.TryUpdate(addr[:], data))
|
||||
}
|
||||
|
||||
// Retrieve a state object given my the address. Returns nil if not found.
|
||||
func (self *TradingStateDB) getStateExchangeObject(addr common.Hash) (stateObject *tradingExchanges) {
|
||||
// Prefer 'live' objects.
|
||||
if obj := self.stateExhangeObjects[addr]; obj != nil {
|
||||
return obj
|
||||
}
|
||||
// Load the object from the database.
|
||||
enc, err := self.trie.TryGet(addr[:])
|
||||
if len(enc) == 0 {
|
||||
self.setError(err)
|
||||
return nil
|
||||
}
|
||||
var data tradingExchangeObject
|
||||
if err := rlp.DecodeBytes(enc, &data); err != nil {
|
||||
log.Error("Failed to decode state object", "addr", addr, "err", err)
|
||||
return nil
|
||||
}
|
||||
// Insert into the live set.
|
||||
obj := newStateExchanges(self, addr, data, self.MarkStateExchangeObjectDirty)
|
||||
self.stateExhangeObjects[addr] = obj
|
||||
return obj
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) setStateExchangeObject(object *tradingExchanges) {
|
||||
self.stateExhangeObjects[object.Hash()] = object
|
||||
self.stateExhangeObjectsDirty[object.Hash()] = struct{}{}
|
||||
}
|
||||
|
||||
// Retrieve a state object or create a new state object if nil.
|
||||
func (self *TradingStateDB) GetOrNewStateExchangeObject(addr common.Hash) *tradingExchanges {
|
||||
stateExchangeObject := self.getStateExchangeObject(addr)
|
||||
if stateExchangeObject == nil {
|
||||
stateExchangeObject = self.createExchangeObject(addr)
|
||||
}
|
||||
return stateExchangeObject
|
||||
}
|
||||
|
||||
// MarkStateAskObjectDirty adds the specified object to the dirty map to avoid costly
|
||||
// state object cache iteration to find a handful of modified ones.
|
||||
func (self *TradingStateDB) MarkStateExchangeObjectDirty(addr common.Hash) {
|
||||
self.stateExhangeObjectsDirty[addr] = struct{}{}
|
||||
}
|
||||
|
||||
// createStateOrderListObject creates a new state object. If there is an existing orderId with
|
||||
// the given address, it is overwritten and returned as the second return value.
|
||||
func (self *TradingStateDB) createExchangeObject(hash common.Hash) (newobj *tradingExchanges) {
|
||||
newobj = newStateExchanges(self, hash, tradingExchangeObject{LendingCount: Zero, MediumPrice: Zero, MediumPriceBeforeEpoch: Zero, TotalQuantity: Zero}, self.MarkStateExchangeObjectDirty)
|
||||
newobj.setNonce(0) // sets the object to dirty
|
||||
self.setStateExchangeObject(newobj)
|
||||
return newobj
|
||||
}
|
||||
|
||||
// Copy creates a deep, independent copy of the state.
|
||||
// Snapshots of the copied state cannot be applied to the copy.
|
||||
func (self *TradingStateDB) Copy() *TradingStateDB {
|
||||
self.lock.Lock()
|
||||
defer self.lock.Unlock()
|
||||
|
||||
// Copy all the basic fields, initialize the memory ones
|
||||
state := &TradingStateDB{
|
||||
db: self.db,
|
||||
trie: self.db.CopyTrie(self.trie),
|
||||
stateExhangeObjects: make(map[common.Hash]*tradingExchanges, len(self.stateExhangeObjectsDirty)),
|
||||
stateExhangeObjectsDirty: make(map[common.Hash]struct{}, len(self.stateExhangeObjectsDirty)),
|
||||
}
|
||||
// Copy the dirty states, logs, and preimages
|
||||
for addr := range self.stateExhangeObjectsDirty {
|
||||
state.stateExhangeObjectsDirty[addr] = struct{}{}
|
||||
}
|
||||
for addr, exchangeObject := range self.stateExhangeObjects {
|
||||
state.stateExhangeObjects[addr] = exchangeObject.deepCopy(state, state.MarkStateExchangeObjectDirty)
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
func (s *TradingStateDB) clearJournalAndRefund() {
|
||||
s.journal = nil
|
||||
s.validRevisions = s.validRevisions[:0]
|
||||
}
|
||||
|
||||
// Snapshot returns an identifier for the current revision of the state.
|
||||
func (self *TradingStateDB) Snapshot() int {
|
||||
id := self.nextRevisionId
|
||||
self.nextRevisionId++
|
||||
self.validRevisions = append(self.validRevisions, revision{id, len(self.journal)})
|
||||
return id
|
||||
}
|
||||
|
||||
// RevertToSnapshot reverts all state changes made since the given revision.
|
||||
func (self *TradingStateDB) RevertToSnapshot(revid int) {
|
||||
// Find the snapshot in the stack of valid snapshots.
|
||||
idx := sort.Search(len(self.validRevisions), func(i int) bool {
|
||||
return self.validRevisions[i].id >= revid
|
||||
})
|
||||
if idx == len(self.validRevisions) || self.validRevisions[idx].id != revid {
|
||||
panic(fmt.Errorf("revision id %v cannot be reverted", revid))
|
||||
}
|
||||
snapshot := self.validRevisions[idx].journalIndex
|
||||
|
||||
// Replay the journal to undo changes.
|
||||
for i := len(self.journal) - 1; i >= snapshot; i-- {
|
||||
self.journal[i].undo(self)
|
||||
}
|
||||
self.journal = self.journal[:snapshot]
|
||||
|
||||
// Remove invalidated snapshots from the stack.
|
||||
self.validRevisions = self.validRevisions[:idx]
|
||||
}
|
||||
|
||||
// Finalise finalises the state by removing the self destructed objects
|
||||
// and clears the journal as well as the refunds.
|
||||
func (s *TradingStateDB) Finalise() {
|
||||
// Commit objects to the trie.
|
||||
for addr, stateObject := range s.stateExhangeObjects {
|
||||
if _, isDirty := s.stateExhangeObjectsDirty[addr]; isDirty {
|
||||
// Write any storage changes in the state object to its storage trie.
|
||||
stateObject.updateAsksRoot(s.db)
|
||||
stateObject.updateBidsRoot(s.db)
|
||||
stateObject.updateOrdersRoot(s.db)
|
||||
stateObject.updateLiquidationPriceRoot(s.db)
|
||||
// Update the object in the main orderId trie.
|
||||
s.updateStateExchangeObject(stateObject)
|
||||
//delete(s.stateExhangeObjectsDirty, addr)
|
||||
}
|
||||
}
|
||||
s.clearJournalAndRefund()
|
||||
}
|
||||
|
||||
// IntermediateRoot computes the current root orderBookHash of the state trie.
|
||||
// It is called in between transactions to get the root orderBookHash that
|
||||
// goes into transaction receipts.
|
||||
func (s *TradingStateDB) IntermediateRoot() common.Hash {
|
||||
s.Finalise()
|
||||
return s.trie.Hash()
|
||||
}
|
||||
|
||||
// Commit writes the state to the underlying in-memory trie database.
|
||||
func (s *TradingStateDB) Commit() (root common.Hash, err error) {
|
||||
defer s.clearJournalAndRefund()
|
||||
// Commit objects to the trie.
|
||||
for addr, stateObject := range s.stateExhangeObjects {
|
||||
if _, isDirty := s.stateExhangeObjectsDirty[addr]; isDirty {
|
||||
// Write any storage changes in the state object to its storage trie.
|
||||
if err := stateObject.CommitAsksTrie(s.db); err != nil {
|
||||
return EmptyHash, err
|
||||
}
|
||||
if err := stateObject.CommitBidsTrie(s.db); err != nil {
|
||||
return EmptyHash, err
|
||||
}
|
||||
if err := stateObject.CommitOrdersTrie(s.db); err != nil {
|
||||
return EmptyHash, err
|
||||
}
|
||||
if err := stateObject.CommitLiquidationPriceTrie(s.db); err != nil {
|
||||
return EmptyHash, err
|
||||
}
|
||||
// Update the object in the main orderId trie.
|
||||
s.updateStateExchangeObject(stateObject)
|
||||
delete(s.stateExhangeObjectsDirty, addr)
|
||||
}
|
||||
}
|
||||
// Write trie changes.
|
||||
root, err = s.trie.Commit(func(leaf []byte, parent common.Hash) error {
|
||||
var exchange tradingExchangeObject
|
||||
if err := rlp.DecodeBytes(leaf, &exchange); err != nil {
|
||||
return nil
|
||||
}
|
||||
if exchange.AskRoot != EmptyRoot {
|
||||
s.db.TrieDB().Reference(exchange.AskRoot, parent)
|
||||
}
|
||||
if exchange.BidRoot != EmptyRoot {
|
||||
s.db.TrieDB().Reference(exchange.BidRoot, parent)
|
||||
}
|
||||
if exchange.OrderRoot != EmptyRoot {
|
||||
s.db.TrieDB().Reference(exchange.OrderRoot, parent)
|
||||
}
|
||||
if exchange.LiquidationPriceRoot != EmptyRoot {
|
||||
s.db.TrieDB().Reference(exchange.LiquidationPriceRoot, parent)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
log.Debug("Trading State Trie cache stats after commit", "root", root.Hex())
|
||||
return root, err
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) GetAllLowerLiquidationPriceData(orderBook common.Hash, limit *big.Int) map[*big.Int]map[common.Hash][]common.Hash {
|
||||
result := map[*big.Int]map[common.Hash][]common.Hash{}
|
||||
orderbookState := self.getStateExchangeObject(orderBook)
|
||||
if orderbookState == nil {
|
||||
return result
|
||||
}
|
||||
mapPrices := orderbookState.getAllLowerLiquidationPrice(self.db, common.BigToHash(limit))
|
||||
for priceHash, liquidationState := range mapPrices {
|
||||
price := new(big.Int).SetBytes(priceHash[:])
|
||||
log.Debug("GetAllLowerLiquidationPriceData", "price", price, "limit", limit)
|
||||
if liquidationState != nil && price.Sign() > 0 && price.Cmp(limit) < 0 {
|
||||
liquidationData := map[common.Hash][]common.Hash{}
|
||||
priceLiquidationData := liquidationState.getAllLiquidationData(self.db)
|
||||
for lendingBook, data := range priceLiquidationData {
|
||||
if len(data) == 0 {
|
||||
continue
|
||||
}
|
||||
oldData := liquidationData[lendingBook]
|
||||
if len(oldData) == 0 {
|
||||
oldData = data
|
||||
} else {
|
||||
oldData = append(oldData, data...)
|
||||
}
|
||||
liquidationData[lendingBook] = oldData
|
||||
}
|
||||
result[price] = liquidationData
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) GetHighestLiquidationPriceData(orderBook common.Hash, price *big.Int) (*big.Int, map[common.Hash][]common.Hash) {
|
||||
liquidationData := map[common.Hash][]common.Hash{}
|
||||
orderbookState := self.getStateExchangeObject(orderBook)
|
||||
if orderbookState == nil {
|
||||
return common.Big0, liquidationData
|
||||
}
|
||||
highestPriceHash, liquidationState := orderbookState.getHighestLiquidationPrice(self.db)
|
||||
highestPrice := new(big.Int).SetBytes(highestPriceHash[:])
|
||||
if liquidationState != nil && highestPrice.Sign() > 0 && price.Cmp(highestPrice) < 0 {
|
||||
priceLiquidationData := liquidationState.getAllLiquidationData(self.db)
|
||||
for lendingBook, data := range priceLiquidationData {
|
||||
if len(data) == 0 {
|
||||
continue
|
||||
}
|
||||
oldData := liquidationData[lendingBook]
|
||||
if len(oldData) == 0 {
|
||||
oldData = data
|
||||
} else {
|
||||
oldData = append(oldData, data...)
|
||||
}
|
||||
liquidationData[lendingBook] = oldData
|
||||
}
|
||||
}
|
||||
return highestPrice, liquidationData
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) InsertLiquidationPrice(orderBook common.Hash, price *big.Int, lendingBook common.Hash, tradeId uint64) {
|
||||
tradIdHash := common.Uint64ToHash(tradeId)
|
||||
priceHash := common.BigToHash(price)
|
||||
orderBookState := self.getStateExchangeObject(orderBook)
|
||||
if orderBookState == nil {
|
||||
orderBookState = self.createExchangeObject(orderBook)
|
||||
}
|
||||
liquidationPriceState := orderBookState.getStateLiquidationPrice(self.db, priceHash)
|
||||
if liquidationPriceState == nil {
|
||||
liquidationPriceState = orderBookState.createStateLiquidationPrice(self.db, priceHash)
|
||||
}
|
||||
lendingBookState := liquidationPriceState.getStateLendingBook(self.db, lendingBook)
|
||||
if lendingBookState == nil {
|
||||
lendingBookState = liquidationPriceState.createLendingBook(self.db, lendingBook)
|
||||
}
|
||||
lendingBookState.insertTradingId(self.db, tradIdHash)
|
||||
lendingBookState.AddVolume(One)
|
||||
liquidationPriceState.AddVolume(One)
|
||||
orderBookState.addLendingCount(One)
|
||||
self.journal = append(self.journal, insertLiquidationPrice{
|
||||
orderBook: orderBook,
|
||||
price: price,
|
||||
lendingBook: lendingBook,
|
||||
tradeId: tradeId,
|
||||
})
|
||||
}
|
||||
|
||||
func (self *TradingStateDB) RemoveLiquidationPrice(orderBook common.Hash, price *big.Int, lendingBook common.Hash, tradeId uint64) error {
|
||||
tradeIdHash := common.Uint64ToHash(tradeId)
|
||||
priceHash := common.BigToHash(price)
|
||||
orderbookState := self.getStateExchangeObject(orderBook)
|
||||
if orderbookState == nil {
|
||||
return fmt.Errorf("order book not found : %s ", orderBook.Hex())
|
||||
}
|
||||
liquidationPriceState := orderbookState.getStateLiquidationPrice(self.db, priceHash)
|
||||
if liquidationPriceState == nil {
|
||||
return fmt.Errorf("liquidation price not found : %s , %s ", orderBook.Hex(), priceHash.Hex())
|
||||
}
|
||||
lendingBookState := liquidationPriceState.getStateLendingBook(self.db, lendingBook)
|
||||
if lendingBookState == nil {
|
||||
return fmt.Errorf("lending book not found : %s , %s ,%s ", orderBook.Hex(), priceHash.Hex(), lendingBook.Hex())
|
||||
}
|
||||
if !lendingBookState.Exist(self.db, tradeIdHash) {
|
||||
return fmt.Errorf("trade id not found : %s , %s ,%s , %d ", orderBook.Hex(), priceHash.Hex(), lendingBook.Hex(), tradeId)
|
||||
}
|
||||
lendingBookState.removeTradingId(self.db, tradeIdHash)
|
||||
lendingBookState.subVolume(One)
|
||||
liquidationPriceState.subVolume(One)
|
||||
if liquidationPriceState.Volume().Sign() == 0 {
|
||||
orderbookState.getLiquidationPriceTrie(self.db).TryDelete(priceHash[:])
|
||||
}
|
||||
orderbookState.subLendingCount(One)
|
||||
self.journal = append(self.journal, removeLiquidationPrice{
|
||||
orderBook: orderBook,
|
||||
price: price,
|
||||
lendingBook: lendingBook,
|
||||
tradeId: tradeId,
|
||||
})
|
||||
return nil
|
||||
}
|
||||
302
XDCx/tradingstate/statedb_test.go
Normal file
302
XDCx/tradingstate/statedb_test.go
Normal file
|
|
@ -0,0 +1,302 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package tradingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/common/math"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEchangeStates(t *testing.T) {
|
||||
t.SkipNow()
|
||||
orderBook := common.StringToHash("BTC/XDC")
|
||||
price := big.NewInt(10000)
|
||||
numberOrder := 20
|
||||
orderItems := []OrderItem{}
|
||||
relayers := []common.Hash{}
|
||||
for i := 0; i < numberOrder; i++ {
|
||||
relayers = append(relayers, common.BigToHash(big.NewInt(int64(i))))
|
||||
id := new(big.Int).SetUint64(uint64(i) + 1)
|
||||
orderItems = append(orderItems, OrderItem{OrderID: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Price: big.NewInt(int64(2*i + 1)), Side: Ask, Signature: &Signature{V: 1, R: common.HexToHash("111111"), S: common.HexToHash("222222222222")}})
|
||||
orderItems = append(orderItems, OrderItem{OrderID: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Price: big.NewInt(int64(2*i + 1)), Side: Bid, Signature: &Signature{V: 1, R: common.HexToHash("3333333333"), S: common.HexToHash("22222222222222222")}})
|
||||
}
|
||||
// Create an empty statedb database
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
stateCache := NewDatabase(db)
|
||||
statedb, _ := New(common.Hash{}, stateCache)
|
||||
|
||||
// Update it with some exchanges
|
||||
for i := 0; i < numberOrder; i++ {
|
||||
statedb.SetNonce(relayers[i], uint64(1))
|
||||
}
|
||||
mapPriceSell := map[uint64]uint64{}
|
||||
mapPriceBuy := map[uint64]uint64{}
|
||||
|
||||
for i := 0; i < len(orderItems); i++ {
|
||||
amount := orderItems[i].Quantity.Uint64()
|
||||
orderIdHash := common.BigToHash(new(big.Int).SetUint64(orderItems[i].OrderID))
|
||||
statedb.InsertOrderItem(orderBook, orderIdHash, orderItems[i])
|
||||
|
||||
switch orderItems[i].Side {
|
||||
case Ask:
|
||||
old := mapPriceSell[amount]
|
||||
mapPriceSell[amount] = old + amount
|
||||
case Bid:
|
||||
old := mapPriceBuy[amount]
|
||||
mapPriceBuy[amount] = old + amount
|
||||
default:
|
||||
}
|
||||
|
||||
}
|
||||
statedb.SetLastPrice(orderBook, price)
|
||||
statedb.InsertLiquidationPrice(orderBook, big.NewInt(1), orderBook, 2)
|
||||
statedb.InsertLiquidationPrice(orderBook, big.NewInt(2), orderBook, 3)
|
||||
statedb.InsertLiquidationPrice(orderBook, big.NewInt(3), orderBook, 1)
|
||||
root := statedb.IntermediateRoot()
|
||||
statedb.Commit()
|
||||
err := stateCache.TrieDB().Commit(root, false)
|
||||
if err != nil {
|
||||
t.Errorf("Error when commit into database: %v", err)
|
||||
}
|
||||
stateCache.TrieDB().Reference(root, common.Hash{})
|
||||
statedb, err = New(root, stateCache)
|
||||
if err != nil {
|
||||
t.Fatalf("Error when get trie in database: %s , err: %v", root.Hex(), err)
|
||||
}
|
||||
liquidationPrice, liquidationData := statedb.GetHighestLiquidationPriceData(orderBook, big.NewInt(1))
|
||||
if len(liquidationData) == 0 {
|
||||
t.Fatalf("Error when get liquidation data save in database: got : %d , %s ", liquidationPrice, liquidationData)
|
||||
}
|
||||
fmt.Println("liquidationPrice", liquidationPrice)
|
||||
for lendingBook, tradingIds := range liquidationData {
|
||||
for _, tradingIdHash := range tradingIds {
|
||||
fmt.Println("lendingBook", lendingBook.Hex(), "tradingIdHash", tradingIdHash.Hex())
|
||||
err := statedb.RemoveLiquidationPrice(orderBook, liquidationPrice, lendingBook, new(big.Int).SetBytes(tradingIdHash.Bytes()).Uint64())
|
||||
if err != nil {
|
||||
t.Fatalf("Error when remove liquidation price in database: %s , err: %v", root.Hex(), err)
|
||||
}
|
||||
}
|
||||
}
|
||||
statedb.RemoveLiquidationPrice(orderBook, big.NewInt(2), orderBook, 2)
|
||||
mapData := statedb.GetAllLowerLiquidationPriceData(orderBook, big.NewInt(2))
|
||||
for price, lendingBooks := range mapData {
|
||||
for lendingBook, tradeIds := range lendingBooks {
|
||||
for _, tradeId := range tradeIds {
|
||||
fmt.Println("price", price, "lendingBook", lendingBook.Hex(), "tradeId", tradeId.Hex())
|
||||
}
|
||||
}
|
||||
}
|
||||
fmt.Println("mapData", mapData)
|
||||
liquidationPrice, liquidationData = statedb.GetHighestLiquidationPriceData(orderBook, big.NewInt(2))
|
||||
for lendingBook, tradingIds := range liquidationData {
|
||||
for _, tradingIdHash := range tradingIds {
|
||||
fmt.Println("liquidationPrice", liquidationPrice, "lendingBook", lendingBook.Hex(), "tradingIdHash", tradingIdHash.Hex())
|
||||
}
|
||||
}
|
||||
gotPrice := statedb.GetLastPrice(orderBook)
|
||||
if gotPrice.Cmp(price) != 0 {
|
||||
t.Fatalf("Error when get price save in database: got : %d , wanted : %d ", gotPrice, price)
|
||||
}
|
||||
fmt.Println(gotPrice)
|
||||
for i := 0; i < numberOrder; i++ {
|
||||
nonce := statedb.GetNonce(relayers[i])
|
||||
if nonce != uint64(1) {
|
||||
t.Fatalf("Error when get nonce save in database: got : %d , wanted : %d ", nonce, i)
|
||||
}
|
||||
}
|
||||
|
||||
minSell := uint64(math.MaxUint64)
|
||||
for price, amount := range mapPriceSell {
|
||||
data := statedb.GetVolume(orderBook, new(big.Int).SetUint64(price), Ask)
|
||||
if data.Uint64() != amount {
|
||||
t.Fatalf("Error when get volume save in database: price %d ,got : %d , wanted : %d ", price, data.Uint64(), amount)
|
||||
}
|
||||
if price < minSell {
|
||||
minSell = price
|
||||
}
|
||||
}
|
||||
maxBuy := uint64(0)
|
||||
for price, amount := range mapPriceBuy {
|
||||
data := statedb.GetVolume(orderBook, new(big.Int).SetUint64(price), Bid)
|
||||
if data.Uint64() != amount {
|
||||
t.Fatalf("Error when get volume save in database: price %d ,got : %d , wanted : %d ", price, data.Uint64(), amount)
|
||||
}
|
||||
if price > maxBuy {
|
||||
maxBuy = price
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(orderItems); i++ {
|
||||
amount := statedb.GetOrder(orderBook, common.BigToHash(new(big.Int).SetUint64(orderItems[i].OrderID))).Quantity
|
||||
if orderItems[i].Quantity.Cmp(amount) != 0 {
|
||||
t.Fatalf("Error when get amount save in database: orderId %d , orderType %s,got : %d , wanted : %d ", orderItems[i].OrderID, orderItems[i].Side, amount.Uint64(), orderItems[i].Quantity.Uint64())
|
||||
}
|
||||
}
|
||||
maxPrice, volumeMax := statedb.GetBestBidPrice(orderBook)
|
||||
minPrice, volumeMin := statedb.GetBestAskPrice(orderBook)
|
||||
fmt.Println("price", minPrice, volumeMin, maxPrice, volumeMax)
|
||||
|
||||
db.Close()
|
||||
}
|
||||
|
||||
func TestRevertStates(t *testing.T) {
|
||||
orderBook := common.StringToHash("BTC/XDC")
|
||||
numberOrder := 20
|
||||
orderItems := []OrderItem{}
|
||||
relayers := []common.Hash{}
|
||||
for i := 0; i < numberOrder; i++ {
|
||||
relayers = append(relayers, common.BigToHash(big.NewInt(int64(i))))
|
||||
id := new(big.Int).SetUint64(uint64(i) + 1)
|
||||
orderItems = append(orderItems, OrderItem{OrderID: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Price: big.NewInt(int64(2*i + 1)), Side: Ask, Signature: &Signature{V: 1, R: common.HexToHash("111111"), S: common.HexToHash("222222222222")}})
|
||||
orderItems = append(orderItems, OrderItem{OrderID: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Price: big.NewInt(int64(2*i + 1)), Side: Bid, Signature: &Signature{V: 1, R: common.HexToHash("3333333333"), S: common.HexToHash("22222222222222222")}})
|
||||
}
|
||||
// Create an empty statedb database
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
stateCache := NewDatabase(db)
|
||||
statedb, _ := New(common.Hash{}, stateCache)
|
||||
|
||||
// Update it with some exchanges
|
||||
for i := 0; i < numberOrder; i++ {
|
||||
statedb.SetNonce(relayers[i], uint64(1))
|
||||
}
|
||||
mapPriceSell := map[uint64]uint64{}
|
||||
mapPriceBuy := map[uint64]uint64{}
|
||||
|
||||
for i := 0; i < len(orderItems); i++ {
|
||||
amount := orderItems[i].Quantity.Uint64()
|
||||
orderIdHash := common.BigToHash(new(big.Int).SetUint64(orderItems[i].OrderID))
|
||||
statedb.InsertOrderItem(orderBook, orderIdHash, orderItems[i])
|
||||
|
||||
switch orderItems[i].Side {
|
||||
case Ask:
|
||||
old := mapPriceSell[amount]
|
||||
mapPriceSell[amount] = old + amount
|
||||
case Bid:
|
||||
old := mapPriceBuy[amount]
|
||||
mapPriceBuy[amount] = old + amount
|
||||
default:
|
||||
}
|
||||
|
||||
}
|
||||
root := statedb.IntermediateRoot()
|
||||
statedb.Commit()
|
||||
//err := stateCache.TrieDB().Commit(root, false)
|
||||
//if err != nil {
|
||||
// t.Errorf("Error when commit into database: %v", err)
|
||||
//}
|
||||
stateCache.TrieDB().Reference(root, common.Hash{})
|
||||
statedb, err := New(root, stateCache)
|
||||
if err != nil {
|
||||
t.Fatalf("Error when get trie in database: %s , err: %v", root.Hex(), err)
|
||||
}
|
||||
|
||||
orderIdHash := common.BigToHash(new(big.Int).SetUint64(orderItems[0].OrderID))
|
||||
order := statedb.GetOrder(orderBook, orderIdHash)
|
||||
// sub amount order
|
||||
wanted := statedb.GetVolume(orderBook, order.Price, order.Side)
|
||||
snap := statedb.Snapshot()
|
||||
statedb.SubAmountOrderItem(orderBook, orderIdHash, order.Price, order.Quantity, order.Side)
|
||||
statedb.RevertToSnapshot(snap)
|
||||
got := statedb.GetVolume(orderBook, order.Price, order.Side)
|
||||
if got.Cmp(wanted) != 0 {
|
||||
t.Fatalf(" err get volume price : %d after try revert snap shot , got : %d ,want : %d", order.Price, got, wanted)
|
||||
}
|
||||
// set nonce
|
||||
wantedNonce := statedb.GetNonce(relayers[1])
|
||||
snap = statedb.Snapshot()
|
||||
statedb.SetNonce(relayers[1], 0)
|
||||
statedb.RevertToSnapshot(snap)
|
||||
gotNonce := statedb.GetNonce(relayers[1])
|
||||
if wantedNonce != gotNonce {
|
||||
t.Fatalf(" err get nonce addr: %v after try revert snap shot , got : %d ,want : %d", relayers[1].Hex(), gotNonce, wantedNonce)
|
||||
}
|
||||
|
||||
// cancel order
|
||||
wantedOrder := statedb.GetOrder(orderBook, orderIdHash)
|
||||
snap = statedb.Snapshot()
|
||||
statedb.CancelOrder(orderBook, &wantedOrder)
|
||||
statedb.RevertToSnapshot(snap)
|
||||
gotOrder := statedb.GetOrder(orderBook, orderIdHash)
|
||||
if gotOrder.Quantity.Cmp(wantedOrder.Quantity) != 0 {
|
||||
t.Fatalf(" err cancel order info : %v after try revert snap shot , got : %v ,want : %v", orderIdHash.Hex(), gotOrder, wantedOrder)
|
||||
}
|
||||
|
||||
// insert order
|
||||
i := 2*numberOrder + 1
|
||||
id := new(big.Int).SetUint64(uint64(i) + 1)
|
||||
testOrder := OrderItem{OrderID: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Price: big.NewInt(int64(2*i + 1)), Side: Ask, Signature: &Signature{V: 1, R: common.HexToHash("111111"), S: common.HexToHash("222222222222")}}
|
||||
orderIdHash = common.BigToHash(new(big.Int).SetUint64(testOrder.OrderID))
|
||||
snap = statedb.Snapshot()
|
||||
statedb.InsertOrderItem(orderBook, orderIdHash, testOrder)
|
||||
statedb.RevertToSnapshot(snap)
|
||||
gotOrder = statedb.GetOrder(orderBook, orderIdHash)
|
||||
if gotOrder.Quantity.Cmp(EmptyOrder.Quantity) != 0 {
|
||||
t.Fatalf(" err insert order info : %v after try revert snap shot , got : %v ,want Empty Order", orderIdHash.Hex(), gotOrder)
|
||||
}
|
||||
// change price
|
||||
price := big.NewInt(10000)
|
||||
statedb.SetLastPrice(orderBook, price)
|
||||
snap = statedb.Snapshot()
|
||||
statedb.SetLastPrice(orderBook, big.NewInt(0))
|
||||
statedb.RevertToSnapshot(snap)
|
||||
gotPrice := statedb.GetLastPrice(orderBook)
|
||||
if gotPrice.Cmp(price) != 0 {
|
||||
t.Fatalf("Error when get price save in database: got : %d , wanted : %d ", gotPrice, price)
|
||||
}
|
||||
db.Close()
|
||||
}
|
||||
|
||||
func TestDumpState(t *testing.T) {
|
||||
orderBook := common.StringToHash("BTC/XDC")
|
||||
numberOrder := 5
|
||||
orderItems := []OrderItem{}
|
||||
for i := 0; i < numberOrder; i++ {
|
||||
id := new(big.Int).SetUint64(uint64(i) + 1)
|
||||
orderItems = append(orderItems, OrderItem{OrderID: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Price: big.NewInt(int64(2*i + 1)), Side: Ask, Signature: &Signature{V: 1, R: common.HexToHash("111111"), S: common.HexToHash("222222222222")}})
|
||||
orderItems = append(orderItems, OrderItem{OrderID: id.Uint64(), Quantity: big.NewInt(int64(2*i + 2)), Price: big.NewInt(int64(2*i + 2)), Side: Bid, Signature: &Signature{V: 1, R: common.HexToHash("3333333333"), S: common.HexToHash("22222222222222222")}})
|
||||
}
|
||||
// Create an empty statedb database
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
stateCache := NewDatabase(db)
|
||||
statedb, _ := New(common.Hash{}, stateCache)
|
||||
|
||||
for i := 0; i < len(orderItems); i++ {
|
||||
orderIdHash := common.BigToHash(new(big.Int).SetUint64(orderItems[i].OrderID))
|
||||
statedb.InsertOrderItem(orderBook, orderIdHash, orderItems[i])
|
||||
}
|
||||
bidTrie, _ := statedb.DumpAskTrie(orderBook)
|
||||
fmt.Println("bidTrie", bidTrie)
|
||||
root := statedb.IntermediateRoot()
|
||||
statedb.Commit()
|
||||
stateCache.TrieDB().Reference(root, common.Hash{})
|
||||
statedb, err := New(root, stateCache)
|
||||
if err != nil {
|
||||
t.Fatalf("Error when get trie in database: %s , err: %v", root.Hex(), err)
|
||||
}
|
||||
maxPrice, volumeMax := statedb.GetBestBidPrice(orderBook)
|
||||
minPrice, volumeMin := statedb.GetBestAskPrice(orderBook)
|
||||
fmt.Println("price", minPrice, volumeMin, maxPrice, volumeMax)
|
||||
|
||||
bidTrie, _ = statedb.DumpBidTrie(orderBook)
|
||||
|
||||
fmt.Println("bidTrie", bidTrie)
|
||||
db.Close()
|
||||
}
|
||||
143
XDCx/tradingstate/trade.go
Normal file
143
XDCx/tradingstate/trade.go
Normal file
|
|
@ -0,0 +1,143 @@
|
|||
package tradingstate
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
const (
|
||||
TradeStatusSuccess = "SUCCESS"
|
||||
|
||||
TradeTakerOrderHash = "takerOrderHash"
|
||||
TradeMakerOrderHash = "makerOrderHash"
|
||||
TradeTimestamp = "timestamp"
|
||||
TradeQuantity = "quantity"
|
||||
TradeMakerExchange = "makerExAddr"
|
||||
TradeMaker = "uAddr"
|
||||
TradeBaseToken = "bToken"
|
||||
TradeQuoteToken = "qToken"
|
||||
TradePrice = "tradedPrice"
|
||||
MakerOrderType = "makerOrderType"
|
||||
MakerFee = "makerFee"
|
||||
TakerFee = "takerFee"
|
||||
)
|
||||
|
||||
type Trade struct {
|
||||
Taker common.Address `json:"taker" bson:"taker"`
|
||||
Maker common.Address `json:"maker" bson:"maker"`
|
||||
BaseToken common.Address `json:"baseToken" bson:"baseToken"`
|
||||
QuoteToken common.Address `json:"quoteToken" bson:"quoteToken"`
|
||||
MakerOrderHash common.Hash `json:"makerOrderHash" bson:"makerOrderHash"`
|
||||
TakerOrderHash common.Hash `json:"takerOrderHash" bson:"takerOrderHash"`
|
||||
MakerExchange common.Address `json:"makerExchange" bson:"makerExchange"`
|
||||
TakerExchange common.Address `json:"takerExchange" bson:"takerExchange"`
|
||||
Hash common.Hash `json:"hash" bson:"hash"`
|
||||
TxHash common.Hash `json:"txHash" bson:"txHash"`
|
||||
PricePoint *big.Int `json:"pricepoint" bson:"pricepoint"`
|
||||
Amount *big.Int `json:"amount" bson:"amount"`
|
||||
MakeFee *big.Int `json:"makeFee" bson:"makeFee"`
|
||||
TakeFee *big.Int `json:"takeFee" bson:"takeFee"`
|
||||
Status string `json:"status" bson:"status"`
|
||||
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
|
||||
TakerOrderSide string `json:"takerOrderSide" bson:"takerOrderSide"`
|
||||
TakerOrderType string `json:"takerOrderType" bson:"takerOrderType"`
|
||||
MakerOrderType string `json:"makerOrderType" bson:"makerOrderType"`
|
||||
}
|
||||
|
||||
type TradeBSON struct {
|
||||
Taker string `json:"taker" bson:"taker"`
|
||||
Maker string `json:"maker" bson:"maker"`
|
||||
BaseToken string `json:"baseToken" bson:"baseToken"`
|
||||
QuoteToken string `json:"quoteToken" bson:"quoteToken"`
|
||||
MakerOrderHash string `json:"makerOrderHash" bson:"makerOrderHash"`
|
||||
TakerOrderHash string `json:"takerOrderHash" bson:"takerOrderHash"`
|
||||
MakerExchange string `json:"makerExchange" bson:"makerExchange"`
|
||||
TakerExchange string `json:"takerExchange" bson:"takerExchange"`
|
||||
Hash string `json:"hash" bson:"hash"`
|
||||
TxHash string `json:"txHash" bson:"txHash"`
|
||||
Amount string `json:"amount" bson:"amount"`
|
||||
MakeFee string `json:"makeFee" bson:"makeFee"`
|
||||
TakeFee string `json:"takeFee" bson:"takeFee"`
|
||||
PricePoint string `json:"pricepoint" bson:"pricepoint"`
|
||||
Status string `json:"status" bson:"status"`
|
||||
CreatedAt time.Time `json:"createdAt" bson:"createdAt"`
|
||||
UpdatedAt time.Time `json:"updatedAt" bson:"updatedAt"`
|
||||
TakerOrderSide string `json:"takerOrderSide" bson:"takerOrderSide"`
|
||||
TakerOrderType string `json:"takerOrderType" bson:"takerOrderType"`
|
||||
MakerOrderType string `json:"makerOrderType" bson:"makerOrderType"`
|
||||
}
|
||||
|
||||
func (t *Trade) GetBSON() (interface{}, error) {
|
||||
tr := TradeBSON{
|
||||
Maker: t.Maker.Hex(),
|
||||
Taker: t.Taker.Hex(),
|
||||
BaseToken: t.BaseToken.Hex(),
|
||||
QuoteToken: t.QuoteToken.Hex(),
|
||||
MakerOrderHash: t.MakerOrderHash.Hex(),
|
||||
TakerOrderHash: t.TakerOrderHash.Hex(),
|
||||
MakerExchange: t.MakerExchange.Hex(),
|
||||
TakerExchange: t.TakerExchange.Hex(),
|
||||
Hash: t.Hash.Hex(),
|
||||
TxHash: t.TxHash.Hex(),
|
||||
CreatedAt: t.CreatedAt,
|
||||
UpdatedAt: t.UpdatedAt,
|
||||
PricePoint: t.PricePoint.String(),
|
||||
Status: t.Status,
|
||||
Amount: t.Amount.String(),
|
||||
MakeFee: t.MakeFee.String(),
|
||||
TakeFee: t.TakeFee.String(),
|
||||
TakerOrderSide: t.TakerOrderSide,
|
||||
TakerOrderType: t.TakerOrderType,
|
||||
MakerOrderType: t.MakerOrderType,
|
||||
}
|
||||
|
||||
return tr, nil
|
||||
}
|
||||
|
||||
func (t *Trade) SetBSON(raw bson.Raw) error {
|
||||
decoded := &TradeBSON{}
|
||||
|
||||
err := raw.Unmarshal(decoded)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
t.Taker = common.HexToAddress(decoded.Taker)
|
||||
t.Maker = common.HexToAddress(decoded.Maker)
|
||||
t.BaseToken = common.HexToAddress(decoded.BaseToken)
|
||||
t.QuoteToken = common.HexToAddress(decoded.QuoteToken)
|
||||
t.MakerOrderHash = common.HexToHash(decoded.MakerOrderHash)
|
||||
t.TakerOrderHash = common.HexToHash(decoded.TakerOrderHash)
|
||||
t.MakerExchange = common.HexToAddress(decoded.MakerExchange)
|
||||
t.TakerExchange = common.HexToAddress(decoded.TakerExchange)
|
||||
t.Hash = common.HexToHash(decoded.Hash)
|
||||
t.TxHash = common.HexToHash(decoded.TxHash)
|
||||
t.Status = decoded.Status
|
||||
t.Amount = ToBigInt(decoded.Amount)
|
||||
t.PricePoint = ToBigInt(decoded.PricePoint)
|
||||
|
||||
t.MakeFee = ToBigInt(decoded.MakeFee)
|
||||
t.TakeFee = ToBigInt(decoded.TakeFee)
|
||||
|
||||
t.CreatedAt = decoded.CreatedAt
|
||||
t.UpdatedAt = decoded.UpdatedAt
|
||||
t.TakerOrderSide = decoded.TakerOrderSide
|
||||
t.TakerOrderType = decoded.TakerOrderType
|
||||
t.MakerOrderType = decoded.MakerOrderType
|
||||
return nil
|
||||
}
|
||||
|
||||
// ComputeHash returns hashes the trade
|
||||
// The OrderHash, Amount, Taker and TradeNonce attributes must be
|
||||
// set before attempting to compute the trade orderBookHash
|
||||
func (t *Trade) ComputeHash() common.Hash {
|
||||
sha := sha3.NewKeccak256()
|
||||
sha.Write(t.MakerOrderHash.Bytes())
|
||||
sha.Write(t.TakerOrderHash.Bytes())
|
||||
return common.BytesToHash(sha.Sum(nil))
|
||||
}
|
||||
59
XDCxDAO/interfaces.go
Normal file
59
XDCxDAO/interfaces.go
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
// Copyright 2019 The XDPoSChain Authors
|
||||
// This file is part of the Core XDPoSChain infrastructure
|
||||
// https://XDPoSChain.com
|
||||
// Package XDCxDAO provides an interface to work with XDCx database, including leveldb for masternode and mongodb for SDK node
|
||||
package XDCxDAO
|
||||
|
||||
import (
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||
)
|
||||
|
||||
const defaultCacheLimit = 1024
|
||||
|
||||
type XDCXDAO interface {
|
||||
// for both leveldb and mongodb
|
||||
IsEmptyKey(key []byte) bool
|
||||
Close() error
|
||||
|
||||
// mongodb methods
|
||||
HasObject(hash common.Hash, val interface{}) (bool, error)
|
||||
GetObject(hash common.Hash, val interface{}) (interface{}, error)
|
||||
PutObject(hash common.Hash, val interface{}) error
|
||||
DeleteObject(hash common.Hash, val interface{}) error // won't return error if key not found
|
||||
GetListItemByTxHash(txhash common.Hash, val interface{}) interface{}
|
||||
GetListItemByHashes(hashes []string, val interface{}) interface{}
|
||||
DeleteItemByTxHash(txhash common.Hash, val interface{})
|
||||
|
||||
// basic XDCx
|
||||
InitBulk()
|
||||
CommitBulk() error
|
||||
|
||||
// XDCx lending
|
||||
InitLendingBulk()
|
||||
CommitLendingBulk() error
|
||||
|
||||
// leveldb methods
|
||||
Put(key []byte, value []byte) error
|
||||
Get(key []byte) ([]byte, error)
|
||||
Has(key []byte) (bool, error)
|
||||
Delete(key []byte) error
|
||||
NewBatch() ethdb.Batch
|
||||
HasAncient(kind string, number uint64) (bool, error)
|
||||
Ancient(kind string, number uint64) ([]byte, error)
|
||||
Ancients() (uint64, error)
|
||||
AncientSize(kind string) (uint64, error)
|
||||
AppendAncient(number uint64, hash, header, body, receipt, td []byte) error
|
||||
TruncateAncients(n uint64) error
|
||||
Sync() error
|
||||
NewIterator(prefix []byte, start []byte) ethdb.Iterator
|
||||
|
||||
Stat(property string) (string, error)
|
||||
Compact(start []byte, limit []byte) error
|
||||
}
|
||||
|
||||
// use alloc to prevent reference manipulation
|
||||
func EmptyKey() []byte {
|
||||
key := make([]byte, common.HashLength)
|
||||
return key
|
||||
}
|
||||
183
XDCxDAO/leveldb.go
Normal file
183
XDCxDAO/leveldb.go
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
package XDCxDAO
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"errors"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"sync"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
type BatchItem struct {
|
||||
Value interface{}
|
||||
}
|
||||
|
||||
type BatchDatabase struct {
|
||||
db ethdb.Database
|
||||
emptyKey []byte
|
||||
cacheItems *lru.Cache // Cache for reading
|
||||
lock sync.RWMutex
|
||||
cacheLimit int
|
||||
Debug bool
|
||||
}
|
||||
|
||||
// NewBatchDatabase use rlp as encoding
|
||||
func NewBatchDatabase(datadir string, cacheLimit int) *BatchDatabase {
|
||||
return NewBatchDatabaseWithEncode(datadir, cacheLimit)
|
||||
}
|
||||
|
||||
// batchdatabase is a fast cache db to retrieve in-mem object
|
||||
func NewBatchDatabaseWithEncode(datadir string, cacheLimit int) *BatchDatabase {
|
||||
db, err := rawdb.NewLevelDBDatabase(datadir, 128, 1024, "")
|
||||
if err != nil {
|
||||
log.Error("Can't create new DB", "error", err)
|
||||
return nil
|
||||
}
|
||||
itemCacheLimit := defaultCacheLimit
|
||||
if cacheLimit > 0 {
|
||||
itemCacheLimit = cacheLimit
|
||||
}
|
||||
|
||||
cacheItems, _ := lru.New(itemCacheLimit)
|
||||
|
||||
batchDB := &BatchDatabase{
|
||||
db: db,
|
||||
cacheItems: cacheItems,
|
||||
emptyKey: EmptyKey(), // pre alloc for comparison
|
||||
cacheLimit: itemCacheLimit,
|
||||
}
|
||||
|
||||
return batchDB
|
||||
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) IsEmptyKey(key []byte) bool {
|
||||
return key == nil || len(key) == 0 || bytes.Equal(key, db.emptyKey)
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) getCacheKey(key []byte) string {
|
||||
return hex.EncodeToString(key)
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) HasObject(hash common.Hash, val interface{}) (bool, error) {
|
||||
// for mongodb only
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) GetObject(hash common.Hash, val interface{}) (interface{}, error) {
|
||||
// for mongodb only
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) PutObject(hash common.Hash, val interface{}) error {
|
||||
// for mongodb only
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) DeleteObject(hash common.Hash, val interface{}) error {
|
||||
// for mongodb only
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) Put(key []byte, val []byte) error {
|
||||
return db.db.Put(key, val)
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) Delete(key []byte) error {
|
||||
return db.db.Delete(key)
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) Has(key []byte) (bool, error) {
|
||||
return db.db.Has(key)
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) Get(key []byte) ([]byte, error) {
|
||||
return db.db.Get(key)
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) Close() error {
|
||||
return db.db.Close()
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) NewBatch() ethdb.Batch {
|
||||
return db.db.NewBatch()
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) DeleteItemByTxHash(txhash common.Hash, val interface{}) {
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) GetListItemByTxHash(txhash common.Hash, val interface{}) interface{} {
|
||||
return []interface{}{}
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) GetListItemByHashes(hashes []string, val interface{}) interface{} {
|
||||
return []interface{}{}
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) InitBulk() {
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) CommitBulk() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) InitLendingBulk() {
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) CommitLendingBulk() error {
|
||||
return nil
|
||||
}
|
||||
|
||||
var errNotSupported = errors.New("this operation is not supported")
|
||||
|
||||
// HasAncient returns an error as we don't have a backing chain freezer.
|
||||
func (db *BatchDatabase) HasAncient(kind string, number uint64) (bool, error) {
|
||||
return false, errNotSupported
|
||||
}
|
||||
|
||||
// Ancient returns an error as we don't have a backing chain freezer.
|
||||
func (db *BatchDatabase) Ancient(kind string, number uint64) ([]byte, error) {
|
||||
return nil, errNotSupported
|
||||
}
|
||||
|
||||
// Ancients returns an error as we don't have a backing chain freezer.
|
||||
func (db *BatchDatabase) Ancients() (uint64, error) {
|
||||
return 0, errNotSupported
|
||||
}
|
||||
|
||||
// AncientSize returns an error as we don't have a backing chain freezer.
|
||||
func (db *BatchDatabase) AncientSize(kind string) (uint64, error) {
|
||||
return 0, errNotSupported
|
||||
}
|
||||
|
||||
// AppendAncient returns an error as we don't have a backing chain freezer.
|
||||
func (db *BatchDatabase) AppendAncient(number uint64, hash, header, body, receipts, td []byte) error {
|
||||
return errNotSupported
|
||||
}
|
||||
|
||||
// TruncateAncients returns an error as we don't have a backing chain freezer.
|
||||
func (db *BatchDatabase) TruncateAncients(items uint64) error {
|
||||
return errNotSupported
|
||||
}
|
||||
|
||||
// Sync returns an error as we don't have a backing chain freezer.
|
||||
func (db *BatchDatabase) Sync() error {
|
||||
return errNotSupported
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) NewIterator(prefix []byte, start []byte) ethdb.Iterator {
|
||||
return db.NewIterator(prefix, start)
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) Stat(property string) (string, error) {
|
||||
return db.Stat(property)
|
||||
}
|
||||
|
||||
func (db *BatchDatabase) Compact(start []byte, limit []byte) error {
|
||||
return db.Compact(start, limit)
|
||||
}
|
||||
935
XDCxDAO/mongodb.go
Normal file
935
XDCxDAO/mongodb.go
Normal file
|
|
@ -0,0 +1,935 @@
|
|||
package XDCxDAO
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/hex"
|
||||
"fmt"
|
||||
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
|
||||
"github.com/XinFinOrg/XDPoSChain/XDCxlending/lendingstate"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/globalsign/mgo"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
"strings"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
ordersCollection = "orders"
|
||||
tradesCollection = "trades"
|
||||
lendingItemsCollection = "lending_items"
|
||||
lendingTradesCollection = "lending_trades"
|
||||
lendingTopUpCollection = "lending_topups"
|
||||
lendingRepayCollection = "lending_repays"
|
||||
lendingRecallCollection = "lending_recalls"
|
||||
epochPriceCollection = "epoch_prices"
|
||||
)
|
||||
|
||||
type MongoDatabase struct {
|
||||
Session *mgo.Session
|
||||
dbName string
|
||||
emptyKey []byte
|
||||
cacheItems *lru.Cache // Cache for reading
|
||||
orderBulk *mgo.Bulk
|
||||
tradeBulk *mgo.Bulk
|
||||
epochPriceBulk *mgo.Bulk
|
||||
lendingItemBulk *mgo.Bulk
|
||||
topUpBulk *mgo.Bulk
|
||||
recallBulk *mgo.Bulk
|
||||
repayBulk *mgo.Bulk
|
||||
lendingTradeBulk *mgo.Bulk
|
||||
}
|
||||
|
||||
// InitSession initializes a new session with mongodb
|
||||
func NewMongoDatabase(session *mgo.Session, dbName string, mongoURL string, replicaSetName string, cacheLimit int) (*MongoDatabase, error) {
|
||||
if session == nil {
|
||||
// in case of multiple database instances
|
||||
hosts := strings.Split(mongoURL, ",")
|
||||
dbInfo := &mgo.DialInfo{
|
||||
Addrs: hosts,
|
||||
Database: dbName,
|
||||
ReplicaSetName: replicaSetName,
|
||||
Timeout: 30 * time.Second,
|
||||
}
|
||||
ns, err := mgo.DialWithInfo(dbInfo)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
session = ns
|
||||
}
|
||||
itemCacheLimit := defaultCacheLimit
|
||||
if cacheLimit > 0 {
|
||||
itemCacheLimit = cacheLimit
|
||||
}
|
||||
cacheItems, _ := lru.New(itemCacheLimit)
|
||||
|
||||
db := &MongoDatabase{
|
||||
Session: session,
|
||||
dbName: dbName,
|
||||
cacheItems: cacheItems,
|
||||
}
|
||||
if err := db.EnsureIndexes(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return db, nil
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) IsEmptyKey(key []byte) bool {
|
||||
return key == nil || len(key) == 0 || bytes.Equal(key, db.emptyKey)
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) getCacheKey(key []byte) string {
|
||||
return hex.EncodeToString(key)
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) HasObject(hash common.Hash, val interface{}) (bool, error) {
|
||||
if db.IsEmptyKey(hash.Bytes()) {
|
||||
return false, nil
|
||||
}
|
||||
cacheKey := db.getCacheKey(hash.Bytes())
|
||||
if db.cacheItems.Contains(cacheKey) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
sc := db.Session.Copy()
|
||||
defer sc.Close()
|
||||
var (
|
||||
count int
|
||||
err error
|
||||
)
|
||||
query := bson.M{"hash": hash.Hex()}
|
||||
switch val.(type) {
|
||||
case *tradingstate.OrderItem:
|
||||
// Find key in ordersCollection collection
|
||||
count, err = sc.DB(db.dbName).C(ordersCollection).Find(query).Limit(1).Count()
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if count == 1 {
|
||||
return true, nil
|
||||
}
|
||||
case *tradingstate.Trade:
|
||||
// Find key in tradesCollection collection
|
||||
count, err = sc.DB(db.dbName).C(tradesCollection).Find(query).Limit(1).Count()
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if count == 1 {
|
||||
return true, nil
|
||||
}
|
||||
case *lendingstate.LendingItem:
|
||||
// Find key in lendingItemsCollection collection
|
||||
item := val.(*lendingstate.LendingItem)
|
||||
switch item.Type {
|
||||
case lendingstate.Repay:
|
||||
count, err = sc.DB(db.dbName).C(lendingRepayCollection).Find(query).Limit(1).Count()
|
||||
case lendingstate.TopUp:
|
||||
count, err = sc.DB(db.dbName).C(lendingTopUpCollection).Find(query).Limit(1).Count()
|
||||
case lendingstate.Recall:
|
||||
count, err = sc.DB(db.dbName).C(lendingRecallCollection).Find(query).Limit(1).Count()
|
||||
default:
|
||||
count, err = sc.DB(db.dbName).C(lendingItemsCollection).Find(query).Limit(1).Count()
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if count == 1 {
|
||||
return true, nil
|
||||
}
|
||||
case *lendingstate.LendingTrade:
|
||||
// Find key in lendingTradesCollection collection
|
||||
count, err = sc.DB(db.dbName).C(lendingTradesCollection).Find(query).Limit(1).Count()
|
||||
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
if count == 1 {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
}
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) GetObject(hash common.Hash, val interface{}) (interface{}, error) {
|
||||
|
||||
if db.IsEmptyKey(hash.Bytes()) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
cacheKey := db.getCacheKey(hash.Bytes())
|
||||
if cached, ok := db.cacheItems.Get(cacheKey); ok {
|
||||
return cached, nil
|
||||
} else {
|
||||
sc := db.Session.Copy()
|
||||
defer sc.Close()
|
||||
|
||||
query := bson.M{"hash": hash.Hex()}
|
||||
|
||||
switch val.(type) {
|
||||
case *tradingstate.OrderItem:
|
||||
var oi *tradingstate.OrderItem
|
||||
err := sc.DB(db.dbName).C(ordersCollection).Find(query).One(&oi)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.cacheItems.Add(cacheKey, oi)
|
||||
return oi, nil
|
||||
case *tradingstate.Trade:
|
||||
var t *tradingstate.Trade
|
||||
err := sc.DB(db.dbName).C(tradesCollection).Find(query).One(&t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.cacheItems.Add(cacheKey, t)
|
||||
return t, nil
|
||||
case *lendingstate.LendingItem:
|
||||
var li *lendingstate.LendingItem
|
||||
var err error
|
||||
item := val.(*lendingstate.LendingItem)
|
||||
switch item.Type {
|
||||
case lendingstate.Repay:
|
||||
err = sc.DB(db.dbName).C(lendingRepayCollection).Find(query).One(&li)
|
||||
case lendingstate.TopUp:
|
||||
err = sc.DB(db.dbName).C(lendingTopUpCollection).Find(query).One(&li)
|
||||
case lendingstate.Recall:
|
||||
err = sc.DB(db.dbName).C(lendingRecallCollection).Find(query).One(&li)
|
||||
default:
|
||||
err = sc.DB(db.dbName).C(lendingItemsCollection).Find(query).One(&li)
|
||||
}
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.cacheItems.Add(cacheKey, li)
|
||||
return li, nil
|
||||
case *lendingstate.LendingTrade:
|
||||
var t *lendingstate.LendingTrade
|
||||
err := sc.DB(db.dbName).C(lendingTradesCollection).Find(query).One(&t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
db.cacheItems.Add(cacheKey, t)
|
||||
return t, nil
|
||||
default:
|
||||
return nil, nil
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) PutObject(hash common.Hash, val interface{}) error {
|
||||
cacheKey := db.getCacheKey(hash.Bytes())
|
||||
db.cacheItems.Add(cacheKey, val)
|
||||
|
||||
switch val.(type) {
|
||||
case *tradingstate.Trade:
|
||||
// PutObject trade into tradesCollection collection
|
||||
db.tradeBulk.Insert(val.(*tradingstate.Trade))
|
||||
case *tradingstate.OrderItem:
|
||||
// PutObject order into ordersCollection collection
|
||||
o := val.(*tradingstate.OrderItem)
|
||||
if o.Status == tradingstate.OrderStatusOpen {
|
||||
db.orderBulk.Insert(o)
|
||||
} else {
|
||||
query := bson.M{"hash": o.Hash.Hex()}
|
||||
db.orderBulk.Upsert(query, o)
|
||||
}
|
||||
return nil
|
||||
case *tradingstate.EpochPriceItem:
|
||||
item := val.(*tradingstate.EpochPriceItem)
|
||||
query := bson.M{"hash": item.Hash.Hex()}
|
||||
db.epochPriceBulk.Upsert(query, item)
|
||||
return nil
|
||||
case *lendingstate.LendingTrade:
|
||||
lt := val.(*lendingstate.LendingTrade)
|
||||
// PutObject LendingTrade into tradesCollection collection
|
||||
if existed, err := db.HasObject(hash, val); err == nil && existed {
|
||||
query := bson.M{"hash": lt.Hash.Hex()}
|
||||
db.lendingTradeBulk.Upsert(query, lt)
|
||||
} else {
|
||||
db.lendingTradeBulk.Insert(lt)
|
||||
}
|
||||
case *lendingstate.LendingItem:
|
||||
// PutObject order into ordersCollection collection
|
||||
li := val.(*lendingstate.LendingItem)
|
||||
switch li.Type {
|
||||
case lendingstate.Repay:
|
||||
if li.Status != lendingstate.LendingStatusReject {
|
||||
li.Status = lendingstate.Repay
|
||||
}
|
||||
db.repayBulk.Insert(li)
|
||||
return nil
|
||||
case lendingstate.TopUp:
|
||||
if li.Status != lendingstate.LendingStatusReject {
|
||||
li.Status = lendingstate.TopUp
|
||||
}
|
||||
db.topUpBulk.Insert(li)
|
||||
return nil
|
||||
case lendingstate.Recall:
|
||||
if li.Status != lendingstate.LendingStatusReject {
|
||||
li.Status = lendingstate.Recall
|
||||
}
|
||||
db.recallBulk.Insert(li)
|
||||
return nil
|
||||
default:
|
||||
if li.Status == lendingstate.LendingStatusOpen {
|
||||
db.lendingItemBulk.Insert(li)
|
||||
} else {
|
||||
query := bson.M{"hash": li.Hash.Hex()}
|
||||
db.lendingItemBulk.Upsert(query, li)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
default:
|
||||
log.Error("PutObject: unknown type of object", "val", val)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) DeleteObject(hash common.Hash, val interface{}) error {
|
||||
cacheKey := db.getCacheKey(hash.Bytes())
|
||||
db.cacheItems.Remove(cacheKey)
|
||||
|
||||
sc := db.Session.Copy()
|
||||
defer sc.Close()
|
||||
|
||||
query := bson.M{"hash": hash.Hex()}
|
||||
|
||||
found, err := db.HasObject(hash, val)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if found {
|
||||
var err error
|
||||
switch val.(type) {
|
||||
case *tradingstate.OrderItem:
|
||||
err = sc.DB(db.dbName).C(ordersCollection).Remove(query)
|
||||
if err != nil && err != mgo.ErrNotFound {
|
||||
return fmt.Errorf("failed to delete orderItem. Err: %v", err)
|
||||
}
|
||||
case *tradingstate.Trade:
|
||||
err = sc.DB(db.dbName).C(tradesCollection).Remove(query)
|
||||
if err != nil && err != mgo.ErrNotFound {
|
||||
return fmt.Errorf("failed to delete XDCx trade. Err: %v", err)
|
||||
}
|
||||
case *lendingstate.LendingItem:
|
||||
item := val.(*lendingstate.LendingItem)
|
||||
switch item.Type {
|
||||
case lendingstate.Repay:
|
||||
err = sc.DB(db.dbName).C(lendingRepayCollection).Remove(query)
|
||||
case lendingstate.TopUp:
|
||||
err = sc.DB(db.dbName).C(lendingTopUpCollection).Remove(query)
|
||||
case lendingstate.Recall:
|
||||
err = sc.DB(db.dbName).C(lendingRecallCollection).Remove(query)
|
||||
default:
|
||||
err = sc.DB(db.dbName).C(lendingItemsCollection).Remove(query)
|
||||
}
|
||||
if err != nil && err != mgo.ErrNotFound {
|
||||
return fmt.Errorf("failed to delete lendingItem. Err: %v", err)
|
||||
}
|
||||
case *lendingstate.LendingTrade:
|
||||
err = sc.DB(db.dbName).C(lendingTradesCollection).Remove(query)
|
||||
if err != nil && err != mgo.ErrNotFound {
|
||||
return fmt.Errorf("failed to delete lendingTrade. Err: %v", err)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) InitBulk() {
|
||||
sc := db.Session
|
||||
db.orderBulk = sc.DB(db.dbName).C(ordersCollection).Bulk()
|
||||
db.tradeBulk = sc.DB(db.dbName).C(tradesCollection).Bulk()
|
||||
db.epochPriceBulk = sc.DB(db.dbName).C(epochPriceCollection).Bulk()
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) InitLendingBulk() {
|
||||
sc := db.Session
|
||||
db.lendingItemBulk = sc.DB(db.dbName).C(lendingItemsCollection).Bulk()
|
||||
db.lendingTradeBulk = sc.DB(db.dbName).C(lendingTradesCollection).Bulk()
|
||||
db.topUpBulk = sc.DB(db.dbName).C(lendingTopUpCollection).Bulk()
|
||||
db.repayBulk = sc.DB(db.dbName).C(lendingRepayCollection).Bulk()
|
||||
db.recallBulk = sc.DB(db.dbName).C(lendingRecallCollection).Bulk()
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) CommitBulk() error {
|
||||
if _, err := db.orderBulk.Run(); err != nil && !mgo.IsDup(err) {
|
||||
return err
|
||||
}
|
||||
if _, err := db.tradeBulk.Run(); err != nil && !mgo.IsDup(err) {
|
||||
return err
|
||||
}
|
||||
if _, err := db.epochPriceBulk.Run(); err != nil && !mgo.IsDup(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) CommitLendingBulk() error {
|
||||
if _, err := db.lendingItemBulk.Run(); err != nil && !mgo.IsDup(err) {
|
||||
return err
|
||||
}
|
||||
if _, err := db.lendingTradeBulk.Run(); err != nil && !mgo.IsDup(err) {
|
||||
return err
|
||||
}
|
||||
if _, err := db.topUpBulk.Run(); err != nil && !mgo.IsDup(err) {
|
||||
return err
|
||||
}
|
||||
if _, err := db.repayBulk.Run(); err != nil && !mgo.IsDup(err) {
|
||||
return err
|
||||
}
|
||||
if _, err := db.recallBulk.Run(); err != nil && !mgo.IsDup(err) {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) Put(key []byte, val []byte) error {
|
||||
// for levelDB only
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) Delete(key []byte) error {
|
||||
// for levelDB only
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) Has(key []byte) (bool, error) {
|
||||
// for levelDB only
|
||||
return false, nil
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) Get(key []byte) ([]byte, error) {
|
||||
// for levelDB only
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) DeleteItemByTxHash(txhash common.Hash, val interface{}) {
|
||||
sc := db.Session.Copy()
|
||||
defer sc.Close()
|
||||
|
||||
query := bson.M{"txHash": txhash.Hex()}
|
||||
switch val.(type) {
|
||||
case *tradingstate.OrderItem:
|
||||
if err := sc.DB(db.dbName).C(ordersCollection).Remove(query); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("DeleteItemByTxHash: failed to delete order", "txhash", txhash, "err", err)
|
||||
}
|
||||
case *tradingstate.Trade:
|
||||
if err := sc.DB(db.dbName).C(tradesCollection).Remove(query); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("DeleteItemByTxHash: failed to delete trade", "txhash", txhash, "err", err)
|
||||
}
|
||||
case *lendingstate.LendingItem:
|
||||
item := val.(*lendingstate.LendingItem)
|
||||
switch item.Type {
|
||||
case lendingstate.Repay:
|
||||
if err := sc.DB(db.dbName).C(lendingRepayCollection).Remove(query); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("DeleteItemByTxHash: failed to delete repayItem", "txhash", txhash, "err", err)
|
||||
}
|
||||
return
|
||||
case lendingstate.TopUp:
|
||||
if err := sc.DB(db.dbName).C(lendingTopUpCollection).Remove(query); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("DeleteItemByTxHash: failed to delete topupItem", "txhash", txhash, "err", err)
|
||||
}
|
||||
return
|
||||
case lendingstate.Recall:
|
||||
if err := sc.DB(db.dbName).C(lendingRecallCollection).Remove(query); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("DeleteItemByTxHash: failed to delete recallItem", "txhash", txhash, "err", err)
|
||||
}
|
||||
return
|
||||
default:
|
||||
if err := sc.DB(db.dbName).C(lendingItemsCollection).Remove(query); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("DeleteItemByTxHash: failed to delete lendingItem", "txhash", txhash, "err", err)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
case *lendingstate.LendingTrade:
|
||||
if err := sc.DB(db.dbName).C(lendingTradesCollection).Remove(query); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("DeleteItemByTxHash: failed to delete lendingTrade", "txhash", txhash, "err", err)
|
||||
}
|
||||
default:
|
||||
log.Error("DeleteItemByTxHash: Unknown object type", "txhash", txhash, "object", val)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) GetListItemByTxHash(txhash common.Hash, val interface{}) interface{} {
|
||||
sc := db.Session.Copy()
|
||||
defer sc.Close()
|
||||
|
||||
query := bson.M{"txHash": txhash.Hex()}
|
||||
switch val.(type) {
|
||||
case *tradingstate.OrderItem:
|
||||
result := []*tradingstate.OrderItem{}
|
||||
if err := sc.DB(db.dbName).C(ordersCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("failed to GetListItemByTxHash (orders)", "err", err, "Txhash", txhash)
|
||||
}
|
||||
return result
|
||||
case *tradingstate.Trade:
|
||||
result := []*tradingstate.Trade{}
|
||||
if err := sc.DB(db.dbName).C(tradesCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("failed to GetListItemByTxHash (trades)", "err", err, "Txhash", txhash)
|
||||
}
|
||||
return result
|
||||
case *lendingstate.LendingItem:
|
||||
item := val.(*lendingstate.LendingItem)
|
||||
result := []*lendingstate.LendingItem{}
|
||||
switch item.Type {
|
||||
case lendingstate.Repay:
|
||||
if err := sc.DB(db.dbName).C(lendingRepayCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("failed to GetListItemByTxHash (repayItems)", "err", err, "txhash", txhash)
|
||||
}
|
||||
return result
|
||||
case lendingstate.TopUp:
|
||||
if err := sc.DB(db.dbName).C(lendingTopUpCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("failed to GetListItemByTxHash (topupItems)", "err", err, "txhash", txhash)
|
||||
}
|
||||
return result
|
||||
case lendingstate.Recall:
|
||||
if err := sc.DB(db.dbName).C(lendingRecallCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("failed to GetListItemByTxHash (recallItems)", "err", err, "txhash", txhash)
|
||||
}
|
||||
return result
|
||||
default:
|
||||
if err := sc.DB(db.dbName).C(lendingItemsCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("failed to GetListItemByTxHash (lendingItems)", "err", err, "txhash", txhash)
|
||||
}
|
||||
return result
|
||||
}
|
||||
case *lendingstate.LendingTrade:
|
||||
result := []*lendingstate.LendingTrade{}
|
||||
if err := sc.DB(db.dbName).C(lendingTradesCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("failed to GetListItemByTxHash (lendingTrades)", "err", err, "Txhash", txhash)
|
||||
}
|
||||
return result
|
||||
default:
|
||||
log.Error("GetListItemByTxHash: Unknown object type", "txhash", txhash, "object", val)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) GetListItemByHashes(hashes []string, val interface{}) interface{} {
|
||||
sc := db.Session.Copy()
|
||||
defer sc.Close()
|
||||
|
||||
query := bson.M{"hash": bson.M{"$in": hashes}}
|
||||
|
||||
switch val.(type) {
|
||||
case *tradingstate.OrderItem:
|
||||
result := []*tradingstate.OrderItem{}
|
||||
if err := sc.DB(db.dbName).C(ordersCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("failed to GetListItemByHashes (orders)", "err", err, "hashes", hashes)
|
||||
}
|
||||
return result
|
||||
case *tradingstate.Trade:
|
||||
result := []*tradingstate.Trade{}
|
||||
if err := sc.DB(db.dbName).C(tradesCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("failed to GetListItemByHashes (trades)", "err", err, "hashes", hashes)
|
||||
}
|
||||
return result
|
||||
case *lendingstate.LendingItem:
|
||||
item := val.(*lendingstate.LendingItem)
|
||||
result := []*lendingstate.LendingItem{}
|
||||
switch item.Type {
|
||||
case lendingstate.Repay:
|
||||
if err := sc.DB(db.dbName).C(lendingRepayCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("failed to GetListItemByHashes (repayItems)", "err", err, "hashes", hashes)
|
||||
}
|
||||
return result
|
||||
case lendingstate.TopUp:
|
||||
if err := sc.DB(db.dbName).C(lendingTopUpCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("failed to GetListItemByHashes (topupItems)", "err", err, "hashes", hashes)
|
||||
}
|
||||
return result
|
||||
case lendingstate.Recall:
|
||||
if err := sc.DB(db.dbName).C(lendingRecallCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("failed to GetListItemByHashes (recallItems)", "err", err, "hashes", hashes)
|
||||
}
|
||||
return result
|
||||
default:
|
||||
if err := sc.DB(db.dbName).C(lendingItemsCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("failed to GetListItemByHashes (lendingItems)", "err", err, "hashes", hashes)
|
||||
}
|
||||
return result
|
||||
}
|
||||
case *lendingstate.LendingTrade:
|
||||
result := []*lendingstate.LendingTrade{}
|
||||
if err := sc.DB(db.dbName).C(lendingTradesCollection).Find(query).All(&result); err != nil && err != mgo.ErrNotFound {
|
||||
log.Error("failed to GetListItemByHashes (lendingTrades)", "err", err, "hashes", hashes)
|
||||
}
|
||||
return result
|
||||
default:
|
||||
log.Error("GetListItemByHashes: Unknown object type", "hashes", hashes, "object", val)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) EnsureIndexes() error {
|
||||
orderHashIndex := mgo.Index{
|
||||
Key: []string{"hash"},
|
||||
Unique: true,
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
Name: "index_order_hash",
|
||||
}
|
||||
orderTxHashIndex := mgo.Index{
|
||||
Key: []string{"txHash"},
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
Name: "index_order_tx_hash",
|
||||
}
|
||||
tradeHashIndex := mgo.Index{
|
||||
Key: []string{"hash"},
|
||||
Unique: true,
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
Name: "index_trade_hash",
|
||||
}
|
||||
tradeTxHashIndex := mgo.Index{
|
||||
Key: []string{"txHash"},
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
Name: "index_trade_tx_hash",
|
||||
}
|
||||
lendingItemHashIndex := mgo.Index{
|
||||
Key: []string{"hash"},
|
||||
Unique: true,
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
Name: "index_lending_item_hash",
|
||||
}
|
||||
lendingItemTxHashIndex := mgo.Index{
|
||||
Key: []string{"txHash"},
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
Name: "index_lending_item_tx_hash",
|
||||
}
|
||||
lendingTradeHashIndex := mgo.Index{
|
||||
Key: []string{"hash"},
|
||||
Unique: true,
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
Name: "index_lending_trade_hash",
|
||||
}
|
||||
lendingTradeTxHashIndex := mgo.Index{
|
||||
Key: []string{"txHash"},
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
Name: "index_lending_trade_tx_hash",
|
||||
}
|
||||
repayHashIndex := mgo.Index{
|
||||
Key: []string{"hash"},
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
Name: "index_lending_repay_hash",
|
||||
}
|
||||
repayTxHashIndex := mgo.Index{
|
||||
Key: []string{"txHash"},
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
Name: "index_lending_repay_tx_hash",
|
||||
}
|
||||
|
||||
repayUniqueIndex := mgo.Index{
|
||||
Key: []string{"txHash", "hash"},
|
||||
Unique: true,
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
Name: "index_lending_repay_unique",
|
||||
}
|
||||
|
||||
recallHashIndex := mgo.Index{
|
||||
Key: []string{"hash"},
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
Name: "index_lending_recall_hash",
|
||||
}
|
||||
recallTxHashIndex := mgo.Index{
|
||||
Key: []string{"txHash"},
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
Name: "index_lending_recall_tx_hash",
|
||||
}
|
||||
|
||||
recallUniqueIndex := mgo.Index{
|
||||
Key: []string{"txHash", "hash"},
|
||||
Unique: true,
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
Name: "index_lending_recall_unique",
|
||||
}
|
||||
|
||||
topupHashIndex := mgo.Index{
|
||||
Key: []string{"hash"},
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
Name: "index_lending_topup_hash",
|
||||
}
|
||||
topupTxHashIndex := mgo.Index{
|
||||
Key: []string{"txHash"},
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
Name: "index_lending_topup_tx_hash",
|
||||
}
|
||||
|
||||
topUpUniqueIndex := mgo.Index{
|
||||
Key: []string{"txHash", "hash"},
|
||||
Unique: true,
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
Name: "index_lending_topup_unique",
|
||||
}
|
||||
|
||||
epochPriceIndex := mgo.Index{
|
||||
Key: []string{"hash"},
|
||||
Unique: true,
|
||||
DropDups: true,
|
||||
Background: true,
|
||||
Sparse: true,
|
||||
Name: "index_epoch_price",
|
||||
}
|
||||
|
||||
sc := db.Session.Copy()
|
||||
defer sc.Close()
|
||||
|
||||
indexes, _ := sc.DB(db.dbName).C(ordersCollection).Indexes()
|
||||
if !existingIndex(orderHashIndex.Name, indexes) {
|
||||
if err := sc.DB(db.dbName).C(ordersCollection).EnsureIndex(orderHashIndex); err != nil {
|
||||
return fmt.Errorf("failed to create index %s . Err: %v", orderHashIndex.Name, err)
|
||||
}
|
||||
}
|
||||
if !existingIndex(orderTxHashIndex.Name, indexes) {
|
||||
if err := sc.DB(db.dbName).C(ordersCollection).EnsureIndex(orderTxHashIndex); err != nil {
|
||||
return fmt.Errorf("failed to create index %s . Err: %v", orderTxHashIndex.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
indexes, _ = sc.DB(db.dbName).C(tradesCollection).Indexes()
|
||||
if !existingIndex(tradeHashIndex.Name, indexes) {
|
||||
if err := sc.DB(db.dbName).C(tradesCollection).EnsureIndex(tradeHashIndex); err != nil {
|
||||
return fmt.Errorf("failed to create index %s . Err: %v", tradeHashIndex.Name, err)
|
||||
}
|
||||
}
|
||||
if !existingIndex(tradeTxHashIndex.Name, indexes) {
|
||||
if err := sc.DB(db.dbName).C(tradesCollection).EnsureIndex(tradeTxHashIndex); err != nil {
|
||||
return fmt.Errorf("failed to create index %s . Err: %v", tradeTxHashIndex.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
indexes, _ = sc.DB(db.dbName).C(lendingItemsCollection).Indexes()
|
||||
if !existingIndex(lendingItemHashIndex.Name, indexes) {
|
||||
if err := sc.DB(db.dbName).C(lendingItemsCollection).EnsureIndex(lendingItemHashIndex); err != nil {
|
||||
return fmt.Errorf("failed to create index %s . Err: %v", lendingItemHashIndex.Name, err)
|
||||
}
|
||||
}
|
||||
if !existingIndex(lendingItemTxHashIndex.Name, indexes) {
|
||||
if err := sc.DB(db.dbName).C(lendingItemsCollection).EnsureIndex(lendingItemTxHashIndex); err != nil {
|
||||
return fmt.Errorf("failed to create index %s . Err: %v", lendingItemTxHashIndex.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
indexes, _ = sc.DB(db.dbName).C(lendingTradesCollection).Indexes()
|
||||
if !existingIndex(lendingTradeHashIndex.Name, indexes) {
|
||||
if err := sc.DB(db.dbName).C(lendingTradesCollection).EnsureIndex(lendingTradeHashIndex); err != nil {
|
||||
return fmt.Errorf("failed to create index %s . Err: %v", lendingTradeHashIndex.Name, err)
|
||||
}
|
||||
}
|
||||
if !existingIndex(lendingTradeTxHashIndex.Name, indexes) {
|
||||
if err := sc.DB(db.dbName).C(lendingTradesCollection).EnsureIndex(lendingTradeTxHashIndex); err != nil {
|
||||
return fmt.Errorf("failed to create index %s . Err: %v", lendingTradeTxHashIndex.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
indexes, _ = sc.DB(db.dbName).C(lendingRepayCollection).Indexes()
|
||||
if !existingIndex(repayHashIndex.Name, indexes) {
|
||||
if err := sc.DB(db.dbName).C(lendingRepayCollection).EnsureIndex(repayHashIndex); err != nil {
|
||||
return fmt.Errorf("failed to create index %s . Err: %v", repayHashIndex.Name, err)
|
||||
}
|
||||
}
|
||||
if !existingIndex(repayTxHashIndex.Name, indexes) {
|
||||
if err := sc.DB(db.dbName).C(lendingRepayCollection).EnsureIndex(repayTxHashIndex); err != nil {
|
||||
return fmt.Errorf("failed to create index %s . Err: %v", repayTxHashIndex.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
if !existingIndex(repayUniqueIndex.Name, indexes) {
|
||||
if err := sc.DB(db.dbName).C(lendingRepayCollection).EnsureIndex(repayUniqueIndex); err != nil {
|
||||
return fmt.Errorf("failed to create index %s . Err: %v", repayUniqueIndex.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
indexes, _ = sc.DB(db.dbName).C(lendingRecallCollection).Indexes()
|
||||
if !existingIndex(recallHashIndex.Name, indexes) {
|
||||
if err := sc.DB(db.dbName).C(lendingRecallCollection).EnsureIndex(recallHashIndex); err != nil {
|
||||
return fmt.Errorf("failed to create index %s . Err: %v", recallHashIndex.Name, err)
|
||||
}
|
||||
}
|
||||
if !existingIndex(recallTxHashIndex.Name, indexes) {
|
||||
if err := sc.DB(db.dbName).C(lendingRecallCollection).EnsureIndex(recallTxHashIndex); err != nil {
|
||||
return fmt.Errorf("failed to create index %s . Err: %v", recallTxHashIndex.Name, err)
|
||||
}
|
||||
}
|
||||
if !existingIndex(recallUniqueIndex.Name, indexes) {
|
||||
if err := sc.DB(db.dbName).C(lendingRecallCollection).EnsureIndex(repayUniqueIndex); err != nil {
|
||||
return fmt.Errorf("failed to create index %s . Err: %v", repayUniqueIndex.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
indexes, _ = sc.DB(db.dbName).C(lendingTopUpCollection).Indexes()
|
||||
if !existingIndex(topupHashIndex.Name, indexes) {
|
||||
if err := sc.DB(db.dbName).C(lendingTopUpCollection).EnsureIndex(topupHashIndex); err != nil {
|
||||
return fmt.Errorf("failed to create index %s . Err: %v", topupHashIndex.Name, err)
|
||||
}
|
||||
}
|
||||
if !existingIndex(topupTxHashIndex.Name, indexes) {
|
||||
if err := sc.DB(db.dbName).C(lendingTopUpCollection).EnsureIndex(topupTxHashIndex); err != nil {
|
||||
return fmt.Errorf("failed to create index %s . Err: %v", topupTxHashIndex.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
if !existingIndex(topUpUniqueIndex.Name, indexes) {
|
||||
if err := sc.DB(db.dbName).C(lendingTopUpCollection).EnsureIndex(repayUniqueIndex); err != nil {
|
||||
return fmt.Errorf("failed to create index %s . Err: %v", repayUniqueIndex.Name, err)
|
||||
}
|
||||
}
|
||||
|
||||
indexes, _ = sc.DB(db.dbName).C(epochPriceCollection).Indexes()
|
||||
if !existingIndex(epochPriceIndex.Name, indexes) {
|
||||
if err := sc.DB(db.dbName).C(epochPriceCollection).EnsureIndex(epochPriceIndex); err != nil {
|
||||
return fmt.Errorf("failed to create index %s . Err: %v", epochPriceIndex.Name, err)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) Close() error {
|
||||
return db.Close()
|
||||
}
|
||||
|
||||
// HasAncient returns an error as we don't have a backing chain freezer.
|
||||
func (db *MongoDatabase) HasAncient(kind string, number uint64) (bool, error) {
|
||||
return false, errNotSupported
|
||||
}
|
||||
|
||||
// Ancient returns an error as we don't have a backing chain freezer.
|
||||
func (db *MongoDatabase) Ancient(kind string, number uint64) ([]byte, error) {
|
||||
return nil, errNotSupported
|
||||
}
|
||||
|
||||
// Ancients returns an error as we don't have a backing chain freezer.
|
||||
func (db *MongoDatabase) Ancients() (uint64, error) {
|
||||
return 0, errNotSupported
|
||||
}
|
||||
|
||||
// AncientSize returns an error as we don't have a backing chain freezer.
|
||||
func (db *MongoDatabase) AncientSize(kind string) (uint64, error) {
|
||||
return 0, errNotSupported
|
||||
}
|
||||
|
||||
// AppendAncient returns an error as we don't have a backing chain freezer.
|
||||
func (db *MongoDatabase) AppendAncient(number uint64, hash, header, body, receipts, td []byte) error {
|
||||
return errNotSupported
|
||||
}
|
||||
|
||||
// TruncateAncients returns an error as we don't have a backing chain freezer.
|
||||
func (db *MongoDatabase) TruncateAncients(items uint64) error {
|
||||
return errNotSupported
|
||||
}
|
||||
|
||||
// Sync returns an error as we don't have a backing chain freezer.
|
||||
func (db *MongoDatabase) Sync() error {
|
||||
return errNotSupported
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) NewIterator(prefix []byte, start []byte) ethdb.Iterator {
|
||||
return db.NewIterator(prefix, start)
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) Stat(property string) (string, error) {
|
||||
return db.Stat(property)
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) Compact(start []byte, limit []byte) error {
|
||||
return db.Compact(start, limit)
|
||||
}
|
||||
|
||||
func (db *MongoDatabase) NewBatch() ethdb.Batch {
|
||||
// for levelDB only
|
||||
return nil
|
||||
}
|
||||
|
||||
type keyvalue struct {
|
||||
key []byte
|
||||
value []byte
|
||||
}
|
||||
type Batch struct {
|
||||
db *MongoDatabase
|
||||
collection string
|
||||
b []keyvalue
|
||||
size int
|
||||
}
|
||||
|
||||
func (b *Batch) SetCollection(collection string) {
|
||||
// for levelDB only
|
||||
}
|
||||
|
||||
func (b *Batch) Put(key, value []byte) error {
|
||||
// for levelDB only
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Batch) Write() error {
|
||||
// for levelDB only
|
||||
return nil
|
||||
}
|
||||
|
||||
func (b *Batch) ValueSize() int {
|
||||
// for levelDB only
|
||||
return int(0)
|
||||
}
|
||||
func (b *Batch) Reset() {
|
||||
// for levelDB only
|
||||
}
|
||||
|
||||
func existingIndex(indexName string, indexes []mgo.Index) bool {
|
||||
if len(indexes) == 0 {
|
||||
return false
|
||||
}
|
||||
for _, index := range indexes {
|
||||
if index.Name == indexName {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
955
XDCxlending/XDCxlending.go
Normal file
955
XDCxlending/XDCxlending.go
Normal 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
37
XDCxlending/api.go
Normal file
|
|
@ -0,0 +1,37 @@
|
|||
package XDCxlending
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"sync"
|
||||
"time"
|
||||
)
|
||||
|
||||
// List of errors
|
||||
var (
|
||||
ErrOrderNonceTooLow = errors.New("OrderNonce too low")
|
||||
ErrOrderNonceTooHigh = errors.New("OrderNonce too high")
|
||||
)
|
||||
|
||||
// PublicXDCXLendingAPI provides the XDCX RPC service that can be
|
||||
// use publicly without security implications.
|
||||
type PublicXDCXLendingAPI struct {
|
||||
t *Lending
|
||||
mu sync.Mutex
|
||||
lastUsed map[string]time.Time // keeps track when a filter was polled for the last time.
|
||||
|
||||
}
|
||||
|
||||
// NewPublicXDCXLendingAPI create a new RPC XDCX service.
|
||||
func NewPublicXDCXLendingAPI(t *Lending) *PublicXDCXLendingAPI {
|
||||
api := &PublicXDCXLendingAPI{
|
||||
t: t,
|
||||
lastUsed: make(map[string]time.Time),
|
||||
}
|
||||
return api
|
||||
}
|
||||
|
||||
// Version returns the Lending sub-protocol version.
|
||||
func (api *PublicXDCXLendingAPI) Version(ctx context.Context) string {
|
||||
return ProtocolVersionStr
|
||||
}
|
||||
216
XDCxlending/lendingstate/XDCx_trie.go
Normal file
216
XDCxlending/lendingstate/XDCx_trie.go
Normal file
|
|
@ -0,0 +1,216 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package lendingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||
"github.com/XinFinOrg/XDPoSChain/trie"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
// XDCXTrie wraps a trie with key hashing. In a secure trie, all
|
||||
// access operations hash the key using keccak256. This prevents
|
||||
// calling code from creating long chains of nodes that
|
||||
// increase the access time.
|
||||
//
|
||||
// Contrary to a regular trie, a XDCXTrie can only be created with
|
||||
// New and must have an attached database. The database also stores
|
||||
// the preimage of each key.
|
||||
//
|
||||
// XDCXTrie is not safe for concurrent use.
|
||||
type XDCXTrie struct {
|
||||
trie trie.Trie
|
||||
hashKeyBuf [common.HashLength]byte
|
||||
secKeyCache map[string][]byte
|
||||
secKeyCacheOwner *XDCXTrie // Pointer to self, replace the key cache on mismatch
|
||||
}
|
||||
|
||||
// NewXDCXTrie creates a trie with an existing root node from a backing database
|
||||
// and optional intermediate in-memory node pool.
|
||||
//
|
||||
// If root is the zero hash or the sha3 hash of an empty string, the
|
||||
// trie is initially empty. Otherwise, New will panic if db is nil
|
||||
// and returns MissingNodeError if the root node cannot be found.
|
||||
//
|
||||
// Accessing the trie loads nodes from the database or node pool on demand.
|
||||
// Loaded nodes are kept around until their 'cache generation' expires.
|
||||
// A new cache generation is created by each call to Commit.
|
||||
// cachelimit sets the number of past cache generations to keep.
|
||||
func NewXDCXTrie(root common.Hash, db *trie.Database) (*XDCXTrie, error) {
|
||||
if db == nil {
|
||||
panic("trie.NewXDCXTrie called without a database")
|
||||
}
|
||||
trie, err := trie.New(root, db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return &XDCXTrie{trie: *trie}, nil
|
||||
}
|
||||
|
||||
// Get returns the value for key stored in the trie.
|
||||
// The value bytes must not be modified by the caller.
|
||||
func (t *XDCXTrie) Get(key []byte) []byte {
|
||||
res, err := t.TryGet(key)
|
||||
if err != nil {
|
||||
log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// TryGet returns the value for key stored in the trie.
|
||||
// The value bytes must not be modified by the caller.
|
||||
// If a node was not found in the database, a MissingNodeError is returned.
|
||||
func (t *XDCXTrie) TryGet(key []byte) ([]byte, error) {
|
||||
return t.trie.TryGet(key)
|
||||
}
|
||||
|
||||
// TryGetBestLeftKey returns the value of max left leaf
|
||||
// If a node was not found in the database, a MissingNodeError is returned.
|
||||
func (t *XDCXTrie) TryGetBestLeftKeyAndValue() ([]byte, []byte, error) {
|
||||
return t.trie.TryGetBestLeftKeyAndValue()
|
||||
}
|
||||
|
||||
// TryGetBestRightKey returns the value of max left leaf
|
||||
// If a node was not found in the database, a MissingNodeError is returned.
|
||||
func (t *XDCXTrie) TryGetBestRightKeyAndValue() ([]byte, []byte, error) {
|
||||
return t.trie.TryGetBestRightKeyAndValue()
|
||||
}
|
||||
|
||||
// Update associates key with value in the trie. Subsequent calls to
|
||||
// Get will return value. If value has length zero, any existing value
|
||||
// is deleted from the trie and calls to Get will return nil.
|
||||
//
|
||||
// The value bytes must not be modified by the caller while they are
|
||||
// stored in the trie.
|
||||
func (t *XDCXTrie) Update(key, value []byte) {
|
||||
if err := t.TryUpdate(key, value); err != nil {
|
||||
log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// TryUpdate associates key with value in the trie. Subsequent calls to
|
||||
// Get will return value. If value has length zero, any existing value
|
||||
// is deleted from the trie and calls to Get will return nil.
|
||||
//
|
||||
// The value bytes must not be modified by the caller while they are
|
||||
// stored in the trie.
|
||||
//
|
||||
// If a node was not found in the database, a MissingNodeError is returned.
|
||||
func (t *XDCXTrie) TryUpdate(key, value []byte) error {
|
||||
err := t.trie.TryUpdate(key, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
t.getSecKeyCache()[string(key)] = common.CopyBytes(key)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Delete removes any existing value for key from the trie.
|
||||
func (t *XDCXTrie) Delete(key []byte) {
|
||||
if err := t.TryDelete(key); err != nil {
|
||||
log.Error(fmt.Sprintf("Unhandled trie error: %v", err))
|
||||
}
|
||||
}
|
||||
|
||||
// TryDelete removes any existing value for key from the trie.
|
||||
// If a node was not found in the database, a MissingNodeError is returned.
|
||||
func (t *XDCXTrie) TryDelete(key []byte) error {
|
||||
delete(t.getSecKeyCache(), string(key))
|
||||
return t.trie.TryDelete(key)
|
||||
}
|
||||
|
||||
// GetKey returns the sha3 preimage of a hashed key that was
|
||||
// previously used to store a value.
|
||||
func (t *XDCXTrie) GetKey(shaKey []byte) []byte {
|
||||
if key, ok := t.getSecKeyCache()[string(shaKey)]; ok {
|
||||
return key
|
||||
}
|
||||
key, _ := t.trie.Db.Preimage(common.BytesToHash(shaKey))
|
||||
return key
|
||||
}
|
||||
|
||||
// Commit writes all nodes and the secure hash pre-images to the trie's database.
|
||||
// Nodes are stored with their sha3 hash as the key.
|
||||
//
|
||||
// Committing flushes nodes from memory. Subsequent Get calls will load nodes
|
||||
// from the database.
|
||||
func (t *XDCXTrie) Commit(onleaf trie.LeafCallback) (root common.Hash, err error) {
|
||||
// Write all the pre-images to the actual disk database
|
||||
if len(t.getSecKeyCache()) > 0 {
|
||||
t.trie.Db.Lock.Lock()
|
||||
for hk, key := range t.secKeyCache {
|
||||
t.trie.Db.InsertPreimage(common.BytesToHash([]byte(hk)), key)
|
||||
}
|
||||
t.trie.Db.Lock.Unlock()
|
||||
|
||||
t.secKeyCache = make(map[string][]byte)
|
||||
}
|
||||
// Commit the trie to its intermediate node database
|
||||
return t.trie.Commit(onleaf)
|
||||
}
|
||||
|
||||
func (t *XDCXTrie) Hash() common.Hash {
|
||||
return t.trie.Hash()
|
||||
}
|
||||
|
||||
func (t *XDCXTrie) Copy() *XDCXTrie {
|
||||
cpy := *t
|
||||
return &cpy
|
||||
}
|
||||
|
||||
// NodeIterator returns an iterator that returns nodes of the underlying trie. Iteration
|
||||
// starts at the key after the given start key.
|
||||
func (t *XDCXTrie) NodeIterator(start []byte) trie.NodeIterator {
|
||||
return t.trie.NodeIterator(start)
|
||||
}
|
||||
|
||||
// hashKey returns the hash of key as an ephemeral buffer.
|
||||
// The caller must not hold onto the return value because it will become
|
||||
// invalid on the next call to hashKey or secKey.
|
||||
//func (t *XDCXTrie) hashKey(key []byte) []byte {
|
||||
// h := newHasher(0, 0, nil)
|
||||
// h.sha.Reset()
|
||||
// h.sha.Write(key)
|
||||
// buf := h.sha.Sum(t.hashKeyBuf[:0])
|
||||
// returnHasherToPool(h)
|
||||
// return buf
|
||||
//}
|
||||
|
||||
// getSecKeyCache returns the current secure key cache, creating a new one if
|
||||
// ownership changed (i.e. the current secure trie is a copy of another owning
|
||||
// the actual cache).
|
||||
func (t *XDCXTrie) getSecKeyCache() map[string][]byte {
|
||||
if t != t.secKeyCacheOwner {
|
||||
t.secKeyCacheOwner = t
|
||||
t.secKeyCache = make(map[string][]byte)
|
||||
}
|
||||
return t.secKeyCache
|
||||
}
|
||||
|
||||
// Prove constructs a merkle proof for key. The result contains all encoded nodes
|
||||
// on the path to the value at key. The value itself is also included in the last
|
||||
// node and can be retrieved by verifying the proof.
|
||||
//
|
||||
// If the trie does not contain a value for key, the returned proof contains all
|
||||
// nodes of the longest existing prefix of the key (at least the root node), ending
|
||||
// with the node that proves the absence of the key.
|
||||
func (t *XDCXTrie) Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error {
|
||||
return t.trie.Prove(key, fromLevel, proofDb)
|
||||
}
|
||||
251
XDCxlending/lendingstate/common.go
Normal file
251
XDCxlending/lendingstate/common.go
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
package lendingstate
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
"math/big"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
)
|
||||
|
||||
var (
|
||||
EmptyAddress = "0x0000000000000000000000000000000000000000"
|
||||
EmptyRoot = common.HexToHash("56e81f171bcc55a6ff8345e692c0f86e5b48e01b996cadc001622fb5e363b421")
|
||||
)
|
||||
|
||||
var EmptyHash = common.Hash{}
|
||||
var Zero = big.NewInt(0)
|
||||
var One = big.NewInt(1)
|
||||
var EmptyLendingOrder = LendingItem{
|
||||
Quantity: Zero,
|
||||
}
|
||||
|
||||
var EmptyLendingTrade = LendingTrade{
|
||||
Amount: big.NewInt(0),
|
||||
}
|
||||
|
||||
type itemList struct {
|
||||
Volume *big.Int
|
||||
Root common.Hash
|
||||
}
|
||||
|
||||
type lendingObject struct {
|
||||
Nonce uint64
|
||||
TradeNonce uint64
|
||||
InvestingRoot common.Hash
|
||||
BorrowingRoot common.Hash
|
||||
LiquidationTimeRoot common.Hash
|
||||
LendingItemRoot common.Hash
|
||||
LendingTradeRoot common.Hash
|
||||
}
|
||||
|
||||
// liquidation reasons
|
||||
const (
|
||||
LiquidatedByTime = uint64(0)
|
||||
LiquidatedByPrice = uint64(1)
|
||||
)
|
||||
|
||||
type LiquidationData struct {
|
||||
RecallAmount *big.Int
|
||||
LiquidationAmount *big.Int
|
||||
CollateralPrice *big.Int
|
||||
Reason uint64
|
||||
}
|
||||
|
||||
var (
|
||||
TokenMappingSlot = map[string]uint64{
|
||||
"balances": 0,
|
||||
}
|
||||
RelayerMappingSlot = map[string]uint64{
|
||||
"CONTRACT_OWNER": 0,
|
||||
"MaximumRelayers": 1,
|
||||
"MaximumTokenList": 2,
|
||||
"RELAYER_LIST": 3,
|
||||
"RELAYER_COINBASES": 4,
|
||||
"RESIGN_REQUESTS": 5,
|
||||
"RELAYER_ON_SALE_LIST": 6,
|
||||
"RelayerCount": 7,
|
||||
"MinimumDeposit": 8,
|
||||
}
|
||||
RelayerStructMappingSlot = map[string]*big.Int{
|
||||
"_deposit": big.NewInt(0),
|
||||
"_fee": big.NewInt(1),
|
||||
"_fromTokens": big.NewInt(2),
|
||||
"_toTokens": big.NewInt(3),
|
||||
"_index": big.NewInt(4),
|
||||
"_owner": big.NewInt(5),
|
||||
}
|
||||
)
|
||||
|
||||
type TxLendingBatch struct {
|
||||
Data []*LendingItem
|
||||
Timestamp int64
|
||||
TxHash common.Hash
|
||||
}
|
||||
|
||||
type MatchingResult struct {
|
||||
Trades []*LendingTrade
|
||||
Rejects []*LendingItem
|
||||
}
|
||||
|
||||
type FinalizedResult struct {
|
||||
Liquidated []common.Hash
|
||||
AutoRepay []common.Hash
|
||||
AutoTopUp []common.Hash
|
||||
AutoRecall []common.Hash
|
||||
TxHash common.Hash
|
||||
Timestamp int64
|
||||
}
|
||||
|
||||
// use orderHash instead of tradeId
|
||||
// because both takerOrders don't have tradeId
|
||||
func GetLendingItemHistoryKey(lendingToken, collateralToken common.Address, lendingItemHash common.Hash) common.Hash {
|
||||
return crypto.Keccak256Hash(lendingToken.Bytes(), collateralToken.Bytes(), lendingItemHash.Bytes())
|
||||
}
|
||||
|
||||
type LendingItemHistoryItem struct {
|
||||
TxHash common.Hash
|
||||
FilledAmount *big.Int
|
||||
Status string
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type LendingTradeHistoryItem struct {
|
||||
TxHash common.Hash
|
||||
CollateralLockedAmount *big.Int
|
||||
LiquidationPrice *big.Int
|
||||
Status string
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type LendingPair struct {
|
||||
LendingToken common.Address
|
||||
CollateralToken common.Address
|
||||
}
|
||||
|
||||
// ToJSON : log json string
|
||||
func ToJSON(object interface{}, args ...string) string {
|
||||
var str []byte
|
||||
if len(args) == 2 {
|
||||
str, _ = json.MarshalIndent(object, args[0], args[1])
|
||||
} else {
|
||||
str, _ = json.Marshal(object)
|
||||
}
|
||||
return string(str)
|
||||
}
|
||||
|
||||
func Mul(x, y *big.Int) *big.Int {
|
||||
return big.NewInt(0).Mul(x, y)
|
||||
}
|
||||
|
||||
func Div(x, y *big.Int) *big.Int {
|
||||
return big.NewInt(0).Div(x, y)
|
||||
}
|
||||
|
||||
func Add(x, y *big.Int) *big.Int {
|
||||
return big.NewInt(0).Add(x, y)
|
||||
}
|
||||
|
||||
func Sub(x, y *big.Int) *big.Int {
|
||||
return big.NewInt(0).Sub(x, y)
|
||||
}
|
||||
|
||||
func Neg(x *big.Int) *big.Int {
|
||||
return big.NewInt(0).Neg(x)
|
||||
}
|
||||
|
||||
func ToBigInt(s string) *big.Int {
|
||||
res := big.NewInt(0)
|
||||
res.SetString(s, 10)
|
||||
return res
|
||||
}
|
||||
|
||||
func CloneBigInt(bigInt *big.Int) *big.Int {
|
||||
res := new(big.Int).SetBytes(bigInt.Bytes())
|
||||
return res
|
||||
}
|
||||
|
||||
func Exp(x, y *big.Int) *big.Int {
|
||||
return big.NewInt(0).Exp(x, y, nil)
|
||||
}
|
||||
|
||||
func Max(a, b *big.Int) *big.Int {
|
||||
if a.Cmp(b) == 1 {
|
||||
return a
|
||||
} else {
|
||||
return b
|
||||
}
|
||||
}
|
||||
|
||||
func GetLendingOrderBookHash(lendingToken common.Address, term uint64) common.Hash {
|
||||
return crypto.Keccak256Hash(append(common.Uint64ToHash(term).Bytes(), lendingToken.Bytes()...))
|
||||
}
|
||||
|
||||
func EncodeTxLendingBatch(batch TxLendingBatch) ([]byte, error) {
|
||||
data, err := json.Marshal(batch)
|
||||
if err != nil || data == nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func DecodeTxLendingBatch(data []byte) (TxLendingBatch, error) {
|
||||
txMatchResult := TxLendingBatch{}
|
||||
if err := json.Unmarshal(data, &txMatchResult); err != nil {
|
||||
return TxLendingBatch{}, err
|
||||
}
|
||||
return txMatchResult, nil
|
||||
}
|
||||
|
||||
func EtherToWei(amount *big.Int) *big.Int {
|
||||
return new(big.Int).Mul(amount, new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil))
|
||||
}
|
||||
|
||||
func WeiToEther(amount *big.Int) *big.Int {
|
||||
return new(big.Int).Div(amount, new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil))
|
||||
}
|
||||
|
||||
func EncodeFinalizedResult(liquidatedTrades, autoRepayTrades, autoTopUpTrades, autoRecallTrades []*LendingTrade) ([]byte, error) {
|
||||
liquidatedHashes := []common.Hash{}
|
||||
autoRepayHashes := []common.Hash{}
|
||||
autoTopUpHashes := []common.Hash{}
|
||||
autoRecallHashes := []common.Hash{}
|
||||
|
||||
for _, trade := range liquidatedTrades {
|
||||
liquidatedHashes = append(liquidatedHashes, trade.Hash)
|
||||
}
|
||||
for _, trade := range autoRepayTrades {
|
||||
autoRepayHashes = append(autoRepayHashes, trade.Hash)
|
||||
}
|
||||
for _, trade := range autoTopUpTrades {
|
||||
autoTopUpHashes = append(autoTopUpHashes, trade.Hash)
|
||||
}
|
||||
for _, trade := range autoRecallTrades {
|
||||
autoRecallHashes = append(autoRecallHashes, trade.Hash)
|
||||
}
|
||||
result := FinalizedResult{
|
||||
Liquidated: liquidatedHashes,
|
||||
AutoRepay: autoRepayHashes,
|
||||
AutoTopUp: autoTopUpHashes,
|
||||
AutoRecall: autoRecallHashes,
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
}
|
||||
data, err := json.Marshal(result)
|
||||
if err != nil || data == nil {
|
||||
return []byte{}, err
|
||||
}
|
||||
return data, nil
|
||||
}
|
||||
|
||||
func DecodeFinalizedResult(data []byte) (result FinalizedResult, err error) {
|
||||
result = FinalizedResult{}
|
||||
if err := json.Unmarshal(data, &result); err != nil {
|
||||
return FinalizedResult{}, err
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func GetLendingCacheKey(item *LendingItem) common.Hash {
|
||||
return crypto.Keccak256Hash(item.UserAddress.Bytes(), item.Nonce.Bytes())
|
||||
}
|
||||
139
XDCxlending/lendingstate/database.go
Normal file
139
XDCxlending/lendingstate/database.go
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
// Copyright 2017 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package lendingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/ethdb"
|
||||
"github.com/XinFinOrg/XDPoSChain/trie"
|
||||
lru "github.com/hashicorp/golang-lru"
|
||||
)
|
||||
|
||||
// Trie cache generation limit after which to evic trie nodes from memory.
|
||||
var MaxTrieCacheGen = uint16(120)
|
||||
|
||||
const (
|
||||
// Number of past tries to keep. This value is chosen such that
|
||||
// reasonable chain reorg depths will hit an existing trie.
|
||||
maxPastTries = 12
|
||||
|
||||
// Number of codehash->size associations to keep.
|
||||
codeSizeCacheSize = 100000
|
||||
)
|
||||
|
||||
// Database wraps access to tries and contract code.
|
||||
type Database interface {
|
||||
// OpenTrie opens the main account trie.
|
||||
OpenTrie(root common.Hash) (Trie, error)
|
||||
|
||||
// OpenStorageTrie opens the storage trie of an account.
|
||||
OpenStorageTrie(addrHash, root common.Hash) (Trie, error)
|
||||
|
||||
// CopyTrie returns an independent copy of the given trie.
|
||||
CopyTrie(Trie) Trie
|
||||
|
||||
// ContractCode retrieves a particular contract's code.
|
||||
ContractCode(addrHash, codeHash common.Hash) ([]byte, error)
|
||||
|
||||
// ContractCodeSize retrieves a particular contracts code's size.
|
||||
ContractCodeSize(addrHash, codeHash common.Hash) (int, error)
|
||||
|
||||
// TrieDB retrieves the low level trie database used for data storage.
|
||||
TrieDB() *trie.Database
|
||||
}
|
||||
|
||||
// Trie is a Ethereum Merkle Trie.
|
||||
type Trie interface {
|
||||
TryGet(key []byte) ([]byte, error)
|
||||
TryGetBestLeftKeyAndValue() ([]byte, []byte, error)
|
||||
TryGetBestRightKeyAndValue() ([]byte, []byte, error)
|
||||
TryUpdate(key, value []byte) error
|
||||
TryDelete(key []byte) error
|
||||
Commit(onleaf trie.LeafCallback) (common.Hash, error)
|
||||
Hash() common.Hash
|
||||
NodeIterator(startKey []byte) trie.NodeIterator
|
||||
GetKey([]byte) []byte // TODO(fjl): remove this when XDCXTrie is removed
|
||||
Prove(key []byte, fromLevel uint, proofDb ethdb.KeyValueWriter) error
|
||||
}
|
||||
|
||||
// NewDatabase creates a backing store for state. The returned database is safe for
|
||||
// concurrent use and retains cached trie nodes in memory. The pool is an optional
|
||||
// intermediate trie-node memory pool between the low level storage layer and the
|
||||
// high level trie abstraction.
|
||||
func NewDatabase(db ethdb.Database) Database {
|
||||
csc, _ := lru.New(codeSizeCacheSize)
|
||||
return &cachingDB{
|
||||
db: trie.NewDatabase(db),
|
||||
codeSizeCache: csc,
|
||||
}
|
||||
}
|
||||
|
||||
type cachingDB struct {
|
||||
db *trie.Database
|
||||
mu sync.Mutex
|
||||
pastTries []*XDCXTrie
|
||||
codeSizeCache *lru.Cache
|
||||
}
|
||||
|
||||
// OpenTrie opens the main account trie.
|
||||
func (db *cachingDB) OpenTrie(root common.Hash) (Trie, error) {
|
||||
return NewXDCXTrie(root, db.db)
|
||||
}
|
||||
|
||||
func (db *cachingDB) pushTrie(t *XDCXTrie) {
|
||||
db.mu.Lock()
|
||||
defer db.mu.Unlock()
|
||||
if len(db.pastTries) >= maxPastTries {
|
||||
copy(db.pastTries, db.pastTries[1:])
|
||||
db.pastTries[len(db.pastTries)-1] = t
|
||||
} else {
|
||||
db.pastTries = append(db.pastTries, t)
|
||||
}
|
||||
}
|
||||
|
||||
// OpenStorageTrie opens the storage trie of an account.
|
||||
func (db *cachingDB) OpenStorageTrie(addrHash, root common.Hash) (Trie, error) {
|
||||
return NewXDCXTrie(root, db.db)
|
||||
}
|
||||
|
||||
// CopyTrie returns an independent copy of the given trie.
|
||||
func (db *cachingDB) CopyTrie(t Trie) Trie {
|
||||
switch t := t.(type) {
|
||||
case *XDCXTrie:
|
||||
return t.Copy()
|
||||
default:
|
||||
panic(fmt.Errorf("unknown trie type %T", t))
|
||||
}
|
||||
}
|
||||
|
||||
// ContractCode retrieves a particular contract's code.
|
||||
func (db *cachingDB) ContractCode(addrHash, codeHash common.Hash) ([]byte, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
// ContractCodeSize retrieves a particular contracts code's size.
|
||||
func (db *cachingDB) ContractCodeSize(addrHash, codeHash common.Hash) (int, error) {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// TrieDB retrieves any intermediate trie-node caching layer.
|
||||
func (db *cachingDB) TrieDB() *trie.Database {
|
||||
return db.db
|
||||
}
|
||||
417
XDCxlending/lendingstate/dump.go
Normal file
417
XDCxlending/lendingstate/dump.go
Normal file
|
|
@ -0,0 +1,417 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package lendingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
"math/big"
|
||||
"sort"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/trie"
|
||||
)
|
||||
|
||||
type DumpOrderList struct {
|
||||
Volume *big.Int
|
||||
Orders map[*big.Int]*big.Int
|
||||
}
|
||||
|
||||
type DumpOrderBookInfo struct {
|
||||
Nonce uint64
|
||||
TradeNonce uint64
|
||||
BestInvesting *big.Int
|
||||
BestBorrowing *big.Int
|
||||
LowestLiquidationTime *big.Int
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) DumpInvestingTrie(orderBook common.Hash) (map[*big.Int]DumpOrderList, error) {
|
||||
exhangeObject := self.getLendingExchange(orderBook)
|
||||
if exhangeObject == nil {
|
||||
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
|
||||
}
|
||||
mapResult := map[*big.Int]DumpOrderList{}
|
||||
it := trie.NewIterator(exhangeObject.getInvestingTrie(self.db).NodeIterator(nil))
|
||||
for it.Next() {
|
||||
interestHash := common.BytesToHash(it.Key)
|
||||
if common.EmptyHash(interestHash) {
|
||||
continue
|
||||
}
|
||||
interest := new(big.Int).SetBytes(interestHash.Bytes())
|
||||
if _, exist := exhangeObject.investingStates[interestHash]; exist {
|
||||
continue
|
||||
} else {
|
||||
var data itemList
|
||||
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
|
||||
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,interest :%v ", orderBook.Hex(), interest)
|
||||
}
|
||||
stateOrderList := newItemListState(orderBook, interestHash, data, nil)
|
||||
mapResult[interest] = stateOrderList.DumpItemList(self.db)
|
||||
}
|
||||
}
|
||||
for interestHash, itemList := range exhangeObject.investingStates {
|
||||
if itemList.Volume().Sign() > 0 {
|
||||
mapResult[new(big.Int).SetBytes(interestHash.Bytes())] = itemList.DumpItemList(self.db)
|
||||
}
|
||||
}
|
||||
listInterest := []*big.Int{}
|
||||
for interest := range mapResult {
|
||||
listInterest = append(listInterest, interest)
|
||||
}
|
||||
sort.Slice(listInterest, func(i, j int) bool {
|
||||
return listInterest[i].Cmp(listInterest[j]) < 0
|
||||
})
|
||||
result := map[*big.Int]DumpOrderList{}
|
||||
for _, interest := range listInterest {
|
||||
result[interest] = mapResult[interest]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) DumpBorrowingTrie(orderBook common.Hash) (map[*big.Int]DumpOrderList, error) {
|
||||
exhangeObject := self.getLendingExchange(orderBook)
|
||||
if exhangeObject == nil {
|
||||
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
|
||||
}
|
||||
mapResult := map[*big.Int]DumpOrderList{}
|
||||
it := trie.NewIterator(exhangeObject.getBorrowingTrie(self.db).NodeIterator(nil))
|
||||
for it.Next() {
|
||||
interestHash := common.BytesToHash(it.Key)
|
||||
if common.EmptyHash(interestHash) {
|
||||
continue
|
||||
}
|
||||
interest := new(big.Int).SetBytes(interestHash.Bytes())
|
||||
if _, exist := exhangeObject.borrowingStates[interestHash]; exist {
|
||||
continue
|
||||
} else {
|
||||
var data itemList
|
||||
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
|
||||
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,interest :%v ", orderBook.Hex(), interest)
|
||||
}
|
||||
stateOrderList := newItemListState(orderBook, interestHash, data, nil)
|
||||
mapResult[interest] = stateOrderList.DumpItemList(self.db)
|
||||
}
|
||||
}
|
||||
for interestHash, itemList := range exhangeObject.borrowingStates {
|
||||
if itemList.Volume().Sign() > 0 {
|
||||
mapResult[new(big.Int).SetBytes(interestHash.Bytes())] = itemList.DumpItemList(self.db)
|
||||
}
|
||||
}
|
||||
listInterest := []*big.Int{}
|
||||
for interest := range mapResult {
|
||||
listInterest = append(listInterest, interest)
|
||||
}
|
||||
sort.Slice(listInterest, func(i, j int) bool {
|
||||
return listInterest[i].Cmp(listInterest[j]) < 0
|
||||
})
|
||||
result := map[*big.Int]DumpOrderList{}
|
||||
for _, interest := range listInterest {
|
||||
result[interest] = mapResult[interest]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) GetInvestings(orderBook common.Hash) (map[*big.Int]*big.Int, error) {
|
||||
exhangeObject := self.getLendingExchange(orderBook)
|
||||
if exhangeObject == nil {
|
||||
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
|
||||
}
|
||||
mapResult := map[*big.Int]*big.Int{}
|
||||
it := trie.NewIterator(exhangeObject.getInvestingTrie(self.db).NodeIterator(nil))
|
||||
for it.Next() {
|
||||
interestHash := common.BytesToHash(it.Key)
|
||||
if common.EmptyHash(interestHash) {
|
||||
continue
|
||||
}
|
||||
interest := new(big.Int).SetBytes(interestHash.Bytes())
|
||||
if _, exist := exhangeObject.investingStates[interestHash]; exist {
|
||||
continue
|
||||
} else {
|
||||
var data itemList
|
||||
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
|
||||
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,interest :%v ", orderBook.Hex(), interest)
|
||||
}
|
||||
stateOrderList := newItemListState(orderBook, interestHash, data, nil)
|
||||
mapResult[interest] = stateOrderList.data.Volume
|
||||
}
|
||||
}
|
||||
for interestHash, itemList := range exhangeObject.investingStates {
|
||||
if itemList.Volume().Sign() > 0 {
|
||||
mapResult[new(big.Int).SetBytes(interestHash.Bytes())] = itemList.data.Volume
|
||||
}
|
||||
}
|
||||
listInterest := []*big.Int{}
|
||||
for interest := range mapResult {
|
||||
listInterest = append(listInterest, interest)
|
||||
}
|
||||
sort.Slice(listInterest, func(i, j int) bool {
|
||||
return listInterest[i].Cmp(listInterest[j]) < 0
|
||||
})
|
||||
result := map[*big.Int]*big.Int{}
|
||||
for _, interest := range listInterest {
|
||||
result[interest] = mapResult[interest]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) GetBorrowings(orderBook common.Hash) (map[*big.Int]*big.Int, error) {
|
||||
exhangeObject := self.getLendingExchange(orderBook)
|
||||
if exhangeObject == nil {
|
||||
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
|
||||
}
|
||||
mapResult := map[*big.Int]*big.Int{}
|
||||
it := trie.NewIterator(exhangeObject.getBorrowingTrie(self.db).NodeIterator(nil))
|
||||
for it.Next() {
|
||||
interestHash := common.BytesToHash(it.Key)
|
||||
if common.EmptyHash(interestHash) {
|
||||
continue
|
||||
}
|
||||
interest := new(big.Int).SetBytes(interestHash.Bytes())
|
||||
if _, exist := exhangeObject.borrowingStates[interestHash]; exist {
|
||||
continue
|
||||
} else {
|
||||
var data itemList
|
||||
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
|
||||
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,interest :%v ", orderBook.Hex(), interest)
|
||||
}
|
||||
stateOrderList := newItemListState(orderBook, interestHash, data, nil)
|
||||
mapResult[interest] = stateOrderList.data.Volume
|
||||
}
|
||||
}
|
||||
for interestHash, itemList := range exhangeObject.borrowingStates {
|
||||
if itemList.Volume().Sign() > 0 {
|
||||
mapResult[new(big.Int).SetBytes(interestHash.Bytes())] = itemList.data.Volume
|
||||
}
|
||||
}
|
||||
listInterest := []*big.Int{}
|
||||
for interest := range mapResult {
|
||||
listInterest = append(listInterest, interest)
|
||||
}
|
||||
sort.Slice(listInterest, func(i, j int) bool {
|
||||
return listInterest[i].Cmp(listInterest[j]) < 0
|
||||
})
|
||||
result := map[*big.Int]*big.Int{}
|
||||
for _, interest := range listInterest {
|
||||
result[interest] = mapResult[interest]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (self *itemListState) DumpItemList(db Database) DumpOrderList {
|
||||
mapResult := DumpOrderList{Volume: self.Volume(), Orders: map[*big.Int]*big.Int{}}
|
||||
orderListIt := trie.NewIterator(self.getTrie(db).NodeIterator(nil))
|
||||
for orderListIt.Next() {
|
||||
keyHash := common.BytesToHash(orderListIt.Key)
|
||||
if common.EmptyHash(keyHash) {
|
||||
continue
|
||||
}
|
||||
if _, exist := self.cachedStorage[keyHash]; exist {
|
||||
continue
|
||||
} else {
|
||||
_, content, _, _ := rlp.Split(orderListIt.Value)
|
||||
mapResult.Orders[new(big.Int).SetBytes(keyHash.Bytes())] = new(big.Int).SetBytes(content)
|
||||
}
|
||||
}
|
||||
for key, value := range self.cachedStorage {
|
||||
if !common.EmptyHash(value) {
|
||||
mapResult.Orders[new(big.Int).SetBytes(key.Bytes())] = new(big.Int).SetBytes(value.Bytes())
|
||||
}
|
||||
}
|
||||
listIds := []*big.Int{}
|
||||
for id := range mapResult.Orders {
|
||||
listIds = append(listIds, id)
|
||||
}
|
||||
sort.Slice(listIds, func(i, j int) bool {
|
||||
return listIds[i].Cmp(listIds[j]) < 0
|
||||
})
|
||||
result := DumpOrderList{Volume: self.Volume(), Orders: map[*big.Int]*big.Int{}}
|
||||
for _, id := range listIds {
|
||||
result.Orders[id] = mapResult.Orders[id]
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) DumpOrderBookInfo(orderBook common.Hash) (*DumpOrderBookInfo, error) {
|
||||
exhangeObject := self.getLendingExchange(orderBook)
|
||||
if exhangeObject == nil {
|
||||
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
|
||||
}
|
||||
result := &DumpOrderBookInfo{}
|
||||
result.Nonce = exhangeObject.data.Nonce
|
||||
result.TradeNonce = exhangeObject.data.TradeNonce
|
||||
result.BestInvesting = new(big.Int).SetBytes(exhangeObject.getBestInvestingInterest(self.db).Bytes())
|
||||
result.BestBorrowing = new(big.Int).SetBytes(exhangeObject.getBestBorrowingInterest(self.db).Bytes())
|
||||
lowestLiquidationTime, _ := exhangeObject.getLowestLiquidationTime(self.db)
|
||||
result.LowestLiquidationTime = new(big.Int).SetBytes(lowestLiquidationTime.Bytes())
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (self *liquidationTimeState) DumpItemList(db Database) DumpOrderList {
|
||||
mapResult := DumpOrderList{Volume: self.Volume(), Orders: map[*big.Int]*big.Int{}}
|
||||
orderListIt := trie.NewIterator(self.getTrie(db).NodeIterator(nil))
|
||||
for orderListIt.Next() {
|
||||
keyHash := common.BytesToHash(orderListIt.Key)
|
||||
if common.EmptyHash(keyHash) {
|
||||
continue
|
||||
}
|
||||
if _, exist := self.cachedStorage[keyHash]; exist {
|
||||
continue
|
||||
} else {
|
||||
_, content, _, _ := rlp.Split(orderListIt.Value)
|
||||
mapResult.Orders[new(big.Int).SetBytes(keyHash.Bytes())] = new(big.Int).SetBytes(content)
|
||||
}
|
||||
}
|
||||
for key, value := range self.cachedStorage {
|
||||
if !common.EmptyHash(value) {
|
||||
mapResult.Orders[new(big.Int).SetBytes(key.Bytes())] = new(big.Int).SetBytes(value.Bytes())
|
||||
}
|
||||
}
|
||||
listIds := []*big.Int{}
|
||||
for id := range mapResult.Orders {
|
||||
listIds = append(listIds, id)
|
||||
}
|
||||
sort.Slice(listIds, func(i, j int) bool {
|
||||
return listIds[i].Cmp(listIds[j]) < 0
|
||||
})
|
||||
result := DumpOrderList{Volume: self.Volume(), Orders: map[*big.Int]*big.Int{}}
|
||||
for _, id := range listIds {
|
||||
result.Orders[id] = mapResult.Orders[id]
|
||||
}
|
||||
return mapResult
|
||||
}
|
||||
func (self *LendingStateDB) DumpLiquidationTimeTrie(orderBook common.Hash) (map[*big.Int]DumpOrderList, error) {
|
||||
exhangeObject := self.getLendingExchange(orderBook)
|
||||
if exhangeObject == nil {
|
||||
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
|
||||
}
|
||||
mapResult := map[*big.Int]DumpOrderList{}
|
||||
it := trie.NewIterator(exhangeObject.getLiquidationTimeTrie(self.db).NodeIterator(nil))
|
||||
for it.Next() {
|
||||
unixTimeHash := common.BytesToHash(it.Key)
|
||||
if common.EmptyHash(unixTimeHash) {
|
||||
continue
|
||||
}
|
||||
unixTime := new(big.Int).SetBytes(unixTimeHash.Bytes())
|
||||
if _, exist := exhangeObject.liquidationTimeStates[unixTimeHash]; exist {
|
||||
continue
|
||||
} else {
|
||||
var data itemList
|
||||
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
|
||||
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,unixTime :%v ", orderBook.Hex(), unixTime)
|
||||
}
|
||||
stateOrderList := newLiquidationTimeState(orderBook, unixTimeHash, data, nil)
|
||||
mapResult[unixTime] = stateOrderList.DumpItemList(self.db)
|
||||
}
|
||||
}
|
||||
for unixTimeHash, itemList := range exhangeObject.liquidationTimeStates {
|
||||
if itemList.Volume().Sign() > 0 {
|
||||
mapResult[new(big.Int).SetBytes(unixTimeHash.Bytes())] = itemList.DumpItemList(self.db)
|
||||
}
|
||||
}
|
||||
listUnixTime := []*big.Int{}
|
||||
for unixTime := range mapResult {
|
||||
listUnixTime = append(listUnixTime, unixTime)
|
||||
}
|
||||
sort.Slice(listUnixTime, func(i, j int) bool {
|
||||
return listUnixTime[i].Cmp(listUnixTime[j]) < 0
|
||||
})
|
||||
result := map[*big.Int]DumpOrderList{}
|
||||
for _, unixTime := range listUnixTime {
|
||||
result[unixTime] = mapResult[unixTime]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) DumpLendingOrderTrie(orderBook common.Hash) (map[*big.Int]LendingItem, error) {
|
||||
exhangeObject := self.getLendingExchange(orderBook)
|
||||
if exhangeObject == nil {
|
||||
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
|
||||
}
|
||||
mapResult := map[*big.Int]LendingItem{}
|
||||
it := trie.NewIterator(exhangeObject.getLendingItemTrie(self.db).NodeIterator(nil))
|
||||
for it.Next() {
|
||||
orderIdHash := common.BytesToHash(it.Key)
|
||||
if common.EmptyHash(orderIdHash) {
|
||||
continue
|
||||
}
|
||||
orderId := new(big.Int).SetBytes(orderIdHash.Bytes())
|
||||
if _, exist := exhangeObject.lendingItemStates[orderIdHash]; exist {
|
||||
continue
|
||||
} else {
|
||||
var data LendingItem
|
||||
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
|
||||
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,orderId :%v ", orderBook.Hex(), orderId)
|
||||
}
|
||||
mapResult[orderId] = data
|
||||
}
|
||||
}
|
||||
for orderIdHash, lendingOrder := range exhangeObject.lendingItemStates {
|
||||
mapResult[new(big.Int).SetBytes(orderIdHash.Bytes())] = lendingOrder.data
|
||||
}
|
||||
listOrderId := []*big.Int{}
|
||||
for orderId := range mapResult {
|
||||
listOrderId = append(listOrderId, orderId)
|
||||
}
|
||||
sort.Slice(listOrderId, func(i, j int) bool {
|
||||
return listOrderId[i].Cmp(listOrderId[j]) < 0
|
||||
})
|
||||
result := map[*big.Int]LendingItem{}
|
||||
for _, orderId := range listOrderId {
|
||||
result[orderId] = mapResult[orderId]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) DumpLendingTradeTrie(orderBook common.Hash) (map[*big.Int]LendingTrade, error) {
|
||||
exhangeObject := self.getLendingExchange(orderBook)
|
||||
if exhangeObject == nil {
|
||||
return nil, fmt.Errorf("Order book not found orderBook : %v ", orderBook.Hex())
|
||||
}
|
||||
mapResult := map[*big.Int]LendingTrade{}
|
||||
it := trie.NewIterator(exhangeObject.getLendingTradeTrie(self.db).NodeIterator(nil))
|
||||
for it.Next() {
|
||||
tradeIdHash := common.BytesToHash(it.Key)
|
||||
if common.EmptyHash(tradeIdHash) {
|
||||
continue
|
||||
}
|
||||
tradeId := new(big.Int).SetBytes(tradeIdHash.Bytes())
|
||||
if _, exist := exhangeObject.lendingTradeStates[tradeIdHash]; exist {
|
||||
continue
|
||||
} else {
|
||||
var data LendingTrade
|
||||
if err := rlp.DecodeBytes(it.Value, &data); err != nil {
|
||||
return nil, fmt.Errorf("Fail when decode order iist orderBook : %v ,tradeId :%v ", orderBook.Hex(), tradeId)
|
||||
}
|
||||
mapResult[tradeId] = data
|
||||
}
|
||||
}
|
||||
for tradeIdHash, lendingTrade := range exhangeObject.lendingTradeStates {
|
||||
mapResult[new(big.Int).SetBytes(tradeIdHash.Bytes())] = lendingTrade.data
|
||||
}
|
||||
listTradeId := []*big.Int{}
|
||||
for tradeId := range mapResult {
|
||||
listTradeId = append(listTradeId, tradeId)
|
||||
}
|
||||
sort.Slice(listTradeId, func(i, j int) bool {
|
||||
return listTradeId[i].Cmp(listTradeId[j]) < 0
|
||||
})
|
||||
result := map[*big.Int]LendingTrade{}
|
||||
for _, tradeId := range listTradeId {
|
||||
result[tradeId] = mapResult[tradeId]
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
138
XDCxlending/lendingstate/journal.go
Normal file
138
XDCxlending/lendingstate/journal.go
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package lendingstate
|
||||
|
||||
import (
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type journalEntry interface {
|
||||
undo(db *LendingStateDB)
|
||||
}
|
||||
|
||||
type journal []journalEntry
|
||||
|
||||
type (
|
||||
// Changes to the account trie.
|
||||
insertOrder struct {
|
||||
orderBook common.Hash
|
||||
orderId common.Hash
|
||||
order *LendingItem
|
||||
}
|
||||
insertTrading struct {
|
||||
orderBook common.Hash
|
||||
tradeId uint64
|
||||
prvTrade *LendingTrade
|
||||
}
|
||||
cancelOrder struct {
|
||||
orderBook common.Hash
|
||||
orderId common.Hash
|
||||
order LendingItem
|
||||
}
|
||||
cancelTrading struct {
|
||||
orderBook common.Hash
|
||||
tradeId uint64
|
||||
order LendingTrade
|
||||
}
|
||||
subAmountOrder struct {
|
||||
orderBook common.Hash
|
||||
orderId common.Hash
|
||||
order LendingItem
|
||||
amount *big.Int
|
||||
}
|
||||
nonceChange struct {
|
||||
hash common.Hash
|
||||
prev uint64
|
||||
}
|
||||
tradeNonceChange struct {
|
||||
hash common.Hash
|
||||
prev uint64
|
||||
}
|
||||
liquidationPriceChange struct {
|
||||
orderBook common.Hash
|
||||
tradeId common.Hash
|
||||
prev *big.Int
|
||||
}
|
||||
collateralLockedAmount struct {
|
||||
orderBook common.Hash
|
||||
tradeId common.Hash
|
||||
prev *big.Int
|
||||
}
|
||||
)
|
||||
|
||||
func (ch insertOrder) undo(s *LendingStateDB) {
|
||||
s.CancelLendingOrder(ch.orderBook, ch.order)
|
||||
}
|
||||
func (ch cancelOrder) undo(s *LendingStateDB) {
|
||||
s.InsertLendingItem(ch.orderBook, ch.orderId, ch.order)
|
||||
}
|
||||
func (ch subAmountOrder) undo(s *LendingStateDB) {
|
||||
interestHash := common.BigToHash(ch.order.Interest)
|
||||
stateOrderBook := s.getLendingExchange(ch.orderBook)
|
||||
var stateOrderList *itemListState
|
||||
switch ch.order.Side {
|
||||
case Investing:
|
||||
stateOrderList = stateOrderBook.getInvestingOrderList(s.db, interestHash)
|
||||
case Borrowing:
|
||||
stateOrderList = stateOrderBook.getBorrowingOrderList(s.db, interestHash)
|
||||
default:
|
||||
return
|
||||
}
|
||||
stateOrderItem := stateOrderBook.getLendingItem(s.db, ch.orderId)
|
||||
newAmount := new(big.Int).Add(stateOrderItem.Quantity(), ch.amount)
|
||||
stateOrderItem.setVolume(newAmount)
|
||||
stateOrderList.insertLendingItem(s.db, ch.orderId, common.BigToHash(newAmount))
|
||||
stateOrderList.AddVolume(ch.amount)
|
||||
}
|
||||
func (ch nonceChange) undo(s *LendingStateDB) {
|
||||
s.SetNonce(ch.hash, ch.prev)
|
||||
}
|
||||
|
||||
func (ch tradeNonceChange) undo(s *LendingStateDB) {
|
||||
s.SetTradeNonce(ch.hash, ch.prev)
|
||||
}
|
||||
func (ch cancelTrading) undo(s *LendingStateDB) {
|
||||
s.InsertTradingItem(ch.orderBook, ch.tradeId, ch.order)
|
||||
}
|
||||
func (ch insertTrading) undo(s *LendingStateDB) {
|
||||
s.InsertTradingItem(ch.orderBook, ch.tradeId, *ch.prvTrade)
|
||||
}
|
||||
|
||||
func (ch liquidationPriceChange) undo(s *LendingStateDB) {
|
||||
stateOrderBook := s.getLendingExchange(ch.orderBook)
|
||||
if stateOrderBook == nil {
|
||||
return
|
||||
}
|
||||
stateLendingTrade := stateOrderBook.getLendingTrade(s.db, ch.tradeId)
|
||||
if stateLendingTrade == nil {
|
||||
return
|
||||
}
|
||||
stateLendingTrade.SetLiquidationPrice(ch.prev)
|
||||
}
|
||||
|
||||
func (ch collateralLockedAmount) undo(s *LendingStateDB) {
|
||||
stateOrderBook := s.getLendingExchange(ch.orderBook)
|
||||
if stateOrderBook == nil {
|
||||
return
|
||||
}
|
||||
stateLendingTrade := stateOrderBook.getLendingTrade(s.db, ch.tradeId)
|
||||
if stateLendingTrade == nil {
|
||||
return
|
||||
}
|
||||
stateLendingTrade.SetCollateralLockedAmount(ch.prev)
|
||||
}
|
||||
314
XDCxlending/lendingstate/lendingcontract.go
Normal file
314
XDCxlending/lendingstate/lendingcontract.go
Normal file
|
|
@ -0,0 +1,314 @@
|
|||
package lendingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/state"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
var (
|
||||
LendingRelayerListSlot = uint64(0)
|
||||
CollateralMapSlot = uint64(1)
|
||||
DefaultCollateralSlot = uint64(2)
|
||||
SupportedBaseSlot = uint64(3)
|
||||
SupportedTermSlot = uint64(4)
|
||||
ILOCollateralSlot = uint64(5)
|
||||
LendingRelayerStructSlots = map[string]*big.Int{
|
||||
"fee": big.NewInt(0),
|
||||
"bases": big.NewInt(1),
|
||||
"terms": big.NewInt(2),
|
||||
"collaterals": big.NewInt(3),
|
||||
}
|
||||
CollateralStructSlots = map[string]*big.Int{
|
||||
"depositRate": big.NewInt(0),
|
||||
"liquidationRate": big.NewInt(1),
|
||||
"recallRate": big.NewInt(2),
|
||||
"price": big.NewInt(3),
|
||||
}
|
||||
PriceStructSlots = map[string]*big.Int{
|
||||
"price": big.NewInt(0),
|
||||
"blockNumber": big.NewInt(1),
|
||||
}
|
||||
)
|
||||
|
||||
// @function IsValidRelayer : return whether the given address is the coinbase of a valid relayer or not
|
||||
// @param statedb : current state
|
||||
// @param coinbase: coinbase address of relayer
|
||||
// @return: true if it's a valid coinbase address of lending protocol, otherwise return false
|
||||
func IsValidRelayer(statedb *state.StateDB, coinbase common.Address) bool {
|
||||
locRelayerState := GetLocMappingAtKey(coinbase.Hash(), LendingRelayerListSlot)
|
||||
|
||||
// a valid relayer must have baseToken
|
||||
locBaseToken := state.GetLocOfStructElement(locRelayerState, LendingRelayerStructSlots["bases"])
|
||||
if v := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), common.BytesToHash(locBaseToken.Bytes())); v != (common.Hash{}) {
|
||||
if tradingstate.IsResignedRelayer(coinbase, statedb) {
|
||||
return false
|
||||
}
|
||||
slot := tradingstate.RelayerMappingSlot["RELAYER_LIST"]
|
||||
locRelayerStateTrading := GetLocMappingAtKey(coinbase.Hash(), slot)
|
||||
|
||||
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locRelayerStateTrading, tradingstate.RelayerStructMappingSlot["_deposit"])
|
||||
locHashDeposit := common.BigToHash(locBigDeposit)
|
||||
balance := statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit).Big()
|
||||
expectedFund := new(big.Int).Mul(common.BasePrice, common.RelayerLockedFund)
|
||||
if balance.Cmp(expectedFund) <= 0 {
|
||||
log.Debug("Relayer is not in relayer list", "relayer", coinbase.String(), "balance", balance, "expected", expectedFund)
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
// @function GetFee
|
||||
// @param statedb : current state
|
||||
// @param coinbase: coinbase address of relayer
|
||||
// @return: feeRate of lending
|
||||
func GetFee(statedb *state.StateDB, coinbase common.Address) *big.Int {
|
||||
locRelayerState := state.GetLocMappingAtKey(coinbase.Hash(), LendingRelayerListSlot)
|
||||
locHash := common.BytesToHash(new(big.Int).Add(locRelayerState, LendingRelayerStructSlots["fee"]).Bytes())
|
||||
return statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locHash).Big()
|
||||
}
|
||||
|
||||
// @function GetBaseList
|
||||
// @param statedb : current state
|
||||
// @param coinbase: coinbase address of relayer
|
||||
// @return: list of base tokens
|
||||
func GetBaseList(statedb *state.StateDB, coinbase common.Address) []common.Address {
|
||||
baseList := []common.Address{}
|
||||
locRelayerState := state.GetLocMappingAtKey(coinbase.Hash(), LendingRelayerListSlot)
|
||||
locBaseHash := state.GetLocOfStructElement(locRelayerState, LendingRelayerStructSlots["bases"])
|
||||
length := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locBaseHash).Big().Uint64()
|
||||
for i := uint64(0); i < length; i++ {
|
||||
loc := state.GetLocDynamicArrAtElement(locBaseHash, i, 1)
|
||||
addr := common.BytesToAddress(statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), loc).Bytes())
|
||||
if addr != (common.Address{}) {
|
||||
baseList = append(baseList, addr)
|
||||
}
|
||||
}
|
||||
return baseList
|
||||
}
|
||||
|
||||
// @function GetTerms
|
||||
// @param statedb : current state
|
||||
// @param coinbase: coinbase address of relayer
|
||||
// @return: list of supported terms of the given relayer
|
||||
func GetTerms(statedb *state.StateDB, coinbase common.Address) []uint64 {
|
||||
terms := []uint64{}
|
||||
locRelayerState := state.GetLocMappingAtKey(coinbase.Hash(), LendingRelayerListSlot)
|
||||
locTermHash := state.GetLocOfStructElement(locRelayerState, LendingRelayerStructSlots["terms"])
|
||||
length := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locTermHash).Big().Uint64()
|
||||
for i := uint64(0); i < length; i++ {
|
||||
loc := state.GetLocDynamicArrAtElement(locTermHash, i, 1)
|
||||
t := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), loc).Big().Uint64()
|
||||
if t != uint64(0) {
|
||||
terms = append(terms, t)
|
||||
}
|
||||
}
|
||||
return terms
|
||||
}
|
||||
|
||||
// @function IsValidPair
|
||||
// @param statedb : current state
|
||||
// @param coinbase: coinbase address of relayer
|
||||
// @param baseToken: address of baseToken
|
||||
// @param terms: term
|
||||
// @return: TRUE if the given baseToken, term organize a valid pair
|
||||
func IsValidPair(statedb *state.StateDB, coinbase common.Address, baseToken common.Address, term uint64) (valid bool, pairIndex uint64) {
|
||||
baseTokenList := GetBaseList(statedb, coinbase)
|
||||
terms := GetTerms(statedb, coinbase)
|
||||
baseIndexes := []uint64{}
|
||||
for i := uint64(0); i < uint64(len(baseTokenList)); i++ {
|
||||
if baseTokenList[i] == baseToken {
|
||||
baseIndexes = append(baseIndexes, i)
|
||||
}
|
||||
}
|
||||
for _, index := range baseIndexes {
|
||||
if terms[index] == term {
|
||||
pairIndex = index
|
||||
return true, pairIndex
|
||||
}
|
||||
}
|
||||
return false, pairIndex
|
||||
}
|
||||
|
||||
// @function GetCollaterals
|
||||
// @param statedb : current state
|
||||
// @param coinbase: coinbase address of relayer
|
||||
// @param baseToken: address of baseToken
|
||||
// @param terms: term
|
||||
// @return:
|
||||
// - collaterals []common.Address : list of addresses of collateral
|
||||
// - isSpecialCollateral : TRUE if collateral is a token which is NOT available for trading in XDCX, otherwise FALSE
|
||||
func GetCollaterals(statedb *state.StateDB, coinbase common.Address, baseToken common.Address, term uint64) (collaterals []common.Address, isSpecialCollateral bool) {
|
||||
validPair, _ := IsValidPair(statedb, coinbase, baseToken, term)
|
||||
if !validPair {
|
||||
return []common.Address{}, false
|
||||
}
|
||||
|
||||
//TODO: ILO Collateral is not supported in release 2.2.0
|
||||
//locRelayerState := state.GetLocMappingAtKey(coinbase.Hash(), LendingRelayerListSlot)
|
||||
//locCollateralHash := state.GetLocOfStructElement(locRelayerState, LendingRelayerStructSlots["collaterals"])
|
||||
//length := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locCollateralHash).Big().Uint64()
|
||||
//
|
||||
//loc := state.GetLocDynamicArrAtElement(locCollateralHash, pairIndex, 1)
|
||||
//collateralAddr := common.BytesToAddress(statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), loc).Bytes())
|
||||
//if collateralAddr != (common.Address{}) && collateralAddr != (common.HexToAddress("0x0")) {
|
||||
// return []common.Address{collateralAddr}, true
|
||||
//}
|
||||
|
||||
// if collaterals is not defined for the relayer, return default collaterals
|
||||
locDefaultCollateralHash := state.GetLocSimpleVariable(DefaultCollateralSlot)
|
||||
length := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locDefaultCollateralHash).Big().Uint64()
|
||||
for i := uint64(0); i < length; i++ {
|
||||
loc := state.GetLocDynamicArrAtElement(locDefaultCollateralHash, i, 1)
|
||||
addr := common.BytesToAddress(statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), loc).Bytes())
|
||||
if addr != (common.Address{}) {
|
||||
collaterals = append(collaterals, addr)
|
||||
}
|
||||
}
|
||||
return collaterals, false
|
||||
}
|
||||
|
||||
// @function GetCollateralDetail
|
||||
// @param statedb : current state
|
||||
// @param token: address of collateral token
|
||||
// @return: depositRate, liquidationRate, price of collateral
|
||||
func GetCollateralDetail(statedb *state.StateDB, token common.Address) (depositRate, liquidationRate, recallRate *big.Int) {
|
||||
collateralState := GetLocMappingAtKey(token.Hash(), CollateralMapSlot)
|
||||
locDepositRate := state.GetLocOfStructElement(collateralState, CollateralStructSlots["depositRate"])
|
||||
locLiquidationRate := state.GetLocOfStructElement(collateralState, CollateralStructSlots["liquidationRate"])
|
||||
locRecallRate := state.GetLocOfStructElement(collateralState, CollateralStructSlots["recallRate"])
|
||||
depositRate = statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locDepositRate).Big()
|
||||
liquidationRate = statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locLiquidationRate).Big()
|
||||
recallRate = statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locRecallRate).Big()
|
||||
return depositRate, liquidationRate, recallRate
|
||||
}
|
||||
|
||||
func GetCollateralPrice(statedb *state.StateDB, collateralToken common.Address, lendingToken common.Address) (price, blockNumber *big.Int) {
|
||||
collateralState := GetLocMappingAtKey(collateralToken.Hash(), CollateralMapSlot)
|
||||
locMapPrices := collateralState.Add(collateralState, CollateralStructSlots["price"])
|
||||
locLendingTokenPriceByte := crypto.Keccak256(lendingToken.Hash().Bytes(), common.BigToHash(locMapPrices).Bytes())
|
||||
|
||||
locCollateralPrice := common.BigToHash(new(big.Int).Add(new(big.Int).SetBytes(locLendingTokenPriceByte), PriceStructSlots["price"]))
|
||||
locBlockNumber := common.BigToHash(new(big.Int).Add(new(big.Int).SetBytes(locLendingTokenPriceByte), PriceStructSlots["blockNumber"]))
|
||||
|
||||
price = statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locCollateralPrice).Big()
|
||||
blockNumber = statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locBlockNumber).Big()
|
||||
return price, blockNumber
|
||||
}
|
||||
|
||||
// @function GetSupportedTerms
|
||||
// @param statedb : current state
|
||||
// @return: list of terms which XDCxlending supports
|
||||
func GetSupportedTerms(statedb *state.StateDB) []uint64 {
|
||||
terms := []uint64{}
|
||||
locSupportedTerm := state.GetLocSimpleVariable(SupportedTermSlot)
|
||||
length := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locSupportedTerm).Big().Uint64()
|
||||
for i := uint64(0); i < length; i++ {
|
||||
loc := state.GetLocDynamicArrAtElement(locSupportedTerm, i, 1)
|
||||
t := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), loc).Big().Uint64()
|
||||
if t != 0 {
|
||||
terms = append(terms, t)
|
||||
}
|
||||
}
|
||||
return terms
|
||||
}
|
||||
|
||||
// @function GetSupportedBaseToken
|
||||
// @param statedb : current state
|
||||
// @return: list of tokens which are available for lending
|
||||
func GetSupportedBaseToken(statedb *state.StateDB) []common.Address {
|
||||
baseTokens := []common.Address{}
|
||||
locSupportedBaseToken := state.GetLocSimpleVariable(SupportedBaseSlot)
|
||||
length := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locSupportedBaseToken).Big().Uint64()
|
||||
for i := uint64(0); i < length; i++ {
|
||||
loc := state.GetLocDynamicArrAtElement(locSupportedBaseToken, i, 1)
|
||||
addr := common.BytesToAddress(statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), loc).Bytes())
|
||||
if addr != (common.Address{}) {
|
||||
baseTokens = append(baseTokens, addr)
|
||||
}
|
||||
}
|
||||
return baseTokens
|
||||
}
|
||||
|
||||
// @function GetAllCollateral
|
||||
// @param statedb : current state
|
||||
// @return: list of address of collateral token
|
||||
func GetAllCollateral(statedb *state.StateDB) []common.Address {
|
||||
collaterals := []common.Address{}
|
||||
|
||||
//TODO: ILO Collateral is not supported in release 2.2.0
|
||||
//locILOCollateral := state.GetLocSimpleVariable(ILOCollateralSlot)
|
||||
//length := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locILOCollateral).Big().Uint64()
|
||||
//for i := uint64(0); i < length; i++ {
|
||||
// loc := state.GetLocDynamicArrAtElement(locILOCollateral, i, 1)
|
||||
// addr := common.BytesToAddress(statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), loc).Bytes())
|
||||
// if addr != (common.Address{}) {
|
||||
// collaterals = append(collaterals, addr)
|
||||
// }
|
||||
//}
|
||||
|
||||
locDefaultCollateralHash := state.GetLocSimpleVariable(DefaultCollateralSlot)
|
||||
length := statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), locDefaultCollateralHash).Big().Uint64()
|
||||
for i := uint64(0); i < length; i++ {
|
||||
loc := state.GetLocDynamicArrAtElement(locDefaultCollateralHash, i, 1)
|
||||
addr := common.BytesToAddress(statedb.GetState(common.HexToAddress(common.LendingRegistrationSMC), loc).Bytes())
|
||||
if addr != (common.Address{}) {
|
||||
collaterals = append(collaterals, addr)
|
||||
}
|
||||
}
|
||||
return collaterals
|
||||
}
|
||||
|
||||
// @function GetAllLendingBooks
|
||||
// @param statedb : current state
|
||||
// @return: a map to specify whether lendingBook (combination of baseToken and term) is valid or not
|
||||
func GetAllLendingBooks(statedb *state.StateDB) (mapLendingBook map[common.Hash]bool, err error) {
|
||||
mapLendingBook = make(map[common.Hash]bool)
|
||||
baseTokens := GetSupportedBaseToken(statedb)
|
||||
terms := GetSupportedTerms(statedb)
|
||||
if len(baseTokens) == 0 {
|
||||
return nil, fmt.Errorf("GetAllLendingBooks: empty baseToken list")
|
||||
}
|
||||
if len(terms) == 0 {
|
||||
return nil, fmt.Errorf("GetAllLendingPairs: empty term list")
|
||||
}
|
||||
for _, baseToken := range baseTokens {
|
||||
for _, term := range terms {
|
||||
if (baseToken != common.Address{}) && (term > 0) {
|
||||
mapLendingBook[GetLendingOrderBookHash(baseToken, term)] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return mapLendingBook, nil
|
||||
}
|
||||
|
||||
// @function GetAllLendingPairs
|
||||
// @param statedb : current state
|
||||
// @return: list of lendingPair (combination of baseToken and collateralToken)
|
||||
func GetAllLendingPairs(statedb *state.StateDB) (allPairs []LendingPair, err error) {
|
||||
baseTokens := GetSupportedBaseToken(statedb)
|
||||
collaterals := GetAllCollateral(statedb)
|
||||
if len(baseTokens) == 0 {
|
||||
return allPairs, fmt.Errorf("GetAllLendingPairs: empty baseToken list")
|
||||
}
|
||||
if len(collaterals) == 0 {
|
||||
return allPairs, fmt.Errorf("GetAllLendingPairs: empty collateral list")
|
||||
}
|
||||
for _, baseToken := range baseTokens {
|
||||
for _, collateral := range collaterals {
|
||||
if (baseToken != common.Address{}) && (collateral != common.Address{}) {
|
||||
allPairs = append(allPairs, LendingPair{
|
||||
LendingToken: baseToken,
|
||||
CollateralToken: collateral,
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
return allPairs, nil
|
||||
}
|
||||
464
XDCxlending/lendingstate/lendingitem.go
Normal file
464
XDCxlending/lendingstate/lendingitem.go
Normal file
|
|
@ -0,0 +1,464 @@
|
|||
package lendingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/state"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
Investing = "INVEST"
|
||||
Borrowing = "BORROW"
|
||||
TopUp = "TOPUP"
|
||||
Repay = "REPAY"
|
||||
Recall = "RECALL"
|
||||
LendingStatusNew = "NEW"
|
||||
LendingStatusOpen = "OPEN"
|
||||
LendingStatusReject = "REJECTED"
|
||||
LendingStatusFilled = "FILLED"
|
||||
LendingStatusPartialFilled = "PARTIAL_FILLED"
|
||||
LendingStatusCancelled = "CANCELLED"
|
||||
Market = "MO"
|
||||
Limit = "LO"
|
||||
)
|
||||
|
||||
var ValidInputLendingStatus = map[string]bool{
|
||||
LendingStatusNew: true,
|
||||
LendingStatusCancelled: true,
|
||||
}
|
||||
|
||||
var ValidInputLendingType = map[string]bool{
|
||||
Market: true,
|
||||
Limit: true,
|
||||
Repay: true,
|
||||
TopUp: true,
|
||||
Recall: true,
|
||||
}
|
||||
|
||||
// Signature struct
|
||||
type Signature struct {
|
||||
V byte `bson:"v" json:"v"`
|
||||
R common.Hash `bson:"r" json:"r"`
|
||||
S common.Hash `bson:"s" json:"s"`
|
||||
}
|
||||
|
||||
type SignatureRecord struct {
|
||||
V byte `bson:"v" json:"v"`
|
||||
R string `bson:"r" json:"r"`
|
||||
S string `bson:"s" json:"s"`
|
||||
}
|
||||
|
||||
type LendingItem struct {
|
||||
Quantity *big.Int `bson:"quantity" json:"quantity"`
|
||||
Interest *big.Int `bson:"interest" json:"interest"`
|
||||
Side string `bson:"side" json:"side"` // INVESTING/BORROWING
|
||||
Type string `bson:"type" json:"type"` // LIMIT/MARKET
|
||||
LendingToken common.Address `bson:"lendingToken" json:"lendingToken"`
|
||||
CollateralToken common.Address `bson:"collateralToken" json:"collateralToken"`
|
||||
AutoTopUp bool `bson:"autoTopUp" json:"autoTopUp"`
|
||||
FilledAmount *big.Int `bson:"filledAmount" json:"filledAmount"`
|
||||
Status string `bson:"status" json:"status"`
|
||||
Relayer common.Address `bson:"relayer" json:"relayer"`
|
||||
Term uint64 `bson:"term" json:"term"`
|
||||
UserAddress common.Address `bson:"userAddress" json:"userAddress"`
|
||||
Signature *Signature `bson:"signature" json:"signature"`
|
||||
Hash common.Hash `bson:"hash" json:"hash"`
|
||||
TxHash common.Hash `bson:"txHash" json:"txHash"`
|
||||
Nonce *big.Int `bson:"nonce" json:"nonce"`
|
||||
CreatedAt time.Time `bson:"createdAt" json:"createdAt"`
|
||||
UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"`
|
||||
LendingId uint64 `bson:"lendingId" json:"lendingId"`
|
||||
LendingTradeId uint64 `bson:"tradeId" json:"tradeId"`
|
||||
ExtraData string `bson:"extraData" json:"extraData"`
|
||||
}
|
||||
|
||||
type LendingItemBSON struct {
|
||||
Quantity string `bson:"quantity" json:"quantity"`
|
||||
Interest string `bson:"interest" json:"interest"`
|
||||
Side string `bson:"side" json:"side"` // INVESTING/BORROWING
|
||||
Type string `bson:"type" json:"type"` // LIMIT/MARKET
|
||||
LendingToken string `bson:"lendingToken" json:"lendingToken"`
|
||||
CollateralToken string `bson:"collateralToken" json:"collateralToken"`
|
||||
AutoTopUp bool `bson:"autoTopUp" json:"autoTopUp"`
|
||||
FilledAmount string `bson:"filledAmount" json:"filledAmount"`
|
||||
Status string `bson:"status" json:"status"`
|
||||
Relayer string `bson:"relayer" json:"relayer"`
|
||||
Term string `bson:"term" json:"term"`
|
||||
UserAddress string `bson:"userAddress" json:"userAddress"`
|
||||
Signature *SignatureRecord `bson:"signature" json:"signature"`
|
||||
Hash string `bson:"hash" json:"hash"`
|
||||
TxHash string `bson:"txHash" json:"txHash"`
|
||||
Nonce string `bson:"nonce" json:"nonce"`
|
||||
CreatedAt time.Time `bson:"createdAt" json:"createdAt"`
|
||||
UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"`
|
||||
LendingId string `bson:"lendingId" json:"lendingId"`
|
||||
LendingTradeId string `bson:"tradeId" json:"tradeId"`
|
||||
ExtraData string `bson:"extraData" json:"extraData"`
|
||||
}
|
||||
|
||||
func (l *LendingItem) GetBSON() (interface{}, error) {
|
||||
lr := LendingItemBSON{
|
||||
Quantity: l.Quantity.String(),
|
||||
Interest: l.Interest.String(),
|
||||
Side: l.Side,
|
||||
Type: l.Type,
|
||||
LendingToken: l.LendingToken.Hex(),
|
||||
CollateralToken: l.CollateralToken.Hex(),
|
||||
AutoTopUp: l.AutoTopUp,
|
||||
Status: l.Status,
|
||||
Relayer: l.Relayer.Hex(),
|
||||
Term: strconv.FormatUint(l.Term, 10),
|
||||
UserAddress: l.UserAddress.Hex(),
|
||||
Hash: l.Hash.Hex(),
|
||||
TxHash: l.TxHash.Hex(),
|
||||
Nonce: l.Nonce.String(),
|
||||
CreatedAt: l.CreatedAt,
|
||||
UpdatedAt: l.UpdatedAt,
|
||||
LendingId: strconv.FormatUint(l.LendingId, 10),
|
||||
LendingTradeId: strconv.FormatUint(l.LendingTradeId, 10),
|
||||
ExtraData: l.ExtraData,
|
||||
}
|
||||
|
||||
if l.FilledAmount != nil {
|
||||
lr.FilledAmount = l.FilledAmount.String()
|
||||
}
|
||||
|
||||
if l.Signature != nil {
|
||||
lr.Signature = &SignatureRecord{
|
||||
V: l.Signature.V,
|
||||
R: l.Signature.R.Hex(),
|
||||
S: l.Signature.S.Hex(),
|
||||
}
|
||||
}
|
||||
|
||||
return lr, nil
|
||||
}
|
||||
|
||||
func (l *LendingItem) SetBSON(raw bson.Raw) error {
|
||||
decoded := new(LendingItemBSON)
|
||||
|
||||
err := raw.Unmarshal(decoded)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if decoded.Quantity != "" {
|
||||
l.Quantity = ToBigInt(decoded.Quantity)
|
||||
}
|
||||
l.Interest = ToBigInt(decoded.Interest)
|
||||
l.Side = decoded.Side
|
||||
l.Type = decoded.Type
|
||||
l.LendingToken = common.HexToAddress(decoded.LendingToken)
|
||||
l.CollateralToken = common.HexToAddress(decoded.CollateralToken)
|
||||
l.AutoTopUp = decoded.AutoTopUp
|
||||
l.FilledAmount = ToBigInt(decoded.FilledAmount)
|
||||
l.Status = decoded.Status
|
||||
l.Relayer = common.HexToAddress(decoded.Relayer)
|
||||
term, err := strconv.ParseInt(decoded.Term, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse lendingItem.term. Err: %v", err)
|
||||
}
|
||||
l.Term = uint64(term)
|
||||
l.UserAddress = common.HexToAddress(decoded.UserAddress)
|
||||
|
||||
if decoded.Signature != nil {
|
||||
l.Signature = &Signature{
|
||||
V: byte(decoded.Signature.V),
|
||||
R: common.HexToHash(decoded.Signature.R),
|
||||
S: common.HexToHash(decoded.Signature.S),
|
||||
}
|
||||
}
|
||||
|
||||
l.Hash = common.HexToHash(decoded.Hash)
|
||||
l.TxHash = common.HexToHash(decoded.TxHash)
|
||||
l.Nonce = ToBigInt(decoded.Nonce)
|
||||
|
||||
l.CreatedAt = decoded.CreatedAt
|
||||
l.UpdatedAt = decoded.UpdatedAt
|
||||
lendingId, err := strconv.ParseInt(decoded.LendingId, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.LendingId = uint64(lendingId)
|
||||
lendingTradeId, err := strconv.ParseInt(decoded.LendingTradeId, 10, 64)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
l.LendingTradeId = uint64(lendingTradeId)
|
||||
l.ExtraData = decoded.ExtraData
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LendingItem) VerifyLendingItem(state *state.StateDB) error {
|
||||
if err := l.VerifyLendingStatus(); err != nil {
|
||||
return err
|
||||
}
|
||||
if valid, _ := IsValidPair(state, l.Relayer, l.LendingToken, l.Term); valid == false {
|
||||
return fmt.Errorf("invalid pair . LendToken %s . Term: %v", l.LendingToken.Hex(), l.Term)
|
||||
}
|
||||
if l.Status == LendingStatusNew {
|
||||
if err := l.VerifyLendingType(); err != nil {
|
||||
return err
|
||||
}
|
||||
if l.Type != Repay {
|
||||
if err := l.VerifyLendingQuantity(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
if l.Type == Limit || l.Type == Market {
|
||||
if err := l.VerifyLendingSide(); err != nil {
|
||||
return err
|
||||
}
|
||||
if l.Side == Borrowing {
|
||||
if err := l.VerifyCollateral(state); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if l.Type == Limit {
|
||||
if err := l.VerifyLendingInterest(); err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
}
|
||||
if !IsValidRelayer(state, l.Relayer) {
|
||||
return fmt.Errorf("VerifyLendingItem: invalid relayer. address: %s", l.Relayer.Hex())
|
||||
}
|
||||
if err := l.VerifyLendingSignature(); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LendingItem) VerifyLendingSide() error {
|
||||
if l.Side != Borrowing && l.Side != Investing {
|
||||
return fmt.Errorf("VerifyLendingSide: invalid side . Side: %s", l.Side)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LendingItem) VerifyCollateral(state *state.StateDB) error {
|
||||
if l.CollateralToken.String() == EmptyAddress || l.CollateralToken.String() == l.LendingToken.String() {
|
||||
return fmt.Errorf("invalid collateral %s", l.CollateralToken.Hex())
|
||||
}
|
||||
validCollateral := false
|
||||
collateralList, _ := GetCollaterals(state, l.Relayer, l.LendingToken, l.Term)
|
||||
for _, collateral := range collateralList {
|
||||
if l.CollateralToken.String() == collateral.String() {
|
||||
validCollateral = true
|
||||
break
|
||||
}
|
||||
}
|
||||
if !validCollateral {
|
||||
return fmt.Errorf("invalid collateral %s", l.CollateralToken.Hex())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LendingItem) VerifyLendingInterest() error {
|
||||
if l.Interest == nil || l.Interest.Sign() <= 0 {
|
||||
return fmt.Errorf("VerifyLendingInterest: invalid interest. Interest: %v", l.Interest)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LendingItem) VerifyLendingQuantity() error {
|
||||
if l.Quantity == nil || l.Quantity.Sign() <= 0 {
|
||||
return fmt.Errorf("VerifyLendingQuantity: invalid quantity. Quantity: %v", l.Quantity)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LendingItem) VerifyLendingType() error {
|
||||
if valid, ok := ValidInputLendingType[l.Type]; !ok && !valid {
|
||||
return fmt.Errorf("VerifyLendingType: invalid lending type. Type: %s", l.Type)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LendingItem) VerifyLendingStatus() error {
|
||||
if valid, ok := ValidInputLendingStatus[l.Status]; !ok && !valid {
|
||||
return fmt.Errorf("VerifyLendingStatus: invalid lending status. Status: %s", l.Status)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (l *LendingItem) ComputeHash() common.Hash {
|
||||
sha := sha3.NewKeccak256()
|
||||
if l.Status == LendingStatusNew {
|
||||
sha.Write(l.Relayer.Bytes())
|
||||
sha.Write(l.UserAddress.Bytes())
|
||||
sha.Write(l.LendingToken.Bytes())
|
||||
sha.Write(l.CollateralToken.Bytes())
|
||||
sha.Write([]byte(strconv.FormatInt(int64(l.Term), 10)))
|
||||
sha.Write(common.BigToHash(l.Quantity).Bytes())
|
||||
if l.Type == Limit {
|
||||
if l.Interest != nil {
|
||||
sha.Write(common.BigToHash(l.Interest).Bytes())
|
||||
}
|
||||
}
|
||||
sha.Write(common.BigToHash(l.EncodedSide()).Bytes())
|
||||
sha.Write([]byte(l.Status))
|
||||
sha.Write([]byte(l.Type))
|
||||
sha.Write(common.BigToHash(l.Nonce).Bytes())
|
||||
} else if l.Status == LendingStatusCancelled {
|
||||
sha.Write(l.Hash.Bytes())
|
||||
sha.Write(common.BigToHash(l.Nonce).Bytes())
|
||||
sha.Write(l.UserAddress.Bytes())
|
||||
sha.Write(common.BigToHash(big.NewInt(int64(l.LendingId))).Bytes())
|
||||
sha.Write([]byte(l.Status))
|
||||
sha.Write(l.Relayer.Bytes())
|
||||
sha.Write(l.LendingToken.Bytes())
|
||||
sha.Write(l.CollateralToken.Bytes())
|
||||
} else {
|
||||
return common.Hash{}
|
||||
}
|
||||
|
||||
return common.BytesToHash(sha.Sum(nil))
|
||||
}
|
||||
|
||||
func (l *LendingItem) EncodedSide() *big.Int {
|
||||
if l.Side == Borrowing {
|
||||
return big.NewInt(0)
|
||||
}
|
||||
return big.NewInt(1)
|
||||
}
|
||||
|
||||
//verify signatures
|
||||
func (l *LendingItem) VerifyLendingSignature() error {
|
||||
V := big.NewInt(int64(l.Signature.V))
|
||||
R := l.Signature.R.Big()
|
||||
S := l.Signature.S.Big()
|
||||
|
||||
//(nonce uint64, quantity *big.Int, interest, duration uint64, relayerAddress, userAddress, lendingToken, collateralToken common.Address, status, side, typeLending string, hash common.Hash, id uint64
|
||||
tx := types.NewLendingTransaction(l.Nonce.Uint64(), l.Quantity, l.Interest.Uint64(), l.Term, l.Relayer, l.UserAddress,
|
||||
l.LendingToken, l.CollateralToken, l.AutoTopUp, l.Status, l.Side, l.Type, l.Hash, l.LendingId, l.LendingTradeId, l.ExtraData)
|
||||
tx.ImportSignature(V, R, S)
|
||||
from, _ := types.LendingSender(types.LendingTxSigner{}, tx)
|
||||
if from != tx.UserAddress() {
|
||||
return fmt.Errorf("verify lending item: invalid signature")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func VerifyBalance(isXDCXLendingFork bool, statedb *state.StateDB, lendingStateDb *LendingStateDB,
|
||||
orderType, side, status string, userAddress, relayer, lendingToken, collateralToken common.Address,
|
||||
quantity, lendingTokenDecimal, collateralTokenDecimal, lendTokenXDCPrice, collateralPrice *big.Int,
|
||||
term uint64, lendingId uint64, lendingTradeId uint64) error {
|
||||
borrowingFeeRate := GetFee(statedb, relayer)
|
||||
switch orderType {
|
||||
case TopUp:
|
||||
lendingBook := GetLendingOrderBookHash(lendingToken, term)
|
||||
lendingTrade := lendingStateDb.GetLendingTrade(lendingBook, common.Uint64ToHash(lendingTradeId))
|
||||
if lendingTrade == EmptyLendingTrade {
|
||||
return fmt.Errorf("VerifyBalance: process deposit for emptyLendingTrade is not allowed. lendingTradeId: %v", lendingTradeId)
|
||||
}
|
||||
tokenBalance := GetTokenBalance(lendingTrade.Borrower, lendingTrade.CollateralToken, statedb)
|
||||
if tokenBalance.Cmp(quantity) < 0 {
|
||||
return fmt.Errorf("VerifyBalance: not enough balance to process deposit for lendingTrade."+
|
||||
"lendingTradeId: %v. Token: %s. ExpectedBalance: %s. ActualBalance: %s",
|
||||
lendingTradeId, lendingTrade.CollateralToken.Hex(), quantity.String(), tokenBalance.String())
|
||||
}
|
||||
case Repay:
|
||||
lendingBook := GetLendingOrderBookHash(lendingToken, term)
|
||||
lendingTrade := lendingStateDb.GetLendingTrade(lendingBook, common.Uint64ToHash(lendingTradeId))
|
||||
if lendingTrade == EmptyLendingTrade {
|
||||
return fmt.Errorf("VerifyBalance: process payment for emptyLendingTrade is not allowed. lendingTradeId: %v", lendingTradeId)
|
||||
}
|
||||
tokenBalance := GetTokenBalance(lendingTrade.Borrower, lendingTrade.LendingToken, statedb)
|
||||
paymentBalance := CalculateTotalRepayValue(uint64(time.Now().Unix()), lendingTrade.LiquidationTime, lendingTrade.Term, lendingTrade.Interest, lendingTrade.Amount)
|
||||
|
||||
if tokenBalance.Cmp(paymentBalance) < 0 {
|
||||
return fmt.Errorf("VerifyBalance: not enough balance to process payment for lendingTrade."+
|
||||
"lendingTradeId: %v. Token: %s. ExpectedBalance: %s. ActualBalance: %s",
|
||||
lendingTradeId, lendingTrade.LendingToken.Hex(), paymentBalance.String(), tokenBalance.String())
|
||||
|
||||
}
|
||||
case Market, Limit:
|
||||
switch side {
|
||||
case Investing:
|
||||
switch status {
|
||||
case LendingStatusNew:
|
||||
// make sure that investor have enough lendingToken
|
||||
if balance := GetTokenBalance(userAddress, lendingToken, statedb); balance.Cmp(quantity) < 0 {
|
||||
return fmt.Errorf("VerifyBalance: investor doesn't have enough lendingToken. User: %s. Token: %s. Expected: %v. Have: %v", userAddress.Hex(), lendingToken.Hex(), quantity, balance)
|
||||
}
|
||||
// check quantity: reject if it's too small
|
||||
if lendTokenXDCPrice != nil && lendTokenXDCPrice.Sign() > 0 {
|
||||
defaultFee := new(big.Int).Mul(quantity, new(big.Int).SetUint64(DefaultFeeRate))
|
||||
defaultFee = new(big.Int).Div(defaultFee, common.XDCXBaseFee)
|
||||
defaultFeeInXDC := common.Big0
|
||||
if lendingToken.String() != common.XDCNativeAddress {
|
||||
defaultFeeInXDC = new(big.Int).Mul(defaultFee, lendTokenXDCPrice)
|
||||
defaultFeeInXDC = new(big.Int).Div(defaultFeeInXDC, lendingTokenDecimal)
|
||||
} else {
|
||||
defaultFeeInXDC = defaultFee
|
||||
}
|
||||
if defaultFeeInXDC.Cmp(common.RelayerLendingFee) <= 0 {
|
||||
return ErrQuantityTradeTooSmall
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
case LendingStatusCancelled:
|
||||
// in case of cancel, investor need to pay cancel fee in lendingToken
|
||||
// make sure actualBalance >= cancel fee
|
||||
lendingBook := GetLendingOrderBookHash(lendingToken, term)
|
||||
item := lendingStateDb.GetLendingOrder(lendingBook, common.BigToHash(new(big.Int).SetUint64(lendingId)))
|
||||
cancelFee := big.NewInt(0)
|
||||
cancelFee = new(big.Int).Mul(item.Quantity, borrowingFeeRate)
|
||||
cancelFee = new(big.Int).Div(cancelFee, common.XDCXBaseCancelFee)
|
||||
|
||||
actualBalance := GetTokenBalance(userAddress, lendingToken, statedb)
|
||||
if actualBalance.Cmp(cancelFee) < 0 {
|
||||
return fmt.Errorf("VerifyBalance: investor doesn't have enough lendingToken to pay cancel fee. LendingToken: %s . ExpectedBalance: %s . ActualBalance: %s",
|
||||
lendingToken.Hex(), cancelFee.String(), actualBalance.String())
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("VerifyBalance: invalid status of investing lendingitem. Status: %s", status)
|
||||
}
|
||||
return nil
|
||||
case Borrowing:
|
||||
switch status {
|
||||
case LendingStatusNew:
|
||||
depositRate, _, _ := GetCollateralDetail(statedb, collateralToken)
|
||||
settleBalanceResult, err := GetSettleBalance(isXDCXLendingFork, Borrowing, lendTokenXDCPrice, collateralPrice, depositRate, borrowingFeeRate, lendingToken, collateralToken, lendingTokenDecimal, collateralTokenDecimal, quantity)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
expectedBalance := settleBalanceResult.CollateralLockedAmount
|
||||
actualBalance := GetTokenBalance(userAddress, collateralToken, statedb)
|
||||
if actualBalance.Cmp(expectedBalance) < 0 {
|
||||
return fmt.Errorf("VerifyBalance: borrower doesn't have enough collateral token. User: %s. CollateralToken: %s . ExpectedBalance: %s . ActualBalance: %s",
|
||||
userAddress.Hex(), collateralToken.Hex(), expectedBalance.String(), actualBalance.String())
|
||||
}
|
||||
case LendingStatusCancelled:
|
||||
lendingBook := GetLendingOrderBookHash(lendingToken, term)
|
||||
item := lendingStateDb.GetLendingOrder(lendingBook, common.BigToHash(new(big.Int).SetUint64(lendingId)))
|
||||
cancelFee := big.NewInt(0)
|
||||
// Fee == quantityToLend/base lend token decimal *price*borrowFee/LendingCancelFee
|
||||
cancelFee = new(big.Int).Div(item.Quantity, collateralPrice)
|
||||
cancelFee = new(big.Int).Mul(cancelFee, borrowingFeeRate)
|
||||
cancelFee = new(big.Int).Div(cancelFee, common.XDCXBaseCancelFee)
|
||||
actualBalance := GetTokenBalance(userAddress, collateralToken, statedb)
|
||||
if actualBalance.Cmp(cancelFee) < 0 {
|
||||
return fmt.Errorf("VerifyBalance: borrower doesn't have enough collateralToken to pay cancel fee. User: %s. CollateralToken: %s . ExpectedBalance: %s . ActualBalance: %s",
|
||||
userAddress.Hex(), lendingToken.Hex(), cancelFee.String(), actualBalance.String())
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("VerifyBalance: invalid status of borrowing lendingitem. Status: %s", status)
|
||||
}
|
||||
return nil
|
||||
default:
|
||||
return fmt.Errorf("VerifyBalance: unknown lending side")
|
||||
}
|
||||
default:
|
||||
return fmt.Errorf("VerifyBalance: unknown lending type")
|
||||
}
|
||||
return nil
|
||||
}
|
||||
575
XDCxlending/lendingstate/lendingitem_test.go
Normal file
575
XDCxlending/lendingstate/lendingitem_test.go
Normal file
|
|
@ -0,0 +1,575 @@
|
|||
package lendingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/state"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
|
||||
"github.com/XinFinOrg/XDPoSChain/rpc"
|
||||
"math/big"
|
||||
"math/rand"
|
||||
"os"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func TestLendingItem_VerifyLendingSide(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fields *LendingItem
|
||||
wantErr bool
|
||||
}{
|
||||
{"wrong side", &LendingItem{Side: "GIVE"}, true},
|
||||
{"side: borrowing", &LendingItem{Side: Borrowing}, false},
|
||||
{"side: investing", &LendingItem{Side: Investing}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := &LendingItem{
|
||||
Side: tt.fields.Side,
|
||||
}
|
||||
if err := l.VerifyLendingSide(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("VerifyLendingSide() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLendingItem_VerifyLendingInterest(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fields *LendingItem
|
||||
wantErr bool
|
||||
}{
|
||||
{"no interest information", &LendingItem{}, true},
|
||||
{"negative interest", &LendingItem{Interest: big.NewInt(-1)}, true},
|
||||
{"zero interest", &LendingItem{Interest: Zero}, true},
|
||||
{"positive interest", &LendingItem{Interest: big.NewInt(2)}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := &LendingItem{
|
||||
Interest: tt.fields.Interest,
|
||||
}
|
||||
if err := l.VerifyLendingInterest(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("VerifyLendingSide() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLendingItem_VerifyLendingQuantity(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fields *LendingItem
|
||||
wantErr bool
|
||||
}{
|
||||
{"no quantity information", &LendingItem{}, true},
|
||||
{"negative quantity", &LendingItem{Quantity: big.NewInt(-1)}, true},
|
||||
{"zero quantity", &LendingItem{Quantity: Zero}, true},
|
||||
{"positive quantity", &LendingItem{Quantity: big.NewInt(2)}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := &LendingItem{
|
||||
Quantity: tt.fields.Quantity,
|
||||
}
|
||||
if err := l.VerifyLendingQuantity(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("VerifyLendingQuantity() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLendingItem_VerifyLendingType(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fields *LendingItem
|
||||
wantErr bool
|
||||
}{
|
||||
{"type: stop limit", &LendingItem{Type: "stop limit"}, true},
|
||||
{"type: take profit", &LendingItem{Type: "take profit"}, true},
|
||||
{"type: limit", &LendingItem{Type: Limit}, false},
|
||||
{"type: market", &LendingItem{Type: Market}, false},
|
||||
{"type: topup", &LendingItem{Type: TopUp}, false},
|
||||
{"type: repay", &LendingItem{Type: Repay}, false},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := &LendingItem{
|
||||
Type: tt.fields.Type,
|
||||
}
|
||||
if err := l.VerifyLendingType(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("VerifyLendingType() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLendingItem_VerifyLendingStatus(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
fields *LendingItem
|
||||
wantErr bool
|
||||
}{
|
||||
|
||||
{"status: new", &LendingItem{Status: LendingStatusNew}, false},
|
||||
{"status: open", &LendingItem{Status: LendingStatusOpen}, true},
|
||||
{"status: partial_filled", &LendingItem{Status: LendingStatusPartialFilled}, true},
|
||||
{"status: filled", &LendingItem{Status: LendingStatusFilled}, true},
|
||||
{"status: cancelled", &LendingItem{Status: LendingStatusCancelled}, false},
|
||||
{"status: rejected", &LendingItem{Status: LendingStatusReject}, true},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
l := &LendingItem{
|
||||
Status: tt.fields.Status,
|
||||
}
|
||||
if err := l.VerifyLendingStatus(); (err != nil) != tt.wantErr {
|
||||
t.Errorf("VerifyLendingStatus() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func SetFee(statedb *state.StateDB, coinbase common.Address, feeRate *big.Int) {
|
||||
locRelayerState := state.GetLocMappingAtKey(coinbase.Hash(), LendingRelayerListSlot)
|
||||
locHash := common.BytesToHash(new(big.Int).Add(locRelayerState, LendingRelayerStructSlots["fee"]).Bytes())
|
||||
statedb.SetState(common.HexToAddress(common.LendingRegistrationSMC), locHash, common.BigToHash(feeRate))
|
||||
}
|
||||
|
||||
func SetCollateralDetail(statedb *state.StateDB, token common.Address, depositRate *big.Int, liquidationRate *big.Int, price *big.Int) {
|
||||
collateralState := GetLocMappingAtKey(token.Hash(), CollateralMapSlot)
|
||||
locDepositRate := state.GetLocOfStructElement(collateralState, CollateralStructSlots["depositRate"])
|
||||
locLiquidationRate := state.GetLocOfStructElement(collateralState, CollateralStructSlots["liquidationRate"])
|
||||
locCollateralPrice := state.GetLocOfStructElement(collateralState, CollateralStructSlots["price"])
|
||||
statedb.SetState(common.HexToAddress(common.LendingRegistrationSMC), locDepositRate, common.BigToHash(depositRate))
|
||||
statedb.SetState(common.HexToAddress(common.LendingRegistrationSMC), locLiquidationRate, common.BigToHash(liquidationRate))
|
||||
statedb.SetState(common.HexToAddress(common.LendingRegistrationSMC), locCollateralPrice, common.BigToHash(price))
|
||||
}
|
||||
|
||||
func TestVerifyBalance(t *testing.T) {
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
statedb, _ := state.New(common.Hash{}, state.NewDatabase(db))
|
||||
relayer := common.HexToAddress("0x0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e")
|
||||
uAddr := common.HexToAddress("0xDeE6238780f98c0ca2c2C28453149bEA49a3Abc9")
|
||||
lendingToken := common.HexToAddress("0xd9bb01454c85247B2ef35BB5BE57384cC275a8cf") // USD
|
||||
collateralToken := common.HexToAddress("0x4d7eA2cE949216D6b120f3AA10164173615A2b6C") // BTC
|
||||
|
||||
SetFee(statedb, relayer, big.NewInt(100))
|
||||
SetCollateralDetail(statedb, collateralToken, big.NewInt(150), big.NewInt(110), big.NewInt(8000)) // BTC price: 8k USD
|
||||
|
||||
// have 10k USD
|
||||
statedb.GetOrNewStateObject(lendingToken)
|
||||
if err := SetTokenBalance(uAddr, EtherToWei(big.NewInt(10000)), lendingToken, statedb); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
|
||||
// have 2 BTC
|
||||
statedb.GetOrNewStateObject(collateralToken)
|
||||
if err := SetTokenBalance(uAddr, EtherToWei(big.NewInt(2)), collateralToken, statedb); err != nil {
|
||||
t.Error(err.Error())
|
||||
}
|
||||
lendingdb := rawdb.NewMemoryDatabase()
|
||||
stateCache := NewDatabase(lendingdb)
|
||||
lendingstatedb, _ := New(EmptyRoot, stateCache)
|
||||
|
||||
// insert lendingItem1 for testing cancel (side investing)
|
||||
lendingItem1 := LendingItem{
|
||||
Quantity: EtherToWei(big.NewInt(11000000000)),
|
||||
Interest: big.NewInt(10),
|
||||
Side: Investing,
|
||||
Type: Limit,
|
||||
LendingToken: lendingToken,
|
||||
CollateralToken: collateralToken,
|
||||
FilledAmount: nil,
|
||||
Status: LendingStatusOpen,
|
||||
Relayer: relayer,
|
||||
Term: uint64(30),
|
||||
UserAddress: uAddr,
|
||||
Signature: nil,
|
||||
Hash: common.Hash{},
|
||||
TxHash: common.Hash{},
|
||||
Nonce: nil,
|
||||
CreatedAt: time.Time{},
|
||||
UpdatedAt: time.Time{},
|
||||
LendingId: uint64(1),
|
||||
ExtraData: "",
|
||||
}
|
||||
lendingstatedb.InsertLendingItem(GetLendingOrderBookHash(lendingItem1.LendingToken, lendingItem1.Term), common.BigToHash(new(big.Int).SetUint64(lendingItem1.LendingId)), lendingItem1)
|
||||
|
||||
// insert lendingItem2 for testing cancel (side borrowing)
|
||||
lendingItem2 := LendingItem{
|
||||
Quantity: EtherToWei(big.NewInt(8000)),
|
||||
Interest: big.NewInt(10),
|
||||
Side: Borrowing,
|
||||
Type: Limit,
|
||||
LendingToken: lendingToken,
|
||||
CollateralToken: collateralToken,
|
||||
FilledAmount: nil,
|
||||
Status: LendingStatusOpen,
|
||||
Relayer: relayer,
|
||||
Term: uint64(30),
|
||||
UserAddress: uAddr,
|
||||
Signature: nil,
|
||||
Hash: common.Hash{},
|
||||
TxHash: common.Hash{},
|
||||
Nonce: nil,
|
||||
CreatedAt: time.Time{},
|
||||
UpdatedAt: time.Time{},
|
||||
LendingId: uint64(2),
|
||||
ExtraData: "",
|
||||
}
|
||||
lendingstatedb.InsertLendingItem(GetLendingOrderBookHash(lendingItem2.LendingToken, lendingItem2.Term), common.BigToHash(new(big.Int).SetUint64(lendingItem2.LendingId)), lendingItem2)
|
||||
|
||||
// insert lendingTrade for testing deposit (side: borrowing)
|
||||
lendingstatedb.InsertTradingItem(
|
||||
GetLendingOrderBookHash(lendingItem2.LendingToken, lendingItem2.Term),
|
||||
uint64(1),
|
||||
LendingTrade{
|
||||
TradeId: uint64(1),
|
||||
CollateralToken: collateralToken,
|
||||
LendingToken: lendingToken,
|
||||
Borrower: uAddr,
|
||||
Amount: EtherToWei(big.NewInt(8000)),
|
||||
LiquidationTime: uint64(time.Now().AddDate(0, 1, 0).UnixNano()),
|
||||
},
|
||||
)
|
||||
|
||||
// make a big lendingTrade to test case: not enough balance to process payment
|
||||
lendingstatedb.InsertTradingItem(
|
||||
GetLendingOrderBookHash(lendingItem2.LendingToken, lendingItem2.Term),
|
||||
uint64(2),
|
||||
LendingTrade{
|
||||
TradeId: uint64(2),
|
||||
CollateralToken: collateralToken,
|
||||
LendingToken: lendingToken,
|
||||
Borrower: uAddr,
|
||||
Amount: EtherToWei(big.NewInt(20000)), // user have 10k USD, expect: fail
|
||||
LiquidationTime: uint64(time.Now().AddDate(0, 1, 0).UnixNano()),
|
||||
},
|
||||
)
|
||||
tests := []struct {
|
||||
name string
|
||||
fields *LendingItem
|
||||
wantErr bool
|
||||
}{
|
||||
{"Investor doesn't have enough balance. side: investing, quantity 11k USD",
|
||||
&LendingItem{
|
||||
UserAddress: uAddr,
|
||||
Relayer: relayer,
|
||||
Side: Investing,
|
||||
Type: Limit,
|
||||
Status: LendingStatusNew,
|
||||
Quantity: EtherToWei(big.NewInt(11000)),
|
||||
LendingToken: lendingToken,
|
||||
CollateralToken: collateralToken,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{"Investor has enough balance. side: investing, quantity 10k USD",
|
||||
&LendingItem{
|
||||
UserAddress: uAddr,
|
||||
Relayer: relayer,
|
||||
Side: Investing,
|
||||
Type: Limit,
|
||||
Status: LendingStatusNew,
|
||||
Quantity: EtherToWei(big.NewInt(10000)),
|
||||
LendingToken: lendingToken,
|
||||
CollateralToken: collateralToken,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{"Investor cancel lendingItem",
|
||||
&LendingItem{
|
||||
UserAddress: uAddr,
|
||||
Relayer: relayer,
|
||||
Side: Investing,
|
||||
Type: Limit,
|
||||
Status: LendingStatusCancelled,
|
||||
LendingToken: lendingToken,
|
||||
CollateralToken: collateralToken,
|
||||
Term: lendingItem1.Term,
|
||||
LendingId: uint64(1),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{"Invalid status",
|
||||
&LendingItem{
|
||||
Side: Investing,
|
||||
Status: "wrong_status",
|
||||
Type: Limit,
|
||||
},
|
||||
true,
|
||||
},
|
||||
// have 2BTC = 16k USD => max borrow = 16 / 1.5 = 10.66
|
||||
{"Borrower doesn't have enough balance. side: borrowing, quantity 12k USD",
|
||||
&LendingItem{
|
||||
UserAddress: uAddr,
|
||||
Relayer: relayer,
|
||||
Side: Borrowing,
|
||||
Type: Limit,
|
||||
Status: LendingStatusNew,
|
||||
Quantity: EtherToWei(big.NewInt(12000)),
|
||||
LendingToken: lendingToken,
|
||||
CollateralToken: collateralToken,
|
||||
},
|
||||
true,
|
||||
},
|
||||
// have 2BTC = 16k USD => max borrow = 16 / 1.5 = 10.66
|
||||
{"Borrower has enough balance. side: borrowing, quantity 10k USD",
|
||||
&LendingItem{
|
||||
UserAddress: uAddr,
|
||||
Relayer: relayer,
|
||||
Side: Borrowing,
|
||||
Type: Limit,
|
||||
Status: LendingStatusNew,
|
||||
Quantity: EtherToWei(big.NewInt(10000)),
|
||||
LendingToken: lendingToken,
|
||||
CollateralToken: collateralToken,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{"Borrower has enough balance to pay cancel fee. side: borrowing",
|
||||
&LendingItem{
|
||||
UserAddress: uAddr,
|
||||
Relayer: relayer,
|
||||
Side: Borrowing,
|
||||
Type: Limit,
|
||||
Status: LendingStatusCancelled,
|
||||
LendingToken: lendingToken,
|
||||
CollateralToken: collateralToken,
|
||||
Term: lendingItem2.Term,
|
||||
LendingId: uint64(2),
|
||||
},
|
||||
false,
|
||||
},
|
||||
{"Make a deposit to an empty LendingTrade.",
|
||||
&LendingItem{
|
||||
UserAddress: uAddr,
|
||||
Relayer: relayer,
|
||||
Side: Borrowing,
|
||||
Status: LendingStatusNew,
|
||||
Type: TopUp,
|
||||
Quantity: EtherToWei(big.NewInt(1)),
|
||||
LendingToken: lendingToken,
|
||||
CollateralToken: collateralToken,
|
||||
ExtraData: common.BigToAddress(big.NewInt(0)).Hex(),
|
||||
},
|
||||
true,
|
||||
},
|
||||
// have 2BTC. make deposit 1 BTC
|
||||
{"Borrower has enough balance to make a deposit. side: borrowing",
|
||||
&LendingItem{
|
||||
UserAddress: uAddr,
|
||||
Relayer: relayer,
|
||||
Side: Borrowing,
|
||||
Status: LendingStatusNew,
|
||||
Type: TopUp,
|
||||
Quantity: EtherToWei(big.NewInt(1)),
|
||||
LendingToken: lendingToken,
|
||||
CollateralToken: collateralToken,
|
||||
Term: uint64(30),
|
||||
LendingTradeId: uint64(1),
|
||||
},
|
||||
false,
|
||||
},
|
||||
{"Make a payment to an empty LendingTrade.",
|
||||
&LendingItem{
|
||||
UserAddress: uAddr,
|
||||
Relayer: relayer,
|
||||
Side: Borrowing,
|
||||
Status: LendingStatusNew,
|
||||
Type: Repay,
|
||||
Quantity: EtherToWei(big.NewInt(1)),
|
||||
LendingToken: lendingToken,
|
||||
CollateralToken: collateralToken,
|
||||
LendingTradeId: uint64(0),
|
||||
},
|
||||
true,
|
||||
},
|
||||
// have 10k USDT
|
||||
{"Borrower has enough balance to make a payment transaction. side: borrowing",
|
||||
&LendingItem{
|
||||
UserAddress: uAddr,
|
||||
Relayer: relayer,
|
||||
Side: Borrowing,
|
||||
Status: LendingStatusNew,
|
||||
Type: Repay,
|
||||
LendingToken: lendingToken,
|
||||
CollateralToken: collateralToken,
|
||||
Term: uint64(30),
|
||||
LendingTradeId: uint64(1),
|
||||
},
|
||||
false,
|
||||
},
|
||||
// have 10k USDT
|
||||
{"Borrower doesn't haave enough balance to make a payment transaction. side: borrowing",
|
||||
&LendingItem{
|
||||
UserAddress: uAddr,
|
||||
Relayer: relayer,
|
||||
Side: Borrowing,
|
||||
Status: LendingStatusNew,
|
||||
Type: Repay,
|
||||
LendingToken: lendingToken,
|
||||
CollateralToken: collateralToken,
|
||||
Term: uint64(30),
|
||||
LendingTradeId: uint64(2),
|
||||
},
|
||||
true,
|
||||
},
|
||||
{"Invalid status",
|
||||
&LendingItem{
|
||||
Side: Borrowing,
|
||||
Status: LendingStatusOpen,
|
||||
},
|
||||
true,
|
||||
},
|
||||
{"Invalid side",
|
||||
&LendingItem{
|
||||
Side: "abc",
|
||||
},
|
||||
true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if err := VerifyBalance(true,
|
||||
statedb,
|
||||
lendingstatedb,
|
||||
tt.fields.Type,
|
||||
tt.fields.Side,
|
||||
tt.fields.Status,
|
||||
tt.fields.UserAddress,
|
||||
tt.fields.Relayer,
|
||||
tt.fields.LendingToken,
|
||||
tt.fields.CollateralToken,
|
||||
tt.fields.Quantity,
|
||||
EtherToWei(big.NewInt(1)),
|
||||
EtherToWei(big.NewInt(1)),
|
||||
EtherToWei(big.NewInt(2)), // XDC price: 0.5 USD => USD/XDC = 2
|
||||
EtherToWei(big.NewInt(8000)), // BTC = 8000 USD
|
||||
tt.fields.Term,
|
||||
tt.fields.LendingId,
|
||||
tt.fields.LendingTradeId,
|
||||
); (err != nil) != tt.wantErr {
|
||||
t.Errorf("VerifyBalance() error = %v, wantErr %v", err, tt.wantErr)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
type LendingOrderMsg struct {
|
||||
AccountNonce uint64 `json:"nonce" gencodec:"required"`
|
||||
Quantity *big.Int `json:"quantity,omitempty"`
|
||||
RelayerAddress common.Address `json:"relayerAddress,omitempty"`
|
||||
UserAddress common.Address `json:"userAddress,omitempty"`
|
||||
CollateralToken common.Address `json:"collateralToken,omitempty"`
|
||||
LendingToken common.Address `json:"lendingToken,omitempty"`
|
||||
Interest uint64 `json:"interest,omitempty"`
|
||||
Term uint64 `json:"term,omitempty"`
|
||||
Status string `json:"status,omitempty"`
|
||||
Side string `json:"side,omitempty"`
|
||||
Type string `json:"type,omitempty"`
|
||||
LendingID uint64 `json:"lendingID,omitempty"`
|
||||
// Signature values
|
||||
V *big.Int `json:"v" gencodec:"required"`
|
||||
R *big.Int `json:"r" gencodec:"required"`
|
||||
S *big.Int `json:"s" gencodec:"required"`
|
||||
|
||||
// This is only used when marshaling to JSON.
|
||||
Hash common.Hash `json:"hash" rlp:"-"`
|
||||
}
|
||||
|
||||
func Test_CreateOrder(t *testing.T) {
|
||||
t.SkipNow()
|
||||
for i := 0; i < 1; i++ {
|
||||
sendOrder(uint64(i))
|
||||
time.Sleep(time.Microsecond)
|
||||
}
|
||||
}
|
||||
|
||||
func sendOrder(nonce uint64) {
|
||||
rpcClient, err := rpc.DialHTTP("http://localhost:8501")
|
||||
defer rpcClient.Close()
|
||||
if err != nil {
|
||||
fmt.Println("rpc.DialHTTP failed", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
rand.Seed(time.Now().UTC().UnixNano())
|
||||
item := &LendingOrderMsg{
|
||||
AccountNonce: nonce,
|
||||
Quantity: EtherToWei(big.NewInt(1000)),
|
||||
RelayerAddress: common.HexToAddress("0x0D3ab14BBaD3D99F4203bd7a11aCB94882050E7e"),
|
||||
UserAddress: common.HexToAddress("0x17F2beD710ba50Ed27aEa52fc4bD7Bda5ED4a037"),
|
||||
CollateralToken: common.HexToAddress("0xC2fa1BA90b15E3612E0067A0020192938784D9C5"),
|
||||
LendingToken: common.HexToAddress("0x45c25041b8e6CBD5c963E7943007187C3673C7c9"),
|
||||
Interest: uint64(100),
|
||||
Term: uint64(30 * 86400),
|
||||
Status: LendingStatusNew,
|
||||
Side: Borrowing,
|
||||
Type: Limit,
|
||||
V: common.Big0,
|
||||
R: common.Big0,
|
||||
S: common.Big0,
|
||||
Hash: common.Hash{},
|
||||
}
|
||||
hash := computeHash(item)
|
||||
if item.Status != LendingStatusCancelled {
|
||||
item.Hash = hash
|
||||
}
|
||||
privKey, _ := crypto.HexToECDSA("65ec4d4dfbcac594a14c36baa462d6f73cd86134840f6cf7b80a1e1cd33473e2")
|
||||
message := crypto.Keccak256(
|
||||
[]byte("\x19Ethereum Signed Message:\n32"),
|
||||
hash.Bytes(),
|
||||
)
|
||||
signatureBytes, _ := crypto.Sign(message, privKey)
|
||||
sig := &Signature{
|
||||
R: common.BytesToHash(signatureBytes[0:32]),
|
||||
S: common.BytesToHash(signatureBytes[32:64]),
|
||||
V: signatureBytes[64] + 27,
|
||||
}
|
||||
item.R = sig.R.Big()
|
||||
item.S = sig.S.Big()
|
||||
item.V = new(big.Int).SetUint64(uint64(sig.V))
|
||||
|
||||
var result interface{}
|
||||
|
||||
err = rpcClient.Call(&result, "XDCx_sendLending", item)
|
||||
fmt.Println("sendLendingitem", "nonce", item.AccountNonce)
|
||||
if err != nil {
|
||||
fmt.Println("rpcClient.Call XDCx_sendLending failed", "err", err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
func computeHash(l *LendingOrderMsg) common.Hash {
|
||||
sha := sha3.NewKeccak256()
|
||||
if l.Status == LendingStatusCancelled {
|
||||
sha := sha3.NewKeccak256()
|
||||
sha.Write(l.Hash.Bytes())
|
||||
sha.Write(common.BigToHash(big.NewInt(int64(l.AccountNonce))).Bytes())
|
||||
sha.Write(l.UserAddress.Bytes())
|
||||
sha.Write(common.BigToHash(big.NewInt(int64(l.LendingID))).Bytes())
|
||||
sha.Write([]byte(l.Status))
|
||||
sha.Write(l.RelayerAddress.Bytes())
|
||||
} else {
|
||||
sha.Write(l.RelayerAddress.Bytes())
|
||||
sha.Write(l.UserAddress.Bytes())
|
||||
sha.Write(l.CollateralToken.Bytes())
|
||||
sha.Write(l.LendingToken.Bytes())
|
||||
sha.Write(common.BigToHash(l.Quantity).Bytes())
|
||||
sha.Write(common.BigToHash(big.NewInt(int64(l.Term))).Bytes())
|
||||
if l.Type == Limit {
|
||||
sha.Write(common.BigToHash(big.NewInt(int64(l.Interest))).Bytes())
|
||||
}
|
||||
sha.Write([]byte(l.Side))
|
||||
sha.Write([]byte(l.Status))
|
||||
sha.Write([]byte(l.Type))
|
||||
sha.Write(common.BigToHash(big.NewInt(int64(l.AccountNonce))).Bytes())
|
||||
}
|
||||
return common.BytesToHash(sha.Sum(nil))
|
||||
|
||||
}
|
||||
140
XDCxlending/lendingstate/managed_state.go
Normal file
140
XDCxlending/lendingstate/managed_state.go
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
// Copyright 2015 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package lendingstate
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
)
|
||||
|
||||
type exchanges struct {
|
||||
stateObject *lendingExchangeState
|
||||
nstart uint64
|
||||
nonces []bool
|
||||
}
|
||||
|
||||
type LendingManagedState struct {
|
||||
*LendingStateDB
|
||||
mu sync.RWMutex
|
||||
lenddinges map[common.Hash]*exchanges
|
||||
}
|
||||
|
||||
// LendingManagedState returns a new managed state with the statedb as it's backing layer
|
||||
func ManageState(statedb *LendingStateDB) *LendingManagedState {
|
||||
return &LendingManagedState{
|
||||
LendingStateDB: statedb.Copy(),
|
||||
lenddinges: make(map[common.Hash]*exchanges),
|
||||
}
|
||||
}
|
||||
|
||||
// SetState sets the backing layer of the managed state
|
||||
func (ms *LendingManagedState) SetState(statedb *LendingStateDB) {
|
||||
ms.mu.Lock()
|
||||
defer ms.mu.Unlock()
|
||||
ms.LendingStateDB = statedb
|
||||
}
|
||||
|
||||
// RemoveNonce removed the nonce from the managed state and all future pending nonces
|
||||
func (ms *LendingManagedState) RemoveNonce(addr common.Hash, n uint64) {
|
||||
if ms.hasAccount(addr) {
|
||||
ms.mu.Lock()
|
||||
defer ms.mu.Unlock()
|
||||
|
||||
account := ms.getAccount(addr)
|
||||
if n-account.nstart <= uint64(len(account.nonces)) {
|
||||
reslice := make([]bool, n-account.nstart)
|
||||
copy(reslice, account.nonces[:n-account.nstart])
|
||||
account.nonces = reslice
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// NewNonce returns the new canonical nonce for the managed tradeId
|
||||
func (ms *LendingManagedState) NewNonce(addr common.Hash) uint64 {
|
||||
ms.mu.Lock()
|
||||
defer ms.mu.Unlock()
|
||||
|
||||
account := ms.getAccount(addr)
|
||||
for i, nonce := range account.nonces {
|
||||
if !nonce {
|
||||
return account.nstart + uint64(i)
|
||||
}
|
||||
}
|
||||
account.nonces = append(account.nonces, true)
|
||||
|
||||
return uint64(len(account.nonces)-1) + account.nstart
|
||||
}
|
||||
|
||||
// GetNonce returns the canonical nonce for the managed or unmanaged tradeId.
|
||||
//
|
||||
// Because GetNonce mutates the DB, we must take a write lock.
|
||||
func (ms *LendingManagedState) GetNonce(addr common.Hash) uint64 {
|
||||
ms.mu.Lock()
|
||||
defer ms.mu.Unlock()
|
||||
if ms.hasAccount(addr) {
|
||||
account := ms.getAccount(addr)
|
||||
return uint64(len(account.nonces)) + account.nstart
|
||||
} else {
|
||||
return ms.LendingStateDB.GetNonce(addr)
|
||||
}
|
||||
}
|
||||
|
||||
// SetNonce sets the new canonical nonce for the managed state
|
||||
func (ms *LendingManagedState) SetNonce(addr common.Hash, nonce uint64) {
|
||||
ms.mu.Lock()
|
||||
defer ms.mu.Unlock()
|
||||
|
||||
so := ms.GetOrNewLendingExchangeObject(addr)
|
||||
so.setNonce(nonce)
|
||||
|
||||
ms.lenddinges[addr] = newAccount(so)
|
||||
}
|
||||
|
||||
// HasAccount returns whether the given address is managed or not
|
||||
func (ms *LendingManagedState) HasAccount(addr common.Hash) bool {
|
||||
ms.mu.RLock()
|
||||
defer ms.mu.RUnlock()
|
||||
return ms.hasAccount(addr)
|
||||
}
|
||||
|
||||
func (ms *LendingManagedState) hasAccount(addr common.Hash) bool {
|
||||
_, ok := ms.lenddinges[addr]
|
||||
return ok
|
||||
}
|
||||
|
||||
// populate the managed state
|
||||
func (ms *LendingManagedState) getAccount(addr common.Hash) *exchanges {
|
||||
if account, ok := ms.lenddinges[addr]; !ok {
|
||||
so := ms.GetOrNewLendingExchangeObject(addr)
|
||||
ms.lenddinges[addr] = newAccount(so)
|
||||
} else {
|
||||
// Always make sure the state tradeId nonce isn't actually higher
|
||||
// than the tracked one.
|
||||
so := ms.LendingStateDB.getLendingExchange(addr)
|
||||
if so != nil && uint64(len(account.nonces))+account.nstart < so.Nonce() {
|
||||
ms.lenddinges[addr] = newAccount(so)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return ms.lenddinges[addr]
|
||||
}
|
||||
|
||||
func newAccount(so *lendingExchangeState) *exchanges {
|
||||
return &exchanges{so, so.Nonce(), nil}
|
||||
}
|
||||
304
XDCxlending/lendingstate/relayer.go
Normal file
304
XDCxlending/lendingstate/relayer.go
Normal file
|
|
@ -0,0 +1,304 @@
|
|||
package lendingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/state"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
func GetLocMappingAtKey(key common.Hash, slot uint64) *big.Int {
|
||||
slotHash := common.BigToHash(new(big.Int).SetUint64(slot))
|
||||
retByte := crypto.Keccak256(key.Bytes(), slotHash.Bytes())
|
||||
ret := new(big.Int)
|
||||
ret.SetBytes(retByte)
|
||||
return ret
|
||||
}
|
||||
|
||||
func GetExRelayerFee(relayer common.Address, statedb *state.StateDB) *big.Int {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_fee"])
|
||||
locHash := common.BigToHash(locBig)
|
||||
return statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash).Big()
|
||||
}
|
||||
|
||||
func GetRelayerOwner(relayer common.Address, statedb *state.StateDB) common.Address {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
log.Debug("GetRelayerOwner", "relayer", relayer.Hex(), "slot", slot, "locBig", locBig)
|
||||
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_owner"])
|
||||
locHash := common.BigToHash(locBig)
|
||||
return common.BytesToAddress(statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash).Bytes())
|
||||
}
|
||||
|
||||
// return true if relayer request to resign and have not withdraw locked fund
|
||||
func IsResignedRelayer(relayer common.Address, statedb *state.StateDB) bool {
|
||||
slot := RelayerMappingSlot["RESIGN_REQUESTS"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
locHash := common.BigToHash(locBig)
|
||||
if statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash) != (common.Hash{}) {
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetBaseTokenLength(relayer common.Address, statedb *state.StateDB) uint64 {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_fromTokens"])
|
||||
locHash := common.BigToHash(locBig)
|
||||
return statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash).Big().Uint64()
|
||||
}
|
||||
|
||||
func GetBaseTokenAtIndex(relayer common.Address, statedb *state.StateDB, index uint64) common.Address {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_fromTokens"])
|
||||
locHash := common.BigToHash(locBig)
|
||||
loc := state.GetLocDynamicArrAtElement(locHash, index, 1)
|
||||
return common.BytesToAddress(statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), loc).Bytes())
|
||||
}
|
||||
|
||||
func GetQuoteTokenLength(relayer common.Address, statedb *state.StateDB) uint64 {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_toTokens"])
|
||||
locHash := common.BigToHash(locBig)
|
||||
return statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHash).Big().Uint64()
|
||||
}
|
||||
|
||||
func GetQuoteTokenAtIndex(relayer common.Address, statedb *state.StateDB, index uint64) common.Address {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
locBig = new(big.Int).Add(locBig, RelayerStructMappingSlot["_toTokens"])
|
||||
locHash := common.BigToHash(locBig)
|
||||
loc := state.GetLocDynamicArrAtElement(locHash, index, 1)
|
||||
return common.BytesToAddress(statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), loc).Bytes())
|
||||
}
|
||||
|
||||
func SubRelayerFee(relayer common.Address, fee *big.Int, statedb *state.StateDB) error {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
|
||||
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"])
|
||||
locHashDeposit := common.BigToHash(locBigDeposit)
|
||||
balance := statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit).Big()
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: SubRelayerFee BEFORE", "relayer", relayer.String(), "balance", balance)
|
||||
if balance.Cmp(fee) < 0 {
|
||||
return errors.Errorf("relayer %s isn't enough XDC fee", relayer.String())
|
||||
} else {
|
||||
balance = new(big.Int).Sub(balance, fee)
|
||||
statedb.SetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit, common.BigToHash(balance))
|
||||
statedb.SubBalance(common.HexToAddress(common.RelayerRegistrationSMC), fee)
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: SubRelayerFee AFTER", "relayer", relayer.String(), "balance", balance)
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func CheckRelayerFee(relayer common.Address, fee *big.Int, statedb *state.StateDB) error {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
|
||||
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"])
|
||||
locHashDeposit := common.BigToHash(locBigDeposit)
|
||||
balance := statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit).Big()
|
||||
if new(big.Int).Sub(balance, fee).Cmp(new(big.Int).Mul(common.BasePrice, common.RelayerLockedFund)) < 0 {
|
||||
return errors.Errorf("relayer %s isn't enough XDC fee : balance %d , fee : %d ", relayer.Hex(), balance.Uint64(), fee.Uint64())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func AddTokenBalance(addr common.Address, value *big.Int, token common.Address, statedb *state.StateDB) error {
|
||||
// XDC native
|
||||
if token.String() == common.XDCNativeAddress {
|
||||
balance := statedb.GetBalance(addr)
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD TOKEN XDC NATIVE BEFORE", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
|
||||
statedb.AddBalance(addr, value)
|
||||
balance = statedb.GetBalance(addr)
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD XDC NATIVE BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TRC tokens
|
||||
if statedb.Exist(token) {
|
||||
slot := TokenMappingSlot["balances"]
|
||||
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
|
||||
balance := statedb.GetState(token, locHash).Big()
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD TOKEN BALANCE BEFORE", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
|
||||
balance = new(big.Int).Add(balance, value)
|
||||
statedb.SetState(token, locHash, common.BigToHash(balance))
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: ADD TOKEN BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
|
||||
return nil
|
||||
} else {
|
||||
return errors.Errorf("token %s isn't exist", token.String())
|
||||
}
|
||||
}
|
||||
|
||||
func SubTokenBalance(addr common.Address, value *big.Int, token common.Address, statedb *state.StateDB) error {
|
||||
// XDC native
|
||||
if token.String() == common.XDCNativeAddress {
|
||||
balance := statedb.GetBalance(addr)
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB XDC NATIVE BALANCE BEFORE", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
|
||||
if balance.Cmp(value) < 0 {
|
||||
return errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), token.String(), balance, value)
|
||||
}
|
||||
statedb.SubBalance(addr, value)
|
||||
balance = statedb.GetBalance(addr)
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB XDC NATIVE BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TRC tokens
|
||||
if statedb.Exist(token) {
|
||||
slot := TokenMappingSlot["balances"]
|
||||
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
|
||||
balance := statedb.GetState(token, locHash).Big()
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB TOKEN BALANCE BEFORE", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
|
||||
if balance.Cmp(value) < 0 {
|
||||
return errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), token.String(), balance, value)
|
||||
}
|
||||
balance = new(big.Int).Sub(balance, value)
|
||||
statedb.SetState(token, locHash, common.BigToHash(balance))
|
||||
log.Debug("ApplyXDCXMatchedTransaction settle balance: SUB TOKEN BALANCE AFTER", "token", token.String(), "address", addr.String(), "balance", balance, "orderValue", value)
|
||||
return nil
|
||||
} else {
|
||||
return errors.Errorf("token %s isn't exist", token.String())
|
||||
}
|
||||
}
|
||||
|
||||
func CheckSubTokenBalance(addr common.Address, value *big.Int, token common.Address, statedb *state.StateDB, mapBalances map[common.Address]map[common.Address]*big.Int) (*big.Int, error) {
|
||||
// XDC native
|
||||
if token.String() == common.XDCNativeAddress {
|
||||
var balance *big.Int
|
||||
if value := mapBalances[token][addr]; value != nil {
|
||||
balance = value
|
||||
} else {
|
||||
balance = statedb.GetBalance(addr)
|
||||
}
|
||||
if balance.Cmp(value) < 0 {
|
||||
return nil, errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), token.String(), balance, value)
|
||||
}
|
||||
newBalance := new(big.Int).Sub(balance, value)
|
||||
log.Debug("CheckSubTokenBalance settle balance: SUB XDC NATIVE BALANCE ", "token", token.String(), "address", addr.String(), "balance", balance, "value", value, "newBalance", newBalance)
|
||||
return newBalance, nil
|
||||
}
|
||||
// TRC tokens
|
||||
if statedb.Exist(token) {
|
||||
var balance *big.Int
|
||||
if value := mapBalances[token][addr]; value != nil {
|
||||
balance = value
|
||||
} else {
|
||||
slot := TokenMappingSlot["balances"]
|
||||
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
|
||||
balance = statedb.GetState(token, locHash).Big()
|
||||
}
|
||||
if balance.Cmp(value) < 0 {
|
||||
return nil, errors.Errorf("value %s in token %s not enough , have : %s , want : %s ", addr.String(), token.String(), balance, value)
|
||||
}
|
||||
newBalance := new(big.Int).Sub(balance, value)
|
||||
log.Debug("CheckSubTokenBalance settle balance: SUB TOKEN BALANCE ", "token", token.String(), "address", addr.String(), "balance", balance, "value", value, "newBalance", newBalance)
|
||||
return newBalance, nil
|
||||
} else {
|
||||
return nil, errors.Errorf("token %s isn't exist", token.String())
|
||||
}
|
||||
}
|
||||
|
||||
func CheckAddTokenBalance(addr common.Address, value *big.Int, token common.Address, statedb *state.StateDB, mapBalances map[common.Address]map[common.Address]*big.Int) (*big.Int, error) {
|
||||
// XDC native
|
||||
if token.String() == common.XDCNativeAddress {
|
||||
var balance *big.Int
|
||||
if value := mapBalances[token][addr]; value != nil {
|
||||
balance = value
|
||||
} else {
|
||||
balance = statedb.GetBalance(addr)
|
||||
}
|
||||
newBalance := new(big.Int).Add(balance, value)
|
||||
log.Debug("CheckAddTokenBalance settle balance: ADD XDC NATIVE BALANCE ", "token", token.String(), "address", addr.String(), "balance", balance, "value", value, "newBalance", newBalance)
|
||||
return newBalance, nil
|
||||
}
|
||||
// TRC tokens
|
||||
if statedb.Exist(token) {
|
||||
var balance *big.Int
|
||||
if value := mapBalances[token][addr]; value != nil {
|
||||
balance = value
|
||||
} else {
|
||||
slot := TokenMappingSlot["balances"]
|
||||
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
|
||||
balance = statedb.GetState(token, locHash).Big()
|
||||
}
|
||||
newBalance := new(big.Int).Add(balance, value)
|
||||
log.Debug("CheckAddTokenBalance settle balance: ADD TOKEN BALANCE ", "token", token.String(), "address", addr.String(), "balance", balance, "value", value, "newBalance", newBalance)
|
||||
if common.BigToHash(newBalance).Big().Cmp(newBalance) != 0 {
|
||||
return nil, fmt.Errorf("Overflow when try add token balance , max is 2^256 , balance : %v , value:%v ", balance, value)
|
||||
} else {
|
||||
return newBalance, nil
|
||||
}
|
||||
} else {
|
||||
return nil, errors.Errorf("token %s isn't exist", token.String())
|
||||
}
|
||||
}
|
||||
|
||||
func CheckSubRelayerFee(relayer common.Address, fee *big.Int, statedb *state.StateDB, mapBalances map[common.Address]*big.Int) (*big.Int, error) {
|
||||
balance := mapBalances[relayer]
|
||||
if balance == nil {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"])
|
||||
locHashDeposit := common.BigToHash(locBigDeposit)
|
||||
balance = statedb.GetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit).Big()
|
||||
}
|
||||
log.Debug("CheckSubRelayerFee settle balance: SubRelayerFee ", "relayer", relayer.String(), "balance", balance, "fee", fee)
|
||||
if balance.Cmp(fee) < 0 {
|
||||
return nil, errors.Errorf("relayer %s isn't enough XDC fee", relayer.String())
|
||||
} else {
|
||||
return new(big.Int).Sub(balance, fee), nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetTokenBalance(addr common.Address, token common.Address, statedb *state.StateDB) *big.Int {
|
||||
// XDC native
|
||||
if token.String() == common.XDCNativeAddress {
|
||||
return statedb.GetBalance(addr)
|
||||
}
|
||||
// TRC tokens
|
||||
if statedb.Exist(token) {
|
||||
slot := TokenMappingSlot["balances"]
|
||||
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
|
||||
return statedb.GetState(token, locHash).Big()
|
||||
} else {
|
||||
return common.Big0
|
||||
}
|
||||
}
|
||||
|
||||
func SetTokenBalance(addr common.Address, balance *big.Int, token common.Address, statedb *state.StateDB) error {
|
||||
// XDC native
|
||||
if token.String() == common.XDCNativeAddress {
|
||||
statedb.SetBalance(addr, balance)
|
||||
return nil
|
||||
}
|
||||
|
||||
// TRC tokens
|
||||
if statedb.Exist(token) {
|
||||
slot := TokenMappingSlot["balances"]
|
||||
locHash := common.BigToHash(GetLocMappingAtKey(addr.Hash(), slot))
|
||||
statedb.SetState(token, locHash, common.BigToHash(balance))
|
||||
return nil
|
||||
} else {
|
||||
return errors.Errorf("token %s isn't exist", token.String())
|
||||
}
|
||||
}
|
||||
|
||||
func SetSubRelayerFee(relayer common.Address, balance *big.Int, fee *big.Int, statedb *state.StateDB) {
|
||||
slot := RelayerMappingSlot["RELAYER_LIST"]
|
||||
locBig := GetLocMappingAtKey(relayer.Hash(), slot)
|
||||
locBigDeposit := new(big.Int).SetUint64(uint64(0)).Add(locBig, RelayerStructMappingSlot["_deposit"])
|
||||
locHashDeposit := common.BigToHash(locBigDeposit)
|
||||
statedb.SetState(common.HexToAddress(common.RelayerRegistrationSMC), locHashDeposit, common.BigToHash(balance))
|
||||
statedb.SubBalance(common.HexToAddress(common.RelayerRegistrationSMC), fee)
|
||||
}
|
||||
256
XDCxlending/lendingstate/settle_balance.go
Normal file
256
XDCxlending/lendingstate/settle_balance.go
Normal file
|
|
@ -0,0 +1,256 @@
|
|||
package lendingstate
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
const DefaultFeeRate = 100 // 100 / XDCXBaseFee = 100 / 10000 = 1%
|
||||
var (
|
||||
ErrQuantityTradeTooSmall = errors.New("quantity trade too small")
|
||||
ErrInvalidCollateralPrice = errors.New("unable to retrieve price of this collateral. Please try another collateral")
|
||||
)
|
||||
|
||||
type TradeResult struct {
|
||||
Fee *big.Int
|
||||
InToken common.Address
|
||||
InTotal *big.Int
|
||||
OutToken common.Address
|
||||
OutTotal *big.Int
|
||||
}
|
||||
type LendingSettleBalance struct {
|
||||
Taker TradeResult
|
||||
Maker TradeResult
|
||||
CollateralLockedAmount *big.Int
|
||||
}
|
||||
|
||||
func (settleBalance *LendingSettleBalance) String() string {
|
||||
jsonData, _ := json.Marshal(settleBalance)
|
||||
return string(jsonData)
|
||||
}
|
||||
|
||||
func GetSettleBalance(isXDCXLendingFork bool,
|
||||
takerSide string,
|
||||
lendTokenXDCPrice,
|
||||
collateralPrice,
|
||||
depositRate,
|
||||
borrowFeeRate *big.Int,
|
||||
lendingToken,
|
||||
collateralToken common.Address,
|
||||
lendTokenDecimal,
|
||||
collateralTokenDecimal *big.Int,
|
||||
quantityToLend *big.Int) (*LendingSettleBalance, error) {
|
||||
log.Debug("GetSettleBalance", "takerSide", takerSide, "borrowFeeRate", borrowFeeRate, "lendingToken", lendingToken, "collateralToken", collateralToken, "quantityToLend", quantityToLend)
|
||||
if collateralPrice == nil || collateralPrice.Sign() <= 0 {
|
||||
return nil, ErrInvalidCollateralPrice
|
||||
}
|
||||
|
||||
//use the defaultFee to validate small orders
|
||||
defaultFee := new(big.Int).Mul(quantityToLend, new(big.Int).SetUint64(DefaultFeeRate))
|
||||
defaultFee = new(big.Int).Div(defaultFee, common.XDCXBaseFee)
|
||||
|
||||
var result *LendingSettleBalance
|
||||
//result = map[common.Address]map[string]interface{}{}
|
||||
|
||||
if !isXDCXLendingFork {
|
||||
if takerSide == Borrowing {
|
||||
// taker = Borrower : takerOutTotal = CollateralLockedAmount = quantityToLend * collateral Token Decimal/ CollateralPrice * deposit rate
|
||||
takerOutTotal := new(big.Int).Mul(quantityToLend, collateralTokenDecimal)
|
||||
takerOutTotal = new(big.Int).Mul(takerOutTotal, depositRate) // eg: depositRate = 150%
|
||||
takerOutTotal = new(big.Int).Div(takerOutTotal, big.NewInt(100))
|
||||
takerOutTotal = new(big.Int).Div(takerOutTotal, collateralPrice)
|
||||
// Fee
|
||||
// takerFee = quantityToLend*borrowFeeRate/baseFee
|
||||
takerFee := new(big.Int).Mul(quantityToLend, borrowFeeRate)
|
||||
takerFee = new(big.Int).Div(takerFee, common.XDCXBaseFee)
|
||||
|
||||
if quantityToLend.Cmp(takerFee) <= 0 || quantityToLend.Cmp(defaultFee) <= 0 {
|
||||
log.Debug("quantity lending too small", "quantityToLend", quantityToLend, "takerFee", takerFee)
|
||||
return result, ErrQuantityTradeTooSmall
|
||||
}
|
||||
if lendingToken.String() != common.XDCNativeAddress && lendTokenXDCPrice != nil && lendTokenXDCPrice.Cmp(common.Big0) > 0 {
|
||||
exTakerReceivedFee := new(big.Int).Mul(takerFee, lendTokenXDCPrice)
|
||||
exTakerReceivedFee = new(big.Int).Div(exTakerReceivedFee, lendTokenDecimal)
|
||||
|
||||
defaultFeeInXDC := new(big.Int).Mul(defaultFee, lendTokenXDCPrice)
|
||||
defaultFeeInXDC = new(big.Int).Div(defaultFeeInXDC, lendTokenDecimal)
|
||||
|
||||
if (exTakerReceivedFee.Cmp(common.RelayerLendingFee) <= 0 && exTakerReceivedFee.Sign() > 0) || defaultFeeInXDC.Cmp(common.RelayerLendingFee) <= 0 {
|
||||
log.Debug("takerFee too small", "quantityToLend", quantityToLend, "takerFee", takerFee, "exTakerReceivedFee", exTakerReceivedFee, "borrowFeeRate", borrowFeeRate, "defaultFeeInXDC", defaultFeeInXDC)
|
||||
return result, ErrQuantityTradeTooSmall
|
||||
}
|
||||
} else if lendingToken.String() == common.XDCNativeAddress {
|
||||
exTakerReceivedFee := takerFee
|
||||
if (exTakerReceivedFee.Cmp(common.RelayerLendingFee) <= 0 && exTakerReceivedFee.Sign() > 0) || defaultFee.Cmp(common.RelayerLendingFee) <= 0 {
|
||||
log.Debug("takerFee too small", "quantityToLend", quantityToLend, "takerFee", takerFee, "exTakerReceivedFee", exTakerReceivedFee, "borrowFeeRate", borrowFeeRate, "defaultFee", defaultFee)
|
||||
return result, ErrQuantityTradeTooSmall
|
||||
}
|
||||
}
|
||||
result = &LendingSettleBalance{
|
||||
//Borrower
|
||||
Taker: TradeResult{
|
||||
Fee: takerFee,
|
||||
InToken: lendingToken,
|
||||
InTotal: new(big.Int).Sub(quantityToLend, takerFee),
|
||||
OutToken: collateralToken,
|
||||
OutTotal: takerOutTotal,
|
||||
},
|
||||
// Investor : makerOutTotal = quantityToLend
|
||||
Maker: TradeResult{
|
||||
Fee: common.Big0,
|
||||
InToken: common.Address{},
|
||||
InTotal: common.Big0,
|
||||
OutToken: lendingToken,
|
||||
OutTotal: quantityToLend,
|
||||
},
|
||||
CollateralLockedAmount: takerOutTotal,
|
||||
}
|
||||
} else {
|
||||
// maker = Borrower : makerOutTotal = CollateralLockedAmount = quantityToLend * collateral Token Decimal / CollateralPrice * deposit rate
|
||||
makerOutTotal := new(big.Int).Mul(quantityToLend, collateralTokenDecimal)
|
||||
makerOutTotal = new(big.Int).Mul(makerOutTotal, depositRate) // eg: depositRate = 150%
|
||||
makerOutTotal = new(big.Int).Div(makerOutTotal, big.NewInt(100))
|
||||
makerOutTotal = new(big.Int).Div(makerOutTotal, collateralPrice)
|
||||
// Fee
|
||||
makerFee := new(big.Int).Mul(quantityToLend, borrowFeeRate)
|
||||
makerFee = new(big.Int).Div(makerFee, common.XDCXBaseFee)
|
||||
if quantityToLend.Cmp(makerFee) <= 0 || quantityToLend.Cmp(defaultFee) <= 0 {
|
||||
log.Debug("quantity lending too small", "quantityToLend", quantityToLend, "makerFee", makerFee)
|
||||
return result, ErrQuantityTradeTooSmall
|
||||
}
|
||||
if lendingToken.String() != common.XDCNativeAddress && lendTokenXDCPrice != nil && lendTokenXDCPrice.Cmp(common.Big0) > 0 {
|
||||
exMakerReceivedFee := new(big.Int).Mul(makerFee, lendTokenXDCPrice)
|
||||
exMakerReceivedFee = new(big.Int).Div(exMakerReceivedFee, lendTokenDecimal)
|
||||
|
||||
defaultFeeInXDC := new(big.Int).Mul(defaultFee, lendTokenXDCPrice)
|
||||
defaultFeeInXDC = new(big.Int).Div(defaultFeeInXDC, lendTokenDecimal)
|
||||
|
||||
if (exMakerReceivedFee.Cmp(common.RelayerLendingFee) <= 0 && exMakerReceivedFee.Sign() > 0) || defaultFeeInXDC.Cmp(common.RelayerLendingFee) <= 0 {
|
||||
log.Debug("makerFee too small", "quantityToLend", quantityToLend, "makerFee", makerFee, "exMakerReceivedFee", exMakerReceivedFee, "borrowFeeRate", borrowFeeRate, "defaultFeeInXDC", defaultFeeInXDC)
|
||||
return result, ErrQuantityTradeTooSmall
|
||||
}
|
||||
} else if lendingToken.String() == common.XDCNativeAddress {
|
||||
exMakerReceivedFee := makerFee
|
||||
if (exMakerReceivedFee.Cmp(common.RelayerLendingFee) <= 0 && exMakerReceivedFee.Sign() > 0) || defaultFee.Cmp(common.RelayerLendingFee) <= 0 {
|
||||
log.Debug("makerFee too small", "quantityToLend", quantityToLend, "makerFee", makerFee, "exMakerReceivedFee", exMakerReceivedFee, "borrowFeeRate", borrowFeeRate, "defaultFee", defaultFee)
|
||||
return result, ErrQuantityTradeTooSmall
|
||||
}
|
||||
}
|
||||
result = &LendingSettleBalance{
|
||||
Taker: TradeResult{
|
||||
Fee: common.Big0,
|
||||
InToken: common.Address{},
|
||||
InTotal: common.Big0,
|
||||
OutToken: lendingToken,
|
||||
OutTotal: quantityToLend,
|
||||
},
|
||||
Maker: TradeResult{
|
||||
Fee: makerFee,
|
||||
InToken: lendingToken,
|
||||
InTotal: new(big.Int).Add(quantityToLend, makerFee),
|
||||
OutToken: collateralToken,
|
||||
OutTotal: makerOutTotal,
|
||||
},
|
||||
CollateralLockedAmount: makerOutTotal,
|
||||
}
|
||||
}
|
||||
} else {
|
||||
|
||||
collateralQuantity := new(big.Int).Mul(quantityToLend, collateralTokenDecimal)
|
||||
collateralQuantity = new(big.Int).Mul(collateralQuantity, depositRate) // eg: depositRate = 150%
|
||||
collateralQuantity = new(big.Int).Div(collateralQuantity, big.NewInt(100))
|
||||
collateralQuantity = new(big.Int).Div(collateralQuantity, collateralPrice)
|
||||
|
||||
borrowFee := new(big.Int).Mul(quantityToLend, borrowFeeRate)
|
||||
borrowFee = new(big.Int).Div(borrowFee, common.XDCXBaseFee)
|
||||
|
||||
if quantityToLend.Cmp(borrowFee) <= 0 || quantityToLend.Cmp(defaultFee) <= 0 {
|
||||
log.Debug("quantity lending too small", "quantityToLend", quantityToLend, "borrowFee", borrowFee)
|
||||
return result, ErrQuantityTradeTooSmall
|
||||
}
|
||||
if lendingToken.String() != common.XDCNativeAddress && lendTokenXDCPrice != nil && lendTokenXDCPrice.Cmp(common.Big0) > 0 {
|
||||
// exReceivedFee: the fee amount which borrowingRelayer will receive
|
||||
exReceivedFee := new(big.Int).Mul(borrowFee, lendTokenXDCPrice)
|
||||
exReceivedFee = new(big.Int).Div(exReceivedFee, lendTokenDecimal)
|
||||
|
||||
defaultFeeInXDC := new(big.Int).Mul(defaultFee, lendTokenXDCPrice)
|
||||
defaultFeeInXDC = new(big.Int).Div(defaultFeeInXDC, lendTokenDecimal)
|
||||
|
||||
if (exReceivedFee.Cmp(common.RelayerLendingFee) <= 0 && exReceivedFee.Sign() > 0) || defaultFeeInXDC.Cmp(common.RelayerLendingFee) <= 0 {
|
||||
log.Debug("takerFee too small", "quantityToLend", quantityToLend, "borrowFee", borrowFee, "exReceivedFee", exReceivedFee, "borrowFeeRate", borrowFeeRate, "defaultFeeInXDC", defaultFeeInXDC)
|
||||
return result, ErrQuantityTradeTooSmall
|
||||
}
|
||||
} else if lendingToken.String() == common.XDCNativeAddress {
|
||||
exReceivedFee := borrowFee
|
||||
if (exReceivedFee.Cmp(common.RelayerLendingFee) <= 0 && exReceivedFee.Sign() > 0) || defaultFee.Cmp(common.RelayerLendingFee) <= 0 {
|
||||
log.Debug("takerFee too small", "quantityToLend", quantityToLend, "borrowFee", borrowFee, "exReceivedFee", exReceivedFee, "borrowFeeRate", borrowFeeRate, "defaultFee", defaultFee)
|
||||
return result, ErrQuantityTradeTooSmall
|
||||
}
|
||||
}
|
||||
borrowerReceivedQuantity := new(big.Int).Sub(quantityToLend, borrowFee)
|
||||
borrowerTradeResult := TradeResult{
|
||||
Fee: borrowFee,
|
||||
InToken: lendingToken,
|
||||
InTotal: borrowerReceivedQuantity,
|
||||
OutToken: collateralToken,
|
||||
OutTotal: collateralQuantity,
|
||||
}
|
||||
investorTradeResult := TradeResult{
|
||||
Fee: common.Big0,
|
||||
InToken: common.Address{},
|
||||
InTotal: common.Big0,
|
||||
OutToken: lendingToken,
|
||||
OutTotal: quantityToLend,
|
||||
}
|
||||
if takerSide == Borrowing {
|
||||
result = &LendingSettleBalance{
|
||||
Taker: borrowerTradeResult,
|
||||
Maker: investorTradeResult,
|
||||
CollateralLockedAmount: collateralQuantity,
|
||||
}
|
||||
} else {
|
||||
result = &LendingSettleBalance{
|
||||
Taker: investorTradeResult,
|
||||
Maker: borrowerTradeResult,
|
||||
CollateralLockedAmount: collateralQuantity,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// apr: annual percentage rate
|
||||
// this function returns actual interest rate base on borrowing time and apr
|
||||
// I = APR *(T + T1) / 2 / 365
|
||||
// T: term
|
||||
// T1: borrowingTime
|
||||
func CalculateInterestRate(finalizeTime, liquidationTime, term uint64, apr uint64) *big.Int {
|
||||
startBorrowingTime := liquidationTime - term
|
||||
borrowingTime := finalizeTime - startBorrowingTime
|
||||
|
||||
// the time interval which borrower have to pay interest
|
||||
// (T + T1) / 2
|
||||
timeToPayInterest := new(big.Int).Add(new(big.Int).SetUint64(term), new(big.Int).SetUint64(borrowingTime))
|
||||
timeToPayInterest = new(big.Int).Div(timeToPayInterest, new(big.Int).SetUint64(2))
|
||||
|
||||
interestRate := new(big.Int).SetUint64(apr)
|
||||
interestRate = new(big.Int).Mul(interestRate, timeToPayInterest)
|
||||
interestRate = new(big.Int).Div(interestRate, new(big.Int).SetUint64(common.OneYear))
|
||||
return interestRate
|
||||
}
|
||||
|
||||
func CalculateTotalRepayValue(finalizeTime, liquidationTime, term uint64, apr uint64, tradeAmount *big.Int) *big.Int {
|
||||
interestRate := CalculateInterestRate(finalizeTime, liquidationTime, term, apr)
|
||||
|
||||
// interest 10%
|
||||
// user should send: 10 * common.BaseLendingInterest
|
||||
// decimal = common.BaseLendingInterest * 100
|
||||
baseInterestDecimal := new(big.Int).Mul(common.BaseLendingInterest, new(big.Int).SetUint64(100))
|
||||
paymentBalance := new(big.Int).Mul(tradeAmount, new(big.Int).Add(baseInterestDecimal, interestRate))
|
||||
paymentBalance = new(big.Int).Div(paymentBalance, baseInterestDecimal)
|
||||
return paymentBalance
|
||||
}
|
||||
461
XDCxlending/lendingstate/settle_balance_test.go
Normal file
461
XDCxlending/lendingstate/settle_balance_test.go
Normal file
|
|
@ -0,0 +1,461 @@
|
|||
package lendingstate
|
||||
|
||||
import (
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestCalculateInterestRate(t *testing.T) {
|
||||
type args struct {
|
||||
repayTime uint64
|
||||
liquidationTime uint64
|
||||
term uint64
|
||||
apr uint64
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
want *big.Int
|
||||
}{
|
||||
// apr = 10% per year
|
||||
// term 365 days
|
||||
// repay after one day
|
||||
// have to pay interest for a half of year
|
||||
// I = APR *(T + T1) / 2 / 365 = 10% * (365 + 1) / 2 /365 = 5,01369863 %
|
||||
// 1e8 is decimal of interestRate
|
||||
{
|
||||
"term 365 days: early repay",
|
||||
args{
|
||||
repayTime: 86400,
|
||||
liquidationTime: common.OneYear,
|
||||
term: common.OneYear,
|
||||
apr: 10 * 1e8,
|
||||
},
|
||||
new(big.Int).SetUint64(501369863),
|
||||
},
|
||||
|
||||
// apr = 10% per year (365 days)
|
||||
// term: 365 days
|
||||
// repay at the end
|
||||
// pay full interestRate 10%
|
||||
// I = APR *(T + T1) / 2 / 365 = 10% * (365 + 365) / 2 /365 = 10 %
|
||||
// 1e8 is decimal of interestRate
|
||||
{
|
||||
"term 365 days: repay at the end",
|
||||
args{
|
||||
repayTime: common.OneYear,
|
||||
liquidationTime: common.OneYear,
|
||||
term: common.OneYear,
|
||||
apr: 10 * 1e8,
|
||||
},
|
||||
new(big.Int).SetUint64(10 * 1e8),
|
||||
},
|
||||
|
||||
// apr = 10% per year
|
||||
// term 30 days
|
||||
// repay after one day
|
||||
// have to pay interest for a half of year
|
||||
// I = APR *(T + T1) / 2 / 365 = 10% * (30 + 1) / 2 /365 = 0,424657534 %
|
||||
// 1e8 is decimal of interestRate
|
||||
{
|
||||
"term 30 days: early repay",
|
||||
args{
|
||||
repayTime: 86400,
|
||||
liquidationTime: 30 * 86400,
|
||||
term: 30 * 86400,
|
||||
apr: 10 * 1e8,
|
||||
},
|
||||
new(big.Int).SetUint64(42465753),
|
||||
},
|
||||
|
||||
// apr = 10% per year (365 days)
|
||||
// term: 30 days
|
||||
// repay at the end
|
||||
// pay full interestRate 10%
|
||||
// I = APR *(T + T1) / 2 / 365 = 10% * (30 + 30) / 2 /365 = 0,821917808 %
|
||||
// 1e8 is decimal of interestRate
|
||||
{
|
||||
"term 30 days: repay at the end",
|
||||
args{
|
||||
repayTime: 30 * 86400,
|
||||
liquidationTime: 30 * 86400,
|
||||
term: 30 * 86400,
|
||||
apr: 10 * 1e8,
|
||||
},
|
||||
new(big.Int).SetUint64(82191780),
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := CalculateInterestRate(tt.args.repayTime, tt.args.liquidationTime, tt.args.term, tt.args.apr); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("CalculateInterestRate() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetSettleBalance(t *testing.T) {
|
||||
lendQuantity, _ := new(big.Int).SetString("1000000000000000000000", 10) // 1000
|
||||
fee, _ := new(big.Int).SetString("10000000000000000000", 10) // 10
|
||||
lendQuantityExcluded, _ := new(big.Int).SetString("990000000000000000000", 10) // 990
|
||||
lendTokenNotXDC := common.HexToAddress("0x0000000000000000000000000000000000000033")
|
||||
collateral := common.HexToAddress("0x0000000000000000000000000000000000000022")
|
||||
collateralLocked, _ := new(big.Int).SetString("1000000000000000000000", 10) // 1000
|
||||
collateralLocked = new(big.Int).Mul(big.NewInt(150), collateralLocked)
|
||||
collateralLocked = new(big.Int).Div(collateralLocked, big.NewInt(100))
|
||||
|
||||
type GetSettleBalanceArg struct {
|
||||
isXDCXLendingFork bool
|
||||
takerSide string
|
||||
lendTokenXDCPrice *big.Int
|
||||
collateralPrice *big.Int
|
||||
depositRate *big.Int
|
||||
borrowFeeRate *big.Int
|
||||
lendingToken common.Address
|
||||
collateralToken common.Address
|
||||
lendTokenDecimal *big.Int
|
||||
collateralTokenDecimal *big.Int
|
||||
quantityToLend *big.Int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args GetSettleBalanceArg
|
||||
want *LendingSettleBalance
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
"collateralPrice nil",
|
||||
GetSettleBalanceArg{
|
||||
true,
|
||||
Borrowing,
|
||||
common.BasePrice,
|
||||
common.Big0,
|
||||
big.NewInt(150),
|
||||
big.NewInt(10000), // 100%
|
||||
common.Address{},
|
||||
common.Address{},
|
||||
common.BasePrice,
|
||||
common.BasePrice,
|
||||
lendQuantity,
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"quantityToLend = borrowFee, taker BORROW",
|
||||
GetSettleBalanceArg{
|
||||
true,
|
||||
Borrowing,
|
||||
common.BasePrice,
|
||||
common.BasePrice,
|
||||
big.NewInt(150),
|
||||
big.NewInt(10000), // 100%
|
||||
common.Address{},
|
||||
common.Address{},
|
||||
common.BasePrice,
|
||||
common.BasePrice,
|
||||
lendQuantity,
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"LendToken is XDC, quantity too small, taker BORROW",
|
||||
GetSettleBalanceArg{
|
||||
true,
|
||||
Borrowing,
|
||||
common.BasePrice,
|
||||
common.BasePrice,
|
||||
big.NewInt(150),
|
||||
big.NewInt(100), // 1%
|
||||
common.HexToAddress(common.XDCNativeAddress),
|
||||
common.Address{},
|
||||
common.BasePrice,
|
||||
common.BasePrice,
|
||||
common.BasePrice,
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"LendToken is not XDC, quantity too small, taker BORROW",
|
||||
GetSettleBalanceArg{
|
||||
true,
|
||||
Borrowing,
|
||||
common.BasePrice,
|
||||
common.BasePrice,
|
||||
big.NewInt(150),
|
||||
big.NewInt(100), // 1%
|
||||
common.Address{},
|
||||
common.Address{},
|
||||
common.BasePrice,
|
||||
common.BasePrice,
|
||||
common.BasePrice,
|
||||
},
|
||||
nil,
|
||||
true,
|
||||
},
|
||||
|
||||
{
|
||||
"LendToken is not XDC, no error, taker BORROW",
|
||||
GetSettleBalanceArg{
|
||||
true,
|
||||
Borrowing,
|
||||
common.BasePrice,
|
||||
common.BasePrice,
|
||||
big.NewInt(150),
|
||||
big.NewInt(100), // 1%
|
||||
lendTokenNotXDC,
|
||||
collateral,
|
||||
common.BasePrice,
|
||||
common.BasePrice,
|
||||
lendQuantity,
|
||||
},
|
||||
&LendingSettleBalance{
|
||||
Taker: TradeResult{
|
||||
Fee: fee,
|
||||
InToken: lendTokenNotXDC,
|
||||
InTotal: lendQuantityExcluded,
|
||||
OutToken: collateral,
|
||||
OutTotal: collateralLocked,
|
||||
},
|
||||
Maker: TradeResult{
|
||||
Fee: common.Big0,
|
||||
InToken: common.Address{},
|
||||
InTotal: common.Big0,
|
||||
OutToken: lendTokenNotXDC,
|
||||
OutTotal: lendQuantity,
|
||||
},
|
||||
CollateralLockedAmount: collateralLocked,
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"LendToken is not XDC, no error, taker INVEST",
|
||||
GetSettleBalanceArg{
|
||||
true,
|
||||
Investing,
|
||||
common.BasePrice,
|
||||
common.BasePrice,
|
||||
big.NewInt(150),
|
||||
big.NewInt(100), // 1%
|
||||
lendTokenNotXDC,
|
||||
collateral,
|
||||
common.BasePrice,
|
||||
common.BasePrice,
|
||||
lendQuantity,
|
||||
},
|
||||
&LendingSettleBalance{
|
||||
Maker: TradeResult{
|
||||
Fee: fee,
|
||||
InToken: lendTokenNotXDC,
|
||||
InTotal: lendQuantityExcluded,
|
||||
OutToken: collateral,
|
||||
OutTotal: collateralLocked,
|
||||
},
|
||||
Taker: TradeResult{
|
||||
Fee: common.Big0,
|
||||
InToken: common.Address{},
|
||||
InTotal: common.Big0,
|
||||
OutToken: lendTokenNotXDC,
|
||||
OutTotal: lendQuantity,
|
||||
},
|
||||
CollateralLockedAmount: collateralLocked,
|
||||
},
|
||||
false,
|
||||
},
|
||||
{
|
||||
"LendToken is XDC, no error, taker invest",
|
||||
GetSettleBalanceArg{
|
||||
true,
|
||||
Investing,
|
||||
common.BasePrice,
|
||||
common.BasePrice,
|
||||
big.NewInt(150),
|
||||
big.NewInt(100), // 1%
|
||||
common.HexToAddress(common.XDCNativeAddress),
|
||||
collateral,
|
||||
common.BasePrice,
|
||||
common.BasePrice,
|
||||
lendQuantity,
|
||||
},
|
||||
&LendingSettleBalance{
|
||||
Taker: TradeResult{
|
||||
Fee: common.Big0,
|
||||
InToken: common.Address{},
|
||||
InTotal: common.Big0,
|
||||
OutToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
OutTotal: lendQuantity,
|
||||
},
|
||||
Maker: TradeResult{
|
||||
Fee: fee,
|
||||
InToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
InTotal: lendQuantityExcluded,
|
||||
OutToken: collateral,
|
||||
OutTotal: collateralLocked,
|
||||
},
|
||||
CollateralLockedAmount: collateralLocked,
|
||||
},
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"LendToken is XDC, no error, taker Borrow",
|
||||
GetSettleBalanceArg{
|
||||
true,
|
||||
Borrowing,
|
||||
common.BasePrice,
|
||||
common.BasePrice,
|
||||
big.NewInt(150),
|
||||
big.NewInt(100), // 1%
|
||||
common.HexToAddress(common.XDCNativeAddress),
|
||||
collateral,
|
||||
common.BasePrice,
|
||||
common.BasePrice,
|
||||
lendQuantity,
|
||||
},
|
||||
&LendingSettleBalance{
|
||||
Maker: TradeResult{
|
||||
Fee: common.Big0,
|
||||
InToken: common.Address{},
|
||||
InTotal: common.Big0,
|
||||
OutToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
OutTotal: lendQuantity,
|
||||
},
|
||||
Taker: TradeResult{
|
||||
Fee: fee,
|
||||
InToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
InTotal: lendQuantityExcluded,
|
||||
OutToken: collateral,
|
||||
OutTotal: collateralLocked,
|
||||
},
|
||||
CollateralLockedAmount: collateralLocked,
|
||||
},
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, err := GetSettleBalance(tt.args.isXDCXLendingFork, tt.args.takerSide, tt.args.lendTokenXDCPrice, tt.args.collateralPrice, tt.args.depositRate, tt.args.borrowFeeRate, tt.args.lendingToken, tt.args.collateralToken, tt.args.lendTokenDecimal, tt.args.collateralTokenDecimal, tt.args.quantityToLend)
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("GetSettleBalance() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
if tt.want != nil {
|
||||
t.Log(tt.want.String())
|
||||
}
|
||||
if !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("GetSettleBalance() got = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestCalculateTotalRepayValue(t *testing.T) {
|
||||
type CalculateTotalRepayValueArg struct {
|
||||
finalizeTime uint64
|
||||
liquidationTime uint64
|
||||
term uint64
|
||||
apr uint64
|
||||
tradeAmount *big.Int
|
||||
}
|
||||
totalRepayOneYearRepayEarly, _ := new(big.Int).SetString("1050136986300000000000", 10)
|
||||
totalRepayOneYearRepayInTime, _ := new(big.Int).SetString("1100000000000000000000", 10)
|
||||
totalRepay30DaysRepayEarly, _ := new(big.Int).SetString("1004246575300000000000", 10)
|
||||
totalRepay30DaysRepayInTime, _ := new(big.Int).SetString("1008219178000000000000", 10)
|
||||
|
||||
tradeAmount := new(big.Int).Mul(big.NewInt(1000), common.BasePrice)
|
||||
tests := []struct {
|
||||
name string
|
||||
args CalculateTotalRepayValueArg
|
||||
want *big.Int
|
||||
}{
|
||||
// apr = 10% per year
|
||||
// term 365 days
|
||||
// repay after one day
|
||||
// have to pay interest for a half of year
|
||||
// I = APR *(T + T1) / 2 / 365 = 10% * (365 + 1) / 2 /365 = 5,01369863 %
|
||||
// 1e8 is decimal of interestRate
|
||||
// amount 1000 USDT
|
||||
// -> totalRepay: 1000 * (1 + 5,01369863 %) = 1050,1369863
|
||||
{
|
||||
"term 365 days, 1000 USDT: early repay",
|
||||
CalculateTotalRepayValueArg{
|
||||
finalizeTime: 86400,
|
||||
liquidationTime: common.OneYear,
|
||||
term: common.OneYear,
|
||||
apr: 10 * 1e8,
|
||||
tradeAmount: tradeAmount,
|
||||
},
|
||||
totalRepayOneYearRepayEarly,
|
||||
},
|
||||
|
||||
// apr = 10% per year (365 days)
|
||||
// term: 365 days
|
||||
// repay at the end
|
||||
// pay full interestRate 10%
|
||||
// I = APR *(T + T1) / 2 / 365 = 10% * (365 + 365) / 2 /365 = 10 %
|
||||
// 1e8 is decimal of interestRate
|
||||
// -> totalRepay: 1000 * (1 + 10 %) = 1100
|
||||
{
|
||||
"term 365 days: repay at the end",
|
||||
CalculateTotalRepayValueArg{
|
||||
finalizeTime: common.OneYear,
|
||||
liquidationTime: common.OneYear,
|
||||
term: common.OneYear,
|
||||
apr: 10 * 1e8,
|
||||
tradeAmount: tradeAmount,
|
||||
},
|
||||
totalRepayOneYearRepayInTime,
|
||||
},
|
||||
|
||||
// apr = 10% per year
|
||||
// term 30 days
|
||||
// repay after one day
|
||||
// have to pay interest for a half of year
|
||||
// I = APR *(T + T1) / 2 / 365 = 10% * (30 + 1) / 2 /365 = 0,424657534 %
|
||||
// 1e8 is decimal of interestRate
|
||||
// -> totalRepay: 1000 * (1 + 0,424657534 %) = 1004,2465753
|
||||
{
|
||||
"term 30 days: early repay",
|
||||
CalculateTotalRepayValueArg{
|
||||
finalizeTime: 86400,
|
||||
liquidationTime: 30 * 86400,
|
||||
term: 30 * 86400,
|
||||
apr: 10 * 1e8,
|
||||
tradeAmount: tradeAmount,
|
||||
},
|
||||
totalRepay30DaysRepayEarly,
|
||||
},
|
||||
|
||||
// apr = 10% per year (365 days)
|
||||
// term: 30 days
|
||||
// repay at the end
|
||||
// pay full interestRate 10%
|
||||
// I = APR *(T + T1) / 2 / 365 = 10% * (30 + 30) / 2 /365 = 0,821917808 %
|
||||
// 1e8 is decimal of interestRate
|
||||
// -> totalRepay: 1000 * (1 + 0,821917808 %) = 1008,2191780
|
||||
{
|
||||
"term 30 days: repay at the end",
|
||||
CalculateTotalRepayValueArg{
|
||||
finalizeTime: 30 * 86400,
|
||||
liquidationTime: 30 * 86400,
|
||||
term: 30 * 86400,
|
||||
apr: 10 * 1e8,
|
||||
tradeAmount: tradeAmount,
|
||||
},
|
||||
totalRepay30DaysRepayInTime,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := CalculateTotalRepayValue(tt.args.finalizeTime, tt.args.liquidationTime, tt.args.term, tt.args.apr, tt.args.tradeAmount); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("CalculateTotalRepayValue() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
193
XDCxlending/lendingstate/state_itemList.go
Normal file
193
XDCxlending/lendingstate/state_itemList.go
Normal file
|
|
@ -0,0 +1,193 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package lendingstate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
"io"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type itemListState struct {
|
||||
lendingBook common.Hash
|
||||
key common.Hash
|
||||
data itemList
|
||||
|
||||
// DB error.
|
||||
// State objects are used by the consensus core and VM which are
|
||||
// unable to deal with database-level errors. Any error that occurs
|
||||
// during a database read is memoized here and will eventually be returned
|
||||
// by LendingStateDB.Commit.
|
||||
dbErr error
|
||||
|
||||
// Write caches.
|
||||
trie Trie // storage trie, which becomes non-nil on first access
|
||||
|
||||
cachedStorage map[common.Hash]common.Hash // Storage entry cache to avoid duplicate reads
|
||||
dirtyStorage map[common.Hash]common.Hash // Storage entries that need to be flushed to disk
|
||||
|
||||
onDirty func(price common.Hash) // Callback method to mark a state object newly dirty
|
||||
}
|
||||
|
||||
func (s *itemListState) empty() bool {
|
||||
return s.data.Volume == nil || s.data.Volume.Sign() == 0
|
||||
}
|
||||
|
||||
func newItemListState(lendingBook common.Hash, key common.Hash, data itemList, onDirty func(price common.Hash)) *itemListState {
|
||||
return &itemListState{
|
||||
lendingBook: lendingBook,
|
||||
key: key,
|
||||
data: data,
|
||||
cachedStorage: make(map[common.Hash]common.Hash),
|
||||
dirtyStorage: make(map[common.Hash]common.Hash),
|
||||
onDirty: onDirty,
|
||||
}
|
||||
}
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (c *itemListState) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, c.data)
|
||||
}
|
||||
|
||||
// setError remembers the first non-nil error it is called with.
|
||||
func (self *itemListState) setError(err error) {
|
||||
if self.dbErr == nil {
|
||||
self.dbErr = err
|
||||
}
|
||||
}
|
||||
|
||||
func (c *itemListState) getTrie(db Database) Trie {
|
||||
if c.trie == nil {
|
||||
var err error
|
||||
c.trie, err = db.OpenStorageTrie(c.key, c.data.Root)
|
||||
if err != nil {
|
||||
c.trie, _ = db.OpenStorageTrie(c.key, EmptyHash)
|
||||
c.setError(fmt.Errorf("can't create storage trie: %v", err))
|
||||
}
|
||||
}
|
||||
return c.trie
|
||||
}
|
||||
|
||||
func (self *itemListState) GetOrderAmount(db Database, orderId common.Hash) common.Hash {
|
||||
amount, exists := self.cachedStorage[orderId]
|
||||
if exists {
|
||||
return amount
|
||||
}
|
||||
// Load from DB in case it is missing.
|
||||
enc, err := self.getTrie(db).TryGet(orderId[:])
|
||||
if err != nil {
|
||||
self.setError(err)
|
||||
return EmptyHash
|
||||
}
|
||||
if len(enc) > 0 {
|
||||
_, content, _, err := rlp.Split(enc)
|
||||
if err != nil {
|
||||
self.setError(err)
|
||||
}
|
||||
amount.SetBytes(content)
|
||||
}
|
||||
if (amount != common.Hash{}) {
|
||||
self.cachedStorage[orderId] = amount
|
||||
}
|
||||
return amount
|
||||
}
|
||||
|
||||
func (self *itemListState) insertLendingItem(db Database, orderId common.Hash, amount common.Hash) {
|
||||
self.setOrderItem(orderId, amount)
|
||||
self.setError(self.getTrie(db).TryUpdate(orderId[:], amount[:]))
|
||||
}
|
||||
|
||||
func (self *itemListState) removeOrderItem(db Database, orderId common.Hash) {
|
||||
tr := self.getTrie(db)
|
||||
self.setError(tr.TryDelete(orderId[:]))
|
||||
self.setOrderItem(orderId, EmptyHash)
|
||||
}
|
||||
|
||||
func (self *itemListState) setOrderItem(orderId common.Hash, amount common.Hash) {
|
||||
self.cachedStorage[orderId] = amount
|
||||
self.dirtyStorage[orderId] = amount
|
||||
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.key)
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
// updateAskTrie writes cached storage modifications into the object's storage trie.
|
||||
func (self *itemListState) updateTrie(db Database) Trie {
|
||||
tr := self.getTrie(db)
|
||||
for orderId, amount := range self.dirtyStorage {
|
||||
delete(self.dirtyStorage, orderId)
|
||||
if amount == EmptyHash {
|
||||
self.setError(tr.TryDelete(orderId[:]))
|
||||
continue
|
||||
}
|
||||
v, _ := rlp.EncodeToBytes(bytes.TrimLeft(amount[:], "\x00"))
|
||||
self.setError(tr.TryUpdate(orderId[:], v))
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
// UpdateRoot sets the trie root to the current root tradeId of
|
||||
func (self *itemListState) updateRoot(db Database) error {
|
||||
self.updateTrie(db)
|
||||
if self.dbErr != nil {
|
||||
return self.dbErr
|
||||
}
|
||||
root, err := self.trie.Commit(nil)
|
||||
if err == nil {
|
||||
self.data.Root = root
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *itemListState) deepCopy(db *LendingStateDB, onDirty func(price common.Hash)) *itemListState {
|
||||
stateOrderList := newItemListState(self.lendingBook, self.key, self.data, onDirty)
|
||||
if self.trie != nil {
|
||||
stateOrderList.trie = db.db.CopyTrie(self.trie)
|
||||
}
|
||||
for orderId, amount := range self.dirtyStorage {
|
||||
stateOrderList.dirtyStorage[orderId] = amount
|
||||
}
|
||||
for orderId, amount := range self.cachedStorage {
|
||||
stateOrderList.cachedStorage[orderId] = amount
|
||||
}
|
||||
return stateOrderList
|
||||
}
|
||||
|
||||
func (c *itemListState) AddVolume(amount *big.Int) {
|
||||
c.setVolume(new(big.Int).Add(c.data.Volume, amount))
|
||||
}
|
||||
|
||||
func (c *itemListState) subVolume(amount *big.Int) {
|
||||
c.setVolume(new(big.Int).Sub(c.data.Volume, amount))
|
||||
}
|
||||
|
||||
func (self *itemListState) setVolume(volume *big.Int) {
|
||||
self.data.Volume = volume
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.key)
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *itemListState) Volume() *big.Int {
|
||||
return self.data.Volume
|
||||
}
|
||||
786
XDCxlending/lendingstate/state_lendingbook.go
Normal file
786
XDCxlending/lendingstate/state_lendingbook.go
Normal file
|
|
@ -0,0 +1,786 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package lendingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
"io"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type lendingExchangeState struct {
|
||||
lendingBook common.Hash
|
||||
data lendingObject
|
||||
db *LendingStateDB
|
||||
|
||||
// DB error.
|
||||
// State objects are used by the consensus core and VM which are
|
||||
// unable to deal with database-level errors. Any error that occurs
|
||||
// during a database read is memoized here and will eventually be returned
|
||||
// by LendingStateDB.Commit.
|
||||
dbErr error
|
||||
|
||||
investingTrie Trie
|
||||
borrowingTrie Trie
|
||||
lendingItemTrie Trie
|
||||
lendingTradeTrie Trie
|
||||
liquidationTimeTrie Trie
|
||||
|
||||
liquidationTimeStates map[common.Hash]*liquidationTimeState
|
||||
liquidationTimestatesDirty map[common.Hash]struct{}
|
||||
|
||||
investingStates map[common.Hash]*itemListState
|
||||
investingStatesDirty map[common.Hash]struct{}
|
||||
|
||||
borrowingStates map[common.Hash]*itemListState
|
||||
borrowingStatesDirty map[common.Hash]struct{}
|
||||
|
||||
lendingItemStates map[common.Hash]*lendingItemState
|
||||
lendingItemStatesDirty map[common.Hash]struct{}
|
||||
|
||||
lendingTradeStates map[common.Hash]*lendingTradeState
|
||||
lendingTradeStatesDirty map[common.Hash]struct{}
|
||||
|
||||
onDirty func(hash common.Hash) // Callback method to mark a state object newly dirty
|
||||
}
|
||||
|
||||
// empty returns whether the tradeId is considered empty.
|
||||
func (s *lendingExchangeState) empty() bool {
|
||||
if s.data.Nonce != 0 {
|
||||
return false
|
||||
}
|
||||
if s.data.TradeNonce != 0 {
|
||||
return false
|
||||
}
|
||||
if !common.EmptyHash(s.data.InvestingRoot) {
|
||||
return false
|
||||
}
|
||||
if !common.EmptyHash(s.data.BorrowingRoot) {
|
||||
return false
|
||||
}
|
||||
if !common.EmptyHash(s.data.LendingItemRoot) {
|
||||
return false
|
||||
}
|
||||
if !common.EmptyHash(s.data.LendingTradeRoot) {
|
||||
return false
|
||||
}
|
||||
if !common.EmptyHash(s.data.LiquidationTimeRoot) {
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func newStateExchanges(db *LendingStateDB, hash common.Hash, data lendingObject, onDirty func(addr common.Hash)) *lendingExchangeState {
|
||||
return &lendingExchangeState{
|
||||
db: db,
|
||||
lendingBook: hash,
|
||||
data: data,
|
||||
investingStates: make(map[common.Hash]*itemListState),
|
||||
borrowingStates: make(map[common.Hash]*itemListState),
|
||||
lendingItemStates: make(map[common.Hash]*lendingItemState),
|
||||
lendingTradeStates: make(map[common.Hash]*lendingTradeState),
|
||||
liquidationTimeStates: make(map[common.Hash]*liquidationTimeState),
|
||||
investingStatesDirty: make(map[common.Hash]struct{}),
|
||||
borrowingStatesDirty: make(map[common.Hash]struct{}),
|
||||
lendingItemStatesDirty: make(map[common.Hash]struct{}),
|
||||
lendingTradeStatesDirty: make(map[common.Hash]struct{}),
|
||||
liquidationTimestatesDirty: make(map[common.Hash]struct{}),
|
||||
onDirty: onDirty,
|
||||
}
|
||||
}
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (self *lendingExchangeState) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, self.data)
|
||||
}
|
||||
|
||||
// setError remembers the first non-nil error it is called with.
|
||||
func (self *lendingExchangeState) setError(err error) {
|
||||
if self.dbErr == nil {
|
||||
self.dbErr = err
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
Get Trie
|
||||
*/
|
||||
|
||||
func (self *lendingExchangeState) getLendingItemTrie(db Database) Trie {
|
||||
if self.lendingItemTrie == nil {
|
||||
var err error
|
||||
self.lendingItemTrie, err = db.OpenStorageTrie(self.lendingBook, self.data.LendingItemRoot)
|
||||
if err != nil {
|
||||
self.lendingItemTrie, _ = db.OpenStorageTrie(self.lendingBook, EmptyHash)
|
||||
self.setError(fmt.Errorf("can't create Lendings trie: %v", err))
|
||||
}
|
||||
}
|
||||
return self.lendingItemTrie
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) getLendingTradeTrie(db Database) Trie {
|
||||
if self.lendingTradeTrie == nil {
|
||||
var err error
|
||||
self.lendingTradeTrie, err = db.OpenStorageTrie(self.lendingBook, self.data.LendingTradeRoot)
|
||||
if err != nil {
|
||||
self.lendingTradeTrie, _ = db.OpenStorageTrie(self.lendingBook, EmptyHash)
|
||||
self.setError(fmt.Errorf("can't create Lendings trie: %v", err))
|
||||
}
|
||||
}
|
||||
return self.lendingTradeTrie
|
||||
}
|
||||
func (self *lendingExchangeState) getInvestingTrie(db Database) Trie {
|
||||
if self.investingTrie == nil {
|
||||
var err error
|
||||
self.investingTrie, err = db.OpenStorageTrie(self.lendingBook, self.data.InvestingRoot)
|
||||
if err != nil {
|
||||
self.investingTrie, _ = db.OpenStorageTrie(self.lendingBook, EmptyHash)
|
||||
self.setError(fmt.Errorf("can't create Lendings trie: %v", err))
|
||||
}
|
||||
}
|
||||
return self.investingTrie
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) getBorrowingTrie(db Database) Trie {
|
||||
if self.borrowingTrie == nil {
|
||||
var err error
|
||||
self.borrowingTrie, err = db.OpenStorageTrie(self.lendingBook, self.data.BorrowingRoot)
|
||||
if err != nil {
|
||||
self.borrowingTrie, _ = db.OpenStorageTrie(self.lendingBook, EmptyHash)
|
||||
self.setError(fmt.Errorf("can't create bids trie: %v", err))
|
||||
}
|
||||
}
|
||||
return self.borrowingTrie
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) getLiquidationTimeTrie(db Database) Trie {
|
||||
if self.liquidationTimeTrie == nil {
|
||||
var err error
|
||||
self.liquidationTimeTrie, err = db.OpenStorageTrie(self.lendingBook, self.data.LiquidationTimeRoot)
|
||||
if err != nil {
|
||||
self.liquidationTimeTrie, _ = db.OpenStorageTrie(self.lendingBook, EmptyHash)
|
||||
self.setError(fmt.Errorf("can't create bids trie: %v", err))
|
||||
}
|
||||
}
|
||||
return self.liquidationTimeTrie
|
||||
}
|
||||
|
||||
/**
|
||||
Get State
|
||||
*/
|
||||
func (self *lendingExchangeState) getBorrowingOrderList(db Database, rate common.Hash) (stateOrderList *itemListState) {
|
||||
// Prefer 'live' objects.
|
||||
if obj := self.borrowingStates[rate]; obj != nil {
|
||||
return obj
|
||||
}
|
||||
|
||||
// Load the object from the database.
|
||||
enc, err := self.getBorrowingTrie(db).TryGet(rate[:])
|
||||
if len(enc) == 0 {
|
||||
self.setError(err)
|
||||
return nil
|
||||
}
|
||||
var data itemList
|
||||
if err := rlp.DecodeBytes(enc, &data); err != nil {
|
||||
log.Error("Failed to decode state order list object", "rate", rate, "err", err)
|
||||
return nil
|
||||
}
|
||||
// Insert into the live set.
|
||||
obj := newItemListState(self.lendingBook, rate, data, self.MarkBorrowingDirty)
|
||||
self.borrowingStates[rate] = obj
|
||||
return obj
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) getInvestingOrderList(db Database, rate common.Hash) (stateOrderList *itemListState) {
|
||||
// Prefer 'live' objects.
|
||||
if obj := self.investingStates[rate]; obj != nil {
|
||||
return obj
|
||||
}
|
||||
|
||||
// Load the object from the database.
|
||||
enc, err := self.getInvestingTrie(db).TryGet(rate[:])
|
||||
if len(enc) == 0 {
|
||||
self.setError(err)
|
||||
return nil
|
||||
}
|
||||
var data itemList
|
||||
if err := rlp.DecodeBytes(enc, &data); err != nil {
|
||||
log.Error("Failed to decode state order list object", "rate", rate, "err", err)
|
||||
return nil
|
||||
}
|
||||
// Insert into the live set.
|
||||
obj := newItemListState(self.lendingBook, rate, data, self.MarkInvestingDirty)
|
||||
self.investingStates[rate] = obj
|
||||
return obj
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) getLiquidationTimeOrderList(db Database, time common.Hash) (stateObject *liquidationTimeState) {
|
||||
// Prefer 'live' objects.
|
||||
if obj := self.liquidationTimeStates[time]; obj != nil {
|
||||
return obj
|
||||
}
|
||||
|
||||
// Load the object from the database.
|
||||
enc, err := self.getLiquidationTimeTrie(db).TryGet(time[:])
|
||||
if len(enc) == 0 {
|
||||
self.setError(err)
|
||||
return nil
|
||||
}
|
||||
var data itemList
|
||||
if err := rlp.DecodeBytes(enc, &data); err != nil {
|
||||
log.Error("Failed to decode state liquidation time", "time", time, "err", err)
|
||||
return nil
|
||||
}
|
||||
// Insert into the live set.
|
||||
obj := newLiquidationTimeState(self.lendingBook, time, data, self.MarkLiquidationTimeDirty)
|
||||
self.liquidationTimeStates[time] = obj
|
||||
return obj
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) getLendingItem(db Database, lendingId common.Hash) (stateObject *lendingItemState) {
|
||||
// Prefer 'live' objects.
|
||||
if obj := self.lendingItemStates[lendingId]; obj != nil {
|
||||
return obj
|
||||
}
|
||||
|
||||
// Load the object from the database.
|
||||
enc, err := self.getLendingItemTrie(db).TryGet(lendingId[:])
|
||||
if len(enc) == 0 {
|
||||
self.setError(err)
|
||||
return nil
|
||||
}
|
||||
var data LendingItem
|
||||
if err := rlp.DecodeBytes(enc, &data); err != nil {
|
||||
log.Error("Failed to decode state lending item", "tradeId", lendingId, "err", err)
|
||||
return nil
|
||||
}
|
||||
// Insert into the live set.
|
||||
obj := newLendinItemState(self.lendingBook, lendingId, data, self.MarkLendingItemDirty)
|
||||
self.lendingItemStates[lendingId] = obj
|
||||
return obj
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) getLendingTrade(db Database, tradeId common.Hash) (stateObject *lendingTradeState) {
|
||||
// Prefer 'live' objects.
|
||||
if obj := self.lendingTradeStates[tradeId]; obj != nil {
|
||||
return obj
|
||||
}
|
||||
|
||||
// Load the object from the database.
|
||||
enc, err := self.getLendingTradeTrie(db).TryGet(tradeId[:])
|
||||
if len(enc) == 0 {
|
||||
self.setError(err)
|
||||
return nil
|
||||
}
|
||||
var data LendingTrade
|
||||
if err := rlp.DecodeBytes(enc, &data); err != nil {
|
||||
log.Error("Failed to decode state lending trade", "tradeId", tradeId, "err", err)
|
||||
return nil
|
||||
}
|
||||
// Insert into the live set.
|
||||
obj := newLendingTradeState(self.lendingBook, tradeId, data, self.MarkLendingTradeDirty)
|
||||
self.lendingTradeStates[tradeId] = obj
|
||||
return obj
|
||||
}
|
||||
|
||||
/**
|
||||
Update Trie
|
||||
*/
|
||||
func (self *lendingExchangeState) updateLendingTimeTrie(db Database) Trie {
|
||||
tr := self.getLendingItemTrie(db)
|
||||
for lendingId, lendingItem := range self.lendingItemStates {
|
||||
if _, isDirty := self.lendingItemStatesDirty[lendingId]; isDirty {
|
||||
delete(self.lendingItemStatesDirty, lendingId)
|
||||
if lendingItem.empty() {
|
||||
self.setError(tr.TryDelete(lendingId[:]))
|
||||
continue
|
||||
}
|
||||
// Encoding []byte cannot fail, ok to ignore the error.
|
||||
v, _ := rlp.EncodeToBytes(lendingItem)
|
||||
self.setError(tr.TryUpdate(lendingId[:], v))
|
||||
}
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) updateLendingTradeTrie(db Database) Trie {
|
||||
tr := self.getLendingTradeTrie(db)
|
||||
for tradeId, lendingTradeItem := range self.lendingTradeStates {
|
||||
if _, isDirty := self.lendingTradeStatesDirty[tradeId]; isDirty {
|
||||
delete(self.lendingTradeStatesDirty, tradeId)
|
||||
if lendingTradeItem.empty() {
|
||||
self.setError(tr.TryDelete(tradeId[:]))
|
||||
continue
|
||||
}
|
||||
// Encoding []byte cannot fail, ok to ignore the error.
|
||||
v, _ := rlp.EncodeToBytes(lendingTradeItem)
|
||||
self.setError(tr.TryUpdate(tradeId[:], v))
|
||||
}
|
||||
}
|
||||
return tr
|
||||
}
|
||||
func (self *lendingExchangeState) updateBorrowingTrie(db Database) Trie {
|
||||
tr := self.getBorrowingTrie(db)
|
||||
for rate, orderList := range self.borrowingStates {
|
||||
if _, isDirty := self.borrowingStatesDirty[rate]; isDirty {
|
||||
delete(self.borrowingStatesDirty, rate)
|
||||
if orderList.empty() {
|
||||
self.setError(tr.TryDelete(rate[:]))
|
||||
continue
|
||||
}
|
||||
orderList.updateRoot(db)
|
||||
// Encoding []byte cannot fail, ok to ignore the error.
|
||||
v, _ := rlp.EncodeToBytes(orderList)
|
||||
self.setError(tr.TryUpdate(rate[:], v))
|
||||
}
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) updateInvestingTrie(db Database) Trie {
|
||||
tr := self.getInvestingTrie(db)
|
||||
for rate, orderList := range self.investingStates {
|
||||
if _, isDirty := self.investingStatesDirty[rate]; isDirty {
|
||||
delete(self.investingStatesDirty, rate)
|
||||
if orderList.empty() {
|
||||
self.setError(tr.TryDelete(rate[:]))
|
||||
continue
|
||||
}
|
||||
orderList.updateRoot(db)
|
||||
// Encoding []byte cannot fail, ok to ignore the error.
|
||||
v, _ := rlp.EncodeToBytes(orderList)
|
||||
self.setError(tr.TryUpdate(rate[:], v))
|
||||
}
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) updateLiquidationTimeTrie(db Database) Trie {
|
||||
tr := self.getLiquidationTimeTrie(db)
|
||||
for time, itemList := range self.liquidationTimeStates {
|
||||
if _, isDirty := self.liquidationTimestatesDirty[time]; isDirty {
|
||||
delete(self.liquidationTimestatesDirty, time)
|
||||
if itemList.empty() {
|
||||
self.setError(tr.TryDelete(time[:]))
|
||||
continue
|
||||
}
|
||||
itemList.updateRoot(db)
|
||||
// Encoding []byte cannot fail, ok to ignore the error.
|
||||
v, _ := rlp.EncodeToBytes(itemList)
|
||||
self.setError(tr.TryUpdate(time[:], v))
|
||||
}
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
/**
|
||||
Update Root
|
||||
*/
|
||||
|
||||
func (self *lendingExchangeState) updateOrderRoot(db Database) {
|
||||
self.updateLendingTimeTrie(db)
|
||||
self.data.LendingItemRoot = self.lendingItemTrie.Hash()
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) updateInvestingRoot(db Database) error {
|
||||
self.updateInvestingTrie(db)
|
||||
if self.dbErr != nil {
|
||||
return self.dbErr
|
||||
}
|
||||
self.data.InvestingRoot = self.investingTrie.Hash()
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) updateBorrowingRoot(db Database) {
|
||||
self.updateBorrowingTrie(db)
|
||||
self.data.BorrowingRoot = self.borrowingTrie.Hash()
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) updateLiquidationTimeRoot(db Database) {
|
||||
self.updateLiquidationTimeTrie(db)
|
||||
self.data.LiquidationTimeRoot = self.liquidationTimeTrie.Hash()
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) updateLendingTradeRoot(db Database) {
|
||||
self.updateLendingTradeTrie(db)
|
||||
self.data.LendingTradeRoot = self.lendingTradeTrie.Hash()
|
||||
}
|
||||
|
||||
/**
|
||||
Commit Trie
|
||||
*/
|
||||
|
||||
func (self *lendingExchangeState) CommitLendingItemTrie(db Database) error {
|
||||
self.updateLendingTimeTrie(db)
|
||||
if self.dbErr != nil {
|
||||
return self.dbErr
|
||||
}
|
||||
root, err := self.lendingItemTrie.Commit(nil)
|
||||
if err == nil {
|
||||
self.data.LendingItemRoot = root
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) CommitLendingTradeTrie(db Database) error {
|
||||
self.updateLendingTradeTrie(db)
|
||||
if self.dbErr != nil {
|
||||
return self.dbErr
|
||||
}
|
||||
root, err := self.lendingTradeTrie.Commit(nil)
|
||||
if err == nil {
|
||||
self.data.LendingTradeRoot = root
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) CommitInvestingTrie(db Database) error {
|
||||
self.updateInvestingTrie(db)
|
||||
if self.dbErr != nil {
|
||||
return self.dbErr
|
||||
}
|
||||
root, err := self.investingTrie.Commit(func(leaf []byte, parent common.Hash) error {
|
||||
var orderList itemList
|
||||
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
|
||||
return nil
|
||||
}
|
||||
if orderList.Root != EmptyRoot {
|
||||
db.TrieDB().Reference(orderList.Root, parent)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err == nil {
|
||||
self.data.InvestingRoot = root
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) CommitBorrowingTrie(db Database) error {
|
||||
self.updateBorrowingTrie(db)
|
||||
if self.dbErr != nil {
|
||||
return self.dbErr
|
||||
}
|
||||
root, err := self.borrowingTrie.Commit(func(leaf []byte, parent common.Hash) error {
|
||||
var orderList itemList
|
||||
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
|
||||
return nil
|
||||
}
|
||||
if orderList.Root != EmptyRoot {
|
||||
db.TrieDB().Reference(orderList.Root, parent)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err == nil {
|
||||
self.data.BorrowingRoot = root
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) CommitLiquidationTimeTrie(db Database) error {
|
||||
self.updateLiquidationTimeTrie(db)
|
||||
if self.dbErr != nil {
|
||||
return self.dbErr
|
||||
}
|
||||
root, err := self.liquidationTimeTrie.Commit(func(leaf []byte, parent common.Hash) error {
|
||||
var orderList itemList
|
||||
if err := rlp.DecodeBytes(leaf, &orderList); err != nil {
|
||||
return nil
|
||||
}
|
||||
if orderList.Root != EmptyRoot {
|
||||
db.TrieDB().Reference(orderList.Root, parent)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
if err == nil {
|
||||
self.data.LiquidationTimeRoot = root
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
/**
|
||||
Get Trie Data
|
||||
*/
|
||||
func (self *lendingExchangeState) getBestInvestingInterest(db Database) common.Hash {
|
||||
trie := self.getInvestingTrie(db)
|
||||
encKey, encValue, err := trie.TryGetBestLeftKeyAndValue()
|
||||
if err != nil {
|
||||
log.Error("Failed find best investing rate", "orderbook", self.lendingBook.Hex())
|
||||
return EmptyHash
|
||||
}
|
||||
if len(encKey) == 0 || len(encValue) == 0 {
|
||||
log.Debug("Not found get best investing rate", "encKey", encKey, "encValue", encValue)
|
||||
return EmptyHash
|
||||
}
|
||||
// Insert into the live set.
|
||||
interest := common.BytesToHash(encKey)
|
||||
if _, exist := self.investingStates[interest]; !exist {
|
||||
var data itemList
|
||||
if err := rlp.DecodeBytes(encValue, &data); err != nil {
|
||||
log.Error("Failed to decode state get best investing rate", "err", err)
|
||||
return EmptyHash
|
||||
}
|
||||
obj := newItemListState(self.lendingBook, interest, data, self.MarkInvestingDirty)
|
||||
self.investingStates[interest] = obj
|
||||
}
|
||||
return interest
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) getBestBorrowingInterest(db Database) common.Hash {
|
||||
trie := self.getBorrowingTrie(db)
|
||||
encKey, encValue, err := trie.TryGetBestRightKeyAndValue()
|
||||
if err != nil {
|
||||
log.Error("Failed find best key bid trie ", "orderbook", self.lendingBook.Hex())
|
||||
return EmptyHash
|
||||
}
|
||||
if len(encKey) == 0 || len(encValue) == 0 {
|
||||
log.Debug("Not found get best bid trie", "encKey", encKey, "encValue", encValue)
|
||||
return EmptyHash
|
||||
}
|
||||
// Insert into the live set.
|
||||
interest := common.BytesToHash(encKey)
|
||||
if _, exist := self.borrowingStates[interest]; !exist {
|
||||
var data itemList
|
||||
if err := rlp.DecodeBytes(encValue, &data); err != nil {
|
||||
log.Error("Failed to decode state get best bid trie", "err", err)
|
||||
return EmptyHash
|
||||
}
|
||||
obj := newItemListState(self.lendingBook, interest, data, self.MarkBorrowingDirty)
|
||||
self.borrowingStates[interest] = obj
|
||||
}
|
||||
return interest
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) getLowestLiquidationTime(db Database) (common.Hash, *liquidationTimeState) {
|
||||
trie := self.getLiquidationTimeTrie(db)
|
||||
encKey, encValue, err := trie.TryGetBestLeftKeyAndValue()
|
||||
if err != nil {
|
||||
log.Error("Failed find best liquidation time trie ", "orderBook", self.lendingBook.Hex())
|
||||
return EmptyHash, nil
|
||||
}
|
||||
if len(encKey) == 0 || len(encValue) == 0 {
|
||||
log.Debug("Not found get liquidation time trie", "encKey", encKey, "encValue", encValue)
|
||||
return EmptyHash, nil
|
||||
}
|
||||
price := common.BytesToHash(encKey)
|
||||
obj, exist := self.liquidationTimeStates[price]
|
||||
if !exist {
|
||||
var data itemList
|
||||
if err := rlp.DecodeBytes(encValue, &data); err != nil {
|
||||
log.Error("Failed to decode state get liquidation time trie", "err", err)
|
||||
return EmptyHash, nil
|
||||
}
|
||||
obj = newLiquidationTimeState(self.lendingBook, price, data, self.MarkLiquidationTimeDirty)
|
||||
self.liquidationTimeStates[price] = obj
|
||||
}
|
||||
if obj.empty() {
|
||||
return EmptyHash, nil
|
||||
}
|
||||
return price, obj
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) deepCopy(db *LendingStateDB, onDirty func(hash common.Hash)) *lendingExchangeState {
|
||||
stateExchanges := newStateExchanges(db, self.lendingBook, self.data, onDirty)
|
||||
if self.investingTrie != nil {
|
||||
stateExchanges.investingTrie = db.db.CopyTrie(self.investingTrie)
|
||||
}
|
||||
if self.borrowingTrie != nil {
|
||||
stateExchanges.borrowingTrie = db.db.CopyTrie(self.borrowingTrie)
|
||||
}
|
||||
if self.lendingItemTrie != nil {
|
||||
stateExchanges.lendingItemTrie = db.db.CopyTrie(self.lendingItemTrie)
|
||||
}
|
||||
for key, value := range self.borrowingStates {
|
||||
stateExchanges.borrowingStates[key] = value.deepCopy(db, self.MarkBorrowingDirty)
|
||||
}
|
||||
for key := range self.borrowingStatesDirty {
|
||||
stateExchanges.borrowingStatesDirty[key] = struct{}{}
|
||||
}
|
||||
for key, value := range self.investingStates {
|
||||
stateExchanges.investingStates[key] = value.deepCopy(db, self.MarkInvestingDirty)
|
||||
}
|
||||
for key := range self.investingStatesDirty {
|
||||
stateExchanges.investingStatesDirty[key] = struct{}{}
|
||||
}
|
||||
for key, value := range self.lendingItemStates {
|
||||
stateExchanges.lendingItemStates[key] = value.deepCopy(self.MarkLendingItemDirty)
|
||||
}
|
||||
for orderId := range self.lendingItemStatesDirty {
|
||||
stateExchanges.lendingItemStatesDirty[orderId] = struct{}{}
|
||||
}
|
||||
for key, value := range self.lendingTradeStates {
|
||||
stateExchanges.lendingTradeStates[key] = value.deepCopy(self.MarkLendingTradeDirty)
|
||||
}
|
||||
for orderId := range self.lendingTradeStatesDirty {
|
||||
stateExchanges.lendingTradeStatesDirty[orderId] = struct{}{}
|
||||
}
|
||||
for time, orderList := range self.liquidationTimeStates {
|
||||
stateExchanges.liquidationTimeStates[time] = orderList.deepCopy(db, self.MarkLiquidationTimeDirty)
|
||||
}
|
||||
for time := range self.liquidationTimestatesDirty {
|
||||
stateExchanges.liquidationTimestatesDirty[time] = struct{}{}
|
||||
}
|
||||
return stateExchanges
|
||||
}
|
||||
|
||||
// Returns the address of the contract/tradeId
|
||||
func (self *lendingExchangeState) Hash() common.Hash {
|
||||
return self.lendingBook
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) setNonce(nonce uint64) {
|
||||
self.data.Nonce = nonce
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) Nonce() uint64 {
|
||||
return self.data.Nonce
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) setTradeNonce(nonce uint64) {
|
||||
self.data.TradeNonce = nonce
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
func (self *lendingExchangeState) TradeNonce() uint64 {
|
||||
return self.data.TradeNonce
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) removeInvestingOrderList(db Database, stateOrderList *itemListState) {
|
||||
self.setError(self.investingTrie.TryDelete(stateOrderList.key[:]))
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) removeBorrowingOrderList(db Database, stateOrderList *itemListState) {
|
||||
self.setError(self.borrowingTrie.TryDelete(stateOrderList.key[:]))
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) createInvestingOrderList(db Database, price common.Hash) (newobj *itemListState) {
|
||||
newobj = newItemListState(self.lendingBook, price, itemList{Volume: Zero}, self.MarkInvestingDirty)
|
||||
self.investingStates[price] = newobj
|
||||
self.investingStatesDirty[price] = struct{}{}
|
||||
data, err := rlp.EncodeToBytes(newobj)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("can't encode order list object at %x: %v", price[:], err))
|
||||
}
|
||||
self.setError(self.getInvestingTrie(db).TryUpdate(price[:], data))
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
return newobj
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) MarkBorrowingDirty(price common.Hash) {
|
||||
self.borrowingStatesDirty[price] = struct{}{}
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) MarkInvestingDirty(price common.Hash) {
|
||||
self.investingStatesDirty[price] = struct{}{}
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) MarkLendingItemDirty(lending common.Hash) {
|
||||
self.lendingItemStatesDirty[lending] = struct{}{}
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) MarkLendingTradeDirty(tradeId common.Hash) {
|
||||
self.lendingTradeStatesDirty[tradeId] = struct{}{}
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) MarkLiquidationTimeDirty(orderId common.Hash) {
|
||||
self.liquidationTimestatesDirty[orderId] = struct{}{}
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) createBorrowingOrderList(db Database, price common.Hash) (newobj *itemListState) {
|
||||
newobj = newItemListState(self.lendingBook, price, itemList{Volume: Zero}, self.MarkBorrowingDirty)
|
||||
self.borrowingStates[price] = newobj
|
||||
self.borrowingStatesDirty[price] = struct{}{}
|
||||
data, err := rlp.EncodeToBytes(newobj)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("can't encode order list object at %x: %v", price[:], err))
|
||||
}
|
||||
self.setError(self.getBorrowingTrie(db).TryUpdate(price[:], data))
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.Hash())
|
||||
self.onDirty = nil
|
||||
}
|
||||
return newobj
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) createLendingItem(db Database, orderId common.Hash, order LendingItem) (newobj *lendingItemState) {
|
||||
newobj = newLendinItemState(self.lendingBook, orderId, order, self.MarkLendingItemDirty)
|
||||
orderIdHash := common.BigToHash(new(big.Int).SetUint64(order.LendingId))
|
||||
self.lendingItemStates[orderIdHash] = newobj
|
||||
self.lendingItemStatesDirty[orderIdHash] = struct{}{}
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.lendingBook)
|
||||
self.onDirty = nil
|
||||
}
|
||||
return newobj
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) createLiquidationTime(db Database, time common.Hash) (newobj *liquidationTimeState) {
|
||||
newobj = newLiquidationTimeState(time, self.lendingBook, itemList{Volume: Zero}, self.MarkLiquidationTimeDirty)
|
||||
self.liquidationTimeStates[time] = newobj
|
||||
self.liquidationTimestatesDirty[time] = struct{}{}
|
||||
data, err := rlp.EncodeToBytes(newobj)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("can't encode liquidation time at %x: %v", time[:], err))
|
||||
}
|
||||
self.setError(self.getLiquidationTimeTrie(db).TryUpdate(time[:], data))
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.lendingBook)
|
||||
self.onDirty = nil
|
||||
}
|
||||
return newobj
|
||||
}
|
||||
|
||||
func (self *lendingExchangeState) insertLendingTrade(tradeId common.Hash, order LendingTrade) (newobj *lendingTradeState) {
|
||||
newobj = newLendingTradeState(self.lendingBook, tradeId, order, self.MarkLendingTradeDirty)
|
||||
self.lendingTradeStates[tradeId] = newobj
|
||||
self.lendingTradeStatesDirty[tradeId] = struct{}{}
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.lendingBook)
|
||||
self.onDirty = nil
|
||||
}
|
||||
return newobj
|
||||
}
|
||||
67
XDCxlending/lendingstate/state_lendingitem.go
Normal file
67
XDCxlending/lendingstate/state_lendingitem.go
Normal file
|
|
@ -0,0 +1,67 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package lendingstate
|
||||
|
||||
import (
|
||||
"io"
|
||||
"math/big"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
)
|
||||
|
||||
type lendingItemState struct {
|
||||
orderBook common.Hash
|
||||
orderId common.Hash
|
||||
data LendingItem
|
||||
onDirty func(orderId common.Hash) // Callback method to mark a state object newly dirty
|
||||
}
|
||||
|
||||
func (s *lendingItemState) empty() bool {
|
||||
return s.data.Quantity == nil || s.data.Quantity.Cmp(Zero) == 0
|
||||
}
|
||||
|
||||
func newLendinItemState(orderBook common.Hash, orderId common.Hash, data LendingItem, onDirty func(orderId common.Hash)) *lendingItemState {
|
||||
return &lendingItemState{
|
||||
orderBook: orderBook,
|
||||
orderId: orderId,
|
||||
data: data,
|
||||
onDirty: onDirty,
|
||||
}
|
||||
}
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (c *lendingItemState) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, c.data)
|
||||
}
|
||||
|
||||
func (self *lendingItemState) deepCopy(onDirty func(orderId common.Hash)) *lendingItemState {
|
||||
stateOrderList := newLendinItemState(self.orderBook, self.orderId, self.data, onDirty)
|
||||
return stateOrderList
|
||||
}
|
||||
|
||||
func (self *lendingItemState) setVolume(volume *big.Int) {
|
||||
self.data.Quantity = volume
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.orderId)
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *lendingItemState) Quantity() *big.Int {
|
||||
return self.data.Quantity
|
||||
}
|
||||
78
XDCxlending/lendingstate/state_lendingtrade.go
Normal file
78
XDCxlending/lendingstate/state_lendingtrade.go
Normal file
|
|
@ -0,0 +1,78 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package lendingstate
|
||||
|
||||
import (
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
"io"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type lendingTradeState struct {
|
||||
orderBook common.Hash
|
||||
tradeId common.Hash
|
||||
data LendingTrade
|
||||
onDirty func(orderId common.Hash) // Callback method to mark a state object newly dirty
|
||||
}
|
||||
|
||||
func (s *lendingTradeState) empty() bool {
|
||||
return s.data.Amount.Sign() == 0
|
||||
}
|
||||
|
||||
func newLendingTradeState(orderBook common.Hash, tradeId common.Hash, data LendingTrade, onDirty func(orderId common.Hash)) *lendingTradeState {
|
||||
return &lendingTradeState{
|
||||
orderBook: orderBook,
|
||||
tradeId: tradeId,
|
||||
data: data,
|
||||
onDirty: onDirty,
|
||||
}
|
||||
}
|
||||
|
||||
// EncodeRLP implements rlp.Encoder.
|
||||
func (c *lendingTradeState) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, c.data)
|
||||
}
|
||||
|
||||
func (self *lendingTradeState) deepCopy(onDirty func(orderId common.Hash)) *lendingTradeState {
|
||||
stateOrderList := newLendingTradeState(self.orderBook, self.tradeId, self.data, onDirty)
|
||||
return stateOrderList
|
||||
}
|
||||
|
||||
func (self *lendingTradeState) SetCollateralLockedAmount(amount *big.Int) {
|
||||
self.data.CollateralLockedAmount = amount
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.tradeId)
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *lendingTradeState) SetLiquidationPrice(price *big.Int) {
|
||||
self.data.LiquidationPrice = price
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.tradeId)
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *lendingTradeState) SetAmount(amount *big.Int) {
|
||||
self.data.Amount = amount
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.tradeId)
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
212
XDCxlending/lendingstate/state_liquidationtime.go
Normal file
212
XDCxlending/lendingstate/state_liquidationtime.go
Normal file
|
|
@ -0,0 +1,212 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package lendingstate
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
"github.com/XinFinOrg/XDPoSChain/trie"
|
||||
"io"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
type liquidationTimeState struct {
|
||||
time common.Hash
|
||||
lendingBook common.Hash
|
||||
data itemList
|
||||
|
||||
// DB error.
|
||||
// State objects are used by the consensus core and VM which are
|
||||
// unable to deal with database-level errors. Any error that occurs
|
||||
// during a database read is memoized here and will eventually be returned
|
||||
// by TradingStateDB.Commit.
|
||||
dbErr error
|
||||
|
||||
// Write caches.
|
||||
trie Trie // storage trie, which becomes non-nil on first access
|
||||
|
||||
cachedStorage map[common.Hash]common.Hash
|
||||
dirtyStorage map[common.Hash]common.Hash
|
||||
|
||||
onDirty func(time common.Hash) // Callback method to mark a state object newly dirty
|
||||
}
|
||||
|
||||
func (s *liquidationTimeState) empty() bool {
|
||||
return s.data.Volume == nil || s.data.Volume.Sign() == 0
|
||||
}
|
||||
|
||||
func newLiquidationTimeState(time common.Hash, lendingBook common.Hash, data itemList, onDirty func(time common.Hash)) *liquidationTimeState {
|
||||
return &liquidationTimeState{
|
||||
lendingBook: lendingBook,
|
||||
time: time,
|
||||
data: data,
|
||||
cachedStorage: make(map[common.Hash]common.Hash),
|
||||
dirtyStorage: make(map[common.Hash]common.Hash),
|
||||
onDirty: onDirty,
|
||||
}
|
||||
}
|
||||
|
||||
func (self *liquidationTimeState) EncodeRLP(w io.Writer) error {
|
||||
return rlp.Encode(w, self.data)
|
||||
}
|
||||
|
||||
func (self *liquidationTimeState) setError(err error) {
|
||||
if self.dbErr == nil {
|
||||
self.dbErr = err
|
||||
}
|
||||
}
|
||||
|
||||
func (self *liquidationTimeState) getTrie(db Database) Trie {
|
||||
if self.trie == nil {
|
||||
var err error
|
||||
self.trie, err = db.OpenStorageTrie(self.lendingBook, self.data.Root)
|
||||
if err != nil {
|
||||
self.trie, _ = db.OpenStorageTrie(self.time, EmptyHash)
|
||||
self.setError(fmt.Errorf("can't create storage trie: %v", err))
|
||||
}
|
||||
}
|
||||
return self.trie
|
||||
}
|
||||
|
||||
func (self *liquidationTimeState) Exist(db Database, tradeId common.Hash) bool {
|
||||
amount, exists := self.cachedStorage[tradeId]
|
||||
if exists {
|
||||
return true
|
||||
}
|
||||
// Load from DB in case it is missing.
|
||||
enc, err := self.getTrie(db).TryGet(tradeId[:])
|
||||
if err != nil {
|
||||
self.setError(err)
|
||||
return false
|
||||
}
|
||||
if len(enc) > 0 {
|
||||
_, content, _, err := rlp.Split(enc)
|
||||
if err != nil {
|
||||
self.setError(err)
|
||||
}
|
||||
amount.SetBytes(content)
|
||||
}
|
||||
if (amount != common.Hash{}) {
|
||||
self.cachedStorage[tradeId] = amount
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func (self *liquidationTimeState) getAllTradeIds(db Database) []common.Hash {
|
||||
tradeIds := []common.Hash{}
|
||||
lendingBookTrie := self.getTrie(db)
|
||||
if lendingBookTrie == nil {
|
||||
return tradeIds
|
||||
}
|
||||
for id, value := range self.cachedStorage {
|
||||
if !common.EmptyHash(value) {
|
||||
tradeIds = append(tradeIds, id)
|
||||
}
|
||||
}
|
||||
orderListIt := trie.NewIterator(lendingBookTrie.NodeIterator(nil))
|
||||
for orderListIt.Next() {
|
||||
id := common.BytesToHash(orderListIt.Key)
|
||||
if _, exist := self.cachedStorage[id]; exist {
|
||||
continue
|
||||
}
|
||||
tradeIds = append(tradeIds, id)
|
||||
}
|
||||
return tradeIds
|
||||
}
|
||||
|
||||
func (self *liquidationTimeState) insertTradeId(db Database, tradeId common.Hash) {
|
||||
self.setTradeId(tradeId, tradeId)
|
||||
self.setError(self.getTrie(db).TryUpdate(tradeId[:], tradeId[:]))
|
||||
}
|
||||
|
||||
func (self *liquidationTimeState) removeTradeId(db Database, tradeId common.Hash) {
|
||||
tr := self.getTrie(db)
|
||||
self.setError(tr.TryDelete(tradeId[:]))
|
||||
self.setTradeId(tradeId, EmptyHash)
|
||||
}
|
||||
|
||||
func (self *liquidationTimeState) setTradeId(tradeId common.Hash, value common.Hash) {
|
||||
self.cachedStorage[tradeId] = value
|
||||
self.dirtyStorage[tradeId] = value
|
||||
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.lendingBook)
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *liquidationTimeState) updateTrie(db Database) Trie {
|
||||
tr := self.getTrie(db)
|
||||
for key, value := range self.dirtyStorage {
|
||||
delete(self.dirtyStorage, key)
|
||||
if value == EmptyHash {
|
||||
self.setError(tr.TryDelete(key[:]))
|
||||
continue
|
||||
}
|
||||
v, _ := rlp.EncodeToBytes(bytes.TrimLeft(value[:], "\x00"))
|
||||
self.setError(tr.TryUpdate(key[:], v))
|
||||
}
|
||||
return tr
|
||||
}
|
||||
|
||||
func (self *liquidationTimeState) updateRoot(db Database) error {
|
||||
self.updateTrie(db)
|
||||
if self.dbErr != nil {
|
||||
return self.dbErr
|
||||
}
|
||||
root, err := self.trie.Commit(nil)
|
||||
if err == nil {
|
||||
self.data.Root = root
|
||||
}
|
||||
return err
|
||||
}
|
||||
|
||||
func (self *liquidationTimeState) deepCopy(db *LendingStateDB, onDirty func(time common.Hash)) *liquidationTimeState {
|
||||
stateLendingBook := newLiquidationTimeState(self.lendingBook, self.time, self.data, onDirty)
|
||||
if self.trie != nil {
|
||||
stateLendingBook.trie = db.db.CopyTrie(self.trie)
|
||||
}
|
||||
for key, value := range self.dirtyStorage {
|
||||
stateLendingBook.dirtyStorage[key] = value
|
||||
}
|
||||
for key, value := range self.cachedStorage {
|
||||
stateLendingBook.cachedStorage[key] = value
|
||||
}
|
||||
return stateLendingBook
|
||||
}
|
||||
|
||||
func (c *liquidationTimeState) AddVolume(amount *big.Int) {
|
||||
c.setVolume(new(big.Int).Add(c.data.Volume, amount))
|
||||
}
|
||||
|
||||
func (c *liquidationTimeState) subVolume(amount *big.Int) {
|
||||
c.setVolume(new(big.Int).Sub(c.data.Volume, amount))
|
||||
}
|
||||
|
||||
func (self *liquidationTimeState) setVolume(volume *big.Int) {
|
||||
self.data.Volume = volume
|
||||
if self.onDirty != nil {
|
||||
self.onDirty(self.lendingBook)
|
||||
self.onDirty = nil
|
||||
}
|
||||
}
|
||||
|
||||
func (self *liquidationTimeState) Volume() *big.Int {
|
||||
return self.data.Volume
|
||||
}
|
||||
668
XDCxlending/lendingstate/statedb.go
Normal file
668
XDCxlending/lendingstate/statedb.go
Normal file
|
|
@ -0,0 +1,668 @@
|
|||
// Copyright 2014 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
// Package state provides a caching layer atop the Ethereum state trie.
|
||||
package lendingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"math/big"
|
||||
"sort"
|
||||
"sync"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/rlp"
|
||||
)
|
||||
|
||||
type revision struct {
|
||||
id int
|
||||
journalIndex int
|
||||
}
|
||||
|
||||
type LendingStateDB struct {
|
||||
db Database
|
||||
trie Trie
|
||||
|
||||
// This map holds 'live' objects, which will get modified while processing a state transition.
|
||||
lendingExchangeStates map[common.Hash]*lendingExchangeState
|
||||
lendingExchangeStatesDirty map[common.Hash]struct{}
|
||||
|
||||
// DB error.
|
||||
// State objects are used by the consensus core and VM which are
|
||||
// unable to deal with database-level errors. Any error that occurs
|
||||
// during a database read is memoized here and will eventually be returned
|
||||
// by LendingStateDB.Commit.
|
||||
dbErr error
|
||||
|
||||
// Journal of state modifications. This is the backbone of
|
||||
// Snapshot and RevertToSnapshot.
|
||||
journal journal
|
||||
validRevisions []revision
|
||||
nextRevisionId int
|
||||
|
||||
lock sync.Mutex
|
||||
}
|
||||
|
||||
// Create a new state from a given trie.
|
||||
func New(root common.Hash, db Database) (*LendingStateDB, error) {
|
||||
tr, err := db.OpenTrie(root)
|
||||
if err != nil {
|
||||
log.Error("Error when init new lending state trie ", "root", root.Hex(), "err", err)
|
||||
return nil, err
|
||||
}
|
||||
return &LendingStateDB{
|
||||
db: db,
|
||||
trie: tr,
|
||||
lendingExchangeStates: make(map[common.Hash]*lendingExchangeState),
|
||||
lendingExchangeStatesDirty: make(map[common.Hash]struct{}),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// setError remembers the first non-nil error it is called with.
|
||||
func (self *LendingStateDB) setError(err error) {
|
||||
if self.dbErr == nil {
|
||||
self.dbErr = err
|
||||
}
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) Error() error {
|
||||
return self.dbErr
|
||||
}
|
||||
|
||||
// Exist reports whether the given tradeId address exists in the state.
|
||||
// Notably this also returns true for suicided lenddinges.
|
||||
func (self *LendingStateDB) Exist(addr common.Hash) bool {
|
||||
return self.getLendingExchange(addr) != nil
|
||||
}
|
||||
|
||||
// Empty returns whether the state object is either non-existent
|
||||
// or empty according to the EIP161 specification (balance = nonce = code = 0)
|
||||
func (self *LendingStateDB) Empty(addr common.Hash) bool {
|
||||
so := self.getLendingExchange(addr)
|
||||
return so == nil || so.empty()
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) GetNonce(addr common.Hash) uint64 {
|
||||
stateObject := self.getLendingExchange(addr)
|
||||
if stateObject != nil {
|
||||
return stateObject.Nonce()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) GetTradeNonce(addr common.Hash) uint64 {
|
||||
stateObject := self.getLendingExchange(addr)
|
||||
if stateObject != nil {
|
||||
return stateObject.TradeNonce()
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// Database retrieves the low level database supporting the lower level trie ops.
|
||||
func (self *LendingStateDB) Database() Database {
|
||||
return self.db
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) SetNonce(addr common.Hash, nonce uint64) {
|
||||
stateObject := self.GetOrNewLendingExchangeObject(addr)
|
||||
if stateObject != nil {
|
||||
self.journal = append(self.journal, nonceChange{
|
||||
hash: addr,
|
||||
prev: stateObject.Nonce(),
|
||||
})
|
||||
stateObject.setNonce(nonce)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) SetTradeNonce(addr common.Hash, nonce uint64) {
|
||||
stateObject := self.GetOrNewLendingExchangeObject(addr)
|
||||
if stateObject != nil {
|
||||
self.journal = append(self.journal, tradeNonceChange{
|
||||
hash: addr,
|
||||
prev: stateObject.TradeNonce(),
|
||||
})
|
||||
stateObject.setTradeNonce(nonce)
|
||||
}
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) InsertLendingItem(orderBook common.Hash, orderId common.Hash, order LendingItem) {
|
||||
interestHash := common.BigToHash(order.Interest)
|
||||
stateExchange := self.getLendingExchange(orderBook)
|
||||
if stateExchange == nil {
|
||||
stateExchange = self.createLendingExchangeObject(orderBook)
|
||||
}
|
||||
var stateOrderList *itemListState
|
||||
switch order.Side {
|
||||
case Investing:
|
||||
stateOrderList = stateExchange.getInvestingOrderList(self.db, interestHash)
|
||||
if stateOrderList == nil {
|
||||
stateOrderList = stateExchange.createInvestingOrderList(self.db, interestHash)
|
||||
}
|
||||
case Borrowing:
|
||||
stateOrderList = stateExchange.getBorrowingOrderList(self.db, interestHash)
|
||||
if stateOrderList == nil {
|
||||
stateOrderList = stateExchange.createBorrowingOrderList(self.db, interestHash)
|
||||
}
|
||||
default:
|
||||
return
|
||||
}
|
||||
self.journal = append(self.journal, insertOrder{
|
||||
orderBook: orderBook,
|
||||
orderId: orderId,
|
||||
order: &order,
|
||||
})
|
||||
stateExchange.createLendingItem(self.db, orderId, order)
|
||||
stateOrderList.insertLendingItem(self.db, orderId, common.BigToHash(order.Quantity))
|
||||
stateOrderList.AddVolume(order.Quantity)
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) InsertTradingItem(orderBook common.Hash, tradeId uint64, order LendingTrade) {
|
||||
tradeIdHash := common.Uint64ToHash(tradeId)
|
||||
stateExchange := self.getLendingExchange(orderBook)
|
||||
if stateExchange == nil {
|
||||
stateExchange = self.createLendingExchangeObject(orderBook)
|
||||
}
|
||||
prvTrade := self.GetLendingTrade(orderBook, tradeIdHash)
|
||||
self.journal = append(self.journal, insertTrading{
|
||||
orderBook: orderBook,
|
||||
tradeId: tradeId,
|
||||
prvTrade: &prvTrade,
|
||||
})
|
||||
stateExchange.insertLendingTrade(tradeIdHash, order)
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) UpdateLiquidationPrice(orderBook common.Hash, tradeId uint64, price *big.Int) {
|
||||
tradeIdHash := common.Uint64ToHash(tradeId)
|
||||
stateExchange := self.getLendingExchange(orderBook)
|
||||
if stateExchange == nil {
|
||||
stateExchange = self.createLendingExchangeObject(orderBook)
|
||||
}
|
||||
stateLendingTrade := stateExchange.getLendingTrade(self.db, tradeIdHash)
|
||||
self.journal = append(self.journal, liquidationPriceChange{
|
||||
orderBook: orderBook,
|
||||
tradeId: tradeIdHash,
|
||||
prev: stateLendingTrade.data.LiquidationPrice,
|
||||
})
|
||||
stateLendingTrade.SetLiquidationPrice(price)
|
||||
}
|
||||
func (self *LendingStateDB) UpdateCollateralLockedAmount(orderBook common.Hash, tradeId uint64, amount *big.Int) {
|
||||
tradeIdHash := common.Uint64ToHash(tradeId)
|
||||
stateExchange := self.getLendingExchange(orderBook)
|
||||
if stateExchange == nil {
|
||||
stateExchange = self.createLendingExchangeObject(orderBook)
|
||||
}
|
||||
stateLendingTrade := stateExchange.getLendingTrade(self.db, tradeIdHash)
|
||||
self.journal = append(self.journal, collateralLockedAmount{
|
||||
orderBook: orderBook,
|
||||
tradeId: tradeIdHash,
|
||||
prev: stateLendingTrade.data.CollateralLockedAmount,
|
||||
})
|
||||
stateLendingTrade.SetCollateralLockedAmount(amount)
|
||||
}
|
||||
func (self *LendingStateDB) GetLendingOrder(orderBook common.Hash, orderId common.Hash) LendingItem {
|
||||
stateObject := self.GetOrNewLendingExchangeObject(orderBook)
|
||||
if stateObject == nil {
|
||||
return EmptyLendingOrder
|
||||
}
|
||||
stateOrderItem := stateObject.getLendingItem(self.db, orderId)
|
||||
if stateOrderItem == nil {
|
||||
return EmptyLendingOrder
|
||||
}
|
||||
return stateOrderItem.data
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) GetLendingTrade(orderBook common.Hash, tradeId common.Hash) LendingTrade {
|
||||
stateObject := self.GetOrNewLendingExchangeObject(orderBook)
|
||||
if stateObject == nil {
|
||||
return EmptyLendingTrade
|
||||
}
|
||||
stateOrderItem := stateObject.getLendingTrade(self.db, tradeId)
|
||||
if stateOrderItem == nil || stateOrderItem.empty() {
|
||||
return EmptyLendingTrade
|
||||
}
|
||||
return stateOrderItem.data
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) SubAmountLendingItem(orderBook common.Hash, orderId common.Hash, price *big.Int, amount *big.Int, side string) error {
|
||||
priceHash := common.BigToHash(price)
|
||||
lendingExchange := self.GetOrNewLendingExchangeObject(orderBook)
|
||||
if lendingExchange == nil {
|
||||
return fmt.Errorf("Order book not found : %s ", orderBook.Hex())
|
||||
}
|
||||
var orderList *itemListState
|
||||
switch side {
|
||||
case Investing:
|
||||
orderList = lendingExchange.getInvestingOrderList(self.db, priceHash)
|
||||
case Borrowing:
|
||||
orderList = lendingExchange.getBorrowingOrderList(self.db, priceHash)
|
||||
default:
|
||||
return fmt.Errorf("Order type not found : %s ", side)
|
||||
}
|
||||
if orderList == nil || orderList.empty() {
|
||||
return fmt.Errorf("Order list empty order book : %s , order id : %s , key : %s ", orderBook, orderId.Hex(), priceHash.Hex())
|
||||
}
|
||||
lendingItem := lendingExchange.getLendingItem(self.db, orderId)
|
||||
if lendingItem == nil || lendingItem.empty() {
|
||||
return fmt.Errorf("Order item empty order book : %s , order id : %s , key : %s ", orderBook, orderId.Hex(), priceHash.Hex())
|
||||
}
|
||||
currentAmount := new(big.Int).SetBytes(orderList.GetOrderAmount(self.db, orderId).Bytes()[:])
|
||||
if currentAmount.Cmp(amount) < 0 {
|
||||
return fmt.Errorf("Order amount not enough : %s , have : %d , want : %d ", orderId.Hex(), currentAmount, amount)
|
||||
}
|
||||
self.journal = append(self.journal, subAmountOrder{
|
||||
orderBook: orderBook,
|
||||
orderId: orderId,
|
||||
order: self.GetLendingOrder(orderBook, orderId),
|
||||
amount: amount,
|
||||
})
|
||||
newAmount := new(big.Int).Sub(currentAmount, amount)
|
||||
lendingItem.setVolume(newAmount)
|
||||
log.Debug("SubAmountOrderItem", "tradeId", orderId.Hex(), "side", side, "key", price.Uint64(), "amount", amount.Uint64(), "new amount", newAmount.Uint64())
|
||||
orderList.subVolume(amount)
|
||||
if newAmount.Sign() == 0 {
|
||||
orderList.removeOrderItem(self.db, orderId)
|
||||
} else {
|
||||
orderList.setOrderItem(orderId, common.BigToHash(newAmount))
|
||||
}
|
||||
if orderList.empty() {
|
||||
switch side {
|
||||
case Investing:
|
||||
lendingExchange.removeInvestingOrderList(self.db, orderList)
|
||||
case Borrowing:
|
||||
lendingExchange.removeBorrowingOrderList(self.db, orderList)
|
||||
default:
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) CancelLendingOrder(orderBook common.Hash, order *LendingItem) error {
|
||||
interestHash := common.BigToHash(order.Interest)
|
||||
orderIdHash := common.BigToHash(new(big.Int).SetUint64(order.LendingId))
|
||||
stateObject := self.GetOrNewLendingExchangeObject(orderBook)
|
||||
if stateObject == nil {
|
||||
return fmt.Errorf("Order book not found : %s ", orderBook.Hex())
|
||||
}
|
||||
lendingItem := stateObject.getLendingItem(self.db, orderIdHash)
|
||||
var orderList *itemListState
|
||||
switch lendingItem.data.Side {
|
||||
case Investing:
|
||||
orderList = stateObject.getInvestingOrderList(self.db, interestHash)
|
||||
case Borrowing:
|
||||
orderList = stateObject.getBorrowingOrderList(self.db, interestHash)
|
||||
default:
|
||||
return fmt.Errorf("Order side not found : %s ", order.Side)
|
||||
}
|
||||
if orderList == nil || orderList.empty() {
|
||||
return fmt.Errorf("Order list empty order book : %s , order id : %s , key : %s ", orderBook, orderIdHash.Hex(), interestHash.Hex())
|
||||
}
|
||||
if lendingItem == nil || lendingItem.empty() {
|
||||
return fmt.Errorf("Order item empty order book : %s , order id : %s , key : %s ", orderBook, orderIdHash.Hex(), interestHash.Hex())
|
||||
}
|
||||
if lendingItem.data.UserAddress != order.UserAddress {
|
||||
return fmt.Errorf("Error Order User Address mismatch when cancel order book : %s , order id : %s , got : %s , expect : %s ", orderBook, orderIdHash.Hex(), lendingItem.data.UserAddress.Hex(), order.UserAddress.Hex())
|
||||
}
|
||||
self.journal = append(self.journal, cancelOrder{
|
||||
orderBook: orderBook,
|
||||
orderId: orderIdHash,
|
||||
order: self.GetLendingOrder(orderBook, orderIdHash),
|
||||
})
|
||||
lendingItem.setVolume(big.NewInt(0))
|
||||
currentAmount := new(big.Int).SetBytes(orderList.GetOrderAmount(self.db, orderIdHash).Bytes()[:])
|
||||
orderList.subVolume(currentAmount)
|
||||
orderList.removeOrderItem(self.db, orderIdHash)
|
||||
if orderList.empty() {
|
||||
switch order.Side {
|
||||
case Investing:
|
||||
stateObject.removeInvestingOrderList(self.db, orderList)
|
||||
case Borrowing:
|
||||
stateObject.removeBorrowingOrderList(self.db, orderList)
|
||||
default:
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) GetBestInvestingRate(orderBook common.Hash) (*big.Int, *big.Int) {
|
||||
stateObject := self.getLendingExchange(orderBook)
|
||||
if stateObject != nil {
|
||||
investingHash := stateObject.getBestInvestingInterest(self.db)
|
||||
if common.EmptyHash(investingHash) {
|
||||
return Zero, Zero
|
||||
}
|
||||
orderList := stateObject.getInvestingOrderList(self.db, investingHash)
|
||||
if orderList == nil {
|
||||
log.Error("order list investing not found", "key", investingHash.Hex())
|
||||
return Zero, Zero
|
||||
}
|
||||
return new(big.Int).SetBytes(investingHash.Bytes()), orderList.Volume()
|
||||
}
|
||||
return Zero, Zero
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) GetBestBorrowRate(orderBook common.Hash) (*big.Int, *big.Int) {
|
||||
stateObject := self.getLendingExchange(orderBook)
|
||||
if stateObject != nil {
|
||||
priceHash := stateObject.getBestBorrowingInterest(self.db)
|
||||
if common.EmptyHash(priceHash) {
|
||||
return Zero, Zero
|
||||
}
|
||||
orderList := stateObject.getBorrowingOrderList(self.db, priceHash)
|
||||
if orderList == nil {
|
||||
log.Error("order list ask not found", "key", priceHash.Hex())
|
||||
return Zero, Zero
|
||||
}
|
||||
return new(big.Int).SetBytes(priceHash.Bytes()), orderList.Volume()
|
||||
}
|
||||
return Zero, Zero
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) GetBestLendingIdAndAmount(orderBook common.Hash, price *big.Int, side string) (common.Hash, *big.Int, error) {
|
||||
stateObject := self.GetOrNewLendingExchangeObject(orderBook)
|
||||
if stateObject != nil {
|
||||
var stateOrderList *itemListState
|
||||
switch side {
|
||||
case Investing:
|
||||
stateOrderList = stateObject.getInvestingOrderList(self.db, common.BigToHash(price))
|
||||
case Borrowing:
|
||||
stateOrderList = stateObject.getBorrowingOrderList(self.db, common.BigToHash(price))
|
||||
default:
|
||||
return EmptyHash, Zero, fmt.Errorf("not found side :%s ", side)
|
||||
}
|
||||
if stateOrderList != nil {
|
||||
key, _, err := stateOrderList.getTrie(self.db).TryGetBestLeftKeyAndValue()
|
||||
if err != nil {
|
||||
return EmptyHash, Zero, err
|
||||
}
|
||||
orderId := common.BytesToHash(key)
|
||||
amount := stateOrderList.GetOrderAmount(self.db, orderId)
|
||||
return orderId, new(big.Int).SetBytes(amount.Bytes()), nil
|
||||
}
|
||||
return EmptyHash, Zero, fmt.Errorf("not found order list with orderBook : %s , key : %d , side :%s ", orderBook.Hex(), price, side)
|
||||
}
|
||||
return EmptyHash, Zero, fmt.Errorf("not found orderBook : %s ", orderBook.Hex())
|
||||
}
|
||||
|
||||
// updateLendingExchange writes the given object to the trie.
|
||||
func (self *LendingStateDB) updateLendingExchange(stateObject *lendingExchangeState) {
|
||||
addr := stateObject.Hash()
|
||||
data, err := rlp.EncodeToBytes(stateObject)
|
||||
if err != nil {
|
||||
panic(fmt.Errorf("can't encode object at %x: %v", addr[:], err))
|
||||
}
|
||||
self.setError(self.trie.TryUpdate(addr[:], data))
|
||||
}
|
||||
|
||||
// Retrieve a state object given my the address. Returns nil if not found.
|
||||
func (self *LendingStateDB) getLendingExchange(addr common.Hash) (stateObject *lendingExchangeState) {
|
||||
// Prefer 'live' objects.
|
||||
if obj := self.lendingExchangeStates[addr]; obj != nil {
|
||||
return obj
|
||||
}
|
||||
// Load the object from the database.
|
||||
enc, err := self.trie.TryGet(addr[:])
|
||||
if len(enc) == 0 {
|
||||
self.setError(err)
|
||||
return nil
|
||||
}
|
||||
var data lendingObject
|
||||
if err := rlp.DecodeBytes(enc, &data); err != nil {
|
||||
log.Error("Failed to decode state object", "addr", addr, "err", err)
|
||||
return nil
|
||||
}
|
||||
// Insert into the live set.
|
||||
obj := newStateExchanges(self, addr, data, self.MarkLendingExchangeObjectDirty)
|
||||
self.lendingExchangeStates[addr] = obj
|
||||
return obj
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) setLendingExchangeObject(object *lendingExchangeState) {
|
||||
self.lendingExchangeStates[object.Hash()] = object
|
||||
self.lendingExchangeStatesDirty[object.Hash()] = struct{}{}
|
||||
}
|
||||
|
||||
// Retrieve a state object or create a new state object if nil.
|
||||
func (self *LendingStateDB) GetOrNewLendingExchangeObject(addr common.Hash) *lendingExchangeState {
|
||||
stateExchangeObject := self.getLendingExchange(addr)
|
||||
if stateExchangeObject == nil {
|
||||
stateExchangeObject = self.createLendingExchangeObject(addr)
|
||||
}
|
||||
return stateExchangeObject
|
||||
}
|
||||
|
||||
// MarkStateLendObjectDirty adds the specified object to the dirty map to avoid costly
|
||||
// state object cache iteration to find a handful of modified ones.
|
||||
func (self *LendingStateDB) MarkLendingExchangeObjectDirty(addr common.Hash) {
|
||||
self.lendingExchangeStatesDirty[addr] = struct{}{}
|
||||
}
|
||||
|
||||
// createStateOrderListObject creates a new state object. If there is an existing tradeId with
|
||||
// the given address, it is overwritten and returned as the second return value.
|
||||
func (self *LendingStateDB) createLendingExchangeObject(hash common.Hash) (newobj *lendingExchangeState) {
|
||||
newobj = newStateExchanges(self, hash, lendingObject{}, self.MarkLendingExchangeObjectDirty)
|
||||
newobj.setNonce(0) // sets the object to dirty
|
||||
self.setLendingExchangeObject(newobj)
|
||||
return newobj
|
||||
}
|
||||
|
||||
// Copy creates a deep, independent copy of the state.
|
||||
// Snapshots of the copied state cannot be applied to the copy.
|
||||
func (self *LendingStateDB) Copy() *LendingStateDB {
|
||||
self.lock.Lock()
|
||||
defer self.lock.Unlock()
|
||||
|
||||
// Copy all the basic fields, initialize the memory ones
|
||||
state := &LendingStateDB{
|
||||
db: self.db,
|
||||
trie: self.db.CopyTrie(self.trie),
|
||||
lendingExchangeStates: make(map[common.Hash]*lendingExchangeState, len(self.lendingExchangeStatesDirty)),
|
||||
lendingExchangeStatesDirty: make(map[common.Hash]struct{}, len(self.lendingExchangeStatesDirty)),
|
||||
}
|
||||
// Copy the dirty states, logs, and preimages
|
||||
for addr := range self.lendingExchangeStatesDirty {
|
||||
state.lendingExchangeStatesDirty[addr] = struct{}{}
|
||||
}
|
||||
for addr, exchangeObject := range self.lendingExchangeStates {
|
||||
state.lendingExchangeStates[addr] = exchangeObject.deepCopy(state, state.MarkLendingExchangeObjectDirty)
|
||||
}
|
||||
|
||||
return state
|
||||
}
|
||||
|
||||
func (s *LendingStateDB) clearJournalAndRefund() {
|
||||
s.journal = nil
|
||||
s.validRevisions = s.validRevisions[:0]
|
||||
}
|
||||
|
||||
// Snapshot returns an identifier for the current revision of the state.
|
||||
func (self *LendingStateDB) Snapshot() int {
|
||||
id := self.nextRevisionId
|
||||
self.nextRevisionId++
|
||||
self.validRevisions = append(self.validRevisions, revision{id, len(self.journal)})
|
||||
return id
|
||||
}
|
||||
|
||||
// RevertToSnapshot reverts all state changes made since the given revision.
|
||||
func (self *LendingStateDB) RevertToSnapshot(revid int) {
|
||||
// Find the snapshot in the stack of valid snapshots.
|
||||
idx := sort.Search(len(self.validRevisions), func(i int) bool {
|
||||
return self.validRevisions[i].id >= revid
|
||||
})
|
||||
if idx == len(self.validRevisions) || self.validRevisions[idx].id != revid {
|
||||
panic(fmt.Errorf("revision id %v cannot be reverted", revid))
|
||||
}
|
||||
snapshot := self.validRevisions[idx].journalIndex
|
||||
|
||||
// Replay the journal to undo changes.
|
||||
for i := len(self.journal) - 1; i >= snapshot; i-- {
|
||||
self.journal[i].undo(self)
|
||||
}
|
||||
self.journal = self.journal[:snapshot]
|
||||
|
||||
// Remove invalidated snapshots from the stack.
|
||||
self.validRevisions = self.validRevisions[:idx]
|
||||
}
|
||||
|
||||
// Finalise finalises the state by removing the self destructed objects
|
||||
// and clears the journal as well as the refunds.
|
||||
func (s *LendingStateDB) Finalise() {
|
||||
// Commit objects to the trie.
|
||||
for addr, stateObject := range s.lendingExchangeStates {
|
||||
if _, isDirty := s.lendingExchangeStatesDirty[addr]; isDirty {
|
||||
// Write any storage changes in the state object to its storage trie.
|
||||
stateObject.updateInvestingRoot(s.db)
|
||||
stateObject.updateBorrowingRoot(s.db)
|
||||
stateObject.updateOrderRoot(s.db)
|
||||
stateObject.updateLendingTradeRoot(s.db)
|
||||
stateObject.updateLiquidationTimeRoot(s.db)
|
||||
// Update the object in the main tradeId trie.
|
||||
s.updateLendingExchange(stateObject)
|
||||
//delete(s.investingStatesDirty, addr)
|
||||
}
|
||||
}
|
||||
s.clearJournalAndRefund()
|
||||
}
|
||||
|
||||
// IntermediateRoot computes the current root orderBook of the state trie.
|
||||
// It is called in between transactions to get the root orderBook that
|
||||
// goes into transaction receipts.
|
||||
func (s *LendingStateDB) IntermediateRoot() common.Hash {
|
||||
s.Finalise()
|
||||
return s.trie.Hash()
|
||||
}
|
||||
|
||||
// Commit writes the state to the underlying in-memory trie database.
|
||||
func (s *LendingStateDB) Commit() (root common.Hash, err error) {
|
||||
defer s.clearJournalAndRefund()
|
||||
// Commit objects to the trie.
|
||||
for addr, stateObject := range s.lendingExchangeStates {
|
||||
if _, isDirty := s.lendingExchangeStatesDirty[addr]; isDirty {
|
||||
// Write any storage changes in the state object to its storage trie.
|
||||
if err := stateObject.CommitInvestingTrie(s.db); err != nil {
|
||||
return EmptyHash, err
|
||||
}
|
||||
if err := stateObject.CommitBorrowingTrie(s.db); err != nil {
|
||||
return EmptyHash, err
|
||||
}
|
||||
if err := stateObject.CommitLendingItemTrie(s.db); err != nil {
|
||||
return EmptyHash, err
|
||||
}
|
||||
if err := stateObject.CommitLendingTradeTrie(s.db); err != nil {
|
||||
return EmptyHash, err
|
||||
}
|
||||
if err := stateObject.CommitLiquidationTimeTrie(s.db); err != nil {
|
||||
return EmptyHash, err
|
||||
}
|
||||
// Update the object in the main tradeId trie.
|
||||
s.updateLendingExchange(stateObject)
|
||||
delete(s.lendingExchangeStatesDirty, addr)
|
||||
}
|
||||
}
|
||||
// Write trie changes.
|
||||
root, err = s.trie.Commit(func(leaf []byte, parent common.Hash) error {
|
||||
var exchange lendingObject
|
||||
if err := rlp.DecodeBytes(leaf, &exchange); err != nil {
|
||||
return nil
|
||||
}
|
||||
if exchange.InvestingRoot != EmptyRoot {
|
||||
s.db.TrieDB().Reference(exchange.InvestingRoot, parent)
|
||||
}
|
||||
if exchange.BorrowingRoot != EmptyRoot {
|
||||
s.db.TrieDB().Reference(exchange.BorrowingRoot, parent)
|
||||
}
|
||||
if exchange.LendingItemRoot != EmptyRoot {
|
||||
s.db.TrieDB().Reference(exchange.LendingItemRoot, parent)
|
||||
}
|
||||
if exchange.LendingTradeRoot != EmptyRoot {
|
||||
s.db.TrieDB().Reference(exchange.LendingTradeRoot, parent)
|
||||
}
|
||||
if exchange.LiquidationTimeRoot != EmptyRoot {
|
||||
s.db.TrieDB().Reference(exchange.LiquidationTimeRoot, parent)
|
||||
}
|
||||
return nil
|
||||
})
|
||||
log.Debug("Lending State Trie cache stats after commit", "root", root.Hex())
|
||||
return root, err
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) InsertLiquidationTime(lendingBook common.Hash, time *big.Int, tradeId uint64) {
|
||||
timeHash := common.BigToHash(time)
|
||||
lendingExchangeState := self.getLendingExchange(lendingBook)
|
||||
if lendingExchangeState == nil {
|
||||
lendingExchangeState = self.createLendingExchangeObject(lendingBook)
|
||||
}
|
||||
liquidationTime := lendingExchangeState.getLiquidationTimeOrderList(self.db, timeHash)
|
||||
if liquidationTime == nil {
|
||||
liquidationTime = lendingExchangeState.createLiquidationTime(self.db, timeHash)
|
||||
}
|
||||
liquidationTime.insertTradeId(self.db, common.Uint64ToHash(tradeId))
|
||||
liquidationTime.AddVolume(One)
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) RemoveLiquidationTime(lendingBook common.Hash, tradeId uint64, time uint64) error {
|
||||
timeHash := common.Uint64ToHash(time)
|
||||
tradeIdHash := common.Uint64ToHash(tradeId)
|
||||
lendingExchangeState := self.getLendingExchange(lendingBook)
|
||||
if lendingExchangeState == nil {
|
||||
return fmt.Errorf("lending book not found : %s ", lendingBook.Hex())
|
||||
}
|
||||
liquidationTime := lendingExchangeState.getLiquidationTimeOrderList(self.db, timeHash)
|
||||
if liquidationTime == nil {
|
||||
return fmt.Errorf("liquidation time not found : %s , %d ", lendingBook.Hex(), time)
|
||||
}
|
||||
if !liquidationTime.Exist(self.db, tradeIdHash) {
|
||||
return fmt.Errorf("tradeId not exist : %s , %d , %d ", lendingBook.Hex(), time, tradeId)
|
||||
}
|
||||
liquidationTime.removeTradeId(self.db, tradeIdHash)
|
||||
liquidationTime.subVolume(One)
|
||||
if liquidationTime.Volume().Sign() == 0 {
|
||||
lendingExchangeState.getLiquidationTimeTrie(self.db).TryDelete(timeHash[:])
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) GetLowestLiquidationTime(lendingBook common.Hash, time *big.Int) (*big.Int, []common.Hash) {
|
||||
liquidationData := []common.Hash{}
|
||||
lendingExchangeState := self.getLendingExchange(lendingBook)
|
||||
if lendingExchangeState == nil {
|
||||
return common.Big0, liquidationData
|
||||
}
|
||||
lowestPriceHash, liquidationState := lendingExchangeState.getLowestLiquidationTime(self.db)
|
||||
lowestTime := new(big.Int).SetBytes(lowestPriceHash[:])
|
||||
if liquidationState != nil && lowestTime.Sign() > 0 && lowestTime.Cmp(time) <= 0 {
|
||||
liquidationData = liquidationState.getAllTradeIds(self.db)
|
||||
}
|
||||
return lowestTime, liquidationData
|
||||
}
|
||||
|
||||
func (self *LendingStateDB) CancelLendingTrade(orderBook common.Hash, tradeId uint64) error {
|
||||
tradeIdHash := common.Uint64ToHash(tradeId)
|
||||
stateObject := self.GetOrNewLendingExchangeObject(orderBook)
|
||||
if stateObject == nil {
|
||||
return fmt.Errorf("Order book not found : %s ", orderBook.Hex())
|
||||
}
|
||||
lendingTrade := stateObject.getLendingTrade(self.db, tradeIdHash)
|
||||
if lendingTrade == nil || lendingTrade.empty() {
|
||||
return fmt.Errorf("lending trade empty order book : %s , trade id : %s , trade id hash : %s ", orderBook, tradeIdHash.Hex(), tradeIdHash.Hex())
|
||||
}
|
||||
self.journal = append(self.journal, cancelTrading{
|
||||
orderBook: orderBook,
|
||||
order: self.GetLendingTrade(orderBook, tradeIdHash),
|
||||
})
|
||||
lendingTrade.SetAmount(Zero)
|
||||
return nil
|
||||
}
|
||||
260
XDCxlending/lendingstate/statedb_test.go
Normal file
260
XDCxlending/lendingstate/statedb_test.go
Normal file
|
|
@ -0,0 +1,260 @@
|
|||
// Copyright 2016 The go-ethereum Authors
|
||||
// This file is part of the go-ethereum library.
|
||||
//
|
||||
// The go-ethereum library is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Lesser General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// The go-ethereum library is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Lesser General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Lesser General Public License
|
||||
// along with the go-ethereum library. If not, see <http://www.gnu.org/licenses/>.
|
||||
|
||||
package lendingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"math/big"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestEchangeStates(t *testing.T) {
|
||||
t.SkipNow()
|
||||
orderBook := common.StringToHash("BTC/XDC")
|
||||
numberOrder := 20000
|
||||
orderItems := []LendingItem{}
|
||||
relayers := []common.Hash{}
|
||||
for i := 0; i < numberOrder; i++ {
|
||||
relayers = append(relayers, common.BigToHash(big.NewInt(int64(i))))
|
||||
id := new(big.Int).SetUint64(uint64(i) + 1)
|
||||
orderItems = append(orderItems, LendingItem{LendingId: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Interest: big.NewInt(int64(2*i + 1)), Side: Investing, Signature: &Signature{V: 1, R: common.HexToHash("111111"), S: common.HexToHash("222222222222")}})
|
||||
orderItems = append(orderItems, LendingItem{LendingId: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Interest: big.NewInt(int64(2*i + 1)), Side: Borrowing, Signature: &Signature{V: 1, R: common.HexToHash("3333333333"), S: common.HexToHash("22222222222222222")}})
|
||||
}
|
||||
// Create an empty statedb database
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
stateCache := NewDatabase(db)
|
||||
statedb, _ := New(common.Hash{}, stateCache)
|
||||
|
||||
// Update it with some lenddinges
|
||||
for i := 0; i < numberOrder; i++ {
|
||||
statedb.SetNonce(relayers[i], uint64(1))
|
||||
}
|
||||
mapPriceSell := map[uint64]uint64{}
|
||||
mapPriceBuy := map[uint64]uint64{}
|
||||
|
||||
for i := 0; i < len(orderItems); i++ {
|
||||
amount := orderItems[i].Quantity.Uint64()
|
||||
orderIdHash := common.BigToHash(new(big.Int).SetUint64(orderItems[i].LendingId))
|
||||
statedb.InsertLendingItem(orderBook, orderIdHash, orderItems[i])
|
||||
|
||||
switch orderItems[i].Side {
|
||||
case Investing:
|
||||
old := mapPriceSell[amount]
|
||||
mapPriceSell[amount] = old + amount
|
||||
case Borrowing:
|
||||
old := mapPriceBuy[amount]
|
||||
mapPriceBuy[amount] = old + amount
|
||||
default:
|
||||
}
|
||||
statedb.InsertLiquidationTime(orderBook, big.NewInt(int64(i)), uint64(i))
|
||||
order := LendingTrade{TradeId: uint64(i), Amount: big.NewInt(int64(i))}
|
||||
statedb.InsertTradingItem(orderBook, order.TradeId, order)
|
||||
root := statedb.IntermediateRoot()
|
||||
size, _ := stateCache.TrieDB().Size()
|
||||
fmt.Println(i, "size", size)
|
||||
statedb.Commit()
|
||||
size, _ = stateCache.TrieDB().Size()
|
||||
fmt.Println(i, "size", size)
|
||||
statedb, _ = New(root, stateCache)
|
||||
}
|
||||
statedb.InsertLiquidationTime(orderBook, big.NewInt(1), 1)
|
||||
order := LendingTrade{TradeId: 1, Amount: big.NewInt(2)}
|
||||
statedb.InsertTradingItem(orderBook, 1, order)
|
||||
root := statedb.IntermediateRoot()
|
||||
size, _ := stateCache.TrieDB().Size()
|
||||
fmt.Println("size", size)
|
||||
statedb.Commit()
|
||||
size, _ = stateCache.TrieDB().Size()
|
||||
fmt.Println("size", size)
|
||||
err := stateCache.TrieDB().Commit(root, false)
|
||||
if err != nil {
|
||||
t.Errorf("Error when commit into database: %v", err)
|
||||
}
|
||||
stateCache.TrieDB().Reference(root, common.Hash{})
|
||||
statedb, err = New(root, stateCache)
|
||||
if err != nil {
|
||||
t.Fatalf("Error when get trie in database: %s , err: %v", root.Hex(), err)
|
||||
}
|
||||
_, liquidationData := statedb.GetLowestLiquidationTime(orderBook, big.NewInt(2))
|
||||
if len(liquidationData) == 0 {
|
||||
t.Fatalf("Error when get liquidation data save in database: got : %s ", liquidationData)
|
||||
}
|
||||
for i := 0; i < numberOrder; i++ {
|
||||
nonce := statedb.GetNonce(relayers[i])
|
||||
if nonce != uint64(1) {
|
||||
t.Fatalf("Error when get nonce save in database: got : %d , wanted : %d ", nonce, i)
|
||||
}
|
||||
}
|
||||
|
||||
for i := 0; i < len(orderItems); i++ {
|
||||
amount := statedb.GetLendingOrder(orderBook, common.BigToHash(new(big.Int).SetUint64(orderItems[i].LendingId))).Quantity
|
||||
if orderItems[i].Quantity.Cmp(amount) != 0 {
|
||||
t.Fatalf("Error when get amount save in database: tradeId %d , lendingType %s,got : %d , wanted : %d ", orderItems[i].LendingId, orderItems[i].Side, amount.Uint64(), orderItems[i].Quantity.Uint64())
|
||||
}
|
||||
}
|
||||
fmt.Println(statedb.GetLendingTrade(orderBook, common.BigToHash(big.NewInt(1))).Amount)
|
||||
db.Close()
|
||||
}
|
||||
|
||||
func TestRevertStates(t *testing.T) {
|
||||
orderBook := common.StringToHash("BTC/XDC")
|
||||
numberOrder := 20
|
||||
orderItems := []LendingItem{}
|
||||
relayers := []common.Hash{}
|
||||
for i := 0; i < numberOrder; i++ {
|
||||
relayers = append(relayers, common.BigToHash(big.NewInt(int64(i))))
|
||||
id := new(big.Int).SetUint64(uint64(i) + 1)
|
||||
orderItems = append(orderItems, LendingItem{LendingId: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Interest: big.NewInt(int64(2*i + 1)), Side: Investing, Signature: &Signature{V: 1, R: common.HexToHash("111111"), S: common.HexToHash("222222222222")}})
|
||||
orderItems = append(orderItems, LendingItem{LendingId: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Interest: big.NewInt(int64(2*i + 1)), Side: Borrowing, Signature: &Signature{V: 1, R: common.HexToHash("3333333333"), S: common.HexToHash("22222222222222222")}})
|
||||
}
|
||||
// Create an empty statedb database
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
stateCache := NewDatabase(db)
|
||||
statedb, _ := New(common.Hash{}, stateCache)
|
||||
|
||||
// Update it with some lenddinges
|
||||
for i := 0; i < numberOrder; i++ {
|
||||
statedb.SetNonce(relayers[i], uint64(1))
|
||||
}
|
||||
mapPriceSell := map[uint64]uint64{}
|
||||
mapPriceBuy := map[uint64]uint64{}
|
||||
|
||||
for i := 0; i < len(orderItems); i++ {
|
||||
amount := orderItems[i].Quantity.Uint64()
|
||||
orderIdHash := common.BigToHash(new(big.Int).SetUint64(orderItems[i].LendingId))
|
||||
statedb.InsertLendingItem(orderBook, orderIdHash, orderItems[i])
|
||||
|
||||
switch orderItems[i].Side {
|
||||
case Investing:
|
||||
old := mapPriceSell[amount]
|
||||
mapPriceSell[amount] = old + amount
|
||||
case Borrowing:
|
||||
old := mapPriceBuy[amount]
|
||||
mapPriceBuy[amount] = old + amount
|
||||
default:
|
||||
}
|
||||
|
||||
}
|
||||
statedb.InsertLiquidationTime(orderBook, big.NewInt(1), 1)
|
||||
root := statedb.IntermediateRoot()
|
||||
statedb.Commit()
|
||||
//err := stateCache.TrieDB().Commit(root, false)
|
||||
//if err != nil {
|
||||
// t.Errorf("Error when commit into database: %v", err)
|
||||
//}
|
||||
stateCache.TrieDB().Reference(root, common.Hash{})
|
||||
statedb, err := New(root, stateCache)
|
||||
if err != nil {
|
||||
t.Fatalf("Error when get trie in database: %s , err: %v", root.Hex(), err)
|
||||
}
|
||||
|
||||
orderIdHash := common.BigToHash(new(big.Int).SetUint64(orderItems[0].LendingId))
|
||||
// set nonce
|
||||
wantedNonce := statedb.GetNonce(relayers[1])
|
||||
snap := statedb.Snapshot()
|
||||
statedb.SetNonce(relayers[1], 0)
|
||||
statedb.RevertToSnapshot(snap)
|
||||
gotNonce := statedb.GetNonce(relayers[1])
|
||||
if wantedNonce != gotNonce {
|
||||
t.Fatalf(" err get nonce addr: %v after try revert snap shot , got : %d ,want : %d", relayers[1].Hex(), gotNonce, wantedNonce)
|
||||
}
|
||||
|
||||
// cancel order
|
||||
wantedOrder := statedb.GetLendingOrder(orderBook, orderIdHash)
|
||||
snap = statedb.Snapshot()
|
||||
statedb.CancelLendingOrder(orderBook, &wantedOrder)
|
||||
statedb.RevertToSnapshot(snap)
|
||||
gotOrder := statedb.GetLendingOrder(orderBook, orderIdHash)
|
||||
if gotOrder.Quantity.Cmp(wantedOrder.Quantity) != 0 {
|
||||
t.Fatalf(" err cancel order info : %v after try revert snap shot , got : %v ,want : %v", orderIdHash.Hex(), gotOrder, wantedOrder)
|
||||
}
|
||||
|
||||
// insert order
|
||||
i := 2*numberOrder + 1
|
||||
id := new(big.Int).SetUint64(uint64(i) + 1)
|
||||
testOrder := LendingItem{LendingId: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Interest: big.NewInt(int64(2*i + 1)), Side: Investing, Signature: &Signature{V: 1, R: common.HexToHash("111111"), S: common.HexToHash("222222222222")}}
|
||||
orderIdHash = common.BigToHash(new(big.Int).SetUint64(testOrder.LendingId))
|
||||
snap = statedb.Snapshot()
|
||||
statedb.InsertLendingItem(orderBook, orderIdHash, testOrder)
|
||||
statedb.RevertToSnapshot(snap)
|
||||
gotOrder = statedb.GetLendingOrder(orderBook, orderIdHash)
|
||||
if gotOrder.Quantity.Cmp(EmptyLendingOrder.Quantity) != 0 {
|
||||
t.Fatalf(" err insert order info : %v after try revert snap shot , got : %v ,want Empty Order", orderIdHash.Hex(), gotOrder)
|
||||
}
|
||||
|
||||
// insert trade order
|
||||
i = 2*numberOrder + 1
|
||||
id = new(big.Int).SetUint64(uint64(i) + 1)
|
||||
order := LendingTrade{TradeId: id.Uint64(), Amount: big.NewInt(int64(2*i + 1))}
|
||||
orderIdHash = common.BigToHash(new(big.Int).SetUint64(order.TradeId))
|
||||
snap = statedb.Snapshot()
|
||||
statedb.InsertTradingItem(orderBook, order.TradeId, order)
|
||||
statedb.RevertToSnapshot(snap)
|
||||
gotLendingTrade := statedb.GetLendingTrade(orderBook, orderIdHash)
|
||||
if gotLendingTrade.Amount.Cmp(EmptyLendingTrade.Amount) != 0 {
|
||||
t.Fatalf(" err insert lending trade : %s after try revert snap shot , got : %v ,want Empty Order", orderIdHash.Hex(), gotLendingTrade.Amount)
|
||||
}
|
||||
|
||||
// insert trade order
|
||||
time, data := statedb.GetLowestLiquidationTime(orderBook, big.NewInt(1))
|
||||
fmt.Println(time, data)
|
||||
statedb.RemoveLiquidationTime(orderBook, 1, 1)
|
||||
time, data = statedb.GetLowestLiquidationTime(orderBook, big.NewInt(1))
|
||||
fmt.Println(time, data)
|
||||
// change key
|
||||
db.Close()
|
||||
}
|
||||
|
||||
func TestDumpStates(t *testing.T) {
|
||||
orderBook := common.StringToHash("BTC/XDC")
|
||||
numberOrder := 20
|
||||
orderItems := []LendingItem{}
|
||||
relayers := []common.Hash{}
|
||||
for i := 0; i < numberOrder; i++ {
|
||||
relayers = append(relayers, common.BigToHash(big.NewInt(int64(i))))
|
||||
id := new(big.Int).SetUint64(uint64(i) + 1)
|
||||
orderItems = append(orderItems, LendingItem{LendingId: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Interest: big.NewInt(1), Side: Investing, Signature: &Signature{V: 1, R: common.HexToHash("111111"), S: common.HexToHash("222222222222")}})
|
||||
orderItems = append(orderItems, LendingItem{LendingId: id.Uint64(), Quantity: big.NewInt(int64(2*i + 1)), Interest: big.NewInt(1), Side: Borrowing, Signature: &Signature{V: 1, R: common.HexToHash("3333333333"), S: common.HexToHash("22222222222222222")}})
|
||||
}
|
||||
// Create an empty statedb database
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
stateCache := NewDatabase(db)
|
||||
statedb, _ := New(common.Hash{}, stateCache)
|
||||
for i := 0; i < len(orderItems); i++ {
|
||||
orderIdHash := common.BigToHash(new(big.Int).SetUint64(orderItems[i].LendingId))
|
||||
statedb.InsertLendingItem(orderBook, orderIdHash, orderItems[i])
|
||||
}
|
||||
statedb.InsertLiquidationTime(orderBook, big.NewInt(1), 1)
|
||||
order := LendingTrade{TradeId: 1, Amount: big.NewInt(2)}
|
||||
statedb.InsertTradingItem(orderBook, 1, order)
|
||||
root := statedb.IntermediateRoot()
|
||||
statedb.Commit()
|
||||
//err := stateCache.TrieDB().Commit(root, false)
|
||||
//if err != nil {
|
||||
// t.Errorf("Error when commit into database: %v", err)
|
||||
//}
|
||||
stateCache.TrieDB().Reference(root, common.Hash{})
|
||||
statedb, err := New(root, stateCache)
|
||||
if err != nil {
|
||||
t.Fatalf("Error when get trie in database: %s , err: %v", root.Hex(), err)
|
||||
}
|
||||
|
||||
fmt.Println(statedb.DumpBorrowingTrie(orderBook))
|
||||
db.Close()
|
||||
}
|
||||
190
XDCxlending/lendingstate/trade.go
Normal file
190
XDCxlending/lendingstate/trade.go
Normal file
|
|
@ -0,0 +1,190 @@
|
|||
package lendingstate
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto/sha3"
|
||||
"math/big"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/globalsign/mgo/bson"
|
||||
)
|
||||
|
||||
const (
|
||||
TradeStatusOpen = "OPEN"
|
||||
TradeStatusClosed = "CLOSED"
|
||||
TradeStatusLiquidated = "LIQUIDATED"
|
||||
)
|
||||
|
||||
type LendingTrade struct {
|
||||
Borrower common.Address `bson:"borrower" json:"borrower"`
|
||||
Investor common.Address `bson:"investor" json:"investor"`
|
||||
LendingToken common.Address `bson:"lendingToken" json:"lendingToken"`
|
||||
CollateralToken common.Address `bson:"collateralToken" json:"collateralToken"`
|
||||
BorrowingOrderHash common.Hash `bson:"borrowingOrderHash" json:"borrowingOrderHash"`
|
||||
InvestingOrderHash common.Hash `bson:"investingOrderHash" json:"investingOrderHash"`
|
||||
BorrowingRelayer common.Address `bson:"borrowingRelayer" json:"borrowingRelayer"`
|
||||
InvestingRelayer common.Address `bson:"investingRelayer" json:"investingRelayer"`
|
||||
Term uint64 `bson:"term" json:"term"`
|
||||
Interest uint64 `bson:"interest" json:"interest"`
|
||||
CollateralPrice *big.Int `bson:"collateralPrice" json:"collateralPrice"`
|
||||
LiquidationPrice *big.Int `bson:"liquidationPrice" json:"liquidationPrice"`
|
||||
CollateralLockedAmount *big.Int `bson:"collateralLockedAmount" json:"collateralLockedAmount"`
|
||||
AutoTopUp bool `bson:"autoTopUp" json:"autoTopUp"`
|
||||
LiquidationTime uint64 `bson:"liquidationTime" json:"liquidationTime"`
|
||||
DepositRate *big.Int `bson:"depositRate" json:"depositRate"`
|
||||
LiquidationRate *big.Int `bson:"liquidationRate" json:"liquidationRate"`
|
||||
RecallRate *big.Int `bson:"recallRate" json:"recallRate"`
|
||||
Amount *big.Int `bson:"amount" json:"amount"`
|
||||
BorrowingFee *big.Int `bson:"borrowingFee" json:"borrowingFee"`
|
||||
InvestingFee *big.Int `bson:"investingFee" json:"investingFee"`
|
||||
Status string `bson:"status" json:"status"`
|
||||
TakerOrderSide string `bson:"takerOrderSide" json:"takerOrderSide"`
|
||||
TakerOrderType string `bson:"takerOrderType" json:"takerOrderType"`
|
||||
MakerOrderType string `bson:"makerOrderType" json:"makerOrderType"`
|
||||
TradeId uint64 `bson:"tradeId" json:"tradeId"`
|
||||
Hash common.Hash `bson:"hash" json:"hash"`
|
||||
TxHash common.Hash `bson:"txHash" json:"txHash"`
|
||||
ExtraData string `bson:"extraData" json:"extraData"`
|
||||
CreatedAt time.Time `bson:"createdAt" json:"createdAt"`
|
||||
UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"`
|
||||
}
|
||||
|
||||
type LendingTradeBSON struct {
|
||||
Borrower string `bson:"borrower" json:"borrower"`
|
||||
Investor string `bson:"investor" json:"investor"`
|
||||
LendingToken string `bson:"lendingToken" json:"lendingToken"`
|
||||
CollateralToken string `bson:"collateralToken" json:"collateralToken"`
|
||||
BorrowingOrderHash string `bson:"borrowingOrderHash" json:"borrowingOrderHash"`
|
||||
InvestingOrderHash string `bson:"investingOrderHash" json:"investingOrderHash"`
|
||||
BorrowingRelayer string `bson:"borrowingRelayer" json:"borrowingRelayer"`
|
||||
InvestingRelayer string `bson:"investingRelayer" json:"investingRelayer"`
|
||||
Term string `bson:"term" json:"term"`
|
||||
Interest string `bson:"interest" json:"interest"`
|
||||
CollateralPrice string `bson:"collateralPrice" json:"collateralPrice"`
|
||||
LiquidationPrice string `bson:"liquidationPrice" json:"liquidationPrice"`
|
||||
LiquidationTime string `bson:"liquidationTime" json:"liquidationTime"`
|
||||
CollateralLockedAmount string `bson:"collateralLockedAmount" json:"collateralLockedAmount"`
|
||||
AutoTopUp bool `bson:"autoTopUp" json:"autoTopUp"`
|
||||
DepositRate string `bson:"depositRate" json:"depositRate"`
|
||||
LiquidationRate string `bson:"liquidationRate" json:"liquidationRate"`
|
||||
RecallRate string `bson:"recallRate" json:"recallRate"`
|
||||
Amount string `bson:"amount" json:"amount"`
|
||||
BorrowingFee string `bson:"borrowingFee" json:"borrowingFee"`
|
||||
InvestingFee string `bson:"investingFee" json:"investingFee"`
|
||||
Status string `bson:"status" json:"status"`
|
||||
TakerOrderSide string `bson:"takerOrderSide" json:"takerOrderSide"`
|
||||
TakerOrderType string `bson:"takerOrderType" json:"takerOrderType"`
|
||||
MakerOrderType string `bson:"makerOrderType" json:"makerOrderType"`
|
||||
TradeId string `bson:"tradeId" json:"tradeId"`
|
||||
Hash string `bson:"hash" json:"hash"`
|
||||
TxHash string `bson:"txHash" json:"txHash"`
|
||||
ExtraData string `bson:"extraData" json:"extraData"`
|
||||
UpdatedAt time.Time `bson:"updatedAt" json:"updatedAt"`
|
||||
}
|
||||
|
||||
func (t *LendingTrade) GetBSON() (interface{}, error) {
|
||||
return bson.M{
|
||||
"$setOnInsert": bson.M{
|
||||
"createdAt": t.CreatedAt,
|
||||
},
|
||||
"$set": LendingTradeBSON{
|
||||
Borrower: t.Borrower.Hex(),
|
||||
Investor: t.Investor.Hex(),
|
||||
LendingToken: t.LendingToken.Hex(),
|
||||
CollateralToken: t.CollateralToken.Hex(),
|
||||
BorrowingOrderHash: t.BorrowingOrderHash.Hex(),
|
||||
InvestingOrderHash: t.InvestingOrderHash.Hex(),
|
||||
BorrowingRelayer: t.BorrowingRelayer.Hex(),
|
||||
InvestingRelayer: t.InvestingRelayer.Hex(),
|
||||
Term: strconv.FormatUint(t.Term, 10),
|
||||
Interest: strconv.FormatUint(t.Interest, 10),
|
||||
CollateralPrice: t.CollateralPrice.String(),
|
||||
LiquidationPrice: t.LiquidationPrice.String(),
|
||||
LiquidationTime: strconv.FormatUint(t.LiquidationTime, 10),
|
||||
CollateralLockedAmount: t.CollateralLockedAmount.String(),
|
||||
AutoTopUp: t.AutoTopUp,
|
||||
DepositRate: t.DepositRate.String(),
|
||||
LiquidationRate: t.LiquidationRate.String(),
|
||||
RecallRate: t.RecallRate.String(),
|
||||
Amount: t.Amount.String(),
|
||||
BorrowingFee: t.BorrowingFee.String(),
|
||||
InvestingFee: t.InvestingFee.String(),
|
||||
Status: t.Status,
|
||||
TakerOrderSide: t.TakerOrderSide,
|
||||
TakerOrderType: t.TakerOrderType,
|
||||
MakerOrderType: t.MakerOrderType,
|
||||
TradeId: strconv.FormatUint(t.TradeId, 10),
|
||||
Hash: t.Hash.Hex(),
|
||||
TxHash: t.TxHash.Hex(),
|
||||
ExtraData: t.ExtraData,
|
||||
UpdatedAt: t.UpdatedAt,
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (t *LendingTrade) SetBSON(raw bson.Raw) error {
|
||||
decoded := new(LendingTradeBSON)
|
||||
|
||||
err := raw.Unmarshal(decoded)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
tradeId, err := strconv.ParseInt(decoded.TradeId, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse lendingItem.TradeId. Err: %v", err)
|
||||
}
|
||||
t.TradeId = uint64(tradeId)
|
||||
t.Borrower = common.HexToAddress(decoded.Borrower)
|
||||
t.Investor = common.HexToAddress(decoded.Investor)
|
||||
t.LendingToken = common.HexToAddress(decoded.LendingToken)
|
||||
t.CollateralToken = common.HexToAddress(decoded.CollateralToken)
|
||||
t.AutoTopUp = decoded.AutoTopUp
|
||||
t.BorrowingOrderHash = common.HexToHash(decoded.BorrowingOrderHash)
|
||||
t.InvestingOrderHash = common.HexToHash(decoded.InvestingOrderHash)
|
||||
t.BorrowingRelayer = common.HexToAddress(decoded.BorrowingRelayer)
|
||||
t.InvestingRelayer = common.HexToAddress(decoded.InvestingRelayer)
|
||||
term, err := strconv.ParseInt(decoded.Term, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse lendingItem.term. Err: %v", err)
|
||||
}
|
||||
t.Term = uint64(term)
|
||||
interest, err := strconv.ParseInt(decoded.Interest, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse lendingItem.interest. Err: %v", err)
|
||||
}
|
||||
t.Interest = uint64(interest)
|
||||
t.CollateralPrice = ToBigInt(decoded.CollateralPrice)
|
||||
t.LiquidationPrice = ToBigInt(decoded.LiquidationPrice)
|
||||
liquidationTime, err := strconv.ParseInt(decoded.LiquidationTime, 10, 64)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to parse lendingItem.LiquidationTime. Err: %v", err)
|
||||
}
|
||||
t.LiquidationTime = uint64(liquidationTime)
|
||||
t.CollateralLockedAmount = ToBigInt(decoded.CollateralLockedAmount)
|
||||
t.DepositRate = ToBigInt(decoded.DepositRate)
|
||||
t.LiquidationRate = ToBigInt(decoded.LiquidationRate)
|
||||
t.RecallRate = ToBigInt(decoded.RecallRate)
|
||||
t.Amount = tradingstate.ToBigInt(decoded.Amount)
|
||||
t.BorrowingFee = tradingstate.ToBigInt(decoded.BorrowingFee)
|
||||
t.InvestingFee = tradingstate.ToBigInt(decoded.InvestingFee)
|
||||
t.Status = decoded.Status
|
||||
t.TakerOrderSide = decoded.TakerOrderSide
|
||||
t.TakerOrderType = decoded.TakerOrderType
|
||||
t.MakerOrderType = decoded.MakerOrderType
|
||||
t.ExtraData = decoded.ExtraData
|
||||
t.Hash = common.HexToHash(decoded.Hash)
|
||||
t.TxHash = common.HexToHash(decoded.TxHash)
|
||||
t.UpdatedAt = decoded.UpdatedAt
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (t *LendingTrade) ComputeHash() common.Hash {
|
||||
sha := sha3.NewKeccak256()
|
||||
sha.Write(t.InvestingOrderHash.Bytes())
|
||||
sha.Write(t.BorrowingOrderHash.Bytes())
|
||||
return common.BytesToHash(sha.Sum(nil))
|
||||
}
|
||||
1216
XDCxlending/order_processor.go
Normal file
1216
XDCxlending/order_processor.go
Normal file
File diff suppressed because it is too large
Load diff
541
XDCxlending/order_processor_test.go
Normal file
541
XDCxlending/order_processor_test.go
Normal file
|
|
@ -0,0 +1,541 @@
|
|||
package XDCxlending
|
||||
|
||||
import (
|
||||
"github.com/XinFinOrg/XDPoSChain/XDCx"
|
||||
"github.com/XinFinOrg/XDPoSChain/XDCx/tradingstate"
|
||||
"github.com/XinFinOrg/XDPoSChain/XDCxlending/lendingstate"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/rawdb"
|
||||
"math/big"
|
||||
"reflect"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_getCancelFeeV1(t *testing.T) {
|
||||
type CancelFeeArg struct {
|
||||
collateralTokenDecimal *big.Int
|
||||
collateralPrice *big.Int
|
||||
borrowFeeRate *big.Int
|
||||
order *lendingstate.LendingItem
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args CancelFeeArg
|
||||
want *big.Int
|
||||
}{
|
||||
// zero fee test: LEND
|
||||
{
|
||||
"zero fee getCancelFeeV1: LEND",
|
||||
CancelFeeArg{
|
||||
collateralTokenDecimal: common.Big1,
|
||||
collateralPrice: common.Big1,
|
||||
borrowFeeRate: common.Big0,
|
||||
order: &lendingstate.LendingItem{
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: tradingstate.Ask,
|
||||
},
|
||||
},
|
||||
common.Big0,
|
||||
},
|
||||
|
||||
// zero fee test: BORROW
|
||||
{
|
||||
"zero fee getCancelFeeV1: BORROW",
|
||||
CancelFeeArg{
|
||||
collateralTokenDecimal: common.Big1,
|
||||
collateralPrice: common.Big1,
|
||||
borrowFeeRate: common.Big0,
|
||||
order: &lendingstate.LendingItem{
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: tradingstate.Bid,
|
||||
},
|
||||
},
|
||||
common.Big0,
|
||||
},
|
||||
|
||||
// test getCancelFee: LEND
|
||||
{
|
||||
"test getCancelFeeV1:: LEND",
|
||||
CancelFeeArg{
|
||||
collateralTokenDecimal: common.Big1,
|
||||
collateralPrice: common.Big1,
|
||||
borrowFeeRate: new(big.Int).SetUint64(30), // 30/10000= 0.3%
|
||||
order: &lendingstate.LendingItem{
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: tradingstate.Ask,
|
||||
},
|
||||
},
|
||||
common.Big3,
|
||||
},
|
||||
|
||||
// test getCancelFee:: BORROW
|
||||
{
|
||||
"test getCancelFeeV1:: BORROW",
|
||||
CancelFeeArg{
|
||||
collateralTokenDecimal: common.Big1,
|
||||
collateralPrice: common.Big1,
|
||||
borrowFeeRate: new(big.Int).SetUint64(30), // 30/10000= 0.3%
|
||||
order: &lendingstate.LendingItem{
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: tradingstate.Bid,
|
||||
},
|
||||
},
|
||||
common.Big3,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got := getCancelFeeV1(tt.args.collateralTokenDecimal, tt.args.collateralPrice, tt.args.borrowFeeRate, tt.args.order); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("getCancelFeeV1() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_getCancelFee(t *testing.T) {
|
||||
XDCx := XDCx.New(&XDCx.DefaultConfig)
|
||||
db := rawdb.NewMemoryDatabase()
|
||||
stateCache := tradingstate.NewDatabase(db)
|
||||
tradingStateDb, _ := tradingstate.New(common.Hash{}, stateCache)
|
||||
|
||||
testTokenA := common.HexToAddress("0x1200000000000000000000000000000000000002")
|
||||
testTokenB := common.HexToAddress("0x1300000000000000000000000000000000000003")
|
||||
// set decimal
|
||||
// tokenA has decimal 10^18
|
||||
XDCx.SetTokenDecimal(testTokenA, common.BasePrice)
|
||||
// tokenB has decimal 10^8
|
||||
XDCx.SetTokenDecimal(testTokenB, new(big.Int).Exp(big.NewInt(10), big.NewInt(8), nil))
|
||||
|
||||
// set tokenAPrice = 1 XDC
|
||||
tradingStateDb.SetMediumPriceBeforeEpoch(tradingstate.GetTradingOrderBookHash(testTokenA, common.HexToAddress(common.XDCNativeAddress)), common.BasePrice)
|
||||
// set tokenBPrice = 1 XDC
|
||||
tradingStateDb.SetMediumPriceBeforeEpoch(tradingstate.GetTradingOrderBookHash(testTokenB, common.HexToAddress(common.XDCNativeAddress)), common.BasePrice)
|
||||
|
||||
l := New(XDCx)
|
||||
|
||||
type CancelFeeArg struct {
|
||||
borrowFeeRate *big.Int
|
||||
order *lendingstate.LendingItem
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args CancelFeeArg
|
||||
want *big.Int
|
||||
}{
|
||||
// LENDING TOKEN: testTokenA
|
||||
// COLLATERAL TOKEN: XDC
|
||||
|
||||
// zero fee test: LEND
|
||||
{
|
||||
"TokenA/XDCzero fee test: LEND",
|
||||
CancelFeeArg{
|
||||
borrowFeeRate: common.Big0,
|
||||
order: &lendingstate.LendingItem{
|
||||
LendingToken: testTokenA,
|
||||
CollateralToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: lendingstate.Investing,
|
||||
},
|
||||
},
|
||||
common.Big0,
|
||||
},
|
||||
|
||||
// zero fee test: BORROW
|
||||
{
|
||||
"TokenA/XDC zero fee test: BORROW",
|
||||
CancelFeeArg{
|
||||
borrowFeeRate: common.Big0,
|
||||
order: &lendingstate.LendingItem{
|
||||
LendingToken: testTokenA,
|
||||
CollateralToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: lendingstate.Borrowing,
|
||||
},
|
||||
},
|
||||
common.Big0,
|
||||
},
|
||||
|
||||
// test getCancelFee: LEND
|
||||
{
|
||||
"TokenA/XDC test getCancelFee:: LEND",
|
||||
CancelFeeArg{
|
||||
borrowFeeRate: new(big.Int).SetUint64(30), // 30/10000= 0.3%
|
||||
order: &lendingstate.LendingItem{
|
||||
LendingToken: testTokenA,
|
||||
CollateralToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: lendingstate.Investing,
|
||||
},
|
||||
},
|
||||
common.RelayerLendingCancelFee,
|
||||
},
|
||||
|
||||
// test getCancelFee:: BORROW
|
||||
{
|
||||
"TokenA/XDC test getCancelFee:: BORROW",
|
||||
CancelFeeArg{
|
||||
borrowFeeRate: new(big.Int).SetUint64(30), // 30/10000= 0.3%
|
||||
order: &lendingstate.LendingItem{
|
||||
LendingToken: testTokenA,
|
||||
CollateralToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: lendingstate.Borrowing,
|
||||
},
|
||||
},
|
||||
common.RelayerLendingCancelFee,
|
||||
},
|
||||
|
||||
// LENDING TOKEN: XDC
|
||||
// COLLATERAL TOKEN: testTokenA
|
||||
|
||||
// zero fee test: LEND
|
||||
{
|
||||
"XDC/TokenA zero fee test: LEND",
|
||||
CancelFeeArg{
|
||||
borrowFeeRate: common.Big0,
|
||||
order: &lendingstate.LendingItem{
|
||||
LendingToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
CollateralToken: testTokenA,
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: lendingstate.Investing,
|
||||
},
|
||||
},
|
||||
common.Big0,
|
||||
},
|
||||
|
||||
// zero fee test: BORROW
|
||||
{
|
||||
"XDC/TokenA zero fee test: BORROW",
|
||||
CancelFeeArg{
|
||||
borrowFeeRate: common.Big0,
|
||||
order: &lendingstate.LendingItem{
|
||||
LendingToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
CollateralToken: testTokenA,
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: lendingstate.Borrowing,
|
||||
},
|
||||
},
|
||||
common.Big0,
|
||||
},
|
||||
|
||||
// test getCancelFee: LEND
|
||||
{
|
||||
"XDC/TokenA test getCancelFee:: LEND",
|
||||
CancelFeeArg{
|
||||
borrowFeeRate: new(big.Int).SetUint64(30), // 30/10000= 0.3%
|
||||
order: &lendingstate.LendingItem{
|
||||
LendingToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
CollateralToken: testTokenA,
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: lendingstate.Investing,
|
||||
},
|
||||
},
|
||||
common.RelayerLendingCancelFee,
|
||||
},
|
||||
|
||||
// test getCancelFee:: BORROW
|
||||
{
|
||||
"XDC/TokenA test getCancelFee:: BORROW",
|
||||
CancelFeeArg{
|
||||
borrowFeeRate: new(big.Int).SetUint64(30), // 30/10000= 0.3%
|
||||
order: &lendingstate.LendingItem{
|
||||
LendingToken: common.HexToAddress(common.XDCNativeAddress),
|
||||
CollateralToken: testTokenA,
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: lendingstate.Borrowing,
|
||||
},
|
||||
},
|
||||
common.RelayerLendingCancelFee,
|
||||
},
|
||||
|
||||
// LENDING TOKEN: testTokenB
|
||||
// COLLATERAL TOKEN: testTokenA
|
||||
|
||||
// zero fee test: LEND
|
||||
{
|
||||
"TokenB/TokenA zero fee test: LEND",
|
||||
CancelFeeArg{
|
||||
borrowFeeRate: common.Big0,
|
||||
order: &lendingstate.LendingItem{
|
||||
LendingToken: testTokenB,
|
||||
CollateralToken: testTokenA,
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: lendingstate.Investing,
|
||||
},
|
||||
},
|
||||
common.Big0,
|
||||
},
|
||||
|
||||
// zero fee test: BORROW
|
||||
{
|
||||
"TokenB/TokenA zero fee test: BORROW",
|
||||
CancelFeeArg{
|
||||
borrowFeeRate: common.Big0,
|
||||
order: &lendingstate.LendingItem{
|
||||
LendingToken: testTokenB,
|
||||
CollateralToken: testTokenA,
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: lendingstate.Borrowing,
|
||||
},
|
||||
},
|
||||
common.Big0,
|
||||
},
|
||||
|
||||
// test getCancelFee: LEND
|
||||
{
|
||||
"TokenB/TokenA test getCancelFee:: LEND",
|
||||
CancelFeeArg{
|
||||
borrowFeeRate: new(big.Int).SetUint64(30), // 30/10000= 0.3%
|
||||
order: &lendingstate.LendingItem{
|
||||
LendingToken: testTokenB,
|
||||
CollateralToken: testTokenA,
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: lendingstate.Investing,
|
||||
},
|
||||
},
|
||||
new(big.Int).Exp(big.NewInt(10), big.NewInt(5), nil),
|
||||
},
|
||||
|
||||
// test getCancelFee:: BORROW
|
||||
{
|
||||
"TokenB/TokenA test getCancelFee:: BORROW",
|
||||
CancelFeeArg{
|
||||
borrowFeeRate: new(big.Int).SetUint64(30), // 30/10000= 0.3%
|
||||
order: &lendingstate.LendingItem{
|
||||
LendingToken: testTokenB,
|
||||
CollateralToken: testTokenA,
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
Side: lendingstate.Borrowing,
|
||||
},
|
||||
},
|
||||
common.RelayerLendingCancelFee,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
if got, _ := l.getCancelFee(nil, nil, tradingStateDb, tt.args.order, tt.args.borrowFeeRate); !reflect.DeepEqual(got, tt.want) {
|
||||
t.Errorf("getCancelFee() = %v, want %v", got, tt.want)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// testcase: can't get price of token in XDC
|
||||
testTokenC := common.HexToAddress("0x1400000000000000000000000000000000000004")
|
||||
XDCx.SetTokenDecimal(testTokenC, big.NewInt(1))
|
||||
tokenCOrder := CancelFeeArg{
|
||||
borrowFeeRate: new(big.Int).SetUint64(100), // 100/10000= 1%
|
||||
order: &lendingstate.LendingItem{
|
||||
Quantity: new(big.Int).SetUint64(10000),
|
||||
CollateralToken: testTokenC,
|
||||
LendingToken: testTokenA,
|
||||
Side: lendingstate.Borrowing,
|
||||
},
|
||||
}
|
||||
if fee, _ := l.getCancelFee(nil, nil, tradingStateDb, tokenCOrder.order, tokenCOrder.borrowFeeRate); fee != nil && fee.Sign() != 0 {
|
||||
t.Errorf("getCancelFee() = %v, want %v", fee, common.Big0)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetLendQuantity(t *testing.T) {
|
||||
depositRate := big.NewInt(150)
|
||||
lendQuantity := new(big.Int).Mul(big.NewInt(1000), common.BasePrice)
|
||||
collateralLocked, _ := new(big.Int).SetString("1000000000000000000000", 10) // 1000
|
||||
collateralLocked = new(big.Int).Mul(big.NewInt(150), collateralLocked)
|
||||
collateralLocked = new(big.Int).Div(collateralLocked, big.NewInt(100))
|
||||
type GetLendQuantityArg struct {
|
||||
takerSide string
|
||||
collateralTokenDecimal *big.Int
|
||||
depositRate *big.Int
|
||||
collateralPrice *big.Int
|
||||
takerBalance *big.Int
|
||||
makerBalance *big.Int
|
||||
quantityToLend *big.Int
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args GetLendQuantityArg
|
||||
lendQuantity *big.Int
|
||||
rejectMaker bool
|
||||
}{
|
||||
{
|
||||
"taker: BORROW, takerBalance = 0, reject taker, makerBalance = 0",
|
||||
GetLendQuantityArg{
|
||||
lendingstate.Borrowing,
|
||||
common.BasePrice,
|
||||
depositRate,
|
||||
common.BasePrice,
|
||||
common.Big0,
|
||||
common.Big0,
|
||||
lendQuantity,
|
||||
},
|
||||
common.Big0,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"taker: BORROW, takerBalance = 0, reject taker, makerBalance > 0",
|
||||
GetLendQuantityArg{
|
||||
lendingstate.Borrowing,
|
||||
common.BasePrice,
|
||||
depositRate,
|
||||
common.BasePrice,
|
||||
common.Big0,
|
||||
lendQuantity,
|
||||
lendQuantity,
|
||||
},
|
||||
common.Big0,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"taker: BORROW, takerBalance not enough, reject partial of taker",
|
||||
GetLendQuantityArg{
|
||||
lendingstate.Borrowing,
|
||||
common.BasePrice,
|
||||
depositRate,
|
||||
common.BasePrice,
|
||||
new(big.Int).Div(collateralLocked, big.NewInt(2)), // 1/2
|
||||
lendQuantity,
|
||||
lendQuantity,
|
||||
},
|
||||
new(big.Int).Div(lendQuantity, big.NewInt(2)),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"taker: BORROW, makerBalance = 0, reject maker",
|
||||
GetLendQuantityArg{
|
||||
lendingstate.Borrowing,
|
||||
common.BasePrice,
|
||||
depositRate,
|
||||
common.BasePrice,
|
||||
new(big.Int).Div(collateralLocked, big.NewInt(2)),
|
||||
common.Big0,
|
||||
lendQuantity,
|
||||
},
|
||||
common.Big0,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"taker: BORROW, makerBalance not enough, reject partial of maker",
|
||||
GetLendQuantityArg{
|
||||
lendingstate.Borrowing,
|
||||
common.BasePrice,
|
||||
depositRate,
|
||||
common.BasePrice,
|
||||
collateralLocked,
|
||||
new(big.Int).Div(lendQuantity, big.NewInt(2)),
|
||||
lendQuantity,
|
||||
},
|
||||
new(big.Int).Div(lendQuantity, big.NewInt(2)),
|
||||
true,
|
||||
},
|
||||
{
|
||||
"taker: BORROW, don't reject",
|
||||
GetLendQuantityArg{
|
||||
lendingstate.Borrowing,
|
||||
common.BasePrice,
|
||||
depositRate,
|
||||
common.BasePrice,
|
||||
collateralLocked,
|
||||
lendQuantity,
|
||||
lendQuantity,
|
||||
},
|
||||
lendQuantity,
|
||||
false,
|
||||
},
|
||||
|
||||
{
|
||||
"taker: INVEST, makerBalance = 0, reject maker",
|
||||
GetLendQuantityArg{
|
||||
lendingstate.Investing,
|
||||
common.BasePrice,
|
||||
depositRate,
|
||||
common.BasePrice,
|
||||
new(big.Int).Div(collateralLocked, big.NewInt(2)),
|
||||
common.Big0,
|
||||
lendQuantity,
|
||||
},
|
||||
common.Big0,
|
||||
true,
|
||||
},
|
||||
{
|
||||
"taker: INVEST, takerBalance not enough, reject partial of taker",
|
||||
GetLendQuantityArg{
|
||||
lendingstate.Investing,
|
||||
common.BasePrice,
|
||||
depositRate,
|
||||
common.BasePrice,
|
||||
new(big.Int).Div(lendQuantity, big.NewInt(2)), // 1/2
|
||||
collateralLocked,
|
||||
lendQuantity,
|
||||
},
|
||||
new(big.Int).Div(lendQuantity, big.NewInt(2)),
|
||||
false,
|
||||
},
|
||||
{
|
||||
"taker: INVEST, makerBalance = 0, reject maker",
|
||||
GetLendQuantityArg{
|
||||
lendingstate.Investing,
|
||||
common.BasePrice,
|
||||
depositRate,
|
||||
common.BasePrice,
|
||||
common.Big0,
|
||||
new(big.Int).Div(collateralLocked, big.NewInt(2)),
|
||||
lendQuantity,
|
||||
},
|
||||
common.Big0,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"taker: INVEST, makerBalance is enough, takerBalance = 0 -> reject taker",
|
||||
GetLendQuantityArg{
|
||||
lendingstate.Investing,
|
||||
common.BasePrice,
|
||||
depositRate,
|
||||
common.BasePrice,
|
||||
common.Big0,
|
||||
collateralLocked,
|
||||
lendQuantity,
|
||||
},
|
||||
common.Big0,
|
||||
false,
|
||||
},
|
||||
{
|
||||
"taker: INVEST, makerBalance not enough, reject partial of maker",
|
||||
GetLendQuantityArg{
|
||||
lendingstate.Investing,
|
||||
common.BasePrice,
|
||||
depositRate,
|
||||
common.BasePrice,
|
||||
collateralLocked,
|
||||
new(big.Int).Div(collateralLocked, big.NewInt(2)),
|
||||
lendQuantity,
|
||||
},
|
||||
new(big.Int).Div(lendQuantity, big.NewInt(2)),
|
||||
true,
|
||||
},
|
||||
{
|
||||
"taker: INVEST, don't reject",
|
||||
GetLendQuantityArg{
|
||||
lendingstate.Investing,
|
||||
common.BasePrice,
|
||||
depositRate,
|
||||
common.BasePrice,
|
||||
lendQuantity,
|
||||
collateralLocked,
|
||||
lendQuantity,
|
||||
},
|
||||
lendQuantity,
|
||||
false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
got, got1 := GetLendQuantity(tt.args.takerSide, tt.args.collateralTokenDecimal, tt.args.depositRate, tt.args.collateralPrice, tt.args.takerBalance, tt.args.makerBalance, tt.args.quantityToLend)
|
||||
if !reflect.DeepEqual(got, tt.lendQuantity) {
|
||||
t.Errorf("GetLendQuantity() got = %v, want %v", got, tt.lendQuantity)
|
||||
}
|
||||
if got1 != tt.rejectMaker {
|
||||
t.Errorf("GetLendQuantity() got1 = %v, want %v", got1, tt.rejectMaker)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,10 +22,10 @@ import (
|
|||
"io"
|
||||
"io/ioutil"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/keystore"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/keystore"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
)
|
||||
|
||||
// NewTransactor is a utility method to easily create a transaction signer from
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ import (
|
|||
"errors"
|
||||
"math/big"
|
||||
|
||||
"github.com/ethereum/go-ethereum"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
)
|
||||
|
||||
var (
|
||||
|
|
@ -49,7 +49,7 @@ type ContractCaller interface {
|
|||
CodeAt(ctx context.Context, contract common.Address, blockNumber *big.Int) ([]byte, error)
|
||||
// ContractCall executes an Ethereum contract call with the specified data as the
|
||||
// input.
|
||||
CallContract(ctx context.Context, call ethereum.CallMsg, blockNumber *big.Int) ([]byte, error)
|
||||
CallContract(ctx context.Context, call XDPoSChain.CallMsg, blockNumber *big.Int) ([]byte, error)
|
||||
}
|
||||
|
||||
// PendingContractCaller defines methods to perform contract calls on the pending state.
|
||||
|
|
@ -59,7 +59,7 @@ type PendingContractCaller interface {
|
|||
// PendingCodeAt returns the code of the given account in the pending state.
|
||||
PendingCodeAt(ctx context.Context, contract common.Address) ([]byte, error)
|
||||
// PendingCallContract executes an Ethereum contract call against the pending state.
|
||||
PendingCallContract(ctx context.Context, call ethereum.CallMsg) ([]byte, error)
|
||||
PendingCallContract(ctx context.Context, call XDPoSChain.CallMsg) ([]byte, error)
|
||||
}
|
||||
|
||||
// ContractTransactor defines the methods needed to allow operating with contract
|
||||
|
|
@ -79,7 +79,7 @@ type ContractTransactor interface {
|
|||
// There is no guarantee that this is the true gas limit requirement as other
|
||||
// transactions may be added or removed by miners, but it should provide a basis
|
||||
// for setting a reasonable default.
|
||||
EstimateGas(ctx context.Context, call ethereum.CallMsg) (gas uint64, err error)
|
||||
EstimateGas(ctx context.Context, call XDPoSChain.CallMsg) (gas uint64, err error)
|
||||
// SendTransaction injects the transaction into the pending pool for execution.
|
||||
SendTransaction(ctx context.Context, tx *types.Transaction) error
|
||||
}
|
||||
|
|
@ -91,11 +91,11 @@ type ContractFilterer interface {
|
|||
// returning all the results in one batch.
|
||||
//
|
||||
// TODO(karalabe): Deprecate when the subscription one can return past data too.
|
||||
FilterLogs(ctx context.Context, query ethereum.FilterQuery) ([]types.Log, error)
|
||||
FilterLogs(ctx context.Context, query XDPoSChain.FilterQuery) ([]types.Log, error)
|
||||
|
||||
// SubscribeFilterLogs creates a background log filtering operation, returning
|
||||
// a subscription immediately, which can be used to stream the found events.
|
||||
SubscribeFilterLogs(ctx context.Context, query ethereum.FilterQuery, ch chan<- types.Log) (ethereum.Subscription, error)
|
||||
SubscribeFilterLogs(ctx context.Context, query XDPoSChain.FilterQuery, ch chan<- types.Log) (XDPoSChain.Subscription, error)
|
||||
}
|
||||
|
||||
// DeployBackend wraps the operations needed by WaitMined and WaitDeployed.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
|
@ -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}}"
|
||||
|
|
|
|||
|
|
@ -22,9 +22,9 @@ import (
|
|||
"math/big"
|
||||
"reflect"
|
||||
|
||||
"github.com/ethereum/go-ethereum/accounts/abi"
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/crypto"
|
||||
"github.com/XinFinOrg/XDPoSChain/accounts/abi"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/crypto"
|
||||
)
|
||||
|
||||
// makeTopics converts a filter query argument list into a filter topic set.
|
||||
|
|
|
|||
|
|
@ -21,9 +21,9 @@ import (
|
|||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/core/types"
|
||||
"github.com/ethereum/go-ethereum/log"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/core/types"
|
||||
"github.com/XinFinOrg/XDPoSChain/log"
|
||||
)
|
||||
|
||||
// WaitMined waits for tx to be mined on the blockchain.
|
||||
|
|
|
|||
|
|
@ -22,12 +22,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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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")
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,8 +20,8 @@ import (
|
|||
"math/big"
|
||||
"reflect"
|
||||
|
||||
"github.com/ethereum/go-ethereum/common"
|
||||
"github.com/ethereum/go-ethereum/common/math"
|
||||
"github.com/XinFinOrg/XDPoSChain/common"
|
||||
"github.com/XinFinOrg/XDPoSChain/common/math"
|
||||
)
|
||||
|
||||
// packBytesSlice packs the given bytes as [L, V] as the canonical representation
|
||||
|
|
|
|||
|
|
@ -24,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) {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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])
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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 {
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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{
|
||||
{
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
Loading…
Reference in a new issue