mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2025-03-14 03:28:39 +00:00
Compare commits
547 commits
Author | SHA1 | Date | |
---|---|---|---|
|
24a6d842b5 | ||
|
ee56f73606 | ||
|
210059fff8 | ||
|
4a510637be | ||
|
2fd51cf6b1 | ||
|
7d72b6538a | ||
|
70b4d005e3 | ||
|
2881961421 | ||
|
5f80a142b0 | ||
|
d411337648 | ||
|
2c8bd056fa | ||
|
9c0274850a | ||
|
94660f6333 | ||
|
c3916ef2c3 | ||
|
22a5bbcef3 | ||
|
e63cbe7c82 | ||
|
13b75c9d1a | ||
|
3ec14d2e8f | ||
|
e9f496a19f | ||
|
68d7e8e01c | ||
|
a227572d01 | ||
|
64e3b6aa48 | ||
|
a7c66dcb0b | ||
|
ff8ea5d4e8 | ||
|
0d8c751e50 | ||
|
d0e47c739a | ||
|
3b54213c08 | ||
|
d7232357d4 | ||
|
e05863d13b | ||
|
75d12081bf | ||
|
62972ebc14 | ||
|
406e8cd840 | ||
|
30b3318503 | ||
|
a1db45d668 | ||
|
25e2d80363 | ||
|
93be186685 | ||
|
d1926e4fb0 | ||
|
cb4aba497e | ||
|
bd170fb849 | ||
|
8d728b4ec1 | ||
|
1ad62ef5f5 | ||
|
91b60ea7d3 | ||
|
3fa02d062b | ||
|
de50d0be49 | ||
|
e70b1063a6 | ||
|
72e0cff82b | ||
|
267d7dca78 | ||
|
83a02fc0c0 | ||
|
847479e6a1 | ||
|
d02e2b629d | ||
|
6969982f77 | ||
|
f09a04d705 | ||
|
5d0cd53d67 | ||
|
e704731f2f | ||
|
50cb21333e | ||
|
8ba2bbf87c | ||
|
7ba8048530 | ||
|
22c5cf3537 | ||
|
4b2ee9a424 | ||
|
00e22b9751 | ||
|
4da753f822 | ||
|
673b26fdb1 | ||
|
3b28749fd1 | ||
|
257fa7c6c9 | ||
|
b2322a804a | ||
|
fcd83e9fd7 | ||
|
16a6a59c43 | ||
|
c047bad446 | ||
|
9513780c45 | ||
|
c07c74df41 | ||
|
c14492e8c8 | ||
|
70aaeb06ad | ||
|
ba0ca6a4ab | ||
|
1f83f472c7 | ||
|
4d2b88dd9d | ||
|
82b400700f | ||
|
4720727bf5 | ||
|
47b679341b | ||
|
e0338b7ed0 | ||
|
64c4de4e38 | ||
|
38b9b13098 | ||
|
4d45be8434 | ||
|
ab5128fb48 | ||
|
dc747ce3b7 | ||
|
663146aa1f | ||
|
86b2493edd | ||
|
80e18222bc | ||
|
b096e68428 | ||
|
dc68e39811 | ||
|
992890721a | ||
|
cffafc4e79 | ||
|
0b21d4caf6 | ||
|
6d20772714 | ||
|
b86c6201d3 | ||
|
c669ae343b | ||
|
c27101a97c | ||
|
4b7b2ef701 | ||
|
0bb6c6e6bd | ||
|
6d509896fb | ||
|
24f81a7f3e | ||
|
f5ea79d649 | ||
|
e741053f0c | ||
|
9189b3eb7d | ||
|
b285cf8c6e | ||
|
fec1ac8aee | ||
|
53ab704971 | ||
|
3f651cde27 | ||
|
39cec7d0d6 | ||
|
388ee25cd6 | ||
|
9e3f75e977 | ||
|
f82088b851 | ||
|
be4fc98041 | ||
|
247ee831e5 | ||
|
e0c8bebd05 | ||
|
9834b83cf0 | ||
|
90b6a42331 | ||
|
9599fba24f | ||
|
7d89a53043 | ||
|
c53b0645bb | ||
|
c7f5f173ae | ||
|
46cbfab264 | ||
|
38f635bce0 | ||
|
65bdc82da8 | ||
|
c79ffa967f | ||
|
283ca5cb6c | ||
|
3c372de570 | ||
|
cb4b21fcf4 | ||
|
ea0a6114d2 | ||
|
15cc559c73 | ||
|
1447158f2b | ||
|
62615f8c7e | ||
|
e530ba0791 | ||
|
b058b6cf21 | ||
|
6d1eea307b | ||
|
e993c1bdac | ||
|
4fa5fdc39c | ||
|
0bd378770d | ||
|
727262a95a | ||
|
827acfca23 | ||
|
35c2c5cc0e | ||
|
3010324c4f | ||
|
5f92da21fa | ||
|
703e066acc | ||
|
c84dac501d | ||
|
703bf6c458 | ||
|
2bc41dbe30 | ||
|
a68856c27c | ||
|
5f09e00e44 | ||
|
fc705433a3 | ||
|
a164db92cc | ||
|
981ae10091 | ||
|
109319d220 | ||
|
c812150d83 | ||
|
49267f3412 | ||
|
1aed0faeec | ||
|
d69f8ebbab | ||
|
121307d349 | ||
|
8952922c4d | ||
|
2244d2ad75 | ||
|
0879018e03 | ||
|
f7ee66ff8b | ||
|
9a3923097b | ||
|
656d098f5c | ||
|
20ea4c40f5 | ||
|
6a7d2bc250 | ||
|
fde3ab0c88 | ||
|
a3c2d82e03 | ||
|
ba822cfa76 | ||
|
7b82c759ce | ||
|
9778fd88fb | ||
|
a5cf1635d8 | ||
|
ebb71497a5 | ||
|
dc2d22110f | ||
|
3f95476ce3 | ||
|
ac073e2ada | ||
|
7ba295ca99 | ||
|
3b7a7dc767 | ||
|
24127c18ff | ||
|
870421e950 | ||
|
718ff9aa0a | ||
|
14c980a685 | ||
|
119ca27994 | ||
|
c1ce6904c4 | ||
|
df05cd2858 | ||
|
8216f538c3 | ||
|
92ff8409a9 | ||
|
9fa07d8d6d | ||
|
c321eed8ee | ||
|
04516e7d26 | ||
|
e89f9fe2a4 | ||
|
f98169a762 | ||
|
97bd73b38c | ||
|
0ba16fe580 | ||
|
9e7fd5180a | ||
|
6b6878706a | ||
|
171e01be3c | ||
|
43609dd984 | ||
|
3d3f3f67e6 | ||
|
616c805ac5 | ||
|
5dab154582 | ||
|
9fd375d56d | ||
|
ff15e39363 | ||
|
85c3b96f82 | ||
|
8fe2ae8437 | ||
|
3d00a19383 | ||
|
cdbc026c27 | ||
|
40dda9f524 | ||
|
2b758c53eb | ||
|
b63c7aad73 | ||
|
8e99ff65e1 | ||
|
b2bd8e4a0a | ||
|
54e3708566 | ||
|
57eec71101 | ||
|
3f1ccdd2ce | ||
|
aba781c2de | ||
|
aeee733479 | ||
|
632092b2cd | ||
|
125f757988 | ||
|
7683f4366d | ||
|
2b1b9a4fca | ||
|
9082c6ea1a | ||
|
176593b31f | ||
|
db2956f1af | ||
|
22c6ab4de9 | ||
|
990634a43a | ||
|
270f0d2d7a | ||
|
a123b75fd9 | ||
|
4a96bd1dc1 | ||
|
5f6284de05 | ||
|
58ed448f8d | ||
|
b97d0b2326 | ||
|
375b095f78 | ||
|
380d112599 | ||
|
6e863e9a06 | ||
|
895a0ae624 | ||
|
7ec0c1155c | ||
|
5707bdd1d1 | ||
|
9cc16d73f2 | ||
|
933d522b82 | ||
|
ccbb198a5b | ||
|
bbec25de43 | ||
|
7af2ab92d2 | ||
|
c2b4d32224 | ||
|
71a89c230a | ||
|
3ec06f316e | ||
|
d8ea4103c7 | ||
|
cb51eeb1b1 | ||
|
8b12b3ac64 | ||
|
dda2cafdf8 | ||
|
0afd9ac0bb | ||
|
66fbcb2f00 | ||
|
b62c0b9118 | ||
|
9cca702ac2 | ||
|
24577d320d | ||
|
7062ab9397 | ||
|
29e2d712f4 | ||
|
18ed3bb3e8 | ||
|
1a540d5883 | ||
|
8c4d9432d7 | ||
|
b6516586d5 | ||
|
23f9c5a43b | ||
|
df9247c00b | ||
|
cee296eb92 | ||
|
ddaf9c01ab | ||
|
a85f3ce83f | ||
|
22cb7feb1d | ||
|
82b508e8f3 | ||
|
95098d4b25 | ||
|
5cffa90774 | ||
|
21d6887969 | ||
|
1c65f32a6d | ||
|
c955c1e4ea | ||
|
35d12779d6 | ||
|
365bbe08ed | ||
|
6a93f70728 | ||
|
a4633ce2c7 | ||
|
cccbe843be | ||
|
e8b8c1a4b1 | ||
|
a0d2f95e42 | ||
|
b8a65d3c37 | ||
|
7b3eeb9061 | ||
|
36e855609d | ||
|
4b10f23aec | ||
|
a5e9ab6979 | ||
|
42c8e40eaa | ||
|
8b43c33e44 | ||
|
5b793bcf1b | ||
|
e83b3e4839 | ||
|
0b79901b7f | ||
|
c47d4e6c5b | ||
|
e82d9a179c | ||
|
8a154d9189 | ||
|
a0def2e5d2 | ||
|
e00d7fec7f | ||
|
29bb3ff1cf | ||
|
c962edcf47 | ||
|
41873e35c6 | ||
|
7b09812069 | ||
|
b5b89881b7 | ||
|
8e1fdd5d70 | ||
|
87bbff9831 | ||
|
14ea5a8d32 | ||
|
6199240598 | ||
|
59fab5d654 | ||
|
852dcb0f64 | ||
|
2435484dc4 | ||
|
86ed214e8a | ||
|
c1444d45a4 | ||
|
d8e945978a | ||
|
c747bb8ff7 | ||
|
49f2e1dc64 | ||
|
2dc588ea95 | ||
|
a9242535db | ||
|
8bdb8afaf5 | ||
|
c5c64f5f07 | ||
|
25b353c9f8 | ||
|
9fb291d5df | ||
|
524ba5fd1b | ||
|
949ed4f2ea | ||
|
8ee13cc35e | ||
|
8ed3ab8c82 | ||
|
ad41d0f9c7 | ||
|
594612bbdf | ||
|
9f93123301 | ||
|
0968c3a81f | ||
|
7fac3bcd6f | ||
|
11151938b9 | ||
|
9aca090644 | ||
|
c960a7eb47 | ||
|
02727b14b7 | ||
|
b1068b1ab9 | ||
|
fadfac7041 | ||
|
4a9003f551 | ||
|
9a38360824 | ||
|
5847ca16ba | ||
|
0ae098bea2 | ||
|
cf2e6296b7 | ||
|
cf4d3b25d7 | ||
|
379ca4b4e6 | ||
|
e1d5ac8557 | ||
|
4445a0c42c | ||
|
80dd6359a5 | ||
|
4e3f17d06f | ||
|
8469f97d09 | ||
|
f80f453933 | ||
|
0b31a29f39 | ||
|
5dff3fc13d | ||
|
8846f648c3 | ||
|
6bbf15f305 | ||
|
5108121c0f | ||
|
44a87769e5 | ||
|
69b655ec7a | ||
|
e5eef2fbc7 | ||
|
6f2712ee55 | ||
|
d9a6a7cd3f | ||
|
585af7e596 | ||
|
f8549a4fb8 | ||
|
32f91dd726 | ||
|
09921bb3a4 | ||
|
f614cc3d45 | ||
|
f0ecc9764d | ||
|
d96f2db6dd | ||
|
9ebf04400e | ||
|
894e53e697 | ||
|
a8e73a632d | ||
|
7da0c900ca | ||
|
702ab131e3 | ||
|
50657ddf32 | ||
|
da4500d8f1 | ||
|
fed47df11a | ||
|
4249fddc36 | ||
|
7c356163e2 | ||
|
d47fe392fb | ||
|
565f8cfb7a | ||
|
74cf5cbeae | ||
|
1b83dc2476 | ||
|
133cd1dcf8 | ||
|
0fec17d7c0 | ||
|
8f45d57612 | ||
|
a50723ff72 | ||
|
357bc76882 | ||
|
d5b7fc54e7 | ||
|
dfcff64acb | ||
|
ff979e7ad2 | ||
|
8bececb511 | ||
|
c07604fbf3 | ||
|
8925b42250 | ||
|
6d4ebdcef3 | ||
|
97506fb48d | ||
|
9bc67f9da6 | ||
|
b4e4567c2b | ||
|
a1a7e3d708 | ||
|
6581bd47fc | ||
|
a0553f740d | ||
|
5431b31d84 | ||
|
74acfe5288 | ||
|
49438798b5 | ||
|
db820cb0dc | ||
|
7f1690a840 | ||
|
e0cf47e6d0 | ||
|
32e3310205 | ||
|
de665b5567 | ||
|
d40ccb3e57 | ||
|
dda3d8b284 | ||
|
a30792dbb6 | ||
|
9cce148d83 | ||
|
93ecd61079 | ||
|
e8a86e617b | ||
|
f15a163cdf | ||
|
d22bdd8369 | ||
|
aefd0da181 | ||
|
35d5495d39 | ||
|
9e112fc024 | ||
|
7243754a0d | ||
|
c7b623cc38 | ||
|
963e22ea95 | ||
|
d9ee31fb52 | ||
|
c2a374541f | ||
|
1c1d77c9b8 | ||
|
f21edef43b | ||
|
dc6c195637 | ||
|
dfd4566a04 | ||
|
42555668da | ||
|
92c6361be8 | ||
|
afbb51e78c | ||
|
027d726b65 | ||
|
3e3991cef8 | ||
|
d0c45477f5 | ||
|
7766168c19 | ||
|
c06543cf50 | ||
|
8ea0bc6e58 | ||
|
2c24cb342e | ||
|
79e78980c4 | ||
|
b27e4f4309 | ||
|
9ba6db491e | ||
|
f0266a9973 | ||
|
9fb6d3266e | ||
|
72d1427109 | ||
|
5799397034 | ||
|
ffcbe6a10d | ||
|
cbc2f73a0c | ||
|
7e8fda6b6d | ||
|
39e0d60221 | ||
|
2c7430583c | ||
|
c46dfee214 | ||
|
5d1d7b104e | ||
|
6651b683a0 | ||
|
c950891298 | ||
|
7e277fa948 | ||
|
ee0d92c6d2 | ||
|
ef20ba3701 | ||
|
a11e433754 | ||
|
6334192a95 | ||
|
d8e3e57f88 | ||
|
7fe176ac27 | ||
|
c2a1c4af53 | ||
|
9fadfef0d7 | ||
|
8d4e8b6047 | ||
|
07da75c254 | ||
|
54fd70fc32 | ||
|
5d29a3fdab | ||
|
8bc06eceb8 | ||
|
19aad75242 | ||
|
1a48f1ce43 | ||
|
c6ed92a169 | ||
|
d6eaf6efc2 | ||
|
4fe9597dd5 | ||
|
f4b61b5a77 | ||
|
e5dd2b2ad6 | ||
|
c4453a2a3f | ||
|
d8cf424e0a | ||
|
b10af1ed31 | ||
|
58ab24efdb | ||
|
e861aeec2e | ||
|
f0ae14e4db | ||
|
37302d5995 | ||
|
d78ed4e8f4 | ||
|
19d5e05d60 | ||
|
c207b9b194 | ||
|
f60d5ee1b3 | ||
|
1aefd7d16a | ||
|
c65d9f40e3 | ||
|
d4a3c912fb | ||
|
fc2798f9d1 | ||
|
7b199af3b7 | ||
|
39559b90e2 | ||
|
acde7bd0de | ||
|
b32e568d21 | ||
|
2f5c26f14b | ||
|
25dddc33cd | ||
|
aff66a23dc | ||
|
d03fd02f79 | ||
|
f37fa6f98c | ||
|
434f94800a | ||
|
7304b2c7fb | ||
|
da40f2de14 | ||
|
c975d728e8 | ||
|
ed9817d35b | ||
|
5566e354d4 | ||
|
3456d92220 | ||
|
6f77195ce3 | ||
|
17de1bf7fe | ||
|
f5794e91a2 | ||
|
0f2229d6a5 | ||
|
a9abd84cc4 | ||
|
ce1edda3f2 | ||
|
ba4ebbd107 | ||
|
aab2620548 | ||
|
d9d9d00775 | ||
|
3889224c3e | ||
|
cc3f528eb6 | ||
|
8336b1b518 | ||
|
a327a82085 | ||
|
4ff2063539 | ||
|
0acdb44c14 | ||
|
05d602b0d8 | ||
|
8c2c75d92d | ||
|
fc82feb982 | ||
|
8f70d05f85 | ||
|
21aaadc4b7 | ||
|
a7aceca74a | ||
|
02627e948f | ||
|
cffef71be7 | ||
|
d156cea24d | ||
|
effba1fa47 | ||
|
55fa12355e | ||
|
b66cea5ccc | ||
|
e5eb20bbae | ||
|
e5a6e9ca32 | ||
|
cf4d4a2611 | ||
|
7a7a5d0322 | ||
|
4d6333866d | ||
|
4a5e8f8592 | ||
|
b4fdf8c3c9 | ||
|
3c471f0b7e | ||
|
41109f442a | ||
|
5c408f7fe4 | ||
|
cd525a1df5 | ||
|
836183ecb6 | ||
|
3c1c650ddf | ||
|
45b8af359d | ||
|
4945145b09 | ||
|
f48e992a78 | ||
|
0b136c1c9c | ||
|
f4aeaa6387 | ||
|
0ae5e7ea83 | ||
|
228052360e |
443 changed files with 16996 additions and 5376 deletions
|
@ -1,5 +1,3 @@
|
|||
version: '2.4'
|
||||
|
||||
networks:
|
||||
default:
|
||||
name: neo_go_network
|
||||
|
|
6
.github/workflows/build.yml
vendored
6
.github/workflows/build.yml
vendored
|
@ -37,7 +37,7 @@ jobs:
|
|||
runs-on: ${{matrix.os.name}}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [{ name: ubuntu-22.04, bin-name: linux }, { name: windows-2022, bin-name: windows }, { name: macos-12, bin-name: darwin }]
|
||||
os: [{ name: ubuntu-22.04, bin-name: linux }, { name: windows-2022, bin-name: windows }, { name: macos-14, bin-name: darwin }]
|
||||
arch: [amd64, arm64]
|
||||
exclude:
|
||||
- os: { name: windows-2022, bin-name: windows }
|
||||
|
@ -53,7 +53,7 @@ jobs:
|
|||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
go-version: '1.23'
|
||||
|
||||
- name: Build CLI
|
||||
run: make build
|
||||
|
@ -135,7 +135,7 @@ jobs:
|
|||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
go-version: '1.23'
|
||||
|
||||
- name: Login to DockerHub
|
||||
if: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_image == 'true') }}
|
||||
|
|
67
.github/workflows/tests.yml
vendored
67
.github/workflows/tests.yml
vendored
|
@ -14,19 +14,32 @@ on:
|
|||
|
||||
jobs:
|
||||
lint:
|
||||
name: Lint
|
||||
runs-on: ubuntu-latest
|
||||
name: 'Lint: NeoGo'
|
||||
uses: nspcc-dev/.github/.github/workflows/go-linter.yml@master
|
||||
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version-file: 'go.mod'
|
||||
- name: golangci-lint
|
||||
uses: golangci/golangci-lint-action@v4
|
||||
with:
|
||||
version: latest
|
||||
skip-pkg-cache: true # golangci-lint can't work with this cache enabled, ref. https://github.com/golangci/golangci-lint-action/issues/135.
|
||||
lint_examples:
|
||||
name: 'Lint: examples (${{ matrix.contract }})'
|
||||
uses: nspcc-dev/.github/.github/workflows/go-linter.yml@master
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
contract: [ 'engine', 'events', 'iterator', 'nft-d', 'nft-nd', 'nft-nd-nns', 'oracle',
|
||||
'runtime', 'storage', 'timer', 'token', 'zkp/cubic_circuit', 'zkp/xor_compat']
|
||||
with:
|
||||
workdir: examples/${{ matrix.contract }}
|
||||
|
||||
lint_scripts:
|
||||
name: 'Lint: scripts'
|
||||
uses: nspcc-dev/.github/.github/workflows/go-linter.yml@master
|
||||
with:
|
||||
workdir: scripts
|
||||
|
||||
lint_interops:
|
||||
name: 'Lint: interop'
|
||||
uses: nspcc-dev/.github/.github/workflows/go-linter.yml@master
|
||||
with:
|
||||
workdir: pkg/interop
|
||||
|
||||
gomodcheck:
|
||||
name: Check internal dependencies
|
||||
|
@ -39,6 +52,14 @@ jobs:
|
|||
- name: Check dependencies
|
||||
run: |
|
||||
./scripts/check_deps.sh
|
||||
- name: Check go.mod is tidy
|
||||
run: |
|
||||
go mod tidy
|
||||
if [[ $(git diff --name-only go.* | grep '' -c) != 0 ]]; then
|
||||
echo "go mod tidy should be executed before the merge, following packages are unused or out of date:";
|
||||
git diff go.*;
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
codegencheck:
|
||||
name: Check code generated with 'go generate' is up-to-date
|
||||
|
@ -127,11 +148,11 @@ jobs:
|
|||
- name: Set up Go
|
||||
uses: actions/setup-go@v5
|
||||
with:
|
||||
go-version: '1.22'
|
||||
go-version: '1.23'
|
||||
cache: true
|
||||
|
||||
- name: Write coverage profile
|
||||
run: go test -timeout 15m -v ./... -coverprofile=./coverage.txt -covermode=atomic -coverpkg=./pkg...,./cli/...
|
||||
run: DISABLE_NEOTEST_COVER=1 go test -timeout 15m -v ./... -coverprofile=./coverage.txt -covermode=atomic -coverpkg=./pkg...,./cli/...
|
||||
|
||||
- name: Upload coverage results to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
|
@ -147,25 +168,17 @@ jobs:
|
|||
runs-on: ${{ matrix.os }}
|
||||
strategy:
|
||||
matrix:
|
||||
os: [ubuntu-22.04, windows-2022, macos-12, macos-14]
|
||||
go_versions: [ '1.20', '1.21', '1.22' ]
|
||||
os: [ubuntu-22.04, windows-2022, macos-14]
|
||||
go_versions: [ '1.22', '1.23' ]
|
||||
exclude:
|
||||
# Only latest Go version for Windows and MacOS.
|
||||
- os: windows-2022
|
||||
go_versions: '1.20'
|
||||
- os: windows-2022
|
||||
go_versions: '1.21'
|
||||
- os: macos-12
|
||||
go_versions: '1.20'
|
||||
- os: macos-12
|
||||
go_versions: '1.21'
|
||||
go_versions: '1.22'
|
||||
- os: macos-14
|
||||
go_versions: '1.20'
|
||||
- os: macos-14
|
||||
go_versions: '1.21'
|
||||
go_versions: '1.22'
|
||||
# Exclude latest Go version for Ubuntu as Coverage uses it.
|
||||
- os: ubuntu-22.04
|
||||
go_versions: '1.22'
|
||||
go_versions: '1.23'
|
||||
fail-fast: false
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
|
3
.gitignore
vendored
3
.gitignore
vendored
|
@ -55,3 +55,6 @@ testdata/
|
|||
pkg/vm/testdata/fuzz
|
||||
!pkg/vm/testdata
|
||||
!pkg/wallet/testdata
|
||||
|
||||
# Linter
|
||||
.golangci.yml
|
||||
|
|
2
.gitmodules
vendored
2
.gitmodules
vendored
|
@ -1,4 +1,4 @@
|
|||
[submodule "pkg/vm/testdata/neo-vm"]
|
||||
path = pkg/vm/testdata/neo-vm
|
||||
url = https://github.com/neo-project/neo-vm.git
|
||||
url = https://github.com/neo-project/neo.git
|
||||
branch = master
|
||||
|
|
|
@ -1,75 +0,0 @@
|
|||
# This file contains all available configuration options
|
||||
# with their default values.
|
||||
|
||||
# options for analysis running
|
||||
run:
|
||||
# timeout for analysis, e.g. 30s, 5m, default is 1m
|
||||
timeout: 5m
|
||||
|
||||
# include test files or not, default is true
|
||||
tests: true
|
||||
|
||||
# output configuration options
|
||||
output:
|
||||
# colored-line-number|line-number|json|tab|checkstyle|code-climate, default is "colored-line-number"
|
||||
format: tab
|
||||
|
||||
# all available settings of specific linters
|
||||
linters-settings:
|
||||
exhaustive:
|
||||
# indicates that switch statements are to be considered exhaustive if a
|
||||
# 'default' case is present, even if all enum members aren't listed in the
|
||||
# switch
|
||||
default-signifies-exhaustive: true
|
||||
govet:
|
||||
# report about shadowed variables
|
||||
check-shadowing: false
|
||||
|
||||
linters:
|
||||
enable:
|
||||
# mandatory linters
|
||||
- govet
|
||||
- revive
|
||||
|
||||
# some default golangci-lint linters
|
||||
- errcheck
|
||||
- gosimple
|
||||
- godot
|
||||
- ineffassign
|
||||
- staticcheck
|
||||
- typecheck
|
||||
- unused
|
||||
|
||||
# extra linters
|
||||
# - exhaustive
|
||||
# - goconst
|
||||
# - goerr113
|
||||
# - gomnd
|
||||
# - nonamedreturns
|
||||
# - unparam
|
||||
- bidichk
|
||||
- bodyclose
|
||||
- contextcheck
|
||||
- decorder
|
||||
- durationcheck
|
||||
- errorlint
|
||||
- exportloopref
|
||||
- gofmt
|
||||
- misspell
|
||||
- predeclared
|
||||
- reassign
|
||||
- whitespace
|
||||
- goimports
|
||||
disable-all: true
|
||||
fast: false
|
||||
|
||||
issues:
|
||||
include:
|
||||
- EXC0002 # should have a comment
|
||||
- EXC0003 # test/Test ... consider calling this
|
||||
- EXC0004 # govet
|
||||
- EXC0005 # C-style breaks
|
||||
exclude-rules:
|
||||
- linters:
|
||||
- revive
|
||||
text: "unused-parameter"
|
300
CHANGELOG.md
300
CHANGELOG.md
|
@ -2,6 +2,306 @@
|
|||
|
||||
This document outlines major changes between releases.
|
||||
|
||||
## 0.108.1 "Revalidation" (13 Feb 2025)
|
||||
|
||||
An urgent fix for a very old behavior difference with C# node in Rules witness
|
||||
condition parsing. It suddenly affected testnet compatibility at block 5450030
|
||||
and made the chain unprocessable by NeoGo. Please upgrade to fix it, DB is
|
||||
compatible, no resynchronization required.
|
||||
|
||||
Bugs fixed:
|
||||
* incorrect rule depth limit for Rules witness conditions (#3810)
|
||||
|
||||
## 0.108.0 "Participation" (11 Feb 2025)
|
||||
|
||||
This version is compatible with the C# node 3.7.6, but also contains some
|
||||
Echidna changes preview. Additional RPC extensions are introduced with this
|
||||
release as well as some important fixes and improvements.
|
||||
|
||||
We recommend to check your configurations wrt NeoFS synchronization options,
|
||||
a new set of containers was introduced recently, old ones will eventually be
|
||||
deleted (which won't break syncrhonization, but can delay it a bit). No DB
|
||||
resync is required unless you want to use the new "SaveInvocations" option.
|
||||
|
||||
New features:
|
||||
* "Designation" event in RoleManagement native contract starting from Echidna
|
||||
HF (#3761)
|
||||
* base64Url encoding and decoding support in StdLib native contract starting
|
||||
from Echidna HF (#3761)
|
||||
* NEO candidate registration via NEP-27 payment starting from Echidna HF (#3700)
|
||||
* ArchivalNode P2P capability (#3778)
|
||||
* NEP-26 and NEP-27 support everywhere (#3792)
|
||||
* "SaveInvocations" node parameter that allows to store more detailed
|
||||
contract invocation data and retrieve it via RPC (#3569)
|
||||
* "getblocknotifications" RPC API allowing to fetch filtered notifications
|
||||
from all block execution contexts (#3805)
|
||||
|
||||
Behavior changes:
|
||||
* updated "upload-bin" command defaults (#3760)
|
||||
* updated NeoFS containers for all networks (#3759)
|
||||
* additional AllowNotify call flag for some NEO methods starting from Echidna
|
||||
HF (#3761)
|
||||
* Dump*Slot methods removed from vm.Context (#3806)
|
||||
|
||||
Improvements:
|
||||
* golang.org/x/crypto update from 0.26.0 to 0.31.0 (#3765)
|
||||
* neotest can load contracts from NEF/manifest files now (#3771)
|
||||
* more accurate memory management in persisting/processing cycles preventing
|
||||
OOM conditions in most cases (#3787)
|
||||
* RPC bindings now have ToStackItem and ToSCParameter methods for structures
|
||||
(#3794, #3796, #3804)
|
||||
* dBFT 0.3.2 with improved timers (#3799)
|
||||
* github.com/consensys/gnark update from 0.11.0 to 0.12.0 (#3800)
|
||||
* ability to fetch headers from NeoFS (#3789)
|
||||
|
||||
Bugs fixed:
|
||||
* potentially incorrect handling of misconfigured NeoFS endpoints (#3758)
|
||||
* duplicate index objects are no longer an error for "upload-bin" (#3763)
|
||||
* old transfer data removal problem in RemoveUntraceableBlocks configuration,
|
||||
0.107.2 regression (#3787)
|
||||
* zkpbinding module producing code that can't be compiled, 0.107.0 regression
|
||||
(#3802)
|
||||
|
||||
## 0.107.2 "Obliteration" (13 Dec 2024)
|
||||
|
||||
One more compatible patch-release that introduces `RemoveUntraceableHeaders`
|
||||
application-level extension allowing to remove untraceable block headers from the DB.
|
||||
This feature significantly reduces the database size, but for now it is supported in
|
||||
an experimental mode, use it with care. Other than that, this release includes a fix
|
||||
of BlockFetcher service that may hang on retry of NeoFS requests preventing the node
|
||||
from syncing. Also, an improved algorithm of blocks uploading and extended list of
|
||||
block and index file attributes are supported for `upload-bin` CLI command.
|
||||
|
||||
No configuration update or DB resync is required. However, starting from this release
|
||||
`NeoFSBlockFetcher` application configuration section is backed by default values for
|
||||
every parameter except `Addresses` and `ContainerID`, hence if you don't like too
|
||||
chatty configuration files, feel free to remove all optional parameters.
|
||||
|
||||
New features:
|
||||
* untraceable headers removal (#3750)
|
||||
|
||||
Behavior changes:
|
||||
* add `BlockTime` attribute to block objects stored in NeoFS block storage (#3749)
|
||||
* use `Timestamp` attribute to hold object creation time for block and index objects
|
||||
stored in NeoFS block storage (#3749)
|
||||
* extended debug logs for `upload-bin` CLI command (#3751)
|
||||
|
||||
Improvements:
|
||||
* embed default UnitTestNet node configuration (#3696)
|
||||
* NeoFS SDK dependency upgrade (#3725, #3756)
|
||||
* dependent packages updates (#3746, #3747, #3748)
|
||||
* refactor and speed up `upload-bin` CLI command (#3735)
|
||||
* backup NeoFS BlockFetcher configuration with default values (#3742)
|
||||
* reuse more of built-in NeoFS SDK functionality in `upload-bin` CLI handler (#3749)
|
||||
|
||||
Bugs fixed:
|
||||
* NeoFS BlockFetcher sometimes is hanging during the node startup (#3736)
|
||||
* RPC server timers are improperly drained (#3737)
|
||||
* basic unit test chain restore configuration (#3696)
|
||||
|
||||
## 0.107.1 "Narrativization" (06 Dec 2024)
|
||||
|
||||
An urgent version that fixes the problem of intensive CPU usage caused by improper
|
||||
NeoFS BlockFetcher shutdown on the node's sync process completion and magnified by
|
||||
additional bug at the peer discovery level.
|
||||
|
||||
No configuration changes or DB resync is required. It is highly recommended to update
|
||||
from 0.107.0 as soon as possible since described problems affect the speed of blocks
|
||||
processing and the overall node functionality.
|
||||
|
||||
Behavior changes:
|
||||
* explicitly enable the list of stable hardforks in default NeoFS testnet
|
||||
configuration (#3722)
|
||||
|
||||
Improvements:
|
||||
* decrease NeoFS storage nodes deal timeout for NeoFS BlockFetcher (#3723)
|
||||
* adjust optimal number of peers for networks with small peer count (#3727)
|
||||
* don't enable unstable hardforks by default (#3724)
|
||||
|
||||
Bugs fixed:
|
||||
* "unexpected empty payload: CMDVersion" error on peer disconnection (#3726)
|
||||
* NeoFS BlockFetcher shutdown (#3728)
|
||||
* connected peers count is not respected on attempt to gather more node addresses
|
||||
(#3730)
|
||||
|
||||
## 0.107.0 "Mongrelization" (03 Dec 2024)
|
||||
|
||||
A large update that introduces a major node extension: NeoFS BlockFetcher service and
|
||||
`util upload-bin` CLI command implemented as a part of [NeoFS snapshot storage
|
||||
proposal](https://github.com/neo-project/neo/issues/3463). BlockFetcher service, as
|
||||
an alternative to P2P synchronization mechanism, allows to download blocks and sync
|
||||
chain from block dumps stored in NeoFS. Starting from this release, NeoSPCC team
|
||||
maintains chain dumps in NeoFS for N3 and NeoFS public networks. The default NeoGo
|
||||
node configuration for these networks has been changed to use NeoFS BlockFetcher as
|
||||
a synchronization mechanism prior to P2P synchronization. Other than that, this
|
||||
release includes NEP-24 standard support at both compiler and SC bindings generator
|
||||
levels. A large number of tiny user-facing enhancements is rolled out for RPC
|
||||
actor/ivoker, `neotest` and `unwrap` packages as far as for CLI utilities. Also,
|
||||
this release contains a set of NeoGo VM bug fixes inspired by differential VM fuzzing
|
||||
study conducted by our external contributor @Slava0135 and a set of tiny VM CLI
|
||||
enhancements introduced by @ixje.
|
||||
|
||||
Some deprecated functionality has been removed according to the schedule, see more
|
||||
details in the `Behaviour changes` section. Also, for those node operators who would
|
||||
like to check out our new NeoFS BlockFetcher node extension, we'd recommend to add
|
||||
corresponding section to the node's configuration (see the default node configuration
|
||||
for N3/NeoFS networks for example).
|
||||
|
||||
This release is fully compatible with 3.7.5 version of C# node, no DB resync is
|
||||
needed.
|
||||
|
||||
New features:
|
||||
* new NeoFS BlockFetcher service that allows to sync node from NeoFS chain dump
|
||||
(#3515, #3636, #3637, #3632, #3691, #3706, #3668, #3713)
|
||||
* new `util upload-bin` CLI command that allows to upload blocks from Neo chains to
|
||||
NeoFS (#3578, #3625, #3626, #3633, #3637, #3638, #3643, #3650, #3662, #3684,
|
||||
#3686, #3691)
|
||||
* new `delete` and `ib` VM CLI commands for brealpoints management (#3674)
|
||||
* NEP-24 standard support (#3560)
|
||||
* Echidna hardfork introduced, but not yet enabled (please, note that this is only
|
||||
a preview that includes a part of scheduled changes and will be changed in an
|
||||
incompatible way before the full support, hence, this hardfork may be enabled for
|
||||
experimental purpose only) (#3554)
|
||||
|
||||
Behavior changes:
|
||||
* neotest's AddSystemFee and TestInvoke are bound to Executor state (#3551)
|
||||
* getversion RPC response is extended with seed list and standby committee (#3540)
|
||||
* support only two latest versions of Go instead of three (#3567)
|
||||
* some deprecated functionality is dropped: unmarshalling code for the old
|
||||
`getpeers` RPC response, `NEOBalance` stackitem deserializer compatibility code,
|
||||
`serv_node_version` Prometheus gauge metric, outdated RPC error codes support,
|
||||
block-based web-socket transaction awaiting (#3690)
|
||||
|
||||
Improvements:
|
||||
* documentation updates (#3545, #3663, #3666, #3678, #3683, #3708)
|
||||
* netmode package is extended with public NeoFS chain IDs (#3539)
|
||||
* dBFT library upgrades (#3541, #3711)
|
||||
* migration to Docker Compose V2 (#3547)
|
||||
* system fee required for contract deployment in neotest is precisely calculated
|
||||
(#3551)
|
||||
* ability to customize awaiting options for PollingBased RPC waiter (#3556)
|
||||
* Go 1.23 support, bump minimum required Go version up to Go 1.22 (#3567)
|
||||
* a set of dependent libraries upgrades (#3570)
|
||||
* extend the list of supported SC parameters for RPC actor/invoker (#3583)
|
||||
* Null stackitem result handling for autogenerated RPC bindings (#3584)
|
||||
* macos-12 support is removed, macos-14 support is added (#3657)
|
||||
* allow to get NEP-11/NEP-17 balances via CLI using account address only (#3659)
|
||||
* expose VM slot getters (#3677)
|
||||
* add `unwrap.ErrNull` error to handle Null stackitem returned (#3695)
|
||||
* extend web-socket notification subsystem with notification parameter filters
|
||||
(#3689)
|
||||
* explicitly prohibit unknown configuration fields for `contract generate-*` CLI
|
||||
commands (#3708)
|
||||
* extend compiled smart contract identifier in neotest cache (#3709)
|
||||
* move `neogo_version` metric out of `network` package (#3712)
|
||||
|
||||
Bugs fixed:
|
||||
* fees of ditched transaction are not cleared from mempool (#3537)
|
||||
* extension of SC permission descriptor doesn't clear wildcard status (#3544)
|
||||
* example of NeoGo VM script fails VM execution (#3593)
|
||||
* a call to `getunclaimedGas` of native Neo contract for account with zero balance
|
||||
results in VM failure (#3589)
|
||||
* `MODMUL` VM opcode handler returns wrong results for negative arguments (#3599)
|
||||
* neotest coverage extension panics on attempt to collect coverage for contract with
|
||||
missing debug information (#3600)
|
||||
* `MODPOW`VM opcode handler returns wrong results for negative base (#3649)
|
||||
* node panic on SIGHUP (#3661)
|
||||
* HTTP return code of RPC requests diverges from C# RPC server's behaviour (#3665)
|
||||
* a set of bugs in unit tests (#3442, #3680)
|
||||
* `POPITEM` VM opcode handler counts stack references improperly (#3688)
|
||||
* `PACKMAP` VM opcode handler ignores duplicating map keys (#3685)
|
||||
* chain restore from genesis block fails with StateRootInHeader extension enabled
|
||||
(#3697)
|
||||
* hardfork-dependent methods are not included into native contract metadata starting
|
||||
from the hardfork height (#3704)
|
||||
* outdated keyword usage in Dockerfiles (#3710)
|
||||
|
||||
## 0.106.3 "Lyophilization" (29 Jul 2024)
|
||||
|
||||
This 3.7.5-compatible version includes a number of important fixes, so please
|
||||
upgrade your nodes. Some minor extensions were also added.
|
||||
|
||||
Resynchronization (or state reset) is required for testnet (because of a bug
|
||||
leading to state difference since 4368840), but not required for mainnet.
|
||||
|
||||
New features:
|
||||
* embedded mainnet/testnet/NeoFS node configuration files (#3477, #3504)
|
||||
|
||||
Behavior changes:
|
||||
* CLI no longer panics if error occurs (#3495)
|
||||
* MaxTraceableBlocks is 17280 now for NeoFS networks (#3518)
|
||||
* minimal default RPC `SessionExpirationTime` is 5s now (#3529)
|
||||
|
||||
Improvements:
|
||||
* RPC actor interface extension with WaitSuccess method (#3491)
|
||||
* Signers() API for RPC invokers (#3492)
|
||||
* SignerAccounts() API for RPC actors (#3492)
|
||||
* getpeers RPC extension with the user agent and last known block height data
|
||||
(#3481)
|
||||
* OnExecHook() API for VM (#3460)
|
||||
* more details in witness verification error message (#3508)
|
||||
* CLI help and error string format unification (#3495, #3520)
|
||||
* CLI library (github.com/urfave/cli) upgrade to 2.27.2 (from v1 API, #3495)
|
||||
* microoptimization of extensible sender list calculation (#3500)
|
||||
* microoptimization of chain dump code (#3514)
|
||||
* documentation and error messages (#3526, #3527)
|
||||
|
||||
Bugs fixed:
|
||||
* RPC `SessionExpirationTime` could be zero in some configurations (#3529)
|
||||
* panic in WSClient unsubscription code in some multithreaded cases (#3532)
|
||||
* missing PrimaryIndex in Ledger's getBlock() result (#3534)
|
||||
* contract manifests with null groups were accepted (#3523)
|
||||
* contract manifests with invalid features were accepted (#3523)
|
||||
* contract manifests with null trusts were accepted (#3523)
|
||||
* WSClient deadlock in some disconnection cases (#3535)
|
||||
|
||||
## 0.106.2 "Keratinization" (13 Jun 2024)
|
||||
|
||||
A 3.7.5-compatible version introducing new Domovoi hardfork that brings two fixes to
|
||||
the protocol: using executing contract state to check contract call permissions
|
||||
(included into this NeoGo release) and proper VM items refcounting for
|
||||
System.Runtime.GetNotifications handler (not included into this NeoGo release because
|
||||
we've never had this bug). Since the second bug is C#-specific and does not lead to
|
||||
the state differences in mainnet/testnet, we've decided not to break the NeoGo node
|
||||
to follow pre-Domovoi C# node implementation. Thus, differences in application logs
|
||||
for several T5 transactions before Domovoi hardfork are expected and won't be fixed.
|
||||
|
||||
Please, ensure your node configuration includes the Domovoi hardfork. No DB
|
||||
resynchronisation is required.
|
||||
|
||||
New features:
|
||||
* Domovoi hardfork scheduled for 5570000 block of mainnet and 4144000 block of T5
|
||||
testnet (#3476, #3473, #3486, #3487)
|
||||
|
||||
Behavior changes:
|
||||
* hide node logs timestamp if the node is running not in TTY (#3468)
|
||||
* distinguish log level for various node peer disconnection reasons (#3469)
|
||||
|
||||
Improvements:
|
||||
* ensure NeoFS nodes are configured when processing NeoFS oracle requests (#3455)
|
||||
* NeoFS SDK dependency upgrade (#3483)
|
||||
* ensure System.Runtime.GetNotifications handler can't break the MaxStackSize
|
||||
constraint before and after Domovoi hardfork (#3485)
|
||||
|
||||
Bugs fixed:
|
||||
* deployed contract script is included into wallet's account (#3470)
|
||||
* updated contract state is used to verify contract call permissions before the
|
||||
Domovoi hardfork (#3473)
|
||||
|
||||
## 0.106.1 "Implication" (3 Jun 2024)
|
||||
|
||||
An urgent release that fixes mainnet state difference at block 5462944 which halts
|
||||
blocks processing starting from the height 5468658. This release requires full node
|
||||
DB resynchronization for mainnet nodes. T5 testnet DB state is not affected by this
|
||||
bug (at least up to the current 4087361 height). Thus, DB resynchronisation may be
|
||||
skipped for testnet nodes. No configuration changes implied.
|
||||
|
||||
Bugs fixed:
|
||||
* mainnet state difference at block 5462944 caused by runtime notification
|
||||
permissions check against the updated contract state instead of executing state
|
||||
(#3472)
|
||||
* unused neofs-contract dependency in the node modules (#3458)
|
||||
|
||||
## 0.106.0 "Zephyranthes" (21 May 2024)
|
||||
|
||||
We're rolling out a large set of updates including all of Neo 3.7.4 protocol changes:
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Builder image
|
||||
# Keep go version in sync with Build GA job.
|
||||
FROM golang:1.22-alpine as builder
|
||||
FROM golang:1.23-alpine AS builder
|
||||
|
||||
# Display go version for information purposes.
|
||||
RUN go version
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
# Builder image
|
||||
# Keep go version in sync with Build GA job.
|
||||
FROM golang:1.22.0-windowsservercore-ltsc2022 as builder
|
||||
FROM golang:1.23.0-windowsservercore-ltsc2022 AS builder
|
||||
|
||||
COPY . /neo-go
|
||||
|
||||
|
|
24
Makefile
24
Makefile
|
@ -3,7 +3,7 @@ REPONAME = "neo-go"
|
|||
NETMODE ?= "privnet"
|
||||
BINARY=neo-go
|
||||
BINARY_PATH=./bin/$(BINARY)$(shell go env GOEXE)
|
||||
GO_VERSION ?= 1.20
|
||||
GO_VERSION ?= 1.23
|
||||
DESTDIR = ""
|
||||
SYSCONFIGDIR = "/etc"
|
||||
BINDIR = "/usr/bin"
|
||||
|
@ -22,6 +22,11 @@ BUILD_FLAGS = "-X '$(REPO)/pkg/config.Version=$(VERSION)' -X '$(REPO)/cli/smartc
|
|||
|
||||
IMAGE_REPO=nspccdev/neo-go
|
||||
|
||||
DISABLE_NEOTEST_COVER=1
|
||||
|
||||
ROOT_DIR:=$(dir $(realpath $(firstword $(MAKEFILE_LIST))))
|
||||
GOMODDIRS=$(dir $(shell find $(ROOT_DIR) -name go.mod))
|
||||
|
||||
# All of the targets are phony here because we don't really use make dependency
|
||||
# tracking for files
|
||||
.PHONY: build $(BINARY) deps image docker/$(BINARY) image-latest image-push image-push-latest clean-cluster \
|
||||
|
@ -107,8 +112,13 @@ test:
|
|||
vet:
|
||||
@go vet ./...
|
||||
|
||||
lint:
|
||||
@golangci-lint run
|
||||
.golangci.yml:
|
||||
curl -L -o $@ https://github.com/nspcc-dev/.github/raw/master/.golangci.yml
|
||||
|
||||
lint: .golangci.yml
|
||||
@for dir in $(GOMODDIRS); do \
|
||||
(cd "$$dir" && golangci-lint run --config $(ROOT_DIR)/$< | sed -r "s,^,$$dir," | sed -r "s,^$(ROOT_DIR),,") \
|
||||
done
|
||||
|
||||
fmt:
|
||||
@gofmt -l -w -s $$(find . -type f -name '*.go'| grep -v "/vendor/")
|
||||
|
@ -131,19 +141,19 @@ env_image:
|
|||
env_up:
|
||||
@echo "=> Bootup environment"
|
||||
@echo " Docker-compose file: $(DC_FILE)"
|
||||
@docker-compose -f $(DC_FILE) up -d node_one node_two node_three node_four
|
||||
@docker compose -f $(DC_FILE) up -d node_one node_two node_three node_four
|
||||
|
||||
env_single:
|
||||
@echo "=> Bootup environment"
|
||||
@docker-compose -f $(DC_FILE) up -d node_single
|
||||
@docker compose -f $(DC_FILE) up -d node_single
|
||||
|
||||
env_down:
|
||||
@echo "=> Stop environment"
|
||||
@docker-compose -f $(DC_FILE) down
|
||||
@docker compose -f $(DC_FILE) down
|
||||
|
||||
env_restart:
|
||||
@echo "=> Stop and start environment"
|
||||
@docker-compose -f $(DC_FILE) restart
|
||||
@docker compose -f $(DC_FILE) restart
|
||||
|
||||
env_clean: env_down
|
||||
@echo "=> Cleanup environment"
|
||||
|
|
|
@ -51,7 +51,7 @@ NeoGo, `:latest` points to the latest release) or build yourself.
|
|||
|
||||
### Building
|
||||
|
||||
Building NeoGo requires Go 1.20+ and `make`:
|
||||
Building NeoGo requires Go 1.22+ and `make`:
|
||||
|
||||
```
|
||||
make
|
||||
|
|
49
ROADMAP.md
49
ROADMAP.md
|
@ -7,11 +7,10 @@ functionality.
|
|||
## Versions 0.7X.Y (as needed)
|
||||
* Neo 2.0 support (bug fixes, minor functionality additions)
|
||||
|
||||
## Version 0.107.0 (~Jun-Jul 2024)
|
||||
## Version 0.109.0 (~April 2025)
|
||||
* protocol updates
|
||||
* bug fixes
|
||||
* node resynchronisation from local DB
|
||||
* CLI library upgrade
|
||||
* NeoFS-based synchronization
|
||||
|
||||
## Version 1.0 (2024, TBD)
|
||||
* stable version
|
||||
|
@ -25,47 +24,3 @@ APIs/commands/configurations will be removed and here is a list of scheduled
|
|||
breaking changes. Consider changing your code/scripts/configurations if you're
|
||||
using anything mentioned here.
|
||||
|
||||
## GetPeers RPC server response type changes and RPC client support
|
||||
|
||||
GetPeers RPC command returns a list of Peers where the port type has changed from
|
||||
string to uint16 to match C#. The RPC client currently supports unmarshalling both
|
||||
formats.
|
||||
|
||||
Removal of Peer unmarshalling with string based ports is scheduled for Jun-Jul 2024
|
||||
(~0.107.0 release).
|
||||
|
||||
## `NEOBalance` from stack item
|
||||
|
||||
We check struct items count before convert LastGasPerVote to let RPC client be compatible with
|
||||
old versions.
|
||||
|
||||
Removal of this compatiblility code is scheduled for Jun-Jul 2024.
|
||||
|
||||
## `serv_node_version` Prometheus gauge metric
|
||||
|
||||
This metric is replaced by the new `neogo_version` and `server_id` Prometheus gauge
|
||||
metrics with proper version formatting. `neogo_version` contains NeoGo version
|
||||
hidden under `version` label and `server_id` contains network server ID hidden
|
||||
under `server_id` label.
|
||||
|
||||
Removal of `serv_node_version` is scheduled for Jun-Jul 2024 (~0.107.0 release).
|
||||
|
||||
## RPC error codes returned by old versions and C#-nodes
|
||||
|
||||
NeoGo retains certain deprecated error codes: `neorpc.ErrCompatGeneric`,
|
||||
`neorpc.ErrCompatNoOpenedWallet`. They returned by nodes not compliant with the
|
||||
neo-project/proposals#156 (NeoGo pre-0.102.0 and all known C# versions).
|
||||
|
||||
Removal of the deprecated RPC error codes is planned for Jun-Jul 2024 (~0.107.0
|
||||
release).
|
||||
|
||||
## Block based web-socket waiter transaction awaiting
|
||||
|
||||
Web-socket RPC based `waiter.EventWaiter` uses `header_of_added_block` notifications
|
||||
subscription to manage transaction awaiting. To support old NeoGo RPC servers
|
||||
(older than 0.105.0) that do not have block headers subscription ability,
|
||||
event-based waiter fallbacks to the old way of block monitoring with
|
||||
`block_added` notifications subscription.
|
||||
|
||||
Removal of stale RPC server compatibility code from `waiter.EventWaiter` is
|
||||
scheduled for Jun-Jul 2024 (~0.107.0 release).
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/cli/vm"
|
||||
"github.com/nspcc-dev/neo-go/cli/wallet"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func versionPrinter(c *cli.Context) {
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -138,16 +138,16 @@ const (
|
|||
|
||||
// GetSignersFromContext returns signers parsed from context args starting
|
||||
// from the specified offset.
|
||||
func GetSignersFromContext(ctx *cli.Context, offset int) ([]transaction.Signer, *cli.ExitError) {
|
||||
func GetSignersFromContext(ctx *cli.Context, offset int) ([]transaction.Signer, cli.ExitCoder) {
|
||||
args := ctx.Args()
|
||||
var (
|
||||
signers []transaction.Signer
|
||||
err error
|
||||
)
|
||||
if args.Present() && len(args) > offset {
|
||||
signers, err = ParseSigners(args[offset:])
|
||||
if args.Present() && args.Len() > offset {
|
||||
signers, err = ParseSigners(args.Slice()[offset:])
|
||||
if err != nil {
|
||||
return nil, cli.NewExitError(err, 1)
|
||||
return nil, cli.Exit(err, 1)
|
||||
}
|
||||
}
|
||||
return signers, nil
|
||||
|
@ -224,13 +224,14 @@ func parseCosigner(c string) (transaction.Signer, error) {
|
|||
|
||||
res.AllowedGroups = append(res.AllowedGroups, pub)
|
||||
}
|
||||
default:
|
||||
}
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetDataFromContext returns data parameter from context args.
|
||||
func GetDataFromContext(ctx *cli.Context) (int, any, *cli.ExitError) {
|
||||
func GetDataFromContext(ctx *cli.Context) (int, any, cli.ExitCoder) {
|
||||
var (
|
||||
data any
|
||||
offset int
|
||||
|
@ -239,17 +240,17 @@ func GetDataFromContext(ctx *cli.Context) (int, any, *cli.ExitError) {
|
|||
)
|
||||
args := ctx.Args()
|
||||
if args.Present() {
|
||||
offset, params, err = ParseParams(args, true)
|
||||
offset, params, err = ParseParams(args.Slice(), true)
|
||||
if err != nil {
|
||||
return offset, nil, cli.NewExitError(fmt.Errorf("unable to parse 'data' parameter: %w", err), 1)
|
||||
return offset, nil, cli.Exit(fmt.Errorf("unable to parse 'data' parameter: %w", err), 1)
|
||||
}
|
||||
if len(params) > 1 {
|
||||
return offset, nil, cli.NewExitError("'data' should be represented as a single parameter", 1)
|
||||
return offset, nil, cli.Exit("'data' should be represented as a single parameter", 1)
|
||||
}
|
||||
if len(params) != 0 {
|
||||
data, err = smartcontract.ExpandParameterToEmitable(params[0])
|
||||
if err != nil {
|
||||
return offset, nil, cli.NewExitError(fmt.Sprintf("failed to convert 'data' to emitable type: %s", err.Error()), 1)
|
||||
return offset, nil, cli.Exit(fmt.Sprintf("failed to convert 'data' to emitable type: %s", err.Error()), 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -258,9 +259,9 @@ func GetDataFromContext(ctx *cli.Context) (int, any, *cli.ExitError) {
|
|||
|
||||
// EnsureNone returns an error if there are any positional arguments present.
|
||||
// It can be used to check for them in commands that don't accept arguments.
|
||||
func EnsureNone(ctx *cli.Context) *cli.ExitError {
|
||||
func EnsureNone(ctx *cli.Context) cli.ExitCoder {
|
||||
if ctx.Args().Present() {
|
||||
return cli.NewExitError("additional arguments given while this command expects none", 1)
|
||||
return cli.Exit(fmt.Errorf("additional arguments given while this command expects none"), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -348,3 +349,14 @@ func GetSignersAccounts(senderAcc *wallet.Account, wall *wallet.Wallet, signers
|
|||
}
|
||||
return signersAccounts, nil
|
||||
}
|
||||
|
||||
// EnsureNotEmpty returns a function that checks if the flag with the given name
|
||||
// is not empty.
|
||||
func EnsureNotEmpty(flagName string) func(*cli.Context, string) error {
|
||||
return func(ctx *cli.Context, name string) error {
|
||||
if ctx.String(flagName) == "" {
|
||||
return cli.Exit(fmt.Errorf("required flag --%s is empty", flagName), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Address is a wrapper for a Uint160 with flag.Value methods.
|
||||
|
@ -16,11 +16,15 @@ type Address struct {
|
|||
Value util.Uint160
|
||||
}
|
||||
|
||||
// AddressFlag is a flag with type string.
|
||||
// AddressFlag is a flag with type Uint160.
|
||||
type AddressFlag struct {
|
||||
Name string
|
||||
Usage string
|
||||
Value Address
|
||||
Name string
|
||||
Usage string
|
||||
Value Address
|
||||
Aliases []string
|
||||
Required bool
|
||||
Hidden bool
|
||||
Action func(*cli.Context, string) error
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -37,7 +41,7 @@ func (a Address) String() string {
|
|||
func (a *Address) Set(s string) error {
|
||||
addr, err := ParseAddress(s)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
a.IsSet = true
|
||||
a.Value = addr
|
||||
|
@ -63,9 +67,9 @@ func (f AddressFlag) IsSet() bool {
|
|||
// (for usage defaults).
|
||||
func (f AddressFlag) String() string {
|
||||
var names []string
|
||||
eachName(f.Name, func(name string) {
|
||||
for _, name := range f.Names() {
|
||||
names = append(names, getNameHelp(name))
|
||||
})
|
||||
}
|
||||
|
||||
return strings.Join(names, ", ") + "\t" + f.Usage
|
||||
}
|
||||
|
@ -77,17 +81,57 @@ func getNameHelp(name string) string {
|
|||
return fmt.Sprintf("--%s value", name)
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag.
|
||||
func (f AddressFlag) GetName() string {
|
||||
return f.Name
|
||||
// Names returns the names of the flag.
|
||||
func (f AddressFlag) Names() []string {
|
||||
return cli.FlagNames(f.Name, f.Aliases)
|
||||
}
|
||||
|
||||
// IsRequired returns whether the flag is required.
|
||||
func (f AddressFlag) IsRequired() bool {
|
||||
return f.Required
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false.
|
||||
func (f AddressFlag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
}
|
||||
|
||||
// TakesValue returns true of the flag takes a value, otherwise false.
|
||||
func (f AddressFlag) TakesValue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag.
|
||||
func (f AddressFlag) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment.
|
||||
// Ignores errors.
|
||||
func (f AddressFlag) Apply(set *flag.FlagSet) {
|
||||
eachName(f.Name, func(name string) {
|
||||
func (f AddressFlag) Apply(set *flag.FlagSet) error {
|
||||
for _, name := range f.Names() {
|
||||
set.Var(&f.Value, name, f.Usage)
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// RunAction executes flag action if set.
|
||||
func (f AddressFlag) RunAction(c *cli.Context) error {
|
||||
if f.Action != nil {
|
||||
return f.Action(c, address.Uint160ToString(f.Value.Value))
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation.
|
||||
func (f AddressFlag) GetValue() string {
|
||||
return address.Uint160ToString(f.Value.Value)
|
||||
}
|
||||
|
||||
// Get returns the flag’s value in the given Context.
|
||||
func (f AddressFlag) Get(ctx *cli.Context) Address {
|
||||
adr := ctx.Generic(f.Name).(*Address)
|
||||
return *adr
|
||||
}
|
||||
|
||||
// ParseAddress parses a Uint160 from either an LE string or an address.
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func TestParseAddress(t *testing.T) {
|
||||
|
@ -109,22 +110,102 @@ func TestAddress_getNameHelp(t *testing.T) {
|
|||
require.Equal(t, "--flag value", getNameHelp("flag"))
|
||||
}
|
||||
|
||||
func TestAddressFlag_GetName(t *testing.T) {
|
||||
func TestAddressFlag_Names(t *testing.T) {
|
||||
flag := AddressFlag{
|
||||
Name: "my flag",
|
||||
Name: "flag",
|
||||
Aliases: []string{"my"},
|
||||
}
|
||||
|
||||
require.Equal(t, "my flag", flag.GetName())
|
||||
require.Equal(t, []string{"flag", "my"}, flag.Names())
|
||||
}
|
||||
|
||||
func TestAddress(t *testing.T) {
|
||||
f := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
f.SetOutput(io.Discard) // don't pollute test output
|
||||
addr := AddressFlag{Name: "addr, a"}
|
||||
addr.Apply(f)
|
||||
addr := AddressFlag{Name: "addr", Aliases: []string{"a"}}
|
||||
err := addr.Apply(f)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.Parse([]string{"--addr", "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR"}))
|
||||
require.Equal(t, "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR", f.Lookup("a").Value.String())
|
||||
require.NoError(t, f.Parse([]string{"-a", "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR"}))
|
||||
require.Equal(t, "NRHkiY2hLy5ypD32CKZtL6pNwhbFMqDEhR", f.Lookup("a").Value.String())
|
||||
require.Error(t, f.Parse([]string{"--addr", "kek"}))
|
||||
}
|
||||
|
||||
func TestAddressFlag_IsRequired(t *testing.T) {
|
||||
flag := AddressFlag{Required: true}
|
||||
require.True(t, flag.IsRequired())
|
||||
|
||||
flag.Required = false
|
||||
require.False(t, flag.IsRequired())
|
||||
}
|
||||
|
||||
func TestAddressFlag_IsVisible(t *testing.T) {
|
||||
flag := AddressFlag{Hidden: false}
|
||||
require.True(t, flag.IsVisible())
|
||||
|
||||
flag.Hidden = true
|
||||
require.False(t, flag.IsVisible())
|
||||
}
|
||||
|
||||
func TestAddressFlag_TakesValue(t *testing.T) {
|
||||
flag := AddressFlag{}
|
||||
require.True(t, flag.TakesValue())
|
||||
}
|
||||
|
||||
func TestAddressFlag_GetUsage(t *testing.T) {
|
||||
flag := AddressFlag{Usage: "Specify the address"}
|
||||
require.Equal(t, "Specify the address", flag.GetUsage())
|
||||
}
|
||||
|
||||
func TestAddressFlag_GetValue(t *testing.T) {
|
||||
addrValue := util.Uint160{1, 2, 3}
|
||||
flag := AddressFlag{Value: Address{IsSet: true, Value: addrValue}}
|
||||
expectedStr := address.Uint160ToString(addrValue)
|
||||
require.Equal(t, expectedStr, flag.GetValue())
|
||||
}
|
||||
|
||||
func TestAddressFlag_Get(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", flag.ContinueOnError)
|
||||
ctx := cli.NewContext(app, set, nil)
|
||||
|
||||
flag := AddressFlag{
|
||||
Name: "testAddress",
|
||||
Value: Address{Value: util.Uint160{1, 2, 3}, IsSet: false},
|
||||
}
|
||||
|
||||
set.Var(&flag.Value, "testAddress", "test usage")
|
||||
require.NoError(t, set.Set("testAddress", address.Uint160ToString(util.Uint160{3, 2, 1})))
|
||||
|
||||
expected := flag.Get(ctx)
|
||||
require.True(t, expected.IsSet)
|
||||
require.Equal(t, util.Uint160{3, 2, 1}, expected.Value)
|
||||
}
|
||||
|
||||
func TestAddressFlag_RunAction(t *testing.T) {
|
||||
called := false
|
||||
action := func(ctx *cli.Context, s string) error {
|
||||
called = true
|
||||
require.Equal(t, address.Uint160ToString(util.Uint160{1, 2, 3}), s)
|
||||
return nil
|
||||
}
|
||||
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", flag.ContinueOnError)
|
||||
ctx := cli.NewContext(app, set, nil)
|
||||
|
||||
flag := AddressFlag{
|
||||
Action: action,
|
||||
Value: Address{IsSet: true, Value: util.Uint160{4, 5, 6}},
|
||||
}
|
||||
|
||||
expected := address.Uint160ToString(util.Uint160{1, 2, 3})
|
||||
set.Var(&flag.Value, "testAddress", "test usage")
|
||||
require.NoError(t, set.Set("testAddress", expected))
|
||||
require.Equal(t, expected, flag.GetValue())
|
||||
|
||||
err := flag.RunAction(ctx)
|
||||
require.NoError(t, err)
|
||||
require.True(t, called)
|
||||
}
|
||||
|
|
|
@ -5,7 +5,7 @@ import (
|
|||
"strings"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// Fixed8 is a wrapper for a Uint160 with flag.Value methods.
|
||||
|
@ -15,9 +15,13 @@ type Fixed8 struct {
|
|||
|
||||
// Fixed8Flag is a flag with type string.
|
||||
type Fixed8Flag struct {
|
||||
Name string
|
||||
Usage string
|
||||
Value Fixed8
|
||||
Name string
|
||||
Usage string
|
||||
Value Fixed8
|
||||
Aliases []string
|
||||
Required bool
|
||||
Hidden bool
|
||||
Action func(*cli.Context, string) error
|
||||
}
|
||||
|
||||
var (
|
||||
|
@ -34,7 +38,7 @@ func (a Fixed8) String() string {
|
|||
func (a *Fixed8) Set(s string) error {
|
||||
f, err := fixedn.Fixed8FromString(s)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
a.Value = f
|
||||
return nil
|
||||
|
@ -45,31 +49,75 @@ func (a *Fixed8) Fixed8() fixedn.Fixed8 {
|
|||
return a.Value
|
||||
}
|
||||
|
||||
// IsSet checks if flag was set to a non-default value.
|
||||
func (f Fixed8Flag) IsSet() bool {
|
||||
return f.Value.Value != 0
|
||||
}
|
||||
|
||||
// String returns a readable representation of this value
|
||||
// (for usage defaults).
|
||||
func (f Fixed8Flag) String() string {
|
||||
var names []string
|
||||
eachName(f.Name, func(name string) {
|
||||
for _, name := range f.Names() {
|
||||
names = append(names, getNameHelp(name))
|
||||
})
|
||||
|
||||
}
|
||||
return strings.Join(names, ", ") + "\t" + f.Usage
|
||||
}
|
||||
|
||||
// GetName returns the name of the flag.
|
||||
func (f Fixed8Flag) GetName() string {
|
||||
return f.Name
|
||||
// Names returns the names of the flag.
|
||||
func (f Fixed8Flag) Names() []string {
|
||||
return cli.FlagNames(f.Name, f.Aliases)
|
||||
}
|
||||
|
||||
// IsRequired returns whether the flag is required.
|
||||
func (f Fixed8Flag) IsRequired() bool {
|
||||
return f.Required
|
||||
}
|
||||
|
||||
// IsVisible returns true if the flag is not hidden, otherwise false.
|
||||
func (f Fixed8Flag) IsVisible() bool {
|
||||
return !f.Hidden
|
||||
}
|
||||
|
||||
// TakesValue returns true if the flag takes a value, otherwise false.
|
||||
func (f Fixed8Flag) TakesValue() bool {
|
||||
return true
|
||||
}
|
||||
|
||||
// GetUsage returns the usage string for the flag.
|
||||
func (f Fixed8Flag) GetUsage() string {
|
||||
return f.Usage
|
||||
}
|
||||
|
||||
// Apply populates the flag given the flag set and environment.
|
||||
// Ignores errors.
|
||||
func (f Fixed8Flag) Apply(set *flag.FlagSet) {
|
||||
eachName(f.Name, func(name string) {
|
||||
func (f Fixed8Flag) Apply(set *flag.FlagSet) error {
|
||||
for _, name := range f.Names() {
|
||||
set.Var(&f.Value, name, f.Usage)
|
||||
})
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Fixed8FromContext returns a parsed util.Fixed8 value provided flag name.
|
||||
func Fixed8FromContext(ctx *cli.Context, name string) fixedn.Fixed8 {
|
||||
return ctx.Generic(name).(*Fixed8).Value
|
||||
}
|
||||
|
||||
// RunAction executes flag action if set.
|
||||
func (f Fixed8Flag) RunAction(c *cli.Context) error {
|
||||
if f.Action != nil {
|
||||
return f.Action(c, f.Value.Value.String())
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetValue returns the flags value as string representation.
|
||||
func (f Fixed8Flag) GetValue() string {
|
||||
return f.Value.Value.String()
|
||||
}
|
||||
|
||||
// Get returns the flag’s value in the given Context.
|
||||
func (f Fixed8Flag) Get(ctx *cli.Context) Fixed8 {
|
||||
adr := ctx.Generic(f.Name).(*Fixed8)
|
||||
return *adr
|
||||
}
|
||||
|
|
|
@ -7,6 +7,7 @@ import (
|
|||
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func TestFixed8_String(t *testing.T) {
|
||||
|
@ -45,22 +46,83 @@ func TestFixed8Flag_String(t *testing.T) {
|
|||
require.Equal(t, "--myFlag value\tGas amount", flag.String())
|
||||
}
|
||||
|
||||
func TestFixed8Flag_GetName(t *testing.T) {
|
||||
func TestFixed8Flag_Names(t *testing.T) {
|
||||
flag := Fixed8Flag{
|
||||
Name: "myFlag",
|
||||
}
|
||||
|
||||
require.Equal(t, "myFlag", flag.GetName())
|
||||
require.Equal(t, []string{"myFlag"}, flag.Names())
|
||||
}
|
||||
|
||||
func TestFixed8(t *testing.T) {
|
||||
f := flag.NewFlagSet("", flag.ContinueOnError)
|
||||
f.SetOutput(io.Discard) // don't pollute test output
|
||||
gas := Fixed8Flag{Name: "gas, g"}
|
||||
gas.Apply(f)
|
||||
gas := Fixed8Flag{Name: "gas", Aliases: []string{"g"}, Usage: "Gas amount", Value: Fixed8{Value: 0}, Required: true, Hidden: false, Action: nil}
|
||||
err := gas.Apply(f)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, f.Parse([]string{"--gas", "0.123"}))
|
||||
require.Equal(t, "0.123", f.Lookup("g").Value.String())
|
||||
require.NoError(t, f.Parse([]string{"-g", "0.456"}))
|
||||
require.Equal(t, "0.456", f.Lookup("g").Value.String())
|
||||
require.Error(t, f.Parse([]string{"--gas", "kek"}))
|
||||
}
|
||||
|
||||
func TestFixed8Flag_Get(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", flag.ContinueOnError)
|
||||
ctx := cli.NewContext(app, set, nil)
|
||||
flag := Fixed8Flag{
|
||||
Name: "testFlag",
|
||||
}
|
||||
fixedFlag := Fixed8{Value: fixedn.Fixed8(123)}
|
||||
set.Var(&fixedFlag, "testFlag", "test usage")
|
||||
require.NoError(t, set.Set("testFlag", "0.00000321"))
|
||||
expected := flag.Get(ctx)
|
||||
require.Equal(t, fixedn.Fixed8(321), expected.Value)
|
||||
}
|
||||
|
||||
func TestFixed8Flag_GetValue(t *testing.T) {
|
||||
f := Fixed8Flag{Value: Fixed8{Value: fixedn.Fixed8(123)}}
|
||||
require.Equal(t, "0.00000123", f.GetValue())
|
||||
require.True(t, f.TakesValue())
|
||||
}
|
||||
|
||||
func TestFixed8Flag_RunAction(t *testing.T) {
|
||||
called := false
|
||||
action := func(ctx *cli.Context, s string) error {
|
||||
called = true
|
||||
require.Equal(t, "0.00000123", s)
|
||||
return nil
|
||||
}
|
||||
app := cli.NewApp()
|
||||
set := flag.NewFlagSet("test", flag.ContinueOnError)
|
||||
ctx := cli.NewContext(app, set, nil)
|
||||
f := Fixed8Flag{
|
||||
Action: action,
|
||||
Value: Fixed8{Value: fixedn.Fixed8(123)},
|
||||
}
|
||||
err := f.RunAction(ctx)
|
||||
require.NoError(t, err)
|
||||
require.True(t, called)
|
||||
}
|
||||
|
||||
func TestFixed8Flag_GetUsage(t *testing.T) {
|
||||
f := Fixed8Flag{Usage: "Use this flag to specify gas amount"}
|
||||
require.Equal(t, "Use this flag to specify gas amount", f.GetUsage())
|
||||
}
|
||||
|
||||
func TestFixed8Flag_IsVisible(t *testing.T) {
|
||||
f := Fixed8Flag{Hidden: false}
|
||||
require.True(t, f.IsVisible())
|
||||
|
||||
f.Hidden = true
|
||||
require.False(t, f.IsVisible())
|
||||
}
|
||||
|
||||
func TestFixed8Flag_IsRequired(t *testing.T) {
|
||||
f := Fixed8Flag{Required: false}
|
||||
require.False(t, f.IsRequired())
|
||||
|
||||
f.Required = true
|
||||
require.True(t, f.IsRequired())
|
||||
}
|
||||
|
|
|
@ -1,11 +0,0 @@
|
|||
package flags
|
||||
|
||||
import "strings"
|
||||
|
||||
func eachName(longName string, fn func(string)) {
|
||||
parts := strings.Split(longName, ",")
|
||||
for _, name := range parts {
|
||||
name = strings.Trim(name, " ")
|
||||
fn(name)
|
||||
}
|
||||
}
|
|
@ -1,17 +0,0 @@
|
|||
package flags
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestEachName(t *testing.T) {
|
||||
expected := "*one*two*three"
|
||||
actual := ""
|
||||
|
||||
eachName(" one,two ,three", func(s string) {
|
||||
actual += "*" + s
|
||||
})
|
||||
require.Equal(t, expected, actual)
|
||||
}
|
|
@ -1,6 +1,7 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/cli/app"
|
||||
|
@ -10,6 +11,7 @@ func main() {
|
|||
ctl := app.New()
|
||||
|
||||
if err := ctl.Run(os.Args); err != nil {
|
||||
panic(err)
|
||||
fmt.Fprintln(ctl.ErrWriter, err)
|
||||
os.Exit(1)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -55,11 +55,14 @@ func TestNEP11Import(t *testing.T) {
|
|||
"--wallet", walletPath,
|
||||
}
|
||||
// missing token hash
|
||||
e.RunWithError(t, args...)
|
||||
e.RunWithErrorCheck(t, `Required flag "token" not set`, args...)
|
||||
|
||||
// excessive parameters
|
||||
e.RunWithError(t, append(args, "--token", nnsContractHash.StringLE(), "something")...)
|
||||
|
||||
// empty token hash
|
||||
e.RunWithErrorCheck(t, `invalid value "" for flag -token: zero length string`, append(args, "--token", "")...)
|
||||
|
||||
// good: non-divisible
|
||||
e.Run(t, append(args, "--token", nnsContractHash.StringLE())...)
|
||||
|
||||
|
@ -229,7 +232,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
cmdOwnerOf := []string{"neo-go", "wallet", "nep11", "ownerOf",
|
||||
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||
}
|
||||
e.RunWithError(t, cmdOwnerOf...)
|
||||
e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdOwnerOf...)
|
||||
cmdOwnerOf = append(cmdOwnerOf, "--token", h.StringLE())
|
||||
|
||||
// ownerOf: missing token ID
|
||||
|
@ -244,11 +247,11 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
cmdTokensOf := []string{"neo-go", "wallet", "nep11", "tokensOf",
|
||||
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||
}
|
||||
e.RunWithError(t, cmdTokensOf...)
|
||||
e.RunWithErrorCheck(t, `Required flags "token, address" not set`, cmdTokensOf...)
|
||||
cmdTokensOf = append(cmdTokensOf, "--token", h.StringLE())
|
||||
|
||||
// tokensOf: missing owner address
|
||||
e.RunWithError(t, cmdTokensOf...)
|
||||
e.RunWithErrorCheck(t, `Required flag "address" not set`, cmdTokensOf...)
|
||||
cmdTokensOf = append(cmdTokensOf, "--address", nftOwnerAddr)
|
||||
|
||||
// tokensOf: good
|
||||
|
@ -260,7 +263,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
"neo-go", "wallet", "nep11", "properties",
|
||||
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||
}
|
||||
e.RunWithError(t, cmdProperties...)
|
||||
e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdProperties...)
|
||||
cmdProperties = append(cmdProperties, "--token", h.StringLE())
|
||||
|
||||
// properties: no token ID
|
||||
|
@ -286,7 +289,7 @@ func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens",
|
||||
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||
}
|
||||
e.RunWithError(t, cmdTokens...)
|
||||
e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdTokens...)
|
||||
cmdTokens = append(cmdTokens, "--token", h.StringLE())
|
||||
|
||||
// tokens: excessive parameters
|
||||
|
@ -514,7 +517,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
cmdOwnerOf := []string{"neo-go", "wallet", "nep11", "ownerOfD",
|
||||
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||
}
|
||||
e.RunWithError(t, cmdOwnerOf...)
|
||||
e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdOwnerOf...)
|
||||
cmdOwnerOf = append(cmdOwnerOf, "--token", h.StringLE())
|
||||
|
||||
// ownerOfD: missing token ID
|
||||
|
@ -529,11 +532,11 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
cmdTokensOf := []string{"neo-go", "wallet", "nep11", "tokensOf",
|
||||
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||
}
|
||||
e.RunWithError(t, cmdTokensOf...)
|
||||
e.RunWithErrorCheck(t, `Required flags "token, address" not set`, cmdTokensOf...)
|
||||
cmdTokensOf = append(cmdTokensOf, "--token", h.StringLE())
|
||||
|
||||
// tokensOf: missing owner address
|
||||
e.RunWithError(t, cmdTokensOf...)
|
||||
e.RunWithErrorCheck(t, `Required flag "address" not set`, cmdTokensOf...)
|
||||
cmdTokensOf = append(cmdTokensOf, "--address", testcli.ValidatorAddr)
|
||||
|
||||
// tokensOf: good
|
||||
|
@ -547,7 +550,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
"neo-go", "wallet", "nep11", "properties",
|
||||
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||
}
|
||||
e.RunWithError(t, cmdProperties...)
|
||||
e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdProperties...)
|
||||
cmdProperties = append(cmdProperties, "--token", h.StringLE())
|
||||
|
||||
// properties: no token ID
|
||||
|
@ -580,7 +583,7 @@ func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
|||
cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens",
|
||||
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||
}
|
||||
e.RunWithError(t, cmdTokens...)
|
||||
e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdTokens...)
|
||||
cmdTokens = append(cmdTokens, "--token", h.StringLE())
|
||||
|
||||
// tokens: good, several tokens
|
||||
|
|
|
@ -3,7 +3,9 @@ package nep_test
|
|||
import (
|
||||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"testing"
|
||||
|
@ -21,52 +23,111 @@ func TestNEP17Balance(t *testing.T) {
|
|||
e := testcli.NewExecutor(t, true)
|
||||
|
||||
args := []string{
|
||||
"neo-go", "wallet", "nep17", "multitransfer",
|
||||
"neo-go", "wallet", "nep17", "multitransfer", "--force",
|
||||
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||
"--wallet", testcli.ValidatorWallet,
|
||||
"--from", testcli.ValidatorAddr,
|
||||
"GAS:" + testcli.TestWalletMultiAccount1 + ":1",
|
||||
"NEO:" + testcli.TestWalletMultiAccount1 + ":10",
|
||||
"GAS:" + testcli.TestWalletMultiAccount3 + ":3",
|
||||
"--force",
|
||||
}
|
||||
e.In.WriteString("one\r")
|
||||
e.Run(t, args...)
|
||||
e.CheckTxPersisted(t)
|
||||
|
||||
cmdbalance := []string{"neo-go", "wallet", "nep17", "balance"}
|
||||
cmdbase := append(cmdbalance,
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||
"--wallet", testcli.TestWalletMultiPath,
|
||||
)
|
||||
cmd := append(cmdbase, "--address", testcli.TestWalletMultiAccount1)
|
||||
t.Run("excessive parameters", func(t *testing.T) {
|
||||
e.RunWithError(t, append(cmd, "--token", "NEO", "gas")...)
|
||||
})
|
||||
t.Run("NEO", func(t *testing.T) {
|
||||
b, index := e.Chain.GetGoverningTokenBalance(testcli.TestWalletMultiAccount1Hash)
|
||||
checkResult := func(t *testing.T) {
|
||||
e.CheckNextLine(t, "^\\s*Account\\s+"+testcli.TestWalletMultiAccount1)
|
||||
e.CheckNextLine(t, "^\\s*NEO:\\s+NeoToken \\("+e.Chain.GoverningTokenHash().StringLE()+"\\)")
|
||||
e.CheckNextLine(t, "^\\s*Amount\\s*:\\s*"+b.String()+"$")
|
||||
e.CheckNextLine(t, "^\\s*Updated\\s*:\\s*"+strconv.FormatUint(uint64(index), 10))
|
||||
e.CheckEOF(t)
|
||||
var checkAcc1NEO = func(t *testing.T, e *testcli.Executor, line string) {
|
||||
if line == "" {
|
||||
line = e.GetNextLine(t)
|
||||
}
|
||||
t.Run("Alias", func(t *testing.T) {
|
||||
e.Run(t, append(cmd, "--token", "NEO")...)
|
||||
checkResult(t)
|
||||
})
|
||||
t.Run("Hash", func(t *testing.T) {
|
||||
e.Run(t, append(cmd, "--token", e.Chain.GoverningTokenHash().StringLE())...)
|
||||
checkResult(t)
|
||||
})
|
||||
balance, index := e.Chain.GetGoverningTokenBalance(testcli.TestWalletMultiAccount1Hash)
|
||||
e.CheckLine(t, line, "^\\s*NEO:\\s+NeoToken \\("+e.Chain.GoverningTokenHash().StringLE()+"\\)")
|
||||
e.CheckNextLine(t, "^\\s*Amount\\s*:\\s*"+balance.String()+"$")
|
||||
e.CheckNextLine(t, "^\\s*Updated\\s*:\\s*"+strconv.FormatUint(uint64(index), 10))
|
||||
}
|
||||
var checkAcc1GAS = func(t *testing.T, e *testcli.Executor, line string) {
|
||||
if line == "" {
|
||||
line = e.GetNextLine(t)
|
||||
}
|
||||
e.CheckLine(t, line, "^\\s*GAS:\\s+GasToken \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)")
|
||||
balance := e.Chain.GetUtilityTokenBalance(testcli.TestWalletMultiAccount1Hash)
|
||||
e.CheckNextLine(t, "^\\s*Amount\\s*:\\s*"+fixedn.Fixed8(balance.Int64()).String()+"$")
|
||||
e.CheckNextLine(t, "^\\s*Updated:")
|
||||
}
|
||||
var checkAcc1Assets = func(t *testing.T, e *testcli.Executor) {
|
||||
e.CheckNextLine(t, "^Account "+testcli.TestWalletMultiAccount1)
|
||||
// The order of assets is undefined.
|
||||
for range 2 {
|
||||
line := e.GetNextLine(t)
|
||||
if strings.Contains(line, "GAS") {
|
||||
checkAcc1GAS(t, e, line)
|
||||
} else {
|
||||
checkAcc1NEO(t, e, line)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
cmdbase = []string{"neo-go", "wallet", "nep17", "balance", "--rpc-endpoint", "http://" + e.RPC.Addresses()[0]}
|
||||
addrparams = []string{"--address", testcli.TestWalletMultiAccount1}
|
||||
walletparams = []string{"--wallet", testcli.TestWalletMultiPath}
|
||||
)
|
||||
t.Run("Bad wallet", func(t *testing.T) {
|
||||
e.RunWithError(t, append(cmdbase, "--wallet", "/dev/null")...)
|
||||
})
|
||||
t.Run("GAS", func(t *testing.T) {
|
||||
e.Run(t, append(cmd, "--token", "GAS")...)
|
||||
e.CheckNextLine(t, "^\\s*Account\\s+"+testcli.TestWalletMultiAccount1)
|
||||
e.CheckNextLine(t, "^\\s*GAS:\\s+GasToken \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)")
|
||||
b := e.Chain.GetUtilityTokenBalance(testcli.TestWalletMultiAccount1Hash)
|
||||
e.CheckNextLine(t, "^\\s*Amount\\s*:\\s*"+fixedn.Fixed8(b.Int64()).String()+"$")
|
||||
t.Run("empty wallet", func(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
walletPath := filepath.Join(tmpDir, "emptywallet.json")
|
||||
require.NoError(t, os.WriteFile(walletPath, []byte("{}"), 0o644))
|
||||
e.RunWithError(t, append(cmdbase, "--wallet", walletPath)...)
|
||||
})
|
||||
t.Run("no wallet or address", func(t *testing.T) {
|
||||
e.RunWithError(t, cmdbase...)
|
||||
})
|
||||
for name, params := range map[string][]string{
|
||||
"address only": addrparams,
|
||||
"address with wallet": slices.Concat(walletparams, addrparams),
|
||||
} {
|
||||
var cmd = append(cmdbase, params...)
|
||||
t.Run(name, func(t *testing.T) {
|
||||
t.Run("all tokens", func(t *testing.T) {
|
||||
e.Run(t, cmd...)
|
||||
checkAcc1Assets(t, e)
|
||||
e.CheckEOF(t)
|
||||
})
|
||||
t.Run("excessive parameters", func(t *testing.T) {
|
||||
e.RunWithError(t, append(cmd, "--token", "NEO", "gas")...)
|
||||
})
|
||||
})
|
||||
t.Run("NEO", func(t *testing.T) {
|
||||
checkResult := func(t *testing.T) {
|
||||
e.CheckNextLine(t, "^\\s*Account\\s+"+testcli.TestWalletMultiAccount1)
|
||||
checkAcc1NEO(t, e, "")
|
||||
e.CheckEOF(t)
|
||||
}
|
||||
t.Run("Alias", func(t *testing.T) {
|
||||
e.Run(t, append(cmd, "--token", "NEO")...)
|
||||
checkResult(t)
|
||||
})
|
||||
t.Run("Hash", func(t *testing.T) {
|
||||
e.Run(t, append(cmd, "--token", e.Chain.GoverningTokenHash().StringLE())...)
|
||||
checkResult(t)
|
||||
})
|
||||
})
|
||||
t.Run("GAS", func(t *testing.T) {
|
||||
e.Run(t, append(cmd, "--token", "GAS")...)
|
||||
e.CheckNextLine(t, "^\\s*Account\\s+"+testcli.TestWalletMultiAccount1)
|
||||
checkAcc1GAS(t, e, "")
|
||||
})
|
||||
t.Run("Bad token", func(t *testing.T) {
|
||||
e.Run(t, append(cmd, "--token", "kek")...)
|
||||
e.CheckNextLine(t, "^\\s*Account\\s+"+testcli.TestWalletMultiAccount1)
|
||||
e.CheckNextLine(t, `^\s*Can't find data for "kek" token\s*`)
|
||||
e.CheckEOF(t)
|
||||
})
|
||||
}
|
||||
t.Run("inexistent wallet account", func(t *testing.T) {
|
||||
var cmd = append(cmdbase, walletparams...)
|
||||
e.RunWithError(t, append(cmd, "--address", "NSPCCpw8YmgNDYWiBfXJHRfz38NDjv6WW3")...)
|
||||
})
|
||||
t.Run("zero balance of known token", func(t *testing.T) {
|
||||
e.Run(t, append(cmdbase, []string{"--token", "NEO", "--address", testcli.TestWalletMultiAccount2}...)...)
|
||||
|
@ -77,24 +138,9 @@ func TestNEP17Balance(t *testing.T) {
|
|||
e.CheckEOF(t)
|
||||
})
|
||||
t.Run("all accounts", func(t *testing.T) {
|
||||
e.Run(t, cmdbase...)
|
||||
e.Run(t, append(cmdbase, walletparams...)...)
|
||||
|
||||
e.CheckNextLine(t, "^Account "+testcli.TestWalletMultiAccount1)
|
||||
// The order of assets is undefined.
|
||||
for i := 0; i < 2; i++ {
|
||||
line := e.GetNextLine(t)
|
||||
if strings.Contains(line, "GAS") {
|
||||
e.CheckLine(t, line, "^\\s*GAS:\\s+GasToken \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)")
|
||||
balance := e.Chain.GetUtilityTokenBalance(testcli.TestWalletMultiAccount1Hash)
|
||||
e.CheckNextLine(t, "^\\s*Amount\\s*:\\s*"+fixedn.Fixed8(balance.Int64()).String()+"$")
|
||||
e.CheckNextLine(t, "^\\s*Updated:")
|
||||
} else {
|
||||
balance, index := e.Chain.GetGoverningTokenBalance(testcli.TestWalletMultiAccount1Hash)
|
||||
e.CheckLine(t, line, "^\\s*NEO:\\s+NeoToken \\("+e.Chain.GoverningTokenHash().StringLE()+"\\)")
|
||||
e.CheckNextLine(t, "^\\s*Amount\\s*:\\s*"+balance.String()+"$")
|
||||
e.CheckNextLine(t, "^\\s*Updated\\s*:\\s*"+strconv.FormatUint(uint64(index), 10))
|
||||
}
|
||||
}
|
||||
checkAcc1Assets(t, e)
|
||||
e.CheckNextLine(t, "^\\s*$")
|
||||
|
||||
e.CheckNextLine(t, "^Account "+testcli.TestWalletMultiAccount2)
|
||||
|
@ -107,15 +153,6 @@ func TestNEP17Balance(t *testing.T) {
|
|||
e.CheckNextLine(t, "^\\s*Updated:")
|
||||
e.CheckEOF(t)
|
||||
})
|
||||
t.Run("Bad token", func(t *testing.T) {
|
||||
e.Run(t, append(cmd, "--token", "kek")...)
|
||||
e.CheckNextLine(t, "^\\s*Account\\s+"+testcli.TestWalletMultiAccount1)
|
||||
e.CheckNextLine(t, `^\s*Can't find data for "kek" token\s*`)
|
||||
e.CheckEOF(t)
|
||||
})
|
||||
t.Run("Bad wallet", func(t *testing.T) {
|
||||
e.RunWithError(t, append(cmdbalance, "--wallet", "/dev/null")...)
|
||||
})
|
||||
}
|
||||
|
||||
func TestNEP17Transfer(t *testing.T) {
|
||||
|
@ -134,10 +171,9 @@ func TestNEP17Transfer(t *testing.T) {
|
|||
}
|
||||
|
||||
t.Run("missing receiver", func(t *testing.T) {
|
||||
as := append([]string{}, args[:8]...)
|
||||
as = append(as, args[10:]...)
|
||||
as := slices.Concat(args[:8], args[10:])
|
||||
e.In.WriteString("one\r")
|
||||
e.RunWithError(t, as...)
|
||||
e.RunWithErrorCheck(t, `Required flag "to" not set`, as...)
|
||||
e.In.Reset()
|
||||
})
|
||||
|
||||
|
@ -327,7 +363,7 @@ func TestNEP17ImportToken(t *testing.T) {
|
|||
e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath)
|
||||
|
||||
// missing token hash
|
||||
e.RunWithError(t, "neo-go", "wallet", "nep17", "import",
|
||||
e.RunWithErrorCheck(t, `Required flag "token" not set`, "neo-go", "wallet", "nep17", "import",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||
"--wallet", walletPath)
|
||||
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/cli/options"
|
||||
"github.com/nspcc-dev/neo-go/internal/testcli"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func TestGetRPCClient(t *testing.T) {
|
||||
|
|
|
@ -14,6 +14,7 @@ import (
|
|||
"strings"
|
||||
"time"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||
"github.com/nspcc-dev/neo-go/cli/input"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
|
@ -24,11 +25,15 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/services/helpers/neofs"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/pool"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
"golang.org/x/term"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
|
@ -41,74 +46,117 @@ const (
|
|||
DefaultAwaitableTimeout = 3 * 15 * time.Second
|
||||
)
|
||||
|
||||
// RPCEndpointFlag is a long flag name for an RPC endpoint. It can be used to
|
||||
// check for flag presence in the context.
|
||||
const RPCEndpointFlag = "rpc-endpoint"
|
||||
const (
|
||||
// RPCEndpointFlag is a long flag name for an RPC endpoint. It can be used to
|
||||
// check for flag presence in the context.
|
||||
RPCEndpointFlag = "rpc-endpoint"
|
||||
// NeoFSRPCEndpointFlag is a long flag name for a NeoFS RPC endpoint.
|
||||
NeoFSRPCEndpointFlag = "fs-rpc-endpoint"
|
||||
)
|
||||
|
||||
// Wallet is a set of flags used for wallet operations.
|
||||
var Wallet = []cli.Flag{cli.StringFlag{
|
||||
Name: "wallet, w",
|
||||
Usage: "wallet to use to get the key for transaction signing; conflicts with --wallet-config flag",
|
||||
}, cli.StringFlag{
|
||||
Name: "wallet-config",
|
||||
Usage: "path to wallet config to use to get the key for transaction signing; conflicts with --wallet flag"},
|
||||
var Wallet = []cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "wallet",
|
||||
Aliases: []string{"w"},
|
||||
Usage: "Wallet to use to get the key for transaction signing; conflicts with --wallet-config flag",
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "wallet-config",
|
||||
Usage: "Path to wallet config to use to get the key for transaction signing; conflicts with --wallet flag",
|
||||
},
|
||||
}
|
||||
|
||||
// Network is a set of flags for choosing the network to operate on
|
||||
// (privnet/mainnet/testnet).
|
||||
var Network = []cli.Flag{
|
||||
cli.BoolFlag{Name: "privnet, p", Usage: "use private network configuration (if --config-file option is not specified)"},
|
||||
cli.BoolFlag{Name: "mainnet, m", Usage: "use mainnet network configuration (if --config-file option is not specified)"},
|
||||
cli.BoolFlag{Name: "testnet, t", Usage: "use testnet network configuration (if --config-file option is not specified)"},
|
||||
cli.BoolFlag{Name: "unittest", Hidden: true},
|
||||
&cli.BoolFlag{
|
||||
Name: "privnet",
|
||||
Aliases: []string{"p"},
|
||||
Usage: "Use private network configuration (if --config-file option is not specified)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "mainnet",
|
||||
Aliases: []string{"m"},
|
||||
Usage: "Use mainnet network configuration (if --config-file option is not specified)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "testnet",
|
||||
Aliases: []string{"t"},
|
||||
Usage: "Use testnet network configuration (if --config-file option is not specified)",
|
||||
},
|
||||
&cli.BoolFlag{
|
||||
Name: "unittest",
|
||||
Hidden: true,
|
||||
},
|
||||
}
|
||||
|
||||
// RPC is a set of flags used for RPC connections (endpoint and timeout).
|
||||
var RPC = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: RPCEndpointFlag + ", r",
|
||||
Usage: "RPC node address",
|
||||
&cli.StringFlag{
|
||||
Name: RPCEndpointFlag,
|
||||
Aliases: []string{"r"},
|
||||
Usage: "RPC node address",
|
||||
Required: true,
|
||||
Action: cmdargs.EnsureNotEmpty("rpc-endpoint"),
|
||||
},
|
||||
cli.DurationFlag{
|
||||
Name: "timeout, s",
|
||||
Value: DefaultTimeout,
|
||||
Usage: "Timeout for the operation",
|
||||
&cli.DurationFlag{
|
||||
Name: "timeout",
|
||||
Aliases: []string{"s"},
|
||||
Value: DefaultTimeout,
|
||||
Usage: "Timeout for the operation",
|
||||
},
|
||||
}
|
||||
|
||||
// NeoFSRPC is a set of flags used for NeoFS RPC connections (endpoint).
|
||||
var NeoFSRPC = []cli.Flag{&cli.StringSliceFlag{
|
||||
Name: NeoFSRPCEndpointFlag,
|
||||
Aliases: []string{"fsr"},
|
||||
Usage: "List of NeoFS storage node RPC addresses (comma-separated or multiple --fs-rpc-endpoint flags)",
|
||||
Required: true,
|
||||
Action: func(ctx *cli.Context, fsRpcEndpoints []string) error {
|
||||
for _, endpoint := range fsRpcEndpoints {
|
||||
if endpoint == "" {
|
||||
return cli.Exit("NeoFS RPC endpoint cannot contain empty values", 1)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
},
|
||||
}}
|
||||
|
||||
// Historic is a flag for commands that can perform historic invocations.
|
||||
var Historic = cli.StringFlag{
|
||||
var Historic = &cli.StringFlag{
|
||||
Name: "historic",
|
||||
Usage: "Use historic state (height, block hash or state root hash)",
|
||||
}
|
||||
|
||||
// Config is a flag for commands that use node configuration.
|
||||
var Config = cli.StringFlag{
|
||||
var Config = &cli.StringFlag{
|
||||
Name: "config-path",
|
||||
Usage: "path to directory with per-network configuration files (may be overridden by --config-file option for the configuration file)",
|
||||
Usage: "Path to directory with per-network configuration files (may be overridden by --config-file option for the configuration file)",
|
||||
}
|
||||
|
||||
// ConfigFile is a flag for commands that use node configuration and provide
|
||||
// path to the specific config file instead of config path.
|
||||
var ConfigFile = cli.StringFlag{
|
||||
var ConfigFile = &cli.StringFlag{
|
||||
Name: "config-file",
|
||||
Usage: "path to the node configuration file (overrides --config-path option)",
|
||||
Usage: "Path to the node configuration file (overrides --config-path option)",
|
||||
}
|
||||
|
||||
// RelativePath is a flag for commands that use node configuration and provide
|
||||
// a prefix to all relative paths in config files.
|
||||
var RelativePath = cli.StringFlag{
|
||||
var RelativePath = &cli.StringFlag{
|
||||
Name: "relative-path",
|
||||
Usage: "a prefix to all relative paths in the node configuration file",
|
||||
Usage: "Prefix to all relative paths in the node configuration file",
|
||||
}
|
||||
|
||||
// Debug is a flag for commands that allow node in debug mode usage.
|
||||
var Debug = cli.BoolFlag{
|
||||
Name: "debug, d",
|
||||
Usage: "enable debug logging (LOTS of output, overrides configuration)",
|
||||
var Debug = &cli.BoolFlag{
|
||||
Name: "debug",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "Enable debug logging (LOTS of output, overrides configuration)",
|
||||
}
|
||||
|
||||
var errNoEndpoint = errors.New("no RPC endpoint specified, use option '--" + RPCEndpointFlag + "' or '-r'")
|
||||
var errInvalidHistoric = errors.New("invalid 'historic' parameter, neither a block number, nor a block/state hash")
|
||||
var errNoWallet = errors.New("no wallet parameter found, specify it with the '--wallet' or '-w' flag or specify wallet config file with the '--wallet-config' flag")
|
||||
var errConflictingWalletFlags = errors.New("--wallet flag conflicts with --wallet-config flag, please, provide one of them to specify wallet location")
|
||||
|
@ -144,20 +192,37 @@ func GetTimeoutContext(ctx *cli.Context) (context.Context, func()) {
|
|||
// GetRPCClient returns an RPC client instance for the given Context.
|
||||
func GetRPCClient(gctx context.Context, ctx *cli.Context) (*rpcclient.Client, cli.ExitCoder) {
|
||||
endpoint := ctx.String(RPCEndpointFlag)
|
||||
if len(endpoint) == 0 {
|
||||
return nil, cli.NewExitError(errNoEndpoint, 1)
|
||||
}
|
||||
c, err := rpcclient.New(gctx, endpoint, rpcclient.Options{})
|
||||
if err != nil {
|
||||
return nil, cli.NewExitError(err, 1)
|
||||
return nil, cli.Exit(err, 1)
|
||||
}
|
||||
err = c.Init()
|
||||
if err != nil {
|
||||
return nil, cli.NewExitError(err, 1)
|
||||
return nil, cli.Exit(err, 1)
|
||||
}
|
||||
return c, nil
|
||||
}
|
||||
|
||||
// GetNeoFSClientPool returns a NeoFS pool and a signer for the given Context.
|
||||
func GetNeoFSClientPool(ctx *cli.Context, acc *wallet.Account) (user.Signer, neofs.PoolWrapper, error) {
|
||||
rpcNeoFS := ctx.StringSlice(NeoFSRPCEndpointFlag)
|
||||
signer := user.NewAutoIDSignerRFC6979(acc.PrivateKey().PrivateKey)
|
||||
|
||||
params := pool.DefaultOptions()
|
||||
params.SetHealthcheckTimeout(neofs.DefaultHealthcheckTimeout)
|
||||
params.SetNodeDialTimeout(neofs.DefaultDialTimeout)
|
||||
params.SetNodeStreamTimeout(neofs.DefaultStreamTimeout)
|
||||
p, err := pool.New(pool.NewFlatNodeParams(rpcNeoFS), signer, params)
|
||||
if err != nil {
|
||||
return nil, neofs.PoolWrapper{}, fmt.Errorf("failed to create NeoFS pool: %w", err)
|
||||
}
|
||||
pWrapper := neofs.PoolWrapper{Pool: p}
|
||||
if err = pWrapper.Dial(context.Background()); err != nil {
|
||||
return nil, neofs.PoolWrapper{}, fmt.Errorf("failed to dial NeoFS pool: %w", err)
|
||||
}
|
||||
return signer, pWrapper, nil
|
||||
}
|
||||
|
||||
// GetInvoker returns an invoker using the given RPC client, context and signers.
|
||||
// It parses "--historic" parameter to adjust it.
|
||||
func GetInvoker(c *rpcclient.Client, ctx *cli.Context, signers []transaction.Signer) (*invoker.Invoker, cli.ExitCoder) {
|
||||
|
@ -172,7 +237,7 @@ func GetInvoker(c *rpcclient.Client, ctx *cli.Context, signers []transaction.Sig
|
|||
// Might as well be a block hash, but it makes no practical difference.
|
||||
return invoker.NewHistoricWithState(u256, c, signers), nil
|
||||
}
|
||||
return nil, cli.NewExitError(errInvalidHistoric, 1)
|
||||
return nil, cli.Exit(errInvalidHistoric, 1)
|
||||
}
|
||||
|
||||
// GetRPCWithInvoker combines GetRPCClient with GetInvoker for cases where it's
|
||||
|
@ -200,7 +265,7 @@ func GetConfigFromContext(ctx *cli.Context) (config.Config, error) {
|
|||
if len(configFile) != 0 {
|
||||
return config.LoadFile(configFile, relativePath)
|
||||
}
|
||||
var configPath = "./config"
|
||||
var configPath = config.DefaultConfigPath
|
||||
if argCp := ctx.String("config-path"); argCp != "" {
|
||||
configPath = argCp
|
||||
}
|
||||
|
@ -219,6 +284,7 @@ var (
|
|||
// If logPath is configured -- function creates a dir and a file for logging.
|
||||
// If logPath is configured on Windows -- function returns closer to be
|
||||
// able to close sink for the opened log output file.
|
||||
// If the program is run in TTY then logger adds timestamp to its entries.
|
||||
func HandleLoggingParams(debug bool, cfg config.ApplicationConfiguration) (*zap.Logger, *zap.AtomicLevel, func() error, error) {
|
||||
var (
|
||||
level = zapcore.InfoLevel
|
||||
|
@ -239,7 +305,11 @@ func HandleLoggingParams(debug bool, cfg config.ApplicationConfiguration) (*zap.
|
|||
cc.DisableStacktrace = true
|
||||
cc.EncoderConfig.EncodeDuration = zapcore.StringDurationEncoder
|
||||
cc.EncoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||
cc.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
if term.IsTerminal(int(os.Stdout.Fd())) {
|
||||
cc.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||
} else {
|
||||
cc.EncoderConfig.EncodeTime = func(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {}
|
||||
}
|
||||
cc.Encoding = "console"
|
||||
cc.Level = zap.NewAtomicLevelAt(level)
|
||||
cc.Sampling = nil
|
||||
|
@ -308,7 +378,7 @@ func GetRPCWithActor(gctx context.Context, ctx *cli.Context, signers []actor.Sig
|
|||
a, actorErr := actor.New(c, signers)
|
||||
if actorErr != nil {
|
||||
c.Close()
|
||||
return nil, nil, cli.NewExitError(fmt.Errorf("failed to create Actor: %w", actorErr), 1)
|
||||
return nil, nil, cli.Exit(fmt.Errorf("failed to create Actor: %w", actorErr), 1)
|
||||
}
|
||||
return c, a, nil
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func TestGetNetwork(t *testing.T) {
|
||||
|
|
|
@ -2,9 +2,10 @@ package query
|
|||
|
||||
import (
|
||||
"bytes"
|
||||
"cmp"
|
||||
"encoding/base64"
|
||||
"fmt"
|
||||
"sort"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
@ -22,21 +23,22 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// NewCommands returns 'query' command.
|
||||
func NewCommands() []cli.Command {
|
||||
func NewCommands() []*cli.Command {
|
||||
queryTxFlags := append([]cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "verbose, v",
|
||||
Usage: "Output full tx info and execution logs",
|
||||
&cli.BoolFlag{
|
||||
Name: "verbose",
|
||||
Aliases: []string{"v"},
|
||||
Usage: "Output full tx info and execution logs",
|
||||
},
|
||||
}, options.RPC...)
|
||||
return []cli.Command{{
|
||||
return []*cli.Command{{
|
||||
Name: "query",
|
||||
Usage: "Query data from RPC node",
|
||||
Subcommands: []cli.Command{
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "candidates",
|
||||
Usage: "Get candidates and votes",
|
||||
|
@ -61,14 +63,14 @@ func NewCommands() []cli.Command {
|
|||
{
|
||||
Name: "tx",
|
||||
Usage: "Query transaction status",
|
||||
UsageText: "neo-go query tx <hash> -r endpoint [-s timeout] [-v]",
|
||||
UsageText: "neo-go query tx -r endpoint [-s timeout] [-v] <hash>",
|
||||
Action: queryTx,
|
||||
Flags: queryTxFlags,
|
||||
},
|
||||
{
|
||||
Name: "voter",
|
||||
Usage: "Print NEO holder account state",
|
||||
UsageText: "neo-go query voter <address> -r endpoint [-s timeout]",
|
||||
UsageText: "neo-go query voter -r endpoint [-s timeout] <address>",
|
||||
Action: queryVoter,
|
||||
Flags: options.RPC,
|
||||
},
|
||||
|
@ -77,16 +79,16 @@ func NewCommands() []cli.Command {
|
|||
}
|
||||
|
||||
func queryTx(ctx *cli.Context) error {
|
||||
args := ctx.Args()
|
||||
args := ctx.Args().Slice()
|
||||
if len(args) == 0 {
|
||||
return cli.NewExitError("Transaction hash is missing", 1)
|
||||
return cli.Exit("transaction hash is missing", 1)
|
||||
} else if len(args) > 1 {
|
||||
return cli.NewExitError("only one transaction hash is accepted", 1)
|
||||
return cli.Exit("only one transaction hash is accepted", 1)
|
||||
}
|
||||
|
||||
txHash, err := util.Uint256DecodeStringLE(strings.TrimPrefix(args[0], "0x"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("Invalid tx hash: %s", args[0]), 1)
|
||||
return cli.Exit(fmt.Sprintf("invalid tx hash: %s", args[0]), 1)
|
||||
}
|
||||
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
|
@ -94,25 +96,25 @@ func queryTx(ctx *cli.Context) error {
|
|||
|
||||
c, err := options.GetRPCClient(gctx, ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
txOut, err := c.GetRawTransactionVerbose(txHash)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
var res *result.ApplicationLog
|
||||
if !txOut.Blockhash.Equals(util.Uint256{}) {
|
||||
res, err = c.GetApplicationLog(txHash, nil)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
}
|
||||
|
||||
err = DumpApplicationLog(ctx, res, &txOut.Transaction, &txOut.TransactionMetadata, ctx.Bool("verbose"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -179,26 +181,29 @@ func queryCandidates(ctx *cli.Context) error {
|
|||
|
||||
c, err := options.GetRPCClient(gctx, ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
vals, err := c.GetCandidates()
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
comm, err := c.GetCommittee()
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
sort.Slice(vals, func(i, j int) bool {
|
||||
if vals[i].Active != vals[j].Active {
|
||||
return vals[i].Active
|
||||
slices.SortFunc(vals, func(a, b result.Candidate) int {
|
||||
if a.Active && !b.Active {
|
||||
return 1
|
||||
}
|
||||
if vals[i].Votes != vals[j].Votes {
|
||||
return vals[i].Votes > vals[j].Votes
|
||||
if !a.Active && b.Active {
|
||||
return -1
|
||||
}
|
||||
return vals[i].PublicKey.Cmp(&vals[j].PublicKey) == -1
|
||||
return cmp.Or(
|
||||
cmp.Compare(a.Votes, b.Votes),
|
||||
a.PublicKey.Cmp(&b.PublicKey),
|
||||
)
|
||||
})
|
||||
var res []byte
|
||||
res = fmt.Appendf(res, "Key\tVotes\tCommittee\tConsensus\n")
|
||||
|
@ -225,12 +230,12 @@ func queryCommittee(ctx *cli.Context) error {
|
|||
|
||||
c, err := options.GetRPCClient(gctx, ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
comm, err := c.GetCommittee()
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
for _, k := range comm {
|
||||
|
@ -251,12 +256,12 @@ func queryHeight(ctx *cli.Context) error {
|
|||
|
||||
c, err := options.GetRPCClient(gctx, ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
blockCount, err := c.GetBlockCount()
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
blockHeight := blockCount - 1 // GetBlockCount returns block count (including 0), not the highest block index.
|
||||
|
||||
|
@ -271,16 +276,16 @@ func queryHeight(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
func queryVoter(ctx *cli.Context) error {
|
||||
args := ctx.Args()
|
||||
args := ctx.Args().Slice()
|
||||
if len(args) == 0 {
|
||||
return cli.NewExitError("No address specified", 1)
|
||||
return cli.Exit("no address specified", 1)
|
||||
} else if len(args) > 1 {
|
||||
return cli.NewExitError("this command only accepts one address", 1)
|
||||
return cli.Exit("this command only accepts one address", 1)
|
||||
}
|
||||
|
||||
addr, err := flags.ParseAddress(args[0])
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("wrong address: %s", args[0]), 1)
|
||||
return cli.Exit(fmt.Sprintf("wrong address: %s", args[0]), 1)
|
||||
}
|
||||
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
|
@ -294,14 +299,14 @@ func queryVoter(ctx *cli.Context) error {
|
|||
|
||||
st, err := neoToken.GetAccountState(addr)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
if st == nil {
|
||||
st = new(state.NEOBalance)
|
||||
}
|
||||
dec, err := neoToken.Decimals()
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to get decimals: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to get decimals: %w", err), 1)
|
||||
}
|
||||
voted := "null"
|
||||
if st.VoteTo != nil {
|
||||
|
|
87
cli/server/dump_bin.go
Normal file
87
cli/server/dump_bin.go
Normal file
|
@ -0,0 +1,87 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||
"github.com/nspcc-dev/neo-go/cli/options"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func dumpBin(ctx *cli.Context) error {
|
||||
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
cfg, err := options.GetConfigFromContext(ctx)
|
||||
if err != nil {
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
log, _, logCloser, err := options.HandleLoggingParams(ctx.Bool("debug"), cfg.ApplicationConfiguration)
|
||||
if err != nil {
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
if logCloser != nil {
|
||||
defer func() { _ = logCloser() }()
|
||||
}
|
||||
count := uint32(ctx.Uint("count"))
|
||||
start := uint32(ctx.Uint("start"))
|
||||
|
||||
chain, _, prometheus, pprof, err := InitBCWithMetrics(cfg, log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
pprof.ShutDown()
|
||||
prometheus.ShutDown()
|
||||
chain.Close()
|
||||
}()
|
||||
|
||||
blocksCount := chain.BlockHeight() + 1
|
||||
if start+count > blocksCount {
|
||||
return cli.Exit(fmt.Errorf("chain is not that high (%d) to dump %d blocks starting from %d", blocksCount-1, count, start), 1)
|
||||
}
|
||||
if count == 0 {
|
||||
count = blocksCount - start
|
||||
}
|
||||
|
||||
out := ctx.String("out")
|
||||
if out == "" {
|
||||
return cli.Exit("output directory is not specified", 1)
|
||||
}
|
||||
if _, err = os.Stat(out); os.IsNotExist(err) {
|
||||
if err = os.MkdirAll(out, os.ModePerm); err != nil {
|
||||
return cli.Exit(fmt.Sprintf("failed to create directory %s: %s", out, err), 1)
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("failed to check directory %s: %s", out, err), 1)
|
||||
}
|
||||
|
||||
for i := start; i < start+count; i++ {
|
||||
blk, err := chain.GetBlock(chain.GetHeaderHash(i))
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("failed to get block %d: %s", i, err), 1)
|
||||
}
|
||||
filePath := filepath.Join(out, fmt.Sprintf("block-%d.bin", i))
|
||||
if err = saveBlockToFile(blk, filePath); err != nil {
|
||||
return cli.Exit(fmt.Sprintf("failed to save block %d to file %s: %s", i, filePath, err), 1)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func saveBlockToFile(blk *block.Block, filePath string) error {
|
||||
file, err := os.Create(filePath)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer file.Close()
|
||||
|
||||
writer := io.NewBinWriterFromIO(file)
|
||||
blk.EncodeBinary(writer)
|
||||
return writer.Err
|
||||
}
|
91
cli/server/dump_bin_test.go
Normal file
91
cli/server/dump_bin_test.go
Normal file
|
@ -0,0 +1,91 @@
|
|||
package server_test
|
||||
|
||||
import (
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strconv"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/testcli"
|
||||
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
|
||||
"github.com/stretchr/testify/require"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
func TestDumpBin(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
|
||||
loadConfig := func(t *testing.T) config.Config {
|
||||
chainPath := filepath.Join(tmpDir, "neogotestchain")
|
||||
cfg, err := config.LoadFile(filepath.Join("..", "..", "config", "protocol.unit_testnet.yml"))
|
||||
require.NoError(t, err, "could not load config")
|
||||
cfg.ApplicationConfiguration.DBConfiguration.Type = dbconfig.LevelDB
|
||||
cfg.ApplicationConfiguration.DBConfiguration.LevelDBOptions.DataDirectoryPath = chainPath
|
||||
return cfg
|
||||
}
|
||||
|
||||
cfg := loadConfig(t)
|
||||
out, err := yaml.Marshal(cfg)
|
||||
require.NoError(t, err)
|
||||
|
||||
cfgPath := filepath.Join(tmpDir, "protocol.unit_testnet.yml")
|
||||
require.NoError(t, os.WriteFile(cfgPath, out, os.ModePerm))
|
||||
|
||||
e := testcli.NewExecutor(t, false)
|
||||
|
||||
restoreArgs := []string{"neo-go", "db", "restore",
|
||||
"--config-file", cfgPath, "--in", inDump}
|
||||
e.Run(t, restoreArgs...)
|
||||
|
||||
t.Run("missing output directory", func(t *testing.T) {
|
||||
args := []string{"neo-go", "db", "dump-bin",
|
||||
"--config-file", cfgPath, "--out", ""}
|
||||
e.RunWithErrorCheck(t, "output directory is not specified", args...)
|
||||
})
|
||||
|
||||
t.Run("successful dump", func(t *testing.T) {
|
||||
outDir := filepath.Join(tmpDir, "blocks")
|
||||
args := []string{"neo-go", "db", "dump-bin",
|
||||
"--config-file", cfgPath, "--out", outDir, "--count", "5", "--start", "0"}
|
||||
|
||||
e.Run(t, args...)
|
||||
|
||||
require.DirExists(t, outDir)
|
||||
|
||||
for i := range 5 {
|
||||
blockFile := filepath.Join(outDir, "block-"+strconv.Itoa(i)+".bin")
|
||||
require.FileExists(t, blockFile)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid block range", func(t *testing.T) {
|
||||
outDir := filepath.Join(tmpDir, "invalid-blocks")
|
||||
args := []string{"neo-go", "db", "dump-bin",
|
||||
"--config-file", cfgPath, "--out", outDir, "--count", "1000", "--start", "0"}
|
||||
|
||||
e.RunWithError(t, args...)
|
||||
})
|
||||
|
||||
t.Run("zero blocks (full chain dump)", func(t *testing.T) {
|
||||
outDir := filepath.Join(tmpDir, "full-dump")
|
||||
args := []string{"neo-go", "db", "dump-bin",
|
||||
"--config-file", cfgPath, "--out", outDir}
|
||||
|
||||
e.Run(t, args...)
|
||||
|
||||
require.DirExists(t, outDir)
|
||||
for i := range 5 {
|
||||
blockFile := filepath.Join(outDir, "block-"+strconv.Itoa(i)+".bin")
|
||||
require.FileExists(t, blockFile)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("invalid config file", func(t *testing.T) {
|
||||
outDir := filepath.Join(tmpDir, "blocks")
|
||||
args := []string{"neo-go", "db", "dump-bin",
|
||||
"--config-file", "invalid-config-path", "--out", outDir}
|
||||
|
||||
e.RunWithError(t, args...)
|
||||
})
|
||||
}
|
23
cli/server/metrics.go
Normal file
23
cli/server/metrics.go
Normal file
|
@ -0,0 +1,23 @@
|
|||
package server
|
||||
|
||||
import (
|
||||
"github.com/prometheus/client_golang/prometheus"
|
||||
)
|
||||
|
||||
var neogoVersion = prometheus.NewGaugeVec(
|
||||
prometheus.GaugeOpts{
|
||||
Help: "NeoGo version",
|
||||
Name: "version",
|
||||
Namespace: "neogo",
|
||||
},
|
||||
[]string{"version"})
|
||||
|
||||
func setNeoGoVersion(nodeVer string) {
|
||||
neogoVersion.WithLabelValues(nodeVer).Add(1)
|
||||
}
|
||||
|
||||
func init() {
|
||||
prometheus.MustRegister(
|
||||
neogoVersion,
|
||||
)
|
||||
}
|
|
@ -6,6 +6,7 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"os/signal"
|
||||
"slices"
|
||||
"syscall"
|
||||
"time"
|
||||
|
||||
|
@ -27,89 +28,97 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/services/oracle"
|
||||
"github.com/nspcc-dev/neo-go/pkg/services/rpcsrv"
|
||||
"github.com/nspcc-dev/neo-go/pkg/services/stateroot"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
||||
// NewCommands returns 'node' command.
|
||||
func NewCommands() []cli.Command {
|
||||
func NewCommands() []*cli.Command {
|
||||
cfgFlags := []cli.Flag{options.Config, options.ConfigFile, options.RelativePath}
|
||||
cfgFlags = append(cfgFlags, options.Network...)
|
||||
var cfgWithCountFlags = make([]cli.Flag, len(cfgFlags))
|
||||
copy(cfgWithCountFlags, cfgFlags)
|
||||
cfgFlags = append(cfgFlags, options.Debug)
|
||||
|
||||
var cfgWithCountFlags = slices.Clone(cfgFlags)
|
||||
cfgFlags = append(cfgFlags, options.Debug)
|
||||
cfgWithCountFlags = append(cfgWithCountFlags,
|
||||
cli.UintFlag{
|
||||
Name: "count, c",
|
||||
Usage: "number of blocks to be processed (default or 0: all chain)",
|
||||
&cli.UintFlag{
|
||||
Name: "count",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Number of blocks to be processed (default or 0: all chain)",
|
||||
},
|
||||
)
|
||||
var cfgCountOutFlags = make([]cli.Flag, len(cfgWithCountFlags))
|
||||
copy(cfgCountOutFlags, cfgWithCountFlags)
|
||||
var cfgCountOutFlags = slices.Clone(cfgWithCountFlags)
|
||||
cfgCountOutFlags = append(cfgCountOutFlags,
|
||||
cli.UintFlag{
|
||||
Name: "start, s",
|
||||
Usage: "block number to start from (default: 0)",
|
||||
&cli.UintFlag{
|
||||
Name: "start",
|
||||
Aliases: []string{"s"},
|
||||
Usage: "Block number to start from",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "out, o",
|
||||
Usage: "Output file (stdout if not given)",
|
||||
&cli.StringFlag{
|
||||
Name: "out",
|
||||
Aliases: []string{"o"},
|
||||
Usage: "Output file (stdout if not given)",
|
||||
},
|
||||
)
|
||||
var cfgCountInFlags = make([]cli.Flag, len(cfgWithCountFlags))
|
||||
copy(cfgCountInFlags, cfgWithCountFlags)
|
||||
var cfgCountInFlags = slices.Clone(cfgWithCountFlags)
|
||||
cfgCountInFlags = append(cfgCountInFlags,
|
||||
cli.StringFlag{
|
||||
Name: "in, i",
|
||||
Usage: "Input file (stdin if not given)",
|
||||
&cli.StringFlag{
|
||||
Name: "in",
|
||||
Aliases: []string{"i"},
|
||||
Usage: "Input file (stdin if not given)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "dump",
|
||||
Usage: "directory for storing JSON dumps",
|
||||
Usage: "Directory for storing JSON dumps",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "incremental, n",
|
||||
Usage: "use if dump is incremental",
|
||||
&cli.BoolFlag{
|
||||
Name: "incremental",
|
||||
Aliases: []string{"n"},
|
||||
Usage: "Use if dump is incremental",
|
||||
},
|
||||
)
|
||||
var cfgHeightFlags = make([]cli.Flag, len(cfgFlags)+1)
|
||||
copy(cfgHeightFlags, cfgFlags)
|
||||
cfgHeightFlags[len(cfgHeightFlags)-1] = cli.UintFlag{
|
||||
var cfgHeightFlags = slices.Clone(cfgFlags)
|
||||
cfgHeightFlags = append(cfgHeightFlags, &cli.UintFlag{
|
||||
Name: "height",
|
||||
Usage: "Height of the state to reset DB to",
|
||||
Required: true,
|
||||
}
|
||||
return []cli.Command{
|
||||
})
|
||||
return []*cli.Command{
|
||||
{
|
||||
Name: "node",
|
||||
Usage: "start a NeoGo node",
|
||||
Usage: "Start a NeoGo node",
|
||||
UsageText: "neo-go node [--config-path path] [-d] [-p/-m/-t] [--config-file file]",
|
||||
Action: startServer,
|
||||
Flags: cfgFlags,
|
||||
},
|
||||
{
|
||||
Name: "db",
|
||||
Usage: "database manipulations",
|
||||
Subcommands: []cli.Command{
|
||||
Usage: "Database manipulations",
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "dump",
|
||||
Usage: "dump blocks (starting with block #1) to the file",
|
||||
UsageText: "neo-go db dump -o file [-s start] [-c count] [--config-path path] [-p/-m/-t] [--config-file file]",
|
||||
Usage: "Dump blocks (starting with the genesis or specified block) to the file",
|
||||
UsageText: "neo-go db dump [-o file] [-s start] [-c count] [--config-path path] [-p/-m/-t] [--config-file file]",
|
||||
Action: dumpDB,
|
||||
Flags: cfgCountOutFlags,
|
||||
},
|
||||
{
|
||||
Name: "dump-bin",
|
||||
Usage: "Dump blocks (starting with the genesis or specified block) to the directory in binary format",
|
||||
UsageText: "neo-go db dump-bin -o directory [-s start] [-c count] [--config-path path] [-p/-m/-t] [--config-file file]",
|
||||
Action: dumpBin,
|
||||
Flags: cfgCountOutFlags,
|
||||
},
|
||||
{
|
||||
Name: "restore",
|
||||
Usage: "restore blocks from the file",
|
||||
UsageText: "neo-go db restore -i file [--dump] [-n] [-c count] [--config-path path] [-p/-m/-t] [--config-file file]",
|
||||
Usage: "Restore blocks from the file",
|
||||
UsageText: "neo-go db restore [-i file] [--dump] [-n] [-c count] [--config-path path] [-p/-m/-t] [--config-file file]",
|
||||
Action: restoreDB,
|
||||
Flags: cfgCountInFlags,
|
||||
},
|
||||
{
|
||||
Name: "reset",
|
||||
Usage: "reset database to the previous state",
|
||||
Usage: "Reset database to the previous state",
|
||||
UsageText: "neo-go db reset --height height [--config-path path] [-p/-m/-t] [--config-file file]",
|
||||
Action: resetDB,
|
||||
Flags: cfgHeightFlags,
|
||||
|
@ -131,10 +140,11 @@ func newGraceContext() context.Context {
|
|||
return ctx
|
||||
}
|
||||
|
||||
func initBCWithMetrics(cfg config.Config, log *zap.Logger) (*core.Blockchain, *metrics.Service, *metrics.Service, error) {
|
||||
chain, _, err := initBlockChain(cfg, log)
|
||||
// InitBCWithMetrics initializes the blockchain with metrics with the given configuration.
|
||||
func InitBCWithMetrics(cfg config.Config, log *zap.Logger) (*core.Blockchain, storage.Store, *metrics.Service, *metrics.Service, error) {
|
||||
chain, store, err := initBlockChain(cfg, log)
|
||||
if err != nil {
|
||||
return nil, nil, nil, cli.NewExitError(err, 1)
|
||||
return nil, nil, nil, nil, cli.Exit(err, 1)
|
||||
}
|
||||
prometheus := metrics.NewPrometheusService(cfg.ApplicationConfiguration.Prometheus, log)
|
||||
pprof := metrics.NewPprofService(cfg.ApplicationConfiguration.Pprof, log)
|
||||
|
@ -142,14 +152,14 @@ func initBCWithMetrics(cfg config.Config, log *zap.Logger) (*core.Blockchain, *m
|
|||
go chain.Run()
|
||||
err = prometheus.Start()
|
||||
if err != nil {
|
||||
return nil, nil, nil, cli.NewExitError(fmt.Errorf("failed to start Prometheus service: %w", err), 1)
|
||||
return nil, nil, nil, nil, cli.Exit(fmt.Errorf("failed to start Prometheus service: %w", err), 1)
|
||||
}
|
||||
err = pprof.Start()
|
||||
if err != nil {
|
||||
return nil, nil, nil, cli.NewExitError(fmt.Errorf("failed to start Pprof service: %w", err), 1)
|
||||
return nil, nil, nil, nil, cli.Exit(fmt.Errorf("failed to start Pprof service: %w", err), 1)
|
||||
}
|
||||
|
||||
return chain, prometheus, pprof, nil
|
||||
return chain, store, prometheus, pprof, nil
|
||||
}
|
||||
|
||||
func dumpDB(ctx *cli.Context) error {
|
||||
|
@ -158,11 +168,11 @@ func dumpDB(ctx *cli.Context) error {
|
|||
}
|
||||
cfg, err := options.GetConfigFromContext(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
log, _, logCloser, err := options.HandleLoggingParams(ctx.Bool("debug"), cfg.ApplicationConfiguration)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
if logCloser != nil {
|
||||
defer func() { _ = logCloser() }()
|
||||
|
@ -174,13 +184,13 @@ func dumpDB(ctx *cli.Context) error {
|
|||
if out := ctx.String("out"); out != "" {
|
||||
outStream, err = os.Create(out)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
}
|
||||
defer outStream.Close()
|
||||
writer := io.NewBinWriterFromIO(outStream)
|
||||
|
||||
chain, prometheus, pprof, err := initBCWithMetrics(cfg, log)
|
||||
chain, _, prometheus, pprof, err := InitBCWithMetrics(cfg, log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -192,7 +202,7 @@ func dumpDB(ctx *cli.Context) error {
|
|||
|
||||
chainCount := chain.BlockHeight() + 1
|
||||
if start+count > chainCount {
|
||||
return cli.NewExitError(fmt.Errorf("chain is not that high (%d) to dump %d blocks starting from %d", chainCount-1, count, start), 1)
|
||||
return cli.Exit(fmt.Errorf("chain is not that high (%d) to dump %d blocks starting from %d", chainCount-1, count, start), 1)
|
||||
}
|
||||
if count == 0 {
|
||||
count = chainCount - start
|
||||
|
@ -203,7 +213,7 @@ func dumpDB(ctx *cli.Context) error {
|
|||
writer.WriteU32LE(count)
|
||||
err = chaindump.Dump(chain, writer, start, count)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err.Error(), 1)
|
||||
return cli.Exit(err.Error(), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -218,7 +228,7 @@ func restoreDB(ctx *cli.Context) error {
|
|||
}
|
||||
log, _, logCloser, err := options.HandleLoggingParams(ctx.Bool("debug"), cfg.ApplicationConfiguration)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
if logCloser != nil {
|
||||
defer func() { _ = logCloser() }()
|
||||
|
@ -229,7 +239,7 @@ func restoreDB(ctx *cli.Context) error {
|
|||
if in := ctx.String("in"); in != "" {
|
||||
inStream, err = os.Open(in)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
}
|
||||
defer inStream.Close()
|
||||
|
@ -240,7 +250,7 @@ func restoreDB(ctx *cli.Context) error {
|
|||
cfg.ApplicationConfiguration.SaveStorageBatch = true
|
||||
}
|
||||
|
||||
chain, prometheus, pprof, err := initBCWithMetrics(cfg, log)
|
||||
chain, _, prometheus, pprof, err := InitBCWithMetrics(cfg, log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -254,7 +264,7 @@ func restoreDB(ctx *cli.Context) error {
|
|||
if ctx.Bool("incremental") {
|
||||
start = reader.ReadU32LE()
|
||||
if chain.BlockHeight()+1 < start {
|
||||
return cli.NewExitError(fmt.Errorf("expected height: %d, dump starts at %d",
|
||||
return cli.Exit(fmt.Errorf("expected height: %d, dump starts at %d",
|
||||
chain.BlockHeight()+1, start), 1)
|
||||
}
|
||||
}
|
||||
|
@ -266,10 +276,10 @@ func restoreDB(ctx *cli.Context) error {
|
|||
|
||||
var allBlocks = reader.ReadU32LE()
|
||||
if reader.Err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
if skip+count > allBlocks {
|
||||
return cli.NewExitError(fmt.Errorf("input file has only %d blocks, can't read %d starting from %d", allBlocks, count, skip), 1)
|
||||
return cli.Exit(fmt.Errorf("input file has only %d blocks, can't read %d starting from %d", allBlocks, count, skip), 1)
|
||||
}
|
||||
if count == 0 {
|
||||
count = allBlocks - skip
|
||||
|
@ -320,7 +330,7 @@ func restoreDB(ctx *cli.Context) error {
|
|||
|
||||
err = chaindump.Restore(chain, reader, skip, count, f)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(fmt.Errorf("wrong dump file or settings mismatch: %w", err), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -331,29 +341,29 @@ func resetDB(ctx *cli.Context) error {
|
|||
}
|
||||
cfg, err := options.GetConfigFromContext(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
h := uint32(ctx.Uint("height"))
|
||||
|
||||
log, _, logCloser, err := options.HandleLoggingParams(ctx.Bool("debug"), cfg.ApplicationConfiguration)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
if logCloser != nil {
|
||||
defer func() { _ = logCloser() }()
|
||||
}
|
||||
chain, store, err := initBlockChain(cfg, log)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to create Blockchain instance: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to create Blockchain instance: %w", err), 1)
|
||||
}
|
||||
|
||||
err = chain.Reset(h)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to reset chain state to height %d: %w", h, err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to reset chain state to height %d: %w", h, err), 1)
|
||||
}
|
||||
err = store.Close()
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to close the DB: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to close the DB: %w", err), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -442,12 +452,12 @@ func startServer(ctx *cli.Context) error {
|
|||
|
||||
cfg, err := options.GetConfigFromContext(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
var logDebug = ctx.Bool("debug")
|
||||
log, logLevel, logCloser, err := options.HandleLoggingParams(logDebug, cfg.ApplicationConfiguration)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
if logCloser != nil {
|
||||
defer func() { _ = logCloser() }()
|
||||
|
@ -458,12 +468,12 @@ func startServer(ctx *cli.Context) error {
|
|||
|
||||
serverConfig, err := network.NewServerConfig(cfg)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
chain, prometheus, pprof, err := initBCWithMetrics(cfg, log)
|
||||
chain, _, prometheus, pprof, err := InitBCWithMetrics(cfg, log)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer func() {
|
||||
pprof.ShutDown()
|
||||
|
@ -473,31 +483,31 @@ func startServer(ctx *cli.Context) error {
|
|||
|
||||
serv, err := network.NewServer(serverConfig, chain, chain.GetStateSyncModule(), log)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to create network server: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to create network server: %w", err), 1)
|
||||
}
|
||||
srMod := chain.GetStateModule().(*corestate.Module) // Take full responsibility here.
|
||||
sr, err := stateroot.New(serverConfig.StateRootCfg, srMod, log, chain, serv.BroadcastExtensible)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't initialize StateRoot service: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't initialize StateRoot service: %w", err), 1)
|
||||
}
|
||||
serv.AddExtensibleService(sr, stateroot.Category, sr.OnPayload)
|
||||
|
||||
oracleSrv, err := mkOracle(cfg.ApplicationConfiguration.Oracle, cfg.ProtocolConfiguration.Magic, chain, serv, log)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
dbftSrv, err := mkConsensus(cfg.ApplicationConfiguration.Consensus, serverConfig.TimePerBlock, chain, serv, log)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
p2pNotary, err := mkP2PNotary(cfg.ApplicationConfiguration.P2PNotary, chain, serv, log)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
errChan := make(chan error)
|
||||
rpcServer := rpcsrv.New(chain, cfg.ApplicationConfiguration.RPC, serv, oracleSrv, log, errChan)
|
||||
serv.AddService(&rpcServer)
|
||||
|
||||
serv.AddService(rpcServer)
|
||||
setNeoGoVersion(config.Version)
|
||||
serv.Start()
|
||||
if !cfg.ApplicationConfiguration.RPC.StartWhenSynchronized {
|
||||
// Run RPC server in a separate routine. This is necessary to avoid a potential
|
||||
|
@ -552,10 +562,10 @@ Main:
|
|||
logLevel.SetLevel(newLogLevel)
|
||||
log.Warn("using new logging level", zap.Stringer("level", newLogLevel))
|
||||
}
|
||||
serv.DelService(&rpcServer)
|
||||
serv.DelService(rpcServer)
|
||||
rpcServer.Shutdown()
|
||||
rpcServer = rpcsrv.New(chain, cfgnew.ApplicationConfiguration.RPC, serv, oracleSrv, log, errChan)
|
||||
serv.AddService(&rpcServer)
|
||||
serv.AddService(rpcServer)
|
||||
if !cfgnew.ApplicationConfiguration.RPC.StartWhenSynchronized || serv.IsInSync() {
|
||||
// Here similar to the initial run (see above for-loop), so async.
|
||||
go rpcServer.Start()
|
||||
|
@ -640,7 +650,7 @@ Main:
|
|||
}
|
||||
|
||||
if shutdownErr != nil {
|
||||
return cli.NewExitError(shutdownErr, 1)
|
||||
return cli.Exit(shutdownErr, 1)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -650,7 +660,7 @@ Main:
|
|||
func initBlockChain(cfg config.Config, log *zap.Logger) (*core.Blockchain, storage.Store, error) {
|
||||
store, err := storage.NewStore(cfg.ApplicationConfiguration.DBConfiguration)
|
||||
if err != nil {
|
||||
return nil, nil, cli.NewExitError(fmt.Errorf("could not initialize storage: %w", err), 1)
|
||||
return nil, nil, cli.Exit(fmt.Errorf("could not initialize storage: %w", err), 1)
|
||||
}
|
||||
|
||||
chain, err := core.NewBlockchain(store, cfg.Blockchain(), log)
|
||||
|
@ -663,7 +673,7 @@ func initBlockChain(cfg config.Config, log *zap.Logger) (*core.Blockchain, stora
|
|||
errArgs = append(errArgs, closeErr)
|
||||
}
|
||||
|
||||
return nil, nil, cli.NewExitError(fmt.Errorf(errText, errArgs...), 1)
|
||||
return nil, nil, cli.Exit(fmt.Errorf(errText, errArgs...), 1)
|
||||
}
|
||||
return chain, store, nil
|
||||
}
|
||||
|
|
|
@ -15,7 +15,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
|
@ -185,11 +185,11 @@ func TestInitBCWithMetrics(t *testing.T) {
|
|||
})
|
||||
|
||||
t.Run("bad store", func(t *testing.T) {
|
||||
_, _, _, err = initBCWithMetrics(config.Config{}, logger)
|
||||
_, _, _, _, err = InitBCWithMetrics(config.Config{}, logger)
|
||||
require.Error(t, err)
|
||||
})
|
||||
|
||||
chain, prometheus, pprof, err := initBCWithMetrics(cfg, logger)
|
||||
chain, _, prometheus, pprof, err := InitBCWithMetrics(cfg, logger)
|
||||
require.NoError(t, err)
|
||||
t.Cleanup(func() {
|
||||
chain.Close()
|
||||
|
|
|
@ -56,13 +56,13 @@ func TestCalcHash(t *testing.T) {
|
|||
|
||||
cmd := []string{"neo-go", "contract", "calc-hash"}
|
||||
t.Run("no sender", func(t *testing.T) {
|
||||
e.RunWithError(t, append(cmd, "--in", nefPath, "--manifest", manifestPath)...)
|
||||
e.RunWithErrorCheck(t, `Required flag "sender" not set`, append(cmd, "--in", nefPath, "--manifest", manifestPath)...)
|
||||
})
|
||||
t.Run("no nef file", func(t *testing.T) {
|
||||
e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), "--manifest", manifestPath)...)
|
||||
e.RunWithErrorCheck(t, `Required flag "in" not set`, append(cmd, "--sender", sender.StringLE(), "--manifest", manifestPath)...)
|
||||
})
|
||||
t.Run("no manifest file", func(t *testing.T) {
|
||||
e.RunWithError(t, append(cmd, "--sender", sender.StringLE(), "--in", nefPath)...)
|
||||
e.RunWithErrorCheck(t, `Required flag "manifest" not set`, append(cmd, "--sender", sender.StringLE(), "--in", nefPath)...)
|
||||
})
|
||||
t.Run("invalid nef path", func(t *testing.T) {
|
||||
e.RunWithError(t, append(cmd, "--sender", sender.StringLE(),
|
||||
|
@ -289,7 +289,7 @@ func TestContractInitAndCompile(t *testing.T) {
|
|||
e := testcli.NewExecutor(t, false)
|
||||
|
||||
t.Run("no path is provided", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "contract", "init")
|
||||
e.RunWithErrorCheck(t, `Required flag "name" not set`, "neo-go", "contract", "init")
|
||||
})
|
||||
t.Run("invalid path", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "contract", "init", "--name", "\x00")
|
||||
|
@ -313,7 +313,7 @@ func TestContractInitAndCompile(t *testing.T) {
|
|||
manifestPath := filepath.Join(tmpDir, "testcontract.manifest.json")
|
||||
cmd := []string{"neo-go", "contract", "compile"}
|
||||
t.Run("missing source", func(t *testing.T) {
|
||||
e.RunWithError(t, cmd...)
|
||||
e.RunWithErrorCheck(t, `Required flag "in" not set`, cmd...)
|
||||
})
|
||||
|
||||
cmd = append(cmd, "--in", srcPath, "--out", nefPath, "--manifest", manifestPath)
|
||||
|
@ -487,10 +487,10 @@ func TestDeployWithSigners(t *testing.T) {
|
|||
e.RunWithError(t, "neo-go", "contract", "deploy",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||
"--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr,
|
||||
"--in", "", "--manifest", manifestName)
|
||||
"--in", nefName, "--manifest", manifestName)
|
||||
})
|
||||
t.Run("missing manifest", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "contract", "deploy",
|
||||
e.RunWithErrorCheck(t, "required flag --manifest is empty", "neo-go", "contract", "deploy",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||
"--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr,
|
||||
"--in", nefName, "--manifest", "")
|
||||
|
@ -517,7 +517,7 @@ func TestDeployWithSigners(t *testing.T) {
|
|||
"[", "str1", "str2", "]")
|
||||
})
|
||||
t.Run("missing RPC", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "contract", "deploy",
|
||||
e.RunWithErrorCheck(t, `Required flag "rpc-endpoint" not set`, "neo-go", "contract", "deploy",
|
||||
"--wallet", testcli.ValidatorWallet, "--address", testcli.ValidatorAddr,
|
||||
"--in", nefName, "--manifest", manifestName,
|
||||
"[", "str1", "str2", "]")
|
||||
|
@ -548,28 +548,29 @@ func TestContractManifestGroups(t *testing.T) {
|
|||
"--out", nefName, "--manifest", manifestName)
|
||||
|
||||
t.Run("missing wallet", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "contract", "manifest", "add-group")
|
||||
e.RunWithErrorCheck(t, `Required flags "sender, address, nef, manifest" not set`, "neo-go", "contract", "manifest", "add-group")
|
||||
})
|
||||
t.Run("invalid wallet", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "contract", "manifest", "add-group",
|
||||
"--wallet", t.TempDir())
|
||||
"--wallet", t.TempDir(), "--sender", testcli.TestWalletAccount, "--address", testcli.TestWalletAccount,
|
||||
"--nef", nefName, "--manifest", manifestName)
|
||||
})
|
||||
t.Run("invalid sender", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "contract", "manifest", "add-group",
|
||||
e.RunWithErrorCheck(t, `invalid value "not-a-sender" for flag -sender: invalid base58 digit ('-')`, "neo-go", "contract", "manifest", "add-group",
|
||||
"--wallet", testcli.TestWalletPath, "--address", testcli.TestWalletAccount,
|
||||
"--sender", "not-a-sender")
|
||||
"--sender", "not-a-sender", "--nef", nefName, "--manifest", manifestName)
|
||||
})
|
||||
t.Run("invalid NEF file", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "contract", "manifest", "add-group",
|
||||
"--wallet", testcli.TestWalletPath, "--address", testcli.TestWalletAccount,
|
||||
"--sender", testcli.TestWalletAccount, "--nef", tmpDir)
|
||||
"--sender", testcli.TestWalletAccount, "--nef", tmpDir, "--manifest", manifestName)
|
||||
})
|
||||
t.Run("corrupted NEF file", func(t *testing.T) {
|
||||
f := filepath.Join(tmpDir, "invalid.nef")
|
||||
require.NoError(t, os.WriteFile(f, []byte{1, 2, 3}, os.ModePerm))
|
||||
e.RunWithError(t, "neo-go", "contract", "manifest", "add-group",
|
||||
"--wallet", testcli.TestWalletPath, "--address", testcli.TestWalletAccount,
|
||||
"--sender", testcli.TestWalletAccount, "--nef", f)
|
||||
"--sender", testcli.TestWalletAccount, "--nef", f, "--manifest", manifestName)
|
||||
})
|
||||
t.Run("invalid manifest file", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "contract", "manifest", "add-group",
|
||||
|
@ -630,9 +631,17 @@ func TestContract_TestInvokeScript(t *testing.T) {
|
|||
"--out", goodNef, "--manifest", manifestName)
|
||||
|
||||
t.Run("missing in", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "contract", "testinvokescript",
|
||||
e.RunWithErrorCheck(t, `Required flag "in" not set`, "neo-go", "contract", "testinvokescript",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0])
|
||||
})
|
||||
t.Run("empty in", func(t *testing.T) {
|
||||
e.RunWithErrorCheck(t, "required flag --in is empty", "neo-go", "contract", "testinvokescript", "-i", "",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0])
|
||||
})
|
||||
t.Run("empty rpc", func(t *testing.T) {
|
||||
e.RunWithErrorCheck(t, "required flag --rpc-endpoint is empty", "neo-go", "contract", "testinvokescript", "-i", goodNef,
|
||||
"--rpc-endpoint", "")
|
||||
})
|
||||
t.Run("unexisting in", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "contract", "testinvokescript",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||
|
@ -723,7 +732,7 @@ func TestComlileAndInvokeFunction(t *testing.T) {
|
|||
|
||||
t.Run("check calc hash", func(t *testing.T) {
|
||||
// missing sender
|
||||
e.RunWithError(t, "neo-go", "contract", "calc-hash",
|
||||
e.RunWithErrorCheck(t, `Required flag "sender" not set`, "neo-go", "contract", "calc-hash",
|
||||
"--in", nefName,
|
||||
"--manifest", manifestName)
|
||||
|
||||
|
@ -755,7 +764,7 @@ func TestComlileAndInvokeFunction(t *testing.T) {
|
|||
e.RunWithError(t, append(cmd, "--", "notahash")...)
|
||||
})
|
||||
t.Run("missing RPC address", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "contract", "testinvokefunction",
|
||||
e.RunWithErrorCheck(t, `Required flag "rpc-endpoint" not set`, "neo-go", "contract", "testinvokefunction",
|
||||
h.StringLE(), "getValue")
|
||||
})
|
||||
|
||||
|
@ -1038,7 +1047,7 @@ func TestContractInspect(t *testing.T) {
|
|||
|
||||
cmd := []string{"neo-go", "contract", "inspect"}
|
||||
t.Run("missing input", func(t *testing.T) {
|
||||
e.RunWithError(t, cmd...)
|
||||
e.RunWithErrorCheck(t, `Required flag "in" not set`, cmd...)
|
||||
})
|
||||
t.Run("with raw '.go'", func(t *testing.T) {
|
||||
e.RunWithError(t, append(cmd, "--in", srcPath)...)
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package smartcontract
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"fmt"
|
||||
"os"
|
||||
"strings"
|
||||
|
@ -9,34 +10,40 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/binding"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/rpcbinding"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
var generatorFlags = []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "config, c",
|
||||
Usage: "Configuration file to use",
|
||||
&cli.StringFlag{
|
||||
Name: "config",
|
||||
Aliases: []string{"c"},
|
||||
Usage: `Configuration bindings file to use (*.yml). Configuration file is
|
||||
generated by 'contract compile' command with --bindings flag`,
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "manifest, m",
|
||||
&cli.StringFlag{
|
||||
Name: "manifest",
|
||||
Aliases: []string{"m"},
|
||||
Required: true,
|
||||
Usage: "Read contract manifest (*.manifest.json) file",
|
||||
Action: cmdargs.EnsureNotEmpty("manifest"),
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "out, o",
|
||||
&cli.StringFlag{
|
||||
Name: "out",
|
||||
Aliases: []string{"o"},
|
||||
Required: true,
|
||||
Usage: "Output of the compiled wrapper",
|
||||
Action: cmdargs.EnsureNotEmpty("out"),
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "hash",
|
||||
Usage: "Smart-contract hash. If not passed, the wrapper will be designed for dynamic hash usage",
|
||||
},
|
||||
}
|
||||
|
||||
var generateWrapperCmd = cli.Command{
|
||||
var generateWrapperCmd = &cli.Command{
|
||||
Name: "generate-wrapper",
|
||||
Usage: "generate wrapper to use in other contracts",
|
||||
Usage: "Generate wrapper to use in other contracts",
|
||||
UsageText: "neo-go contract generate-wrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]",
|
||||
Description: `Generates a Go wrapper to use it in other smart contracts. If the
|
||||
--hash flag is provided, CALLT instruction is used for the target contract
|
||||
|
@ -48,9 +55,9 @@ var generateWrapperCmd = cli.Command{
|
|||
Flags: generatorFlags,
|
||||
}
|
||||
|
||||
var generateRPCWrapperCmd = cli.Command{
|
||||
var generateRPCWrapperCmd = &cli.Command{
|
||||
Name: "generate-rpcwrapper",
|
||||
Usage: "generate RPC wrapper to use for data reads",
|
||||
Usage: "Generate RPC wrapper to use for data reads",
|
||||
UsageText: "neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]",
|
||||
Action: contractGenerateRPCWrapper,
|
||||
Flags: generatorFlags,
|
||||
|
@ -76,23 +83,26 @@ func contractGenerateSomething(ctx *cli.Context, cb func(binding.Config) error)
|
|||
if hStr := ctx.String("hash"); len(hStr) != 0 {
|
||||
h, err = util.Uint160DecodeStringLE(strings.TrimPrefix(hStr, "0x"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid contract hash: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("invalid contract hash: %w", err), 1)
|
||||
}
|
||||
}
|
||||
m, _, err := readManifest(ctx.String("manifest"), h)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't read contract manifest: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't read contract manifest: %w", err), 1)
|
||||
}
|
||||
|
||||
cfg := binding.NewConfig()
|
||||
if cfgPath := ctx.String("config"); cfgPath != "" {
|
||||
bs, err := os.ReadFile(cfgPath)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't read config file: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't read config file: %w", err), 1)
|
||||
}
|
||||
err = yaml.Unmarshal(bs, &cfg)
|
||||
decoder := yaml.NewDecoder(bytes.NewReader(bs))
|
||||
decoder.KnownFields(true)
|
||||
|
||||
err = decoder.Decode(&cfg)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't parse config file: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't parse config file: %w", err), 1)
|
||||
}
|
||||
}
|
||||
cfg.Manifest = m
|
||||
|
@ -100,7 +110,7 @@ func contractGenerateSomething(ctx *cli.Context, cb func(binding.Config) error)
|
|||
|
||||
f, err := os.Create(ctx.String("out"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't create output file: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't create output file: %w", err), 1)
|
||||
}
|
||||
defer f.Close()
|
||||
|
||||
|
@ -108,7 +118,7 @@ func contractGenerateSomething(ctx *cli.Context, cb func(binding.Config) error)
|
|||
|
||||
err = cb(cfg)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("error during generation: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("error during generation: %w", err), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
package smartcontract
|
||||
package smartcontract_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
|
@ -6,14 +6,13 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/testcli"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli"
|
||||
)
|
||||
|
||||
func TestGenerate(t *testing.T) {
|
||||
|
@ -124,8 +123,7 @@ func TestGenerate(t *testing.T) {
|
|||
0x04, 0x08, 0x15, 0x16, 0x23, 0x42, 0x43, 0x44, 0x00, 0x01,
|
||||
0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF, 0x03, 0x04,
|
||||
}
|
||||
app := cli.NewApp()
|
||||
app.Commands = []cli.Command{generateWrapperCmd}
|
||||
e := testcli.NewExecutor(t, false)
|
||||
|
||||
rawCfg := `package: wrapper
|
||||
hash: ` + h.StringLE() + `
|
||||
|
@ -144,12 +142,12 @@ callflags:
|
|||
cfgPath := filepath.Join(t.TempDir(), "binding.yml")
|
||||
require.NoError(t, os.WriteFile(cfgPath, []byte(rawCfg), os.ModePerm))
|
||||
|
||||
require.NoError(t, app.Run([]string{"", "generate-wrapper",
|
||||
e.Run(t, []string{"", "contract", "generate-wrapper",
|
||||
"--manifest", manifestFile,
|
||||
"--config", cfgPath,
|
||||
"--out", outFile,
|
||||
"--hash", h.StringLE(),
|
||||
}))
|
||||
}...)
|
||||
|
||||
const expected = `// Code generated by neo-go contract generate-wrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||
|
||||
|
@ -234,11 +232,11 @@ func MyFunc(in map[int]mycontract.Input) []mycontract.Output {
|
|||
require.NoError(t, err)
|
||||
require.Equal(t, expected, string(data))
|
||||
|
||||
require.NoError(t, app.Run([]string{"", "generate-wrapper",
|
||||
e.Run(t, []string{"", "contract", "generate-wrapper",
|
||||
"--manifest", manifestFile,
|
||||
"--config", cfgPath,
|
||||
"--out", outFile,
|
||||
}))
|
||||
}...)
|
||||
expectedWithDynamicHash := `// Code generated by neo-go contract generate-wrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||
|
||||
// Package wrapper contains wrappers for MyContract contract.
|
||||
|
@ -350,13 +348,12 @@ func TestGenerateValidPackageName(t *testing.T) {
|
|||
0x04, 0x08, 0x15, 0x16, 0x23, 0x42, 0x43, 0x44, 0x00, 0x01,
|
||||
0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF, 0x03, 0x04,
|
||||
}
|
||||
app := cli.NewApp()
|
||||
app.Commands = []cli.Command{generateWrapperCmd, generateRPCWrapperCmd}
|
||||
require.NoError(t, app.Run([]string{"", "generate-wrapper",
|
||||
e := testcli.NewExecutor(t, false)
|
||||
e.Run(t, []string{"", "contract", "generate-wrapper",
|
||||
"--manifest", manifestFile,
|
||||
"--out", outFile,
|
||||
"--hash", "0x" + h.StringLE(),
|
||||
}))
|
||||
}...)
|
||||
|
||||
data, err := os.ReadFile(outFile)
|
||||
require.NoError(t, err)
|
||||
|
@ -378,11 +375,11 @@ func Get() int {
|
|||
return neogointernal.CallWithToken(Hash, "get", int(contract.ReadOnly)).(int)
|
||||
}
|
||||
`, string(data))
|
||||
require.NoError(t, app.Run([]string{"", "generate-rpcwrapper",
|
||||
e.Run(t, []string{"", "contract", "generate-rpcwrapper",
|
||||
"--manifest", manifestFile,
|
||||
"--out", outFile,
|
||||
"--hash", "0x" + h.StringLE(),
|
||||
}))
|
||||
}...)
|
||||
|
||||
data, err = os.ReadFile(outFile)
|
||||
require.NoError(t, err)
|
||||
|
@ -431,17 +428,16 @@ const rewriteExpectedOutputs = false
|
|||
|
||||
func TestGenerateRPCBindings(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
app := cli.NewApp()
|
||||
app.Commands = []cli.Command{generateWrapperCmd, generateRPCWrapperCmd}
|
||||
e := testcli.NewExecutor(t, false)
|
||||
|
||||
var checkBinding = func(manifest string, hash string, good string) {
|
||||
t.Run(manifest, func(t *testing.T) {
|
||||
outFile := filepath.Join(tmpDir, "out.go")
|
||||
require.NoError(t, app.Run([]string{"", "generate-rpcwrapper",
|
||||
e.Run(t, []string{"", "contract", "generate-rpcwrapper",
|
||||
"--manifest", manifest,
|
||||
"--out", outFile,
|
||||
"--hash", hash,
|
||||
}))
|
||||
}...)
|
||||
|
||||
data, err := os.ReadFile(outFile)
|
||||
require.NoError(t, err)
|
||||
|
@ -478,10 +474,9 @@ func TestGenerateRPCBindings(t *testing.T) {
|
|||
|
||||
func TestAssistedRPCBindings(t *testing.T) {
|
||||
tmpDir := t.TempDir()
|
||||
app := cli.NewApp()
|
||||
app.Commands = NewCommands()
|
||||
e := testcli.NewExecutor(t, false)
|
||||
|
||||
var checkBinding = func(source string, hasDefinedHash bool, guessEventTypes bool, suffix ...string) {
|
||||
var checkBinding = func(source, configFile, expectedFile string, hasDefinedHash, guessEventTypes bool, suffix ...string) {
|
||||
testName := source
|
||||
if len(suffix) != 0 {
|
||||
testName += suffix[0]
|
||||
|
@ -489,13 +484,20 @@ func TestAssistedRPCBindings(t *testing.T) {
|
|||
testName += fmt.Sprintf(", predefined hash: %t", hasDefinedHash)
|
||||
t.Run(testName, func(t *testing.T) {
|
||||
outFile := filepath.Join(tmpDir, "out.go")
|
||||
configFile := filepath.Join(source, "config.yml")
|
||||
expectedFile := filepath.Join(source, "rpcbindings.out")
|
||||
if len(suffix) != 0 {
|
||||
configFile = filepath.Join(source, "config"+suffix[0]+".yml")
|
||||
expectedFile = filepath.Join(source, "rpcbindings"+suffix[0]+".out")
|
||||
} else if !hasDefinedHash {
|
||||
expectedFile = filepath.Join(source, "rpcbindings_dynamic_hash.out")
|
||||
if configFile == "" {
|
||||
if len(suffix) != 0 {
|
||||
configFile = filepath.Join(source, "config"+suffix[0]+".yml")
|
||||
} else {
|
||||
configFile = filepath.Join(source, "config.yml")
|
||||
}
|
||||
}
|
||||
if expectedFile == "" {
|
||||
expectedFile = filepath.Join(source, "rpcbindings.out")
|
||||
if len(suffix) != 0 {
|
||||
expectedFile = filepath.Join(source, "rpcbindings"+suffix[0]+".out")
|
||||
} else if !hasDefinedHash {
|
||||
expectedFile = filepath.Join(source, "rpcbindings_dynamic_hash.out")
|
||||
}
|
||||
}
|
||||
manifestF := filepath.Join(tmpDir, "manifest.json")
|
||||
bindingF := filepath.Join(tmpDir, "binding.yml")
|
||||
|
@ -510,7 +512,7 @@ func TestAssistedRPCBindings(t *testing.T) {
|
|||
if guessEventTypes {
|
||||
cmd = append(cmd, "--guess-eventtypes")
|
||||
}
|
||||
require.NoError(t, app.Run(cmd))
|
||||
e.Run(t, cmd...)
|
||||
|
||||
cmds := []string{"", "contract", "generate-rpcwrapper",
|
||||
"--config", bindingF,
|
||||
|
@ -520,7 +522,7 @@ func TestAssistedRPCBindings(t *testing.T) {
|
|||
if hasDefinedHash {
|
||||
cmds = append(cmds, "--hash", "0x00112233445566778899aabbccddeeff00112233")
|
||||
}
|
||||
require.NoError(t, app.Run(cmds))
|
||||
e.Run(t, cmds...)
|
||||
|
||||
data, err := os.ReadFile(outFile)
|
||||
require.NoError(t, err)
|
||||
|
@ -537,39 +539,39 @@ func TestAssistedRPCBindings(t *testing.T) {
|
|||
}
|
||||
|
||||
for _, hasDefinedHash := range []bool{true, false} {
|
||||
checkBinding(filepath.Join("testdata", "rpcbindings", "types"), hasDefinedHash, false)
|
||||
checkBinding(filepath.Join("testdata", "rpcbindings", "structs"), hasDefinedHash, false)
|
||||
checkBinding(filepath.Join("testdata", "rpcbindings", "types"), "", "", hasDefinedHash, false)
|
||||
checkBinding(filepath.Join("testdata", "rpcbindings", "structs"), "", "", hasDefinedHash, false)
|
||||
checkBinding(filepath.Join("testdata", "rpcbindings", "royalty"), "", "", hasDefinedHash, false)
|
||||
}
|
||||
checkBinding(filepath.Join("testdata", "rpcbindings", "notifications"), true, false)
|
||||
checkBinding(filepath.Join("testdata", "rpcbindings", "notifications"), true, false, "_extended")
|
||||
checkBinding(filepath.Join("testdata", "rpcbindings", "notifications"), true, true, "_guessed")
|
||||
checkBinding(filepath.Join("testdata", "rpcbindings", "notifications"), "", "", true, false)
|
||||
checkBinding(filepath.Join("testdata", "rpcbindings", "notifications"), "", "", true, false, "_extended")
|
||||
checkBinding(filepath.Join("testdata", "rpcbindings", "notifications"), "", "", true, true, "_guessed")
|
||||
|
||||
checkBinding(filepath.Join("..", "..", "examples", "nft-d"), filepath.Join("..", "..", "examples", "nft-d", "nft.yml"), filepath.Join("testdata", "rpcbindings", "nft-d", "rpcbindings_dynamic_hash.out"), false, false)
|
||||
checkBinding(filepath.Join("..", "..", "examples", "nft-d"), filepath.Join("..", "..", "examples", "nft-d", "nft.yml"), filepath.Join("testdata", "rpcbindings", "nft-d", "rpcbindings.out"), true, true)
|
||||
checkBinding(filepath.Join("..", "..", "examples", "nft-nd"), filepath.Join("..", "..", "examples", "nft-nd", "nft.yml"), filepath.Join("testdata", "rpcbindings", "nft-nd", "rpcbindings_dynamic_hash.out"), false, false)
|
||||
checkBinding(filepath.Join("..", "..", "examples", "nft-nd"), filepath.Join("..", "..", "examples", "nft-nd", "nft.yml"), filepath.Join("testdata", "rpcbindings", "nft-nd", "rpcbindings.out"), true, true)
|
||||
|
||||
require.False(t, rewriteExpectedOutputs)
|
||||
}
|
||||
|
||||
func TestGenerate_Errors(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
app.Commands = []cli.Command{generateWrapperCmd}
|
||||
app.ExitErrHandler = func(*cli.Context, error) {}
|
||||
e := testcli.NewExecutor(t, false)
|
||||
args := []string{"neo-go", "contract", "generate-wrapper"}
|
||||
|
||||
checkError := func(t *testing.T, msg string, args ...string) {
|
||||
// cli.ExitError doesn't implement wraping properly, so we check for an error message.
|
||||
err := app.Run(append([]string{"", "generate-wrapper"}, args...))
|
||||
require.True(t, strings.Contains(err.Error(), msg), "got: %v", err)
|
||||
}
|
||||
t.Run("invalid hash", func(t *testing.T) {
|
||||
checkError(t, "invalid contract hash", "--hash", "xxx", "--manifest", "yyy", "--out", "zzz")
|
||||
e.RunWithErrorCheckExit(t, "invalid contract hash", append(args, "--hash", "xxx", "--manifest", "yyy", "--out", "zzz")...)
|
||||
})
|
||||
t.Run("missing manifest argument", func(t *testing.T) {
|
||||
checkError(t, "Required flag \"manifest\" not set", "--hash", util.Uint160{}.StringLE(), "--out", "zzz")
|
||||
e.RunWithErrorCheck(t, `Required flag "manifest" not set`, append(args, "--hash", util.Uint160{}.StringLE(), "--out", "zzz")...)
|
||||
})
|
||||
t.Run("missing manifest file", func(t *testing.T) {
|
||||
checkError(t, "can't read contract manifest", "--manifest", "notexists", "--hash", util.Uint160{}.StringLE(), "--out", "zzz")
|
||||
e.RunWithErrorCheckExit(t, "can't read contract manifest", append(args, "--manifest", "notexists", "--hash", util.Uint160{}.StringLE(), "--out", "zzz")...)
|
||||
})
|
||||
t.Run("empty manifest", func(t *testing.T) {
|
||||
manifestFile := filepath.Join(t.TempDir(), "invalid.json")
|
||||
require.NoError(t, os.WriteFile(manifestFile, []byte("[]"), os.ModePerm))
|
||||
checkError(t, "json: cannot unmarshal array into Go value of type manifest.Manifest", "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), "--out", "zzz")
|
||||
e.RunWithErrorCheckExit(t, "json: cannot unmarshal array into Go value of type manifest.Manifest", append(args, "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), "--out", "zzz")...)
|
||||
})
|
||||
t.Run("invalid manifest", func(t *testing.T) {
|
||||
manifestFile := filepath.Join(t.TempDir(), "invalid.json")
|
||||
|
@ -577,7 +579,7 @@ func TestGenerate_Errors(t *testing.T) {
|
|||
rawManifest, err := json.Marshal(m)
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm))
|
||||
checkError(t, "ABI: no methods", "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), "--out", "zzz")
|
||||
e.RunWithErrorCheckExit(t, "ABI: no methods", append(args, "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), "--out", "zzz")...)
|
||||
})
|
||||
|
||||
manifestFile := filepath.Join(t.TempDir(), "manifest.json")
|
||||
|
@ -593,9 +595,8 @@ func TestGenerate_Errors(t *testing.T) {
|
|||
require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm))
|
||||
|
||||
t.Run("missing config", func(t *testing.T) {
|
||||
checkError(t, "can't read config file",
|
||||
"--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(),
|
||||
"--config", filepath.Join(t.TempDir(), "not.exists.yml"), "--out", "zzz")
|
||||
e.RunWithErrorCheckExit(t, "can't read config file", append(args, "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(),
|
||||
"--config", filepath.Join(t.TempDir(), "not.exists.yml"), "--out", "zzz")...)
|
||||
})
|
||||
t.Run("invalid config", func(t *testing.T) {
|
||||
rawCfg := `package: wrapper
|
||||
|
@ -605,23 +606,13 @@ callflags:
|
|||
cfgPath := filepath.Join(t.TempDir(), "binding.yml")
|
||||
require.NoError(t, os.WriteFile(cfgPath, []byte(rawCfg), os.ModePerm))
|
||||
|
||||
checkError(t, "can't parse config file",
|
||||
"--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(),
|
||||
"--config", cfgPath, "--out", "zzz")
|
||||
e.RunWithErrorCheckExit(t, "can't parse config file", append(args, "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(),
|
||||
"--config", cfgPath, "--out", "zzz")...)
|
||||
})
|
||||
}
|
||||
|
||||
func TestCompile_GuessEventTypes(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
app.Commands = NewCommands()
|
||||
app.ExitErrHandler = func(*cli.Context, error) {}
|
||||
|
||||
checkError := func(t *testing.T, msg string, args ...string) {
|
||||
// cli.ExitError doesn't implement wraping properly, so we check for an error message.
|
||||
err := app.Run(args)
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.Contains(err.Error(), msg), "got: %v", err)
|
||||
}
|
||||
e := testcli.NewExecutor(t, false)
|
||||
check := func(t *testing.T, source string, expectedErrText string) {
|
||||
tmpDir := t.TempDir()
|
||||
configFile := filepath.Join(source, "invalid.yml")
|
||||
|
@ -636,7 +627,7 @@ func TestCompile_GuessEventTypes(t *testing.T) {
|
|||
"--out", nefF,
|
||||
"--guess-eventtypes",
|
||||
}
|
||||
checkError(t, expectedErrText, cmd...)
|
||||
e.RunWithErrorCheckExit(t, expectedErrText, cmd...)
|
||||
}
|
||||
|
||||
t.Run("not declared in manifest", func(t *testing.T) {
|
||||
|
@ -664,10 +655,7 @@ func TestCompile_GuessEventTypes(t *testing.T) {
|
|||
}
|
||||
|
||||
func TestGenerateRPCBindings_Errors(t *testing.T) {
|
||||
app := cli.NewApp()
|
||||
app.Commands = NewCommands()
|
||||
app.ExitErrHandler = func(*cli.Context, error) {}
|
||||
|
||||
e := testcli.NewExecutor(t, false)
|
||||
t.Run("duplicating resulting fields", func(t *testing.T) {
|
||||
check := func(t *testing.T, packageName string, autogen bool, expectedError string) {
|
||||
tmpDir := t.TempDir()
|
||||
|
@ -687,16 +675,14 @@ func TestGenerateRPCBindings_Errors(t *testing.T) {
|
|||
if autogen {
|
||||
cmd = append(cmd, "--guess-eventtypes")
|
||||
}
|
||||
require.NoError(t, app.Run(cmd))
|
||||
e.Run(t, cmd...)
|
||||
|
||||
cmds := []string{"", "contract", "generate-rpcwrapper",
|
||||
"--config", bindingF,
|
||||
"--manifest", manifestF,
|
||||
"--out", out,
|
||||
}
|
||||
err := app.Run(cmds)
|
||||
require.Error(t, err)
|
||||
require.True(t, strings.Contains(err.Error(), expectedError), err.Error())
|
||||
e.RunWithErrorCheckExit(t, expectedError, cmds...)
|
||||
}
|
||||
|
||||
t.Run("event", func(t *testing.T) {
|
||||
|
|
|
@ -2,7 +2,6 @@ package smartcontract
|
|||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
|
@ -13,34 +12,30 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func manifestAddGroup(ctx *cli.Context) error {
|
||||
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
sender, err := flags.ParseAddress(ctx.String("sender"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid sender: %w", err), 1)
|
||||
}
|
||||
|
||||
sender := ctx.Generic("sender").(*flags.Address)
|
||||
nf, _, err := readNEFFile(ctx.String("nef"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't read NEF file: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't read NEF file: %w", err), 1)
|
||||
}
|
||||
|
||||
mPath := ctx.String("manifest")
|
||||
m, _, err := readManifest(mPath, util.Uint160{})
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't read contract manifest: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't read contract manifest: %w", err), 1)
|
||||
}
|
||||
|
||||
h := state.CreateContractHash(sender, nf.Checksum, m.Name)
|
||||
h := state.CreateContractHash(sender.Uint160(), nf.Checksum, m.Name)
|
||||
|
||||
gAcc, w, err := options.GetAccFromContext(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't get account to sign group with: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't get account to sign group with: %w", err), 1)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
|
@ -64,21 +59,17 @@ func manifestAddGroup(ctx *cli.Context) error {
|
|||
|
||||
rawM, err := json.Marshal(m)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't marshal manifest: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't marshal manifest: %w", err), 1)
|
||||
}
|
||||
|
||||
err = os.WriteFile(mPath, rawM, os.ModePerm)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't write manifest file: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't write manifest file: %w", err), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func readNEFFile(filename string) (*nef.File, []byte, error) {
|
||||
if len(filename) == 0 {
|
||||
return nil, nil, errors.New("no nef file was provided")
|
||||
}
|
||||
|
||||
f, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
@ -96,10 +87,6 @@ func readNEFFile(filename string) (*nef.File, []byte, error) {
|
|||
// it for validness against the provided contract hash. If empty hash is specified
|
||||
// then no hash-related manifest groups check is performed.
|
||||
func readManifest(filename string, hash util.Uint160) (*manifest.Manifest, []byte, error) {
|
||||
if len(filename) == 0 {
|
||||
return nil, nil, errNoManifestFile
|
||||
}
|
||||
|
||||
manifestBytes, err := os.ReadFile(filename)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
|
|
|
@ -27,25 +27,27 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
// addressFlagName is a flag name used for address-related operations. It should be
|
||||
// the same within the smartcontract package, thus, use this constant.
|
||||
const addressFlagName = "address, a"
|
||||
// addressFlagName and addressFlagAlias are a flag name and its alias
|
||||
// used for address-related operations. It should be the same within
|
||||
// the smartcontract package, thus, use this constant.
|
||||
const (
|
||||
addressFlagName = "address"
|
||||
addressFlagAlias = "a"
|
||||
)
|
||||
|
||||
var (
|
||||
errNoInput = errors.New("no input file was found, specify an input file with the '--in or -i' flag")
|
||||
errNoConfFile = errors.New("no config file was found, specify a config file with the '--config' or '-c' flag")
|
||||
errNoManifestFile = errors.New("no manifest file was found, specify manifest file with '--manifest' or '-m' flag")
|
||||
errNoMethod = errors.New("no method specified for function invocation command")
|
||||
errNoScriptHash = errors.New("no smart contract hash was provided, specify one as the first argument")
|
||||
errNoSmartContractName = errors.New("no name was provided, specify the '--name or -n' flag")
|
||||
errFileExist = errors.New("A file with given smart-contract name already exists")
|
||||
addressFlag = flags.AddressFlag{
|
||||
Name: addressFlagName,
|
||||
Usage: "address to use as transaction signee (and gas source)",
|
||||
errNoConfFile = errors.New("no config file was found, specify a config file with the '--config' or '-c' flag")
|
||||
errNoMethod = errors.New("no method specified for function invocation command")
|
||||
errNoScriptHash = errors.New("no smart contract hash was provided, specify one as the first argument")
|
||||
errFileExist = errors.New("A file with given smart-contract name already exists")
|
||||
addressFlag = &flags.AddressFlag{
|
||||
Name: addressFlagName,
|
||||
Aliases: []string{addressFlagAlias},
|
||||
Usage: "Address to use as transaction signee (and gas source)",
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -74,11 +76,14 @@ func RuntimeNotify(args []any) {
|
|||
)
|
||||
|
||||
// NewCommands returns 'contract' command.
|
||||
func NewCommands() []cli.Command {
|
||||
func NewCommands() []*cli.Command {
|
||||
testInvokeScriptFlags := []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "in, i",
|
||||
Usage: "Input location of the .nef file that needs to be invoked",
|
||||
&cli.StringFlag{
|
||||
Name: "in",
|
||||
Aliases: []string{"i"},
|
||||
Required: true,
|
||||
Usage: "Input location of the .nef file that needs to be invoked",
|
||||
Action: cmdargs.EnsureNotEmpty("in"),
|
||||
},
|
||||
options.Historic,
|
||||
}
|
||||
|
@ -96,40 +101,56 @@ func NewCommands() []cli.Command {
|
|||
invokeFunctionFlags = append(invokeFunctionFlags, options.Wallet...)
|
||||
invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...)
|
||||
deployFlags := append(invokeFunctionFlags, []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "in, i",
|
||||
Usage: "Input file for the smart contract (*.nef)",
|
||||
&cli.StringFlag{
|
||||
Name: "in",
|
||||
Aliases: []string{"i"},
|
||||
Required: true,
|
||||
Usage: "Input file for the smart contract (*.nef)",
|
||||
Action: cmdargs.EnsureNotEmpty("in"),
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "manifest, m",
|
||||
Usage: "Manifest input file (*.manifest.json)",
|
||||
&cli.StringFlag{
|
||||
Name: "manifest",
|
||||
Aliases: []string{"m"},
|
||||
Required: true,
|
||||
Usage: "Manifest input file (*.manifest.json)",
|
||||
Action: cmdargs.EnsureNotEmpty("manifest"),
|
||||
},
|
||||
}...)
|
||||
manifestAddGroupFlags := append([]cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "sender, s",
|
||||
Usage: "deploy transaction sender",
|
||||
&flags.AddressFlag{
|
||||
Name: "sender",
|
||||
Aliases: []string{"s"},
|
||||
Required: true,
|
||||
Usage: "Deploy transaction sender",
|
||||
},
|
||||
flags.AddressFlag{
|
||||
Name: addressFlagName, // use the same name for handler code unification.
|
||||
Usage: "account to sign group with",
|
||||
&flags.AddressFlag{
|
||||
Name: addressFlagName, // use the same name for handler code unification.
|
||||
Aliases: []string{addressFlagAlias},
|
||||
Required: true,
|
||||
Usage: "Account to sign group with",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "nef, n",
|
||||
Usage: "path to the NEF file",
|
||||
&cli.StringFlag{
|
||||
Name: "nef",
|
||||
Aliases: []string{"n"},
|
||||
Required: true,
|
||||
Usage: "Path to the NEF file",
|
||||
Action: cmdargs.EnsureNotEmpty("nef"),
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "manifest, m",
|
||||
Usage: "path to the manifest",
|
||||
&cli.StringFlag{
|
||||
Name: "manifest",
|
||||
Aliases: []string{"m"},
|
||||
Required: true,
|
||||
Usage: "Path to the manifest",
|
||||
Action: cmdargs.EnsureNotEmpty("manifest"),
|
||||
},
|
||||
}, options.Wallet...)
|
||||
return []cli.Command{{
|
||||
return []*cli.Command{{
|
||||
Name: "contract",
|
||||
Usage: "compile - debug - deploy smart contracts",
|
||||
Subcommands: []cli.Command{
|
||||
Usage: "Compile - debug - deploy smart contracts",
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "compile",
|
||||
Usage: "compile a smart contract to a .nef file",
|
||||
Usage: "Compile a smart contract to a .nef file",
|
||||
UsageText: "neo-go contract compile -i path [-o nef] [-v] [-d] [-m manifest] [-c yaml] [--bindings file] [--no-standards] [--no-events] [--no-permissions] [--guess-eventtypes]",
|
||||
Description: `Compiles given smart contract to a .nef file and emits other associated
|
||||
information (manifest, bindings configuration, debug information files) if
|
||||
|
@ -141,55 +162,63 @@ func NewCommands() []cli.Command {
|
|||
`,
|
||||
Action: contractCompile,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "in, i",
|
||||
Usage: "Input file for the smart contract to be compiled (*.go file or directory)",
|
||||
&cli.StringFlag{
|
||||
Name: "in",
|
||||
Aliases: []string{"i"},
|
||||
Required: true,
|
||||
Usage: "Input file for the smart contract to be compiled (*.go file or directory)",
|
||||
Action: cmdargs.EnsureNotEmpty("in"),
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "out, o",
|
||||
Usage: "Output of the compiled contract",
|
||||
&cli.StringFlag{
|
||||
Name: "out",
|
||||
Aliases: []string{"o"},
|
||||
Usage: "Output of the compiled contract",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "verbose, v",
|
||||
Usage: "Print out additional information after a compiling",
|
||||
&cli.BoolFlag{
|
||||
Name: "verbose",
|
||||
Aliases: []string{"v"},
|
||||
Usage: "Print out additional information after a compiling",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "debug, d",
|
||||
Usage: "Emit debug info in a separate file",
|
||||
&cli.StringFlag{
|
||||
Name: "debug",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "Emit debug info in a separate file",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "manifest, m",
|
||||
Usage: "Emit contract manifest (*.manifest.json) file into separate file using configuration input file (*.yml)",
|
||||
&cli.StringFlag{
|
||||
Name: "manifest",
|
||||
Aliases: []string{"m"},
|
||||
Usage: "Emit contract manifest (*.manifest.json) file into separate file using configuration input file (*.yml)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "config, c",
|
||||
Usage: "Configuration input file (*.yml)",
|
||||
&cli.StringFlag{
|
||||
Name: "config",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Configuration input file (*.yml)",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "no-standards",
|
||||
Usage: "do not check compliance with supported standards",
|
||||
Usage: "Do not check compliance with supported standards",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "no-events",
|
||||
Usage: "do not check emitted events with the manifest",
|
||||
Usage: "Do not check emitted events with the manifest",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "no-permissions",
|
||||
Usage: "do not check if invoked contracts are allowed in manifest",
|
||||
Usage: "Do not check if invoked contracts are allowed in manifest",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "guess-eventtypes",
|
||||
Usage: "guess event types for smart-contract bindings configuration from the code usages",
|
||||
Usage: "Guess event types for smart-contract bindings configuration from the code usages",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "bindings",
|
||||
Usage: "output file for smart-contract bindings configuration",
|
||||
Usage: "Output file for smart-contract bindings configuration",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "deploy",
|
||||
Usage: "deploy a smart contract (.nef with description)",
|
||||
Usage: "Deploy a smart contract (.nef with description)",
|
||||
UsageText: "neo-go contract deploy -r endpoint -w wallet [-a address] [-g gas] [-e sysgas] --in contract.nef --manifest contract.manifest.json [--out file] [--force] [--await] [data]",
|
||||
Description: `Deploys given contract into the chain. The gas parameter is for additional
|
||||
gas to be added as a network fee to prioritize the transaction. The data
|
||||
|
@ -204,7 +233,7 @@ func NewCommands() []cli.Command {
|
|||
generateRPCWrapperCmd,
|
||||
{
|
||||
Name: "invokefunction",
|
||||
Usage: "invoke deployed contract on the blockchain",
|
||||
Usage: "Invoke deployed contract on the blockchain",
|
||||
UsageText: "neo-go contract invokefunction -r endpoint -w wallet [-a address] [-g gas] [-e sysgas] [--out file] [--force] [--await] scripthash [method] [arguments...] [--] [signers...]",
|
||||
Description: `Executes given (as a script hash) deployed script with the given method,
|
||||
arguments and signers. Sender is included in the list of signers by default
|
||||
|
@ -219,7 +248,7 @@ func NewCommands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "testinvokefunction",
|
||||
Usage: "invoke deployed contract on the blockchain (test mode)",
|
||||
Usage: "Invoke deployed contract on the blockchain (test mode)",
|
||||
UsageText: "neo-go contract testinvokefunction -r endpoint [--historic index/hash] scripthash [method] [arguments...] [--] [signers...]",
|
||||
Description: `Executes given (as a script hash) deployed script with the given method,
|
||||
arguments and signers (sender is not included by default). If no method is given
|
||||
|
@ -250,63 +279,78 @@ func NewCommands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "init",
|
||||
Usage: "initialize a new smart-contract in a directory with boiler plate code",
|
||||
Usage: "Initialize a new smart-contract in a directory with boiler plate code",
|
||||
UsageText: "neo-go contract init -n name [--skip-details]",
|
||||
Action: initSmartContract,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "name, n",
|
||||
Usage: "name of the smart-contract to be initialized",
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Aliases: []string{"n"},
|
||||
Required: true,
|
||||
Usage: "Name of the smart-contract to be initialized",
|
||||
Action: cmdargs.EnsureNotEmpty("name"),
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "skip-details, skip",
|
||||
Usage: "skip filling in the projects and contract details",
|
||||
&cli.BoolFlag{
|
||||
Name: "skip-details",
|
||||
Aliases: []string{"skip"},
|
||||
Usage: "Skip filling in the projects and contract details",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "inspect",
|
||||
Usage: "creates a user readable dump of the program instructions",
|
||||
Usage: "Creates a user readable dump of the program instructions",
|
||||
UsageText: "neo-go contract inspect -i file [-c]",
|
||||
Action: inspect,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: "compile, c",
|
||||
Usage: "compile input file (it should be go code then)",
|
||||
&cli.BoolFlag{
|
||||
Name: "compile",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Compile input file (it should be go code then)",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "in, i",
|
||||
Usage: "input file of the program (either .go or .nef)",
|
||||
&cli.StringFlag{
|
||||
Name: "in",
|
||||
Aliases: []string{"i"},
|
||||
Required: true,
|
||||
Usage: "Input file of the program (either .go or .nef)",
|
||||
Action: cmdargs.EnsureNotEmpty("in"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "calc-hash",
|
||||
Usage: "calculates hash of a contract after deployment",
|
||||
Usage: "Calculates hash of a contract after deployment",
|
||||
UsageText: "neo-go contract calc-hash -i nef -m manifest -s address",
|
||||
Action: calcHash,
|
||||
Flags: []cli.Flag{
|
||||
flags.AddressFlag{
|
||||
Name: "sender, s",
|
||||
Usage: "sender script hash or address",
|
||||
&flags.AddressFlag{
|
||||
Name: "sender",
|
||||
Aliases: []string{"s"},
|
||||
Required: true,
|
||||
Usage: "Sender script hash or address",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "in",
|
||||
Usage: "path to NEF file",
|
||||
&cli.StringFlag{
|
||||
Name: "in",
|
||||
Required: true,
|
||||
Usage: "Path to NEF file",
|
||||
Action: cmdargs.EnsureNotEmpty("in"),
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "manifest, m",
|
||||
Usage: "path to manifest file",
|
||||
&cli.StringFlag{
|
||||
Name: "manifest",
|
||||
Aliases: []string{"m"},
|
||||
Required: true,
|
||||
Usage: "Path to manifest file",
|
||||
Action: cmdargs.EnsureNotEmpty("manifest"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "manifest",
|
||||
Usage: "manifest-related commands",
|
||||
Subcommands: []cli.Command{
|
||||
Usage: "Manifest-related commands",
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "add-group",
|
||||
Usage: "adds group to the manifest",
|
||||
Usage: "Adds group to the manifest",
|
||||
UsageText: "neo-go contract manifest add-group -w wallet [--wallet-config path] -n nef -m manifest -a address -s address",
|
||||
Action: manifestAddGroup,
|
||||
Flags: manifestAddGroupFlags,
|
||||
|
@ -323,13 +367,10 @@ func initSmartContract(ctx *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
contractName := ctx.String("name")
|
||||
if contractName == "" {
|
||||
return cli.NewExitError(errNoSmartContractName, 1)
|
||||
}
|
||||
|
||||
// Check if the file already exists, if yes, exit
|
||||
if _, err := os.Stat(contractName); err == nil {
|
||||
return cli.NewExitError(errFileExist, 1)
|
||||
return cli.Exit(errFileExist, 1)
|
||||
}
|
||||
|
||||
basePath := contractName
|
||||
|
@ -338,7 +379,7 @@ func initSmartContract(ctx *cli.Context) error {
|
|||
|
||||
// create base directory
|
||||
if err := os.Mkdir(basePath, os.ModePerm); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
m := ProjectConfig{
|
||||
|
@ -363,10 +404,10 @@ func initSmartContract(ctx *cli.Context) error {
|
|||
}
|
||||
b, err := yaml.Marshal(m)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
if err := os.WriteFile(filepath.Join(basePath, "neo-go.yml"), b, 0644); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
ver := ModVersion
|
||||
|
@ -376,18 +417,18 @@ func initSmartContract(ctx *cli.Context) error {
|
|||
|
||||
gm := []byte("module " + contractName + `
|
||||
|
||||
go 1.20
|
||||
go 1.22
|
||||
|
||||
require (
|
||||
github.com/nspcc-dev/neo-go/pkg/interop ` + ver + `
|
||||
)`)
|
||||
if err := os.WriteFile(filepath.Join(basePath, "go.mod"), gm, 0644); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
data := []byte(fmt.Sprintf(smartContractTmpl, contractName))
|
||||
if err := os.WriteFile(filepath.Join(basePath, fileName), data, 0644); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
fmt.Fprintf(ctx.App.Writer, "Successfully initialized smart contract [%s]\n", contractName)
|
||||
|
@ -400,16 +441,13 @@ func contractCompile(ctx *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
src := ctx.String("in")
|
||||
if len(src) == 0 {
|
||||
return cli.NewExitError(errNoInput, 1)
|
||||
}
|
||||
manifestFile := ctx.String("manifest")
|
||||
confFile := ctx.String("config")
|
||||
debugFile := ctx.String("debug")
|
||||
out := ctx.String("out")
|
||||
bindings := ctx.String("bindings")
|
||||
if len(confFile) == 0 && (len(manifestFile) != 0 || len(debugFile) != 0 || len(bindings) != 0) {
|
||||
return cli.NewExitError(errNoConfFile, 1)
|
||||
return cli.Exit(errNoConfFile, 1)
|
||||
}
|
||||
autocomplete := len(manifestFile) == 0 &&
|
||||
len(confFile) == 0 &&
|
||||
|
@ -419,7 +457,7 @@ func contractCompile(ctx *cli.Context) error {
|
|||
var root string
|
||||
fileInfo, err := os.Stat(src)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to stat source file or directory: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to stat source file or directory: %w", err), 1)
|
||||
}
|
||||
if fileInfo.IsDir() {
|
||||
base := filepath.Base(fileInfo.Name())
|
||||
|
@ -470,7 +508,7 @@ func contractCompile(ctx *cli.Context) error {
|
|||
|
||||
result, err := compiler.CompileAndSave(src, o)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
if ctx.Bool("verbose") {
|
||||
fmt.Fprintln(ctx.App.Writer, hex.EncodeToString(result))
|
||||
|
@ -484,34 +522,25 @@ func calcHash(ctx *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
sender := ctx.Generic("sender").(*flags.Address)
|
||||
if !sender.IsSet {
|
||||
return cli.NewExitError("sender is not set", 1)
|
||||
}
|
||||
|
||||
p := ctx.String("in")
|
||||
if p == "" {
|
||||
return cli.NewExitError(errors.New("no .nef file was provided"), 1)
|
||||
}
|
||||
mpath := ctx.String("manifest")
|
||||
if mpath == "" {
|
||||
return cli.NewExitError(errors.New("no manifest file provided"), 1)
|
||||
}
|
||||
|
||||
f, err := os.ReadFile(p)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't read .nef file: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't read .nef file: %w", err), 1)
|
||||
}
|
||||
nefFile, err := nef.FileFromBytes(f)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't unmarshal .nef file: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't unmarshal .nef file: %w", err), 1)
|
||||
}
|
||||
manifestBytes, err := os.ReadFile(mpath)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to read manifest file: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to read manifest file: %w", err), 1)
|
||||
}
|
||||
m := &manifest.Manifest{}
|
||||
err = json.Unmarshal(manifestBytes, m)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to restore manifest file: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to restore manifest file: %w", err), 1)
|
||||
}
|
||||
fmt.Fprintln(ctx.App.Writer, "Contract hash:", state.CreateContractHash(sender.Uint160(), nefFile.Checksum, m.Name).StringLE())
|
||||
return nil
|
||||
|
@ -528,7 +557,7 @@ func invokeFunction(ctx *cli.Context) error {
|
|||
func invokeInternal(ctx *cli.Context, signAndPush bool) error {
|
||||
var (
|
||||
err error
|
||||
exitErr *cli.ExitError
|
||||
exitErr cli.ExitCoder
|
||||
operation string
|
||||
params []any
|
||||
paramsStart = 1
|
||||
|
@ -539,22 +568,23 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error {
|
|||
|
||||
args := ctx.Args()
|
||||
if !args.Present() {
|
||||
return cli.NewExitError(errNoScriptHash, 1)
|
||||
return cli.Exit(errNoScriptHash, 1)
|
||||
}
|
||||
script, err := flags.ParseAddress(args[0])
|
||||
argsSlice := args.Slice()
|
||||
script, err := flags.ParseAddress(argsSlice[0])
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("incorrect script hash: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("incorrect script hash: %w", err), 1)
|
||||
}
|
||||
if len(args) <= 1 {
|
||||
return cli.NewExitError(errNoMethod, 1)
|
||||
if len(argsSlice) <= 1 {
|
||||
return cli.Exit(errNoMethod, 1)
|
||||
}
|
||||
operation = args[1]
|
||||
operation = argsSlice[1]
|
||||
paramsStart++
|
||||
|
||||
if len(args) > paramsStart {
|
||||
cosignersOffset, scParams, err = cmdargs.ParseParams(args[paramsStart:], true)
|
||||
if len(argsSlice) > paramsStart {
|
||||
cosignersOffset, scParams, err = cmdargs.ParseParams(argsSlice[paramsStart:], true)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
params = make([]any, len(scParams))
|
||||
for i := range scParams {
|
||||
|
@ -575,7 +605,7 @@ func invokeInternal(ctx *cli.Context, signAndPush bool) error {
|
|||
if signAndPush {
|
||||
acc, w, err = options.GetAccFromContext(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer w.Close()
|
||||
}
|
||||
|
@ -595,7 +625,7 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet,
|
|||
if signAndPush {
|
||||
signersAccounts, err = cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.None)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("invalid signers: %w", err), 1)
|
||||
}
|
||||
}
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
|
@ -615,12 +645,12 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet,
|
|||
out := ctx.String("out")
|
||||
resp, err = inv.Call(script, operation, params...)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
if resp.State != "HALT" {
|
||||
errText := fmt.Sprintf("Warning: %s VM state returned from the RPC node: %s", resp.State, resp.FaultException)
|
||||
if !signAndPush {
|
||||
return cli.NewExitError(errText, 1)
|
||||
return cli.Exit(errText, 1)
|
||||
}
|
||||
|
||||
action := "send"
|
||||
|
@ -630,42 +660,38 @@ func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet,
|
|||
process = "Saving"
|
||||
}
|
||||
if !ctx.Bool("force") {
|
||||
return cli.NewExitError(errText+".\nUse --force flag to "+action+" the transaction anyway.", 1)
|
||||
return cli.Exit(errText+".\nUse --force flag to "+action+" the transaction anyway.", 1)
|
||||
}
|
||||
fmt.Fprintln(ctx.App.Writer, errText+".\n"+process+" transaction...")
|
||||
}
|
||||
if !signAndPush {
|
||||
b, err := json.MarshalIndent(resp, "", " ")
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
fmt.Fprintln(ctx.App.Writer, string(b))
|
||||
return nil
|
||||
}
|
||||
if len(resp.Script) == 0 {
|
||||
return cli.NewExitError(errors.New("no script returned from the RPC node"), 1)
|
||||
return cli.Exit(errors.New("no script returned from the RPC node"), 1)
|
||||
}
|
||||
tx, err := act.MakeUnsignedUncheckedRun(resp.Script, resp.GasConsumed, nil)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to create tx: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to create tx: %w", err), 1)
|
||||
}
|
||||
return txctx.SignAndSend(ctx, act, acc, tx)
|
||||
}
|
||||
|
||||
func testInvokeScript(ctx *cli.Context) error {
|
||||
src := ctx.String("in")
|
||||
if len(src) == 0 {
|
||||
return cli.NewExitError(errNoInput, 1)
|
||||
}
|
||||
|
||||
b, err := os.ReadFile(src)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
nefFile, err := nef.FileFromBytes(b)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to restore .nef file: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to restore .nef file: %w", err), 1)
|
||||
}
|
||||
|
||||
signers, exitErr := cmdargs.GetSignersFromContext(ctx, 0)
|
||||
|
@ -683,12 +709,12 @@ func testInvokeScript(ctx *cli.Context) error {
|
|||
|
||||
resp, err := inv.Run(nefFile.Script)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
b, err = json.MarshalIndent(resp, "", " ")
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
fmt.Fprintln(ctx.App.Writer, string(b))
|
||||
|
@ -714,9 +740,6 @@ func inspect(ctx *cli.Context) error {
|
|||
}
|
||||
in := ctx.String("in")
|
||||
compile := ctx.Bool("compile")
|
||||
if len(in) == 0 {
|
||||
return cli.NewExitError(errNoInput, 1)
|
||||
}
|
||||
var (
|
||||
b []byte
|
||||
err error
|
||||
|
@ -724,16 +747,16 @@ func inspect(ctx *cli.Context) error {
|
|||
if compile {
|
||||
b, err = compiler.Compile(in, nil)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to compile: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to compile: %w", err), 1)
|
||||
}
|
||||
} else {
|
||||
f, err := os.ReadFile(in)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to read .nef file: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to read .nef file: %w", err), 1)
|
||||
}
|
||||
nefFile, err := nef.FileFromBytes(f)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to restore .nef file: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to restore .nef file: %w", err), 1)
|
||||
}
|
||||
b = nefFile.Script
|
||||
}
|
||||
|
@ -748,22 +771,22 @@ func inspect(ctx *cli.Context) error {
|
|||
func contractDeploy(ctx *cli.Context) error {
|
||||
nefFile, f, err := readNEFFile(ctx.String("in"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
m, manifestBytes, err := readManifest(ctx.String("manifest"), util.Uint160{})
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to read manifest file: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to read manifest file: %w", err), 1)
|
||||
}
|
||||
|
||||
var appCallParams = []any{f, manifestBytes}
|
||||
|
||||
signOffset, data, err := cmdargs.ParseParams(ctx.Args(), true)
|
||||
signOffset, data, err := cmdargs.ParseParams(ctx.Args().Slice(), true)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("unable to parse 'data' parameter: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("unable to parse 'data' parameter: %w", err), 1)
|
||||
}
|
||||
if len(data) > 1 {
|
||||
return cli.NewExitError("'data' should be represented as a single parameter", 1)
|
||||
return cli.Exit("'data' should be represented as a single parameter", 1)
|
||||
}
|
||||
if len(data) != 0 {
|
||||
appCallParams = append(appCallParams, data[0])
|
||||
|
@ -771,7 +794,7 @@ func contractDeploy(ctx *cli.Context) error {
|
|||
|
||||
acc, w, err := options.GetAccFromContext(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't get sender address: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't get sender address: %w", err), 1)
|
||||
}
|
||||
defer w.Close()
|
||||
sender := acc.ScriptHash()
|
||||
|
@ -801,12 +824,12 @@ func ParseContractConfig(confFile string) (ProjectConfig, error) {
|
|||
conf := ProjectConfig{}
|
||||
confBytes, err := os.ReadFile(confFile)
|
||||
if err != nil {
|
||||
return conf, cli.NewExitError(err, 1)
|
||||
return conf, cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
err = yaml.Unmarshal(confBytes, &conf)
|
||||
if err != nil {
|
||||
return conf, cli.NewExitError(fmt.Errorf("bad config: %w", err), 1)
|
||||
return conf, cli.Exit(fmt.Errorf("bad config: %w", err), 1)
|
||||
}
|
||||
return conf, nil
|
||||
}
|
||||
|
|
|
@ -9,7 +9,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/stretchr/testify/require"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
"gopkg.in/yaml.v3"
|
||||
)
|
||||
|
||||
|
|
146
cli/smartcontract/testdata/rpcbindings/nft-d/rpcbindings.out
vendored
Normal file
146
cli/smartcontract/testdata/rpcbindings/nft-d/rpcbindings.out
vendored
Normal file
|
@ -0,0 +1,146 @@
|
|||
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||
|
||||
// Package nft contains RPC wrappers for NeoFS Object NFT contract.
|
||||
package nft
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep24"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// Hash contains contract hash.
|
||||
var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0}
|
||||
|
||||
// Invoker is used by ContractReader to call various safe methods.
|
||||
type Invoker interface {
|
||||
nep11.Invoker
|
||||
}
|
||||
|
||||
// Actor is used by Contract to call state-changing methods.
|
||||
type Actor interface {
|
||||
Invoker
|
||||
|
||||
nep11.Actor
|
||||
|
||||
MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error)
|
||||
MakeRun(script []byte) (*transaction.Transaction, error)
|
||||
MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error)
|
||||
MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error)
|
||||
SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error)
|
||||
SendRun(script []byte) (util.Uint256, uint32, error)
|
||||
}
|
||||
|
||||
// ContractReader implements safe contract methods.
|
||||
type ContractReader struct {
|
||||
nep11.DivisibleReader
|
||||
nep24.RoyaltyReader
|
||||
invoker Invoker
|
||||
hash util.Uint160
|
||||
}
|
||||
|
||||
// Contract implements all contract methods.
|
||||
type Contract struct {
|
||||
ContractReader
|
||||
nep11.DivisibleWriter
|
||||
actor Actor
|
||||
hash util.Uint160
|
||||
}
|
||||
|
||||
// NewReader creates an instance of ContractReader using Hash and the given Invoker.
|
||||
func NewReader(invoker Invoker) *ContractReader {
|
||||
var hash = Hash
|
||||
return &ContractReader{*nep11.NewDivisibleReader(invoker, hash), *nep24.NewRoyaltyReader(invoker, hash), invoker, hash}
|
||||
}
|
||||
|
||||
// New creates an instance of Contract using Hash and the given Actor.
|
||||
func New(actor Actor) *Contract {
|
||||
var hash = Hash
|
||||
var nep11dt = nep11.NewDivisible(actor, hash)
|
||||
var nep24t = nep24.NewRoyaltyReader(actor, hash)
|
||||
return &Contract{ContractReader{nep11dt.DivisibleReader, *nep24t, actor, hash}, nep11dt.DivisibleWriter, actor, hash}
|
||||
}
|
||||
|
||||
// Destroy creates a transaction invoking `destroy` method of the contract.
|
||||
// This transaction is signed and immediately sent to the network.
|
||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||
func (c *Contract) Destroy() (util.Uint256, uint32, error) {
|
||||
return c.actor.SendCall(c.hash, "destroy")
|
||||
}
|
||||
|
||||
// DestroyTransaction creates a transaction invoking `destroy` method of the contract.
|
||||
// This transaction is signed, but not sent to the network, instead it's
|
||||
// returned to the caller.
|
||||
func (c *Contract) DestroyTransaction() (*transaction.Transaction, error) {
|
||||
return c.actor.MakeCall(c.hash, "destroy")
|
||||
}
|
||||
|
||||
// DestroyUnsigned creates a transaction invoking `destroy` method of the contract.
|
||||
// This transaction is not signed, it's simply returned to the caller.
|
||||
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||
func (c *Contract) DestroyUnsigned() (*transaction.Transaction, error) {
|
||||
return c.actor.MakeUnsignedCall(c.hash, "destroy", nil)
|
||||
}
|
||||
|
||||
// Update creates a transaction invoking `update` method of the contract.
|
||||
// This transaction is signed and immediately sent to the network.
|
||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||
func (c *Contract) Update(nef []byte, manifest []byte) (util.Uint256, uint32, error) {
|
||||
return c.actor.SendCall(c.hash, "update", nef, manifest)
|
||||
}
|
||||
|
||||
// UpdateTransaction creates a transaction invoking `update` method of the contract.
|
||||
// This transaction is signed, but not sent to the network, instead it's
|
||||
// returned to the caller.
|
||||
func (c *Contract) UpdateTransaction(nef []byte, manifest []byte) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeCall(c.hash, "update", nef, manifest)
|
||||
}
|
||||
|
||||
// UpdateUnsigned creates a transaction invoking `update` method of the contract.
|
||||
// This transaction is not signed, it's simply returned to the caller.
|
||||
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||
func (c *Contract) UpdateUnsigned(nef []byte, manifest []byte) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeUnsignedCall(c.hash, "update", nil, nef, manifest)
|
||||
}
|
||||
|
||||
func (c *Contract) scriptForVerify() ([]byte, error) {
|
||||
return smartcontract.CreateCallWithAssertScript(c.hash, "verify")
|
||||
}
|
||||
|
||||
// Verify creates a transaction invoking `verify` method of the contract.
|
||||
// This transaction is signed and immediately sent to the network.
|
||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||
func (c *Contract) Verify() (util.Uint256, uint32, error) {
|
||||
script, err := c.scriptForVerify()
|
||||
if err != nil {
|
||||
return util.Uint256{}, 0, err
|
||||
}
|
||||
return c.actor.SendRun(script)
|
||||
}
|
||||
|
||||
// VerifyTransaction creates a transaction invoking `verify` method of the contract.
|
||||
// This transaction is signed, but not sent to the network, instead it's
|
||||
// returned to the caller.
|
||||
func (c *Contract) VerifyTransaction() (*transaction.Transaction, error) {
|
||||
script, err := c.scriptForVerify()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.actor.MakeRun(script)
|
||||
}
|
||||
|
||||
// VerifyUnsigned creates a transaction invoking `verify` method of the contract.
|
||||
// This transaction is not signed, it's simply returned to the caller.
|
||||
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||
func (c *Contract) VerifyUnsigned() (*transaction.Transaction, error) {
|
||||
script, err := c.scriptForVerify()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.actor.MakeUnsignedRun(script, nil)
|
||||
}
|
141
cli/smartcontract/testdata/rpcbindings/nft-d/rpcbindings_dynamic_hash.out
vendored
Normal file
141
cli/smartcontract/testdata/rpcbindings/nft-d/rpcbindings_dynamic_hash.out
vendored
Normal file
|
@ -0,0 +1,141 @@
|
|||
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||
|
||||
// Package nft contains RPC wrappers for NeoFS Object NFT contract.
|
||||
package nft
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep24"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// Invoker is used by ContractReader to call various safe methods.
|
||||
type Invoker interface {
|
||||
nep11.Invoker
|
||||
}
|
||||
|
||||
// Actor is used by Contract to call state-changing methods.
|
||||
type Actor interface {
|
||||
Invoker
|
||||
|
||||
nep11.Actor
|
||||
|
||||
MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error)
|
||||
MakeRun(script []byte) (*transaction.Transaction, error)
|
||||
MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error)
|
||||
MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error)
|
||||
SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error)
|
||||
SendRun(script []byte) (util.Uint256, uint32, error)
|
||||
}
|
||||
|
||||
// ContractReader implements safe contract methods.
|
||||
type ContractReader struct {
|
||||
nep11.DivisibleReader
|
||||
nep24.RoyaltyReader
|
||||
invoker Invoker
|
||||
hash util.Uint160
|
||||
}
|
||||
|
||||
// Contract implements all contract methods.
|
||||
type Contract struct {
|
||||
ContractReader
|
||||
nep11.DivisibleWriter
|
||||
actor Actor
|
||||
hash util.Uint160
|
||||
}
|
||||
|
||||
// NewReader creates an instance of ContractReader using provided contract hash and the given Invoker.
|
||||
func NewReader(invoker Invoker, hash util.Uint160) *ContractReader {
|
||||
return &ContractReader{*nep11.NewDivisibleReader(invoker, hash), *nep24.NewRoyaltyReader(invoker, hash), invoker, hash}
|
||||
}
|
||||
|
||||
// New creates an instance of Contract using provided contract hash and the given Actor.
|
||||
func New(actor Actor, hash util.Uint160) *Contract {
|
||||
var nep11dt = nep11.NewDivisible(actor, hash)
|
||||
var nep24t = nep24.NewRoyaltyReader(actor, hash)
|
||||
return &Contract{ContractReader{nep11dt.DivisibleReader, *nep24t, actor, hash}, nep11dt.DivisibleWriter, actor, hash}
|
||||
}
|
||||
|
||||
// Destroy creates a transaction invoking `destroy` method of the contract.
|
||||
// This transaction is signed and immediately sent to the network.
|
||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||
func (c *Contract) Destroy() (util.Uint256, uint32, error) {
|
||||
return c.actor.SendCall(c.hash, "destroy")
|
||||
}
|
||||
|
||||
// DestroyTransaction creates a transaction invoking `destroy` method of the contract.
|
||||
// This transaction is signed, but not sent to the network, instead it's
|
||||
// returned to the caller.
|
||||
func (c *Contract) DestroyTransaction() (*transaction.Transaction, error) {
|
||||
return c.actor.MakeCall(c.hash, "destroy")
|
||||
}
|
||||
|
||||
// DestroyUnsigned creates a transaction invoking `destroy` method of the contract.
|
||||
// This transaction is not signed, it's simply returned to the caller.
|
||||
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||
func (c *Contract) DestroyUnsigned() (*transaction.Transaction, error) {
|
||||
return c.actor.MakeUnsignedCall(c.hash, "destroy", nil)
|
||||
}
|
||||
|
||||
// Update creates a transaction invoking `update` method of the contract.
|
||||
// This transaction is signed and immediately sent to the network.
|
||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||
func (c *Contract) Update(nef []byte, manifest []byte) (util.Uint256, uint32, error) {
|
||||
return c.actor.SendCall(c.hash, "update", nef, manifest)
|
||||
}
|
||||
|
||||
// UpdateTransaction creates a transaction invoking `update` method of the contract.
|
||||
// This transaction is signed, but not sent to the network, instead it's
|
||||
// returned to the caller.
|
||||
func (c *Contract) UpdateTransaction(nef []byte, manifest []byte) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeCall(c.hash, "update", nef, manifest)
|
||||
}
|
||||
|
||||
// UpdateUnsigned creates a transaction invoking `update` method of the contract.
|
||||
// This transaction is not signed, it's simply returned to the caller.
|
||||
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||
func (c *Contract) UpdateUnsigned(nef []byte, manifest []byte) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeUnsignedCall(c.hash, "update", nil, nef, manifest)
|
||||
}
|
||||
|
||||
func (c *Contract) scriptForVerify() ([]byte, error) {
|
||||
return smartcontract.CreateCallWithAssertScript(c.hash, "verify")
|
||||
}
|
||||
|
||||
// Verify creates a transaction invoking `verify` method of the contract.
|
||||
// This transaction is signed and immediately sent to the network.
|
||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||
func (c *Contract) Verify() (util.Uint256, uint32, error) {
|
||||
script, err := c.scriptForVerify()
|
||||
if err != nil {
|
||||
return util.Uint256{}, 0, err
|
||||
}
|
||||
return c.actor.SendRun(script)
|
||||
}
|
||||
|
||||
// VerifyTransaction creates a transaction invoking `verify` method of the contract.
|
||||
// This transaction is signed, but not sent to the network, instead it's
|
||||
// returned to the caller.
|
||||
func (c *Contract) VerifyTransaction() (*transaction.Transaction, error) {
|
||||
script, err := c.scriptForVerify()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.actor.MakeRun(script)
|
||||
}
|
||||
|
||||
// VerifyUnsigned creates a transaction invoking `verify` method of the contract.
|
||||
// This transaction is not signed, it's simply returned to the caller.
|
||||
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||
func (c *Contract) VerifyUnsigned() (*transaction.Transaction, error) {
|
||||
script, err := c.scriptForVerify()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.actor.MakeUnsignedRun(script, nil)
|
||||
}
|
311
cli/smartcontract/testdata/rpcbindings/nft-nd/rpcbindings.out
vendored
Normal file
311
cli/smartcontract/testdata/rpcbindings/nft-nd/rpcbindings.out
vendored
Normal file
|
@ -0,0 +1,311 @@
|
|||
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||
|
||||
// Package nft contains RPC wrappers for HASHY NFT contract.
|
||||
package nft
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep24"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// Hash contains contract hash.
|
||||
var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0}
|
||||
|
||||
// NftRoyaltyRecipientShare is a contract-specific nft.RoyaltyRecipientShare type used by its methods.
|
||||
type NftRoyaltyRecipientShare struct {
|
||||
Address util.Uint160
|
||||
Share *big.Int
|
||||
}
|
||||
|
||||
// Invoker is used by ContractReader to call various safe methods.
|
||||
type Invoker interface {
|
||||
nep11.Invoker
|
||||
}
|
||||
|
||||
// Actor is used by Contract to call state-changing methods.
|
||||
type Actor interface {
|
||||
Invoker
|
||||
|
||||
nep11.Actor
|
||||
|
||||
MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error)
|
||||
MakeRun(script []byte) (*transaction.Transaction, error)
|
||||
MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error)
|
||||
MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error)
|
||||
SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error)
|
||||
SendRun(script []byte) (util.Uint256, uint32, error)
|
||||
}
|
||||
|
||||
// ContractReader implements safe contract methods.
|
||||
type ContractReader struct {
|
||||
nep11.NonDivisibleReader
|
||||
nep24.RoyaltyReader
|
||||
invoker Invoker
|
||||
hash util.Uint160
|
||||
}
|
||||
|
||||
// Contract implements all contract methods.
|
||||
type Contract struct {
|
||||
ContractReader
|
||||
nep11.BaseWriter
|
||||
actor Actor
|
||||
hash util.Uint160
|
||||
}
|
||||
|
||||
// NewReader creates an instance of ContractReader using Hash and the given Invoker.
|
||||
func NewReader(invoker Invoker) *ContractReader {
|
||||
var hash = Hash
|
||||
return &ContractReader{*nep11.NewNonDivisibleReader(invoker, hash), *nep24.NewRoyaltyReader(invoker, hash), invoker, hash}
|
||||
}
|
||||
|
||||
// New creates an instance of Contract using Hash and the given Actor.
|
||||
func New(actor Actor) *Contract {
|
||||
var hash = Hash
|
||||
var nep11ndt = nep11.NewNonDivisible(actor, hash)
|
||||
var nep24t = nep24.NewRoyaltyReader(actor, hash)
|
||||
return &Contract{ContractReader{nep11ndt.NonDivisibleReader, *nep24t, actor, hash}, nep11ndt.BaseWriter, actor, hash}
|
||||
}
|
||||
|
||||
// Destroy creates a transaction invoking `destroy` method of the contract.
|
||||
// This transaction is signed and immediately sent to the network.
|
||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||
func (c *Contract) Destroy() (util.Uint256, uint32, error) {
|
||||
return c.actor.SendCall(c.hash, "destroy")
|
||||
}
|
||||
|
||||
// DestroyTransaction creates a transaction invoking `destroy` method of the contract.
|
||||
// This transaction is signed, but not sent to the network, instead it's
|
||||
// returned to the caller.
|
||||
func (c *Contract) DestroyTransaction() (*transaction.Transaction, error) {
|
||||
return c.actor.MakeCall(c.hash, "destroy")
|
||||
}
|
||||
|
||||
// DestroyUnsigned creates a transaction invoking `destroy` method of the contract.
|
||||
// This transaction is not signed, it's simply returned to the caller.
|
||||
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||
func (c *Contract) DestroyUnsigned() (*transaction.Transaction, error) {
|
||||
return c.actor.MakeUnsignedCall(c.hash, "destroy", nil)
|
||||
}
|
||||
|
||||
func (c *Contract) scriptForSetRoyaltyInfo(ctx any, tokenID []byte, recipients []*NftRoyaltyRecipientShare) ([]byte, error) {
|
||||
return smartcontract.CreateCallWithAssertScript(c.hash, "setRoyaltyInfo", ctx, tokenID, recipients)
|
||||
}
|
||||
|
||||
// SetRoyaltyInfo creates a transaction invoking `setRoyaltyInfo` method of the contract.
|
||||
// This transaction is signed and immediately sent to the network.
|
||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||
func (c *Contract) SetRoyaltyInfo(ctx any, tokenID []byte, recipients []*NftRoyaltyRecipientShare) (util.Uint256, uint32, error) {
|
||||
script, err := c.scriptForSetRoyaltyInfo(ctx, tokenID, recipients)
|
||||
if err != nil {
|
||||
return util.Uint256{}, 0, err
|
||||
}
|
||||
return c.actor.SendRun(script)
|
||||
}
|
||||
|
||||
// SetRoyaltyInfoTransaction creates a transaction invoking `setRoyaltyInfo` method of the contract.
|
||||
// This transaction is signed, but not sent to the network, instead it's
|
||||
// returned to the caller.
|
||||
func (c *Contract) SetRoyaltyInfoTransaction(ctx any, tokenID []byte, recipients []*NftRoyaltyRecipientShare) (*transaction.Transaction, error) {
|
||||
script, err := c.scriptForSetRoyaltyInfo(ctx, tokenID, recipients)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.actor.MakeRun(script)
|
||||
}
|
||||
|
||||
// SetRoyaltyInfoUnsigned creates a transaction invoking `setRoyaltyInfo` method of the contract.
|
||||
// This transaction is not signed, it's simply returned to the caller.
|
||||
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||
func (c *Contract) SetRoyaltyInfoUnsigned(ctx any, tokenID []byte, recipients []*NftRoyaltyRecipientShare) (*transaction.Transaction, error) {
|
||||
script, err := c.scriptForSetRoyaltyInfo(ctx, tokenID, recipients)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.actor.MakeUnsignedRun(script, nil)
|
||||
}
|
||||
|
||||
// Update creates a transaction invoking `update` method of the contract.
|
||||
// This transaction is signed and immediately sent to the network.
|
||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||
func (c *Contract) Update(nef []byte, manifest []byte) (util.Uint256, uint32, error) {
|
||||
return c.actor.SendCall(c.hash, "update", nef, manifest)
|
||||
}
|
||||
|
||||
// UpdateTransaction creates a transaction invoking `update` method of the contract.
|
||||
// This transaction is signed, but not sent to the network, instead it's
|
||||
// returned to the caller.
|
||||
func (c *Contract) UpdateTransaction(nef []byte, manifest []byte) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeCall(c.hash, "update", nef, manifest)
|
||||
}
|
||||
|
||||
// UpdateUnsigned creates a transaction invoking `update` method of the contract.
|
||||
// This transaction is not signed, it's simply returned to the caller.
|
||||
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||
func (c *Contract) UpdateUnsigned(nef []byte, manifest []byte) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeUnsignedCall(c.hash, "update", nil, nef, manifest)
|
||||
}
|
||||
|
||||
func (c *Contract) scriptForVerify() ([]byte, error) {
|
||||
return smartcontract.CreateCallWithAssertScript(c.hash, "verify")
|
||||
}
|
||||
|
||||
// Verify creates a transaction invoking `verify` method of the contract.
|
||||
// This transaction is signed and immediately sent to the network.
|
||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||
func (c *Contract) Verify() (util.Uint256, uint32, error) {
|
||||
script, err := c.scriptForVerify()
|
||||
if err != nil {
|
||||
return util.Uint256{}, 0, err
|
||||
}
|
||||
return c.actor.SendRun(script)
|
||||
}
|
||||
|
||||
// VerifyTransaction creates a transaction invoking `verify` method of the contract.
|
||||
// This transaction is signed, but not sent to the network, instead it's
|
||||
// returned to the caller.
|
||||
func (c *Contract) VerifyTransaction() (*transaction.Transaction, error) {
|
||||
script, err := c.scriptForVerify()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.actor.MakeRun(script)
|
||||
}
|
||||
|
||||
// VerifyUnsigned creates a transaction invoking `verify` method of the contract.
|
||||
// This transaction is not signed, it's simply returned to the caller.
|
||||
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||
func (c *Contract) VerifyUnsigned() (*transaction.Transaction, error) {
|
||||
script, err := c.scriptForVerify()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.actor.MakeUnsignedRun(script, nil)
|
||||
}
|
||||
|
||||
// itemToNftRoyaltyRecipientShare converts stack item into *NftRoyaltyRecipientShare.
|
||||
// NULL item is returned as nil pointer without error.
|
||||
func itemToNftRoyaltyRecipientShare(item stackitem.Item, err error) (*NftRoyaltyRecipientShare, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, null := item.(stackitem.Null)
|
||||
if null {
|
||||
return nil, nil
|
||||
}
|
||||
var res = new(NftRoyaltyRecipientShare)
|
||||
err = res.FromStackItem(item)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Ensure *NftRoyaltyRecipientShare is a proper [stackitem.Convertible].
|
||||
var _ = stackitem.Convertible(&NftRoyaltyRecipientShare{})
|
||||
|
||||
// Ensure *NftRoyaltyRecipientShare is a proper [smartcontract.Convertible].
|
||||
var _ = smartcontract.Convertible(&NftRoyaltyRecipientShare{})
|
||||
|
||||
// FromStackItem retrieves fields of NftRoyaltyRecipientShare from the given
|
||||
// [stackitem.Item] or returns an error if it's not possible to do to so.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *NftRoyaltyRecipientShare) FromStackItem(item stackitem.Item) error {
|
||||
arr, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return errors.New("not an array")
|
||||
}
|
||||
if len(arr) != 2 {
|
||||
return errors.New("wrong number of structure elements")
|
||||
}
|
||||
|
||||
var (
|
||||
index = -1
|
||||
err error
|
||||
)
|
||||
index++
|
||||
res.Address, err = func(item stackitem.Item) (util.Uint160, error) {
|
||||
b, err := item.TryBytes()
|
||||
if err != nil {
|
||||
return util.Uint160{}, err
|
||||
}
|
||||
u, err := util.Uint160DecodeBytesBE(b)
|
||||
if err != nil {
|
||||
return util.Uint160{}, err
|
||||
}
|
||||
return u, nil
|
||||
}(arr[index])
|
||||
if err != nil {
|
||||
return fmt.Errorf("field Address: %w", err)
|
||||
}
|
||||
|
||||
index++
|
||||
res.Share, err = arr[index].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("field Share: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToStackItem creates [stackitem.Item] representing NftRoyaltyRecipientShare.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *NftRoyaltyRecipientShare) ToStackItem() (stackitem.Item, error) {
|
||||
if res == nil {
|
||||
return stackitem.Null{}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
itm stackitem.Item
|
||||
items = make([]stackitem.Item, 0, 2)
|
||||
)
|
||||
itm, err = stackitem.NewByteArray(res.Address.BytesBE()), error(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field Address: %w", err)
|
||||
}
|
||||
items = append(items, itm)
|
||||
|
||||
itm, err = (*stackitem.BigInteger)(res.Share), error(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field Share: %w", err)
|
||||
}
|
||||
items = append(items, itm)
|
||||
|
||||
return stackitem.NewStruct(items), nil
|
||||
}
|
||||
|
||||
// ToSCParameter creates [smartcontract.Parameter] representing NftRoyaltyRecipientShare.
|
||||
// It implements [smartcontract.Convertible] interface so that NftRoyaltyRecipientShare
|
||||
// could be used with invokers.
|
||||
func (res *NftRoyaltyRecipientShare) ToSCParameter() (smartcontract.Parameter, error) {
|
||||
if res == nil {
|
||||
return smartcontract.Parameter{Type: smartcontract.AnyType}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
prm smartcontract.Parameter
|
||||
prms = make([]smartcontract.Parameter, 0, 2)
|
||||
)
|
||||
prm, err = smartcontract.NewParameterFromValue(res.Address)
|
||||
if err != nil {
|
||||
return smartcontract.Parameter{}, fmt.Errorf("field Address: %w", err)
|
||||
}
|
||||
prms = append(prms, prm)
|
||||
|
||||
prm, err = smartcontract.NewParameterFromValue(res.Share)
|
||||
if err != nil {
|
||||
return smartcontract.Parameter{}, fmt.Errorf("field Share: %w", err)
|
||||
}
|
||||
prms = append(prms, prm)
|
||||
|
||||
return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil
|
||||
}
|
306
cli/smartcontract/testdata/rpcbindings/nft-nd/rpcbindings_dynamic_hash.out
vendored
Normal file
306
cli/smartcontract/testdata/rpcbindings/nft-nd/rpcbindings_dynamic_hash.out
vendored
Normal file
|
@ -0,0 +1,306 @@
|
|||
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||
|
||||
// Package nft contains RPC wrappers for HASHY NFT contract.
|
||||
package nft
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep24"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// NftRoyaltyRecipientShare is a contract-specific nft.RoyaltyRecipientShare type used by its methods.
|
||||
type NftRoyaltyRecipientShare struct {
|
||||
Address util.Uint160
|
||||
Share *big.Int
|
||||
}
|
||||
|
||||
// Invoker is used by ContractReader to call various safe methods.
|
||||
type Invoker interface {
|
||||
nep11.Invoker
|
||||
}
|
||||
|
||||
// Actor is used by Contract to call state-changing methods.
|
||||
type Actor interface {
|
||||
Invoker
|
||||
|
||||
nep11.Actor
|
||||
|
||||
MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error)
|
||||
MakeRun(script []byte) (*transaction.Transaction, error)
|
||||
MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error)
|
||||
MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error)
|
||||
SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error)
|
||||
SendRun(script []byte) (util.Uint256, uint32, error)
|
||||
}
|
||||
|
||||
// ContractReader implements safe contract methods.
|
||||
type ContractReader struct {
|
||||
nep11.NonDivisibleReader
|
||||
nep24.RoyaltyReader
|
||||
invoker Invoker
|
||||
hash util.Uint160
|
||||
}
|
||||
|
||||
// Contract implements all contract methods.
|
||||
type Contract struct {
|
||||
ContractReader
|
||||
nep11.BaseWriter
|
||||
actor Actor
|
||||
hash util.Uint160
|
||||
}
|
||||
|
||||
// NewReader creates an instance of ContractReader using provided contract hash and the given Invoker.
|
||||
func NewReader(invoker Invoker, hash util.Uint160) *ContractReader {
|
||||
return &ContractReader{*nep11.NewNonDivisibleReader(invoker, hash), *nep24.NewRoyaltyReader(invoker, hash), invoker, hash}
|
||||
}
|
||||
|
||||
// New creates an instance of Contract using provided contract hash and the given Actor.
|
||||
func New(actor Actor, hash util.Uint160) *Contract {
|
||||
var nep11ndt = nep11.NewNonDivisible(actor, hash)
|
||||
var nep24t = nep24.NewRoyaltyReader(actor, hash)
|
||||
return &Contract{ContractReader{nep11ndt.NonDivisibleReader, *nep24t, actor, hash}, nep11ndt.BaseWriter, actor, hash}
|
||||
}
|
||||
|
||||
// Destroy creates a transaction invoking `destroy` method of the contract.
|
||||
// This transaction is signed and immediately sent to the network.
|
||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||
func (c *Contract) Destroy() (util.Uint256, uint32, error) {
|
||||
return c.actor.SendCall(c.hash, "destroy")
|
||||
}
|
||||
|
||||
// DestroyTransaction creates a transaction invoking `destroy` method of the contract.
|
||||
// This transaction is signed, but not sent to the network, instead it's
|
||||
// returned to the caller.
|
||||
func (c *Contract) DestroyTransaction() (*transaction.Transaction, error) {
|
||||
return c.actor.MakeCall(c.hash, "destroy")
|
||||
}
|
||||
|
||||
// DestroyUnsigned creates a transaction invoking `destroy` method of the contract.
|
||||
// This transaction is not signed, it's simply returned to the caller.
|
||||
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||
func (c *Contract) DestroyUnsigned() (*transaction.Transaction, error) {
|
||||
return c.actor.MakeUnsignedCall(c.hash, "destroy", nil)
|
||||
}
|
||||
|
||||
func (c *Contract) scriptForSetRoyaltyInfo(ctx any, tokenID []byte, recipients []*NftRoyaltyRecipientShare) ([]byte, error) {
|
||||
return smartcontract.CreateCallWithAssertScript(c.hash, "setRoyaltyInfo", ctx, tokenID, recipients)
|
||||
}
|
||||
|
||||
// SetRoyaltyInfo creates a transaction invoking `setRoyaltyInfo` method of the contract.
|
||||
// This transaction is signed and immediately sent to the network.
|
||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||
func (c *Contract) SetRoyaltyInfo(ctx any, tokenID []byte, recipients []*NftRoyaltyRecipientShare) (util.Uint256, uint32, error) {
|
||||
script, err := c.scriptForSetRoyaltyInfo(ctx, tokenID, recipients)
|
||||
if err != nil {
|
||||
return util.Uint256{}, 0, err
|
||||
}
|
||||
return c.actor.SendRun(script)
|
||||
}
|
||||
|
||||
// SetRoyaltyInfoTransaction creates a transaction invoking `setRoyaltyInfo` method of the contract.
|
||||
// This transaction is signed, but not sent to the network, instead it's
|
||||
// returned to the caller.
|
||||
func (c *Contract) SetRoyaltyInfoTransaction(ctx any, tokenID []byte, recipients []*NftRoyaltyRecipientShare) (*transaction.Transaction, error) {
|
||||
script, err := c.scriptForSetRoyaltyInfo(ctx, tokenID, recipients)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.actor.MakeRun(script)
|
||||
}
|
||||
|
||||
// SetRoyaltyInfoUnsigned creates a transaction invoking `setRoyaltyInfo` method of the contract.
|
||||
// This transaction is not signed, it's simply returned to the caller.
|
||||
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||
func (c *Contract) SetRoyaltyInfoUnsigned(ctx any, tokenID []byte, recipients []*NftRoyaltyRecipientShare) (*transaction.Transaction, error) {
|
||||
script, err := c.scriptForSetRoyaltyInfo(ctx, tokenID, recipients)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.actor.MakeUnsignedRun(script, nil)
|
||||
}
|
||||
|
||||
// Update creates a transaction invoking `update` method of the contract.
|
||||
// This transaction is signed and immediately sent to the network.
|
||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||
func (c *Contract) Update(nef []byte, manifest []byte) (util.Uint256, uint32, error) {
|
||||
return c.actor.SendCall(c.hash, "update", nef, manifest)
|
||||
}
|
||||
|
||||
// UpdateTransaction creates a transaction invoking `update` method of the contract.
|
||||
// This transaction is signed, but not sent to the network, instead it's
|
||||
// returned to the caller.
|
||||
func (c *Contract) UpdateTransaction(nef []byte, manifest []byte) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeCall(c.hash, "update", nef, manifest)
|
||||
}
|
||||
|
||||
// UpdateUnsigned creates a transaction invoking `update` method of the contract.
|
||||
// This transaction is not signed, it's simply returned to the caller.
|
||||
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||
func (c *Contract) UpdateUnsigned(nef []byte, manifest []byte) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeUnsignedCall(c.hash, "update", nil, nef, manifest)
|
||||
}
|
||||
|
||||
func (c *Contract) scriptForVerify() ([]byte, error) {
|
||||
return smartcontract.CreateCallWithAssertScript(c.hash, "verify")
|
||||
}
|
||||
|
||||
// Verify creates a transaction invoking `verify` method of the contract.
|
||||
// This transaction is signed and immediately sent to the network.
|
||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||
func (c *Contract) Verify() (util.Uint256, uint32, error) {
|
||||
script, err := c.scriptForVerify()
|
||||
if err != nil {
|
||||
return util.Uint256{}, 0, err
|
||||
}
|
||||
return c.actor.SendRun(script)
|
||||
}
|
||||
|
||||
// VerifyTransaction creates a transaction invoking `verify` method of the contract.
|
||||
// This transaction is signed, but not sent to the network, instead it's
|
||||
// returned to the caller.
|
||||
func (c *Contract) VerifyTransaction() (*transaction.Transaction, error) {
|
||||
script, err := c.scriptForVerify()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.actor.MakeRun(script)
|
||||
}
|
||||
|
||||
// VerifyUnsigned creates a transaction invoking `verify` method of the contract.
|
||||
// This transaction is not signed, it's simply returned to the caller.
|
||||
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||
func (c *Contract) VerifyUnsigned() (*transaction.Transaction, error) {
|
||||
script, err := c.scriptForVerify()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.actor.MakeUnsignedRun(script, nil)
|
||||
}
|
||||
|
||||
// itemToNftRoyaltyRecipientShare converts stack item into *NftRoyaltyRecipientShare.
|
||||
// NULL item is returned as nil pointer without error.
|
||||
func itemToNftRoyaltyRecipientShare(item stackitem.Item, err error) (*NftRoyaltyRecipientShare, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, null := item.(stackitem.Null)
|
||||
if null {
|
||||
return nil, nil
|
||||
}
|
||||
var res = new(NftRoyaltyRecipientShare)
|
||||
err = res.FromStackItem(item)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Ensure *NftRoyaltyRecipientShare is a proper [stackitem.Convertible].
|
||||
var _ = stackitem.Convertible(&NftRoyaltyRecipientShare{})
|
||||
|
||||
// Ensure *NftRoyaltyRecipientShare is a proper [smartcontract.Convertible].
|
||||
var _ = smartcontract.Convertible(&NftRoyaltyRecipientShare{})
|
||||
|
||||
// FromStackItem retrieves fields of NftRoyaltyRecipientShare from the given
|
||||
// [stackitem.Item] or returns an error if it's not possible to do to so.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *NftRoyaltyRecipientShare) FromStackItem(item stackitem.Item) error {
|
||||
arr, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return errors.New("not an array")
|
||||
}
|
||||
if len(arr) != 2 {
|
||||
return errors.New("wrong number of structure elements")
|
||||
}
|
||||
|
||||
var (
|
||||
index = -1
|
||||
err error
|
||||
)
|
||||
index++
|
||||
res.Address, err = func(item stackitem.Item) (util.Uint160, error) {
|
||||
b, err := item.TryBytes()
|
||||
if err != nil {
|
||||
return util.Uint160{}, err
|
||||
}
|
||||
u, err := util.Uint160DecodeBytesBE(b)
|
||||
if err != nil {
|
||||
return util.Uint160{}, err
|
||||
}
|
||||
return u, nil
|
||||
}(arr[index])
|
||||
if err != nil {
|
||||
return fmt.Errorf("field Address: %w", err)
|
||||
}
|
||||
|
||||
index++
|
||||
res.Share, err = arr[index].TryInteger()
|
||||
if err != nil {
|
||||
return fmt.Errorf("field Share: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToStackItem creates [stackitem.Item] representing NftRoyaltyRecipientShare.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *NftRoyaltyRecipientShare) ToStackItem() (stackitem.Item, error) {
|
||||
if res == nil {
|
||||
return stackitem.Null{}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
itm stackitem.Item
|
||||
items = make([]stackitem.Item, 0, 2)
|
||||
)
|
||||
itm, err = stackitem.NewByteArray(res.Address.BytesBE()), error(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field Address: %w", err)
|
||||
}
|
||||
items = append(items, itm)
|
||||
|
||||
itm, err = (*stackitem.BigInteger)(res.Share), error(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field Share: %w", err)
|
||||
}
|
||||
items = append(items, itm)
|
||||
|
||||
return stackitem.NewStruct(items), nil
|
||||
}
|
||||
|
||||
// ToSCParameter creates [smartcontract.Parameter] representing NftRoyaltyRecipientShare.
|
||||
// It implements [smartcontract.Convertible] interface so that NftRoyaltyRecipientShare
|
||||
// could be used with invokers.
|
||||
func (res *NftRoyaltyRecipientShare) ToSCParameter() (smartcontract.Parameter, error) {
|
||||
if res == nil {
|
||||
return smartcontract.Parameter{Type: smartcontract.AnyType}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
prm smartcontract.Parameter
|
||||
prms = make([]smartcontract.Parameter, 0, 2)
|
||||
)
|
||||
prm, err = smartcontract.NewParameterFromValue(res.Address)
|
||||
if err != nil {
|
||||
return smartcontract.Parameter{}, fmt.Errorf("field Address: %w", err)
|
||||
}
|
||||
prms = append(prms, prm)
|
||||
|
||||
prm, err = smartcontract.NewParameterFromValue(res.Share)
|
||||
if err != nil {
|
||||
return smartcontract.Parameter{}, fmt.Errorf("field Share: %w", err)
|
||||
}
|
||||
prms = append(prms, prm)
|
||||
|
||||
return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil
|
||||
}
|
|
@ -8,6 +8,7 @@ import (
|
|||
"fmt"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"math/big"
|
||||
|
@ -186,17 +187,29 @@ func (c *Contract) UnexportedFieldUnsigned() (*transaction.Transaction, error) {
|
|||
}
|
||||
|
||||
// itemToCrazyStruct converts stack item into *CrazyStruct.
|
||||
// NULL item is returned as nil pointer without error.
|
||||
func itemToCrazyStruct(item stackitem.Item, err error) (*CrazyStruct, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, null := item.(stackitem.Null)
|
||||
if null {
|
||||
return nil, nil
|
||||
}
|
||||
var res = new(CrazyStruct)
|
||||
err = res.FromStackItem(item)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Ensure *CrazyStruct is a proper [stackitem.Convertible].
|
||||
var _ = stackitem.Convertible(&CrazyStruct{})
|
||||
|
||||
// Ensure *CrazyStruct is a proper [smartcontract.Convertible].
|
||||
var _ = smartcontract.Convertible(&CrazyStruct{})
|
||||
|
||||
// FromStackItem retrieves fields of CrazyStruct from the given
|
||||
// [stackitem.Item] or returns an error if it's not possible to do to so.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *CrazyStruct) FromStackItem(item stackitem.Item) error {
|
||||
arr, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
|
@ -225,18 +238,85 @@ func (res *CrazyStruct) FromStackItem(item stackitem.Item) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ToStackItem creates [stackitem.Item] representing CrazyStruct.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *CrazyStruct) ToStackItem() (stackitem.Item, error) {
|
||||
if res == nil {
|
||||
return stackitem.Null{}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
itm stackitem.Item
|
||||
items = make([]stackitem.Item, 0, 2)
|
||||
)
|
||||
itm, err = (*stackitem.BigInteger)(res.I), error(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field I: %w", err)
|
||||
}
|
||||
items = append(items, itm)
|
||||
|
||||
itm, err = stackitem.NewBool(res.B), error(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field B: %w", err)
|
||||
}
|
||||
items = append(items, itm)
|
||||
|
||||
return stackitem.NewStruct(items), nil
|
||||
}
|
||||
|
||||
// ToSCParameter creates [smartcontract.Parameter] representing CrazyStruct.
|
||||
// It implements [smartcontract.Convertible] interface so that CrazyStruct
|
||||
// could be used with invokers.
|
||||
func (res *CrazyStruct) ToSCParameter() (smartcontract.Parameter, error) {
|
||||
if res == nil {
|
||||
return smartcontract.Parameter{Type: smartcontract.AnyType}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
prm smartcontract.Parameter
|
||||
prms = make([]smartcontract.Parameter, 0, 2)
|
||||
)
|
||||
prm, err = smartcontract.NewParameterFromValue(res.I)
|
||||
if err != nil {
|
||||
return smartcontract.Parameter{}, fmt.Errorf("field I: %w", err)
|
||||
}
|
||||
prms = append(prms, prm)
|
||||
|
||||
prm, err = smartcontract.NewParameterFromValue(res.B)
|
||||
if err != nil {
|
||||
return smartcontract.Parameter{}, fmt.Errorf("field B: %w", err)
|
||||
}
|
||||
prms = append(prms, prm)
|
||||
|
||||
return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil
|
||||
}
|
||||
|
||||
// itemToSimpleStruct converts stack item into *SimpleStruct.
|
||||
// NULL item is returned as nil pointer without error.
|
||||
func itemToSimpleStruct(item stackitem.Item, err error) (*SimpleStruct, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, null := item.(stackitem.Null)
|
||||
if null {
|
||||
return nil, nil
|
||||
}
|
||||
var res = new(SimpleStruct)
|
||||
err = res.FromStackItem(item)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Ensure *SimpleStruct is a proper [stackitem.Convertible].
|
||||
var _ = stackitem.Convertible(&SimpleStruct{})
|
||||
|
||||
// Ensure *SimpleStruct is a proper [smartcontract.Convertible].
|
||||
var _ = smartcontract.Convertible(&SimpleStruct{})
|
||||
|
||||
// FromStackItem retrieves fields of SimpleStruct from the given
|
||||
// [stackitem.Item] or returns an error if it's not possible to do to so.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *SimpleStruct) FromStackItem(item stackitem.Item) error {
|
||||
arr, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
|
@ -259,6 +339,49 @@ func (res *SimpleStruct) FromStackItem(item stackitem.Item) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ToStackItem creates [stackitem.Item] representing SimpleStruct.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *SimpleStruct) ToStackItem() (stackitem.Item, error) {
|
||||
if res == nil {
|
||||
return stackitem.Null{}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
itm stackitem.Item
|
||||
items = make([]stackitem.Item, 0, 1)
|
||||
)
|
||||
itm, err = (*stackitem.BigInteger)(res.I), error(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field I: %w", err)
|
||||
}
|
||||
items = append(items, itm)
|
||||
|
||||
return stackitem.NewStruct(items), nil
|
||||
}
|
||||
|
||||
// ToSCParameter creates [smartcontract.Parameter] representing SimpleStruct.
|
||||
// It implements [smartcontract.Convertible] interface so that SimpleStruct
|
||||
// could be used with invokers.
|
||||
func (res *SimpleStruct) ToSCParameter() (smartcontract.Parameter, error) {
|
||||
if res == nil {
|
||||
return smartcontract.Parameter{Type: smartcontract.AnyType}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
prm smartcontract.Parameter
|
||||
prms = make([]smartcontract.Parameter, 0, 1)
|
||||
)
|
||||
prm, err = smartcontract.NewParameterFromValue(res.I)
|
||||
if err != nil {
|
||||
return smartcontract.Parameter{}, fmt.Errorf("field I: %w", err)
|
||||
}
|
||||
prms = append(prms, prm)
|
||||
|
||||
return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil
|
||||
}
|
||||
|
||||
// ComplicatedNameEventsFromApplicationLog retrieves a set of all emitted events
|
||||
// with "! complicated name %$#" name from the provided [result.ApplicationLog].
|
||||
func ComplicatedNameEventsFromApplicationLog(log *result.ApplicationLog) ([]*ComplicatedNameEvent, error) {
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"fmt"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"math/big"
|
||||
|
@ -186,17 +187,29 @@ func (c *Contract) UnexportedFieldUnsigned() (*transaction.Transaction, error) {
|
|||
}
|
||||
|
||||
// itemToUnnamed converts stack item into *Unnamed.
|
||||
// NULL item is returned as nil pointer without error.
|
||||
func itemToUnnamed(item stackitem.Item, err error) (*Unnamed, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, null := item.(stackitem.Null)
|
||||
if null {
|
||||
return nil, nil
|
||||
}
|
||||
var res = new(Unnamed)
|
||||
err = res.FromStackItem(item)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Ensure *Unnamed is a proper [stackitem.Convertible].
|
||||
var _ = stackitem.Convertible(&Unnamed{})
|
||||
|
||||
// Ensure *Unnamed is a proper [smartcontract.Convertible].
|
||||
var _ = smartcontract.Convertible(&Unnamed{})
|
||||
|
||||
// FromStackItem retrieves fields of Unnamed from the given
|
||||
// [stackitem.Item] or returns an error if it's not possible to do to so.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *Unnamed) FromStackItem(item stackitem.Item) error {
|
||||
arr, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
|
@ -225,18 +238,85 @@ func (res *Unnamed) FromStackItem(item stackitem.Item) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ToStackItem creates [stackitem.Item] representing Unnamed.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *Unnamed) ToStackItem() (stackitem.Item, error) {
|
||||
if res == nil {
|
||||
return stackitem.Null{}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
itm stackitem.Item
|
||||
items = make([]stackitem.Item, 0, 2)
|
||||
)
|
||||
itm, err = (*stackitem.BigInteger)(res.I), error(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field I: %w", err)
|
||||
}
|
||||
items = append(items, itm)
|
||||
|
||||
itm, err = stackitem.NewBool(res.B), error(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field B: %w", err)
|
||||
}
|
||||
items = append(items, itm)
|
||||
|
||||
return stackitem.NewStruct(items), nil
|
||||
}
|
||||
|
||||
// ToSCParameter creates [smartcontract.Parameter] representing Unnamed.
|
||||
// It implements [smartcontract.Convertible] interface so that Unnamed
|
||||
// could be used with invokers.
|
||||
func (res *Unnamed) ToSCParameter() (smartcontract.Parameter, error) {
|
||||
if res == nil {
|
||||
return smartcontract.Parameter{Type: smartcontract.AnyType}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
prm smartcontract.Parameter
|
||||
prms = make([]smartcontract.Parameter, 0, 2)
|
||||
)
|
||||
prm, err = smartcontract.NewParameterFromValue(res.I)
|
||||
if err != nil {
|
||||
return smartcontract.Parameter{}, fmt.Errorf("field I: %w", err)
|
||||
}
|
||||
prms = append(prms, prm)
|
||||
|
||||
prm, err = smartcontract.NewParameterFromValue(res.B)
|
||||
if err != nil {
|
||||
return smartcontract.Parameter{}, fmt.Errorf("field B: %w", err)
|
||||
}
|
||||
prms = append(prms, prm)
|
||||
|
||||
return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil
|
||||
}
|
||||
|
||||
// itemToUnnamedX converts stack item into *UnnamedX.
|
||||
// NULL item is returned as nil pointer without error.
|
||||
func itemToUnnamedX(item stackitem.Item, err error) (*UnnamedX, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, null := item.(stackitem.Null)
|
||||
if null {
|
||||
return nil, nil
|
||||
}
|
||||
var res = new(UnnamedX)
|
||||
err = res.FromStackItem(item)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Ensure *UnnamedX is a proper [stackitem.Convertible].
|
||||
var _ = stackitem.Convertible(&UnnamedX{})
|
||||
|
||||
// Ensure *UnnamedX is a proper [smartcontract.Convertible].
|
||||
var _ = smartcontract.Convertible(&UnnamedX{})
|
||||
|
||||
// FromStackItem retrieves fields of UnnamedX from the given
|
||||
// [stackitem.Item] or returns an error if it's not possible to do to so.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *UnnamedX) FromStackItem(item stackitem.Item) error {
|
||||
arr, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
|
@ -259,6 +339,49 @@ func (res *UnnamedX) FromStackItem(item stackitem.Item) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ToStackItem creates [stackitem.Item] representing UnnamedX.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *UnnamedX) ToStackItem() (stackitem.Item, error) {
|
||||
if res == nil {
|
||||
return stackitem.Null{}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
itm stackitem.Item
|
||||
items = make([]stackitem.Item, 0, 1)
|
||||
)
|
||||
itm, err = (*stackitem.BigInteger)(res.I), error(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field I: %w", err)
|
||||
}
|
||||
items = append(items, itm)
|
||||
|
||||
return stackitem.NewStruct(items), nil
|
||||
}
|
||||
|
||||
// ToSCParameter creates [smartcontract.Parameter] representing UnnamedX.
|
||||
// It implements [smartcontract.Convertible] interface so that UnnamedX
|
||||
// could be used with invokers.
|
||||
func (res *UnnamedX) ToSCParameter() (smartcontract.Parameter, error) {
|
||||
if res == nil {
|
||||
return smartcontract.Parameter{Type: smartcontract.AnyType}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
prm smartcontract.Parameter
|
||||
prms = make([]smartcontract.Parameter, 0, 1)
|
||||
)
|
||||
prm, err = smartcontract.NewParameterFromValue(res.I)
|
||||
if err != nil {
|
||||
return smartcontract.Parameter{}, fmt.Errorf("field I: %w", err)
|
||||
}
|
||||
prms = append(prms, prm)
|
||||
|
||||
return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil
|
||||
}
|
||||
|
||||
// ComplicatedNameEventsFromApplicationLog retrieves a set of all emitted events
|
||||
// with "! complicated name %$#" name from the provided [result.ApplicationLog].
|
||||
func ComplicatedNameEventsFromApplicationLog(log *result.ApplicationLog) ([]*ComplicatedNameEvent, error) {
|
||||
|
|
16
cli/smartcontract/testdata/rpcbindings/royalty/config.yml
vendored
Normal file
16
cli/smartcontract/testdata/rpcbindings/royalty/config.yml
vendored
Normal file
|
@ -0,0 +1,16 @@
|
|||
name: Test royalty
|
||||
sourceurl: https://github.com/nspcc-dev/neo-go/
|
||||
supportedstandards: ["NEP-24-Payable"]
|
||||
events:
|
||||
- name: RoyaltiesTransferred
|
||||
parameters:
|
||||
- name: royaltyToken
|
||||
type: Hash160
|
||||
- name: royaltyRecipient
|
||||
type: Hash160
|
||||
- name: buyer
|
||||
type: Hash160
|
||||
- name: tokenId
|
||||
type: ByteArray
|
||||
- name: amount
|
||||
type: Integer
|
13
cli/smartcontract/testdata/rpcbindings/royalty/royalty.go
vendored
Normal file
13
cli/smartcontract/testdata/rpcbindings/royalty/royalty.go
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
package royalty
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/native/std"
|
||||
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||
)
|
||||
|
||||
// RoyaltiesTransferred notifies about royalty payment. This method is called by marketplace
|
||||
// contract when royalties are transferred.
|
||||
func RoyaltiesTransferred(royaltyToken, royaltyRecipient, buyer interop.Hash160, tokenId []byte, amount int) {
|
||||
runtime.Notify("RoyaltiesTransferred", royaltyToken, royaltyRecipient, buyer, std.Deserialize(tokenId), amount)
|
||||
}
|
57
cli/smartcontract/testdata/rpcbindings/royalty/rpcbindings.out
vendored
Normal file
57
cli/smartcontract/testdata/rpcbindings/royalty/rpcbindings.out
vendored
Normal file
|
@ -0,0 +1,57 @@
|
|||
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||
|
||||
// Package royalty contains RPC wrappers for Test royalty contract.
|
||||
package royalty
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// Hash contains contract hash.
|
||||
var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0}
|
||||
|
||||
// Actor is used by Contract to call state-changing methods.
|
||||
type Actor interface {
|
||||
MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error)
|
||||
MakeRun(script []byte) (*transaction.Transaction, error)
|
||||
MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error)
|
||||
MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error)
|
||||
SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error)
|
||||
SendRun(script []byte) (util.Uint256, uint32, error)
|
||||
}
|
||||
|
||||
// Contract implements all contract methods.
|
||||
type Contract struct {
|
||||
actor Actor
|
||||
hash util.Uint160
|
||||
}
|
||||
|
||||
// New creates an instance of Contract using Hash and the given Actor.
|
||||
func New(actor Actor) *Contract {
|
||||
var hash = Hash
|
||||
return &Contract{actor, hash}
|
||||
}
|
||||
|
||||
// RoyaltiesTransferred creates a transaction invoking `royaltiesTransferred` method of the contract.
|
||||
// This transaction is signed and immediately sent to the network.
|
||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||
func (c *Contract) RoyaltiesTransferred(royaltyToken util.Uint160, royaltyRecipient util.Uint160, buyer util.Uint160, tokenId []byte, amount *big.Int) (util.Uint256, uint32, error) {
|
||||
return c.actor.SendCall(c.hash, "royaltiesTransferred", royaltyToken, royaltyRecipient, buyer, tokenId, amount)
|
||||
}
|
||||
|
||||
// RoyaltiesTransferredTransaction creates a transaction invoking `royaltiesTransferred` method of the contract.
|
||||
// This transaction is signed, but not sent to the network, instead it's
|
||||
// returned to the caller.
|
||||
func (c *Contract) RoyaltiesTransferredTransaction(royaltyToken util.Uint160, royaltyRecipient util.Uint160, buyer util.Uint160, tokenId []byte, amount *big.Int) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeCall(c.hash, "royaltiesTransferred", royaltyToken, royaltyRecipient, buyer, tokenId, amount)
|
||||
}
|
||||
|
||||
// RoyaltiesTransferredUnsigned creates a transaction invoking `royaltiesTransferred` method of the contract.
|
||||
// This transaction is not signed, it's simply returned to the caller.
|
||||
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||
func (c *Contract) RoyaltiesTransferredUnsigned(royaltyToken util.Uint160, royaltyRecipient util.Uint160, buyer util.Uint160, tokenId []byte, amount *big.Int) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeUnsignedCall(c.hash, "royaltiesTransferred", nil, royaltyToken, royaltyRecipient, buyer, tokenId, amount)
|
||||
}
|
53
cli/smartcontract/testdata/rpcbindings/royalty/rpcbindings_dynamic_hash.out
vendored
Normal file
53
cli/smartcontract/testdata/rpcbindings/royalty/rpcbindings_dynamic_hash.out
vendored
Normal file
|
@ -0,0 +1,53 @@
|
|||
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||
|
||||
// Package royalty contains RPC wrappers for Test royalty contract.
|
||||
package royalty
|
||||
|
||||
import (
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"math/big"
|
||||
)
|
||||
|
||||
// Actor is used by Contract to call state-changing methods.
|
||||
type Actor interface {
|
||||
MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error)
|
||||
MakeRun(script []byte) (*transaction.Transaction, error)
|
||||
MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error)
|
||||
MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error)
|
||||
SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error)
|
||||
SendRun(script []byte) (util.Uint256, uint32, error)
|
||||
}
|
||||
|
||||
// Contract implements all contract methods.
|
||||
type Contract struct {
|
||||
actor Actor
|
||||
hash util.Uint160
|
||||
}
|
||||
|
||||
// New creates an instance of Contract using provided contract hash and the given Actor.
|
||||
func New(actor Actor, hash util.Uint160) *Contract {
|
||||
return &Contract{actor, hash}
|
||||
}
|
||||
|
||||
// RoyaltiesTransferred creates a transaction invoking `royaltiesTransferred` method of the contract.
|
||||
// This transaction is signed and immediately sent to the network.
|
||||
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||
func (c *Contract) RoyaltiesTransferred(royaltyToken util.Uint160, royaltyRecipient util.Uint160, buyer util.Uint160, tokenId []byte, amount *big.Int) (util.Uint256, uint32, error) {
|
||||
return c.actor.SendCall(c.hash, "royaltiesTransferred", royaltyToken, royaltyRecipient, buyer, tokenId, amount)
|
||||
}
|
||||
|
||||
// RoyaltiesTransferredTransaction creates a transaction invoking `royaltiesTransferred` method of the contract.
|
||||
// This transaction is signed, but not sent to the network, instead it's
|
||||
// returned to the caller.
|
||||
func (c *Contract) RoyaltiesTransferredTransaction(royaltyToken util.Uint160, royaltyRecipient util.Uint160, buyer util.Uint160, tokenId []byte, amount *big.Int) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeCall(c.hash, "royaltiesTransferred", royaltyToken, royaltyRecipient, buyer, tokenId, amount)
|
||||
}
|
||||
|
||||
// RoyaltiesTransferredUnsigned creates a transaction invoking `royaltiesTransferred` method of the contract.
|
||||
// This transaction is not signed, it's simply returned to the caller.
|
||||
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||
func (c *Contract) RoyaltiesTransferredUnsigned(royaltyToken util.Uint160, royaltyRecipient util.Uint160, buyer util.Uint160, tokenId []byte, amount *big.Int) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeUnsignedCall(c.hash, "royaltiesTransferred", nil, royaltyToken, royaltyRecipient, buyer, tokenId, amount)
|
||||
}
|
File diff suppressed because it is too large
Load diff
File diff suppressed because it is too large
Load diff
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"math/big"
|
||||
|
@ -370,17 +371,29 @@ func (c *ContractReader) UnnamedStructsX() (*UnnamedX, error) {
|
|||
}
|
||||
|
||||
// itemToUnnamed converts stack item into *Unnamed.
|
||||
// NULL item is returned as nil pointer without error.
|
||||
func itemToUnnamed(item stackitem.Item, err error) (*Unnamed, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, null := item.(stackitem.Null)
|
||||
if null {
|
||||
return nil, nil
|
||||
}
|
||||
var res = new(Unnamed)
|
||||
err = res.FromStackItem(item)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Ensure *Unnamed is a proper [stackitem.Convertible].
|
||||
var _ = stackitem.Convertible(&Unnamed{})
|
||||
|
||||
// Ensure *Unnamed is a proper [smartcontract.Convertible].
|
||||
var _ = smartcontract.Convertible(&Unnamed{})
|
||||
|
||||
// FromStackItem retrieves fields of Unnamed from the given
|
||||
// [stackitem.Item] or returns an error if it's not possible to do to so.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *Unnamed) FromStackItem(item stackitem.Item) error {
|
||||
arr, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
|
@ -403,18 +416,73 @@ func (res *Unnamed) FromStackItem(item stackitem.Item) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ToStackItem creates [stackitem.Item] representing Unnamed.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *Unnamed) ToStackItem() (stackitem.Item, error) {
|
||||
if res == nil {
|
||||
return stackitem.Null{}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
itm stackitem.Item
|
||||
items = make([]stackitem.Item, 0, 1)
|
||||
)
|
||||
itm, err = (*stackitem.BigInteger)(res.I), error(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field I: %w", err)
|
||||
}
|
||||
items = append(items, itm)
|
||||
|
||||
return stackitem.NewStruct(items), nil
|
||||
}
|
||||
|
||||
// ToSCParameter creates [smartcontract.Parameter] representing Unnamed.
|
||||
// It implements [smartcontract.Convertible] interface so that Unnamed
|
||||
// could be used with invokers.
|
||||
func (res *Unnamed) ToSCParameter() (smartcontract.Parameter, error) {
|
||||
if res == nil {
|
||||
return smartcontract.Parameter{Type: smartcontract.AnyType}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
prm smartcontract.Parameter
|
||||
prms = make([]smartcontract.Parameter, 0, 1)
|
||||
)
|
||||
prm, err = smartcontract.NewParameterFromValue(res.I)
|
||||
if err != nil {
|
||||
return smartcontract.Parameter{}, fmt.Errorf("field I: %w", err)
|
||||
}
|
||||
prms = append(prms, prm)
|
||||
|
||||
return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil
|
||||
}
|
||||
|
||||
// itemToUnnamedX converts stack item into *UnnamedX.
|
||||
// NULL item is returned as nil pointer without error.
|
||||
func itemToUnnamedX(item stackitem.Item, err error) (*UnnamedX, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, null := item.(stackitem.Null)
|
||||
if null {
|
||||
return nil, nil
|
||||
}
|
||||
var res = new(UnnamedX)
|
||||
err = res.FromStackItem(item)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Ensure *UnnamedX is a proper [stackitem.Convertible].
|
||||
var _ = stackitem.Convertible(&UnnamedX{})
|
||||
|
||||
// Ensure *UnnamedX is a proper [smartcontract.Convertible].
|
||||
var _ = smartcontract.Convertible(&UnnamedX{})
|
||||
|
||||
// FromStackItem retrieves fields of UnnamedX from the given
|
||||
// [stackitem.Item] or returns an error if it's not possible to do to so.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *UnnamedX) FromStackItem(item stackitem.Item) error {
|
||||
arr, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
|
@ -442,3 +510,58 @@ func (res *UnnamedX) FromStackItem(item stackitem.Item) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToStackItem creates [stackitem.Item] representing UnnamedX.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *UnnamedX) ToStackItem() (stackitem.Item, error) {
|
||||
if res == nil {
|
||||
return stackitem.Null{}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
itm stackitem.Item
|
||||
items = make([]stackitem.Item, 0, 2)
|
||||
)
|
||||
itm, err = (*stackitem.BigInteger)(res.I), error(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field I: %w", err)
|
||||
}
|
||||
items = append(items, itm)
|
||||
|
||||
itm, err = stackitem.NewBool(res.B), error(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field B: %w", err)
|
||||
}
|
||||
items = append(items, itm)
|
||||
|
||||
return stackitem.NewStruct(items), nil
|
||||
}
|
||||
|
||||
// ToSCParameter creates [smartcontract.Parameter] representing UnnamedX.
|
||||
// It implements [smartcontract.Convertible] interface so that UnnamedX
|
||||
// could be used with invokers.
|
||||
func (res *UnnamedX) ToSCParameter() (smartcontract.Parameter, error) {
|
||||
if res == nil {
|
||||
return smartcontract.Parameter{Type: smartcontract.AnyType}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
prm smartcontract.Parameter
|
||||
prms = make([]smartcontract.Parameter, 0, 2)
|
||||
)
|
||||
prm, err = smartcontract.NewParameterFromValue(res.I)
|
||||
if err != nil {
|
||||
return smartcontract.Parameter{}, fmt.Errorf("field I: %w", err)
|
||||
}
|
||||
prms = append(prms, prm)
|
||||
|
||||
prm, err = smartcontract.NewParameterFromValue(res.B)
|
||||
if err != nil {
|
||||
return smartcontract.Parameter{}, fmt.Errorf("field B: %w", err)
|
||||
}
|
||||
prms = append(prms, prm)
|
||||
|
||||
return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil
|
||||
}
|
||||
|
|
|
@ -9,6 +9,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"math/big"
|
||||
|
@ -366,17 +367,29 @@ func (c *ContractReader) UnnamedStructsX() (*UnnamedX, error) {
|
|||
}
|
||||
|
||||
// itemToUnnamed converts stack item into *Unnamed.
|
||||
// NULL item is returned as nil pointer without error.
|
||||
func itemToUnnamed(item stackitem.Item, err error) (*Unnamed, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, null := item.(stackitem.Null)
|
||||
if null {
|
||||
return nil, nil
|
||||
}
|
||||
var res = new(Unnamed)
|
||||
err = res.FromStackItem(item)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Ensure *Unnamed is a proper [stackitem.Convertible].
|
||||
var _ = stackitem.Convertible(&Unnamed{})
|
||||
|
||||
// Ensure *Unnamed is a proper [smartcontract.Convertible].
|
||||
var _ = smartcontract.Convertible(&Unnamed{})
|
||||
|
||||
// FromStackItem retrieves fields of Unnamed from the given
|
||||
// [stackitem.Item] or returns an error if it's not possible to do to so.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *Unnamed) FromStackItem(item stackitem.Item) error {
|
||||
arr, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
|
@ -399,18 +412,73 @@ func (res *Unnamed) FromStackItem(item stackitem.Item) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
// ToStackItem creates [stackitem.Item] representing Unnamed.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *Unnamed) ToStackItem() (stackitem.Item, error) {
|
||||
if res == nil {
|
||||
return stackitem.Null{}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
itm stackitem.Item
|
||||
items = make([]stackitem.Item, 0, 1)
|
||||
)
|
||||
itm, err = (*stackitem.BigInteger)(res.I), error(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field I: %w", err)
|
||||
}
|
||||
items = append(items, itm)
|
||||
|
||||
return stackitem.NewStruct(items), nil
|
||||
}
|
||||
|
||||
// ToSCParameter creates [smartcontract.Parameter] representing Unnamed.
|
||||
// It implements [smartcontract.Convertible] interface so that Unnamed
|
||||
// could be used with invokers.
|
||||
func (res *Unnamed) ToSCParameter() (smartcontract.Parameter, error) {
|
||||
if res == nil {
|
||||
return smartcontract.Parameter{Type: smartcontract.AnyType}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
prm smartcontract.Parameter
|
||||
prms = make([]smartcontract.Parameter, 0, 1)
|
||||
)
|
||||
prm, err = smartcontract.NewParameterFromValue(res.I)
|
||||
if err != nil {
|
||||
return smartcontract.Parameter{}, fmt.Errorf("field I: %w", err)
|
||||
}
|
||||
prms = append(prms, prm)
|
||||
|
||||
return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil
|
||||
}
|
||||
|
||||
// itemToUnnamedX converts stack item into *UnnamedX.
|
||||
// NULL item is returned as nil pointer without error.
|
||||
func itemToUnnamedX(item stackitem.Item, err error) (*UnnamedX, error) {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
_, null := item.(stackitem.Null)
|
||||
if null {
|
||||
return nil, nil
|
||||
}
|
||||
var res = new(UnnamedX)
|
||||
err = res.FromStackItem(item)
|
||||
return res, err
|
||||
}
|
||||
|
||||
// Ensure *UnnamedX is a proper [stackitem.Convertible].
|
||||
var _ = stackitem.Convertible(&UnnamedX{})
|
||||
|
||||
// Ensure *UnnamedX is a proper [smartcontract.Convertible].
|
||||
var _ = smartcontract.Convertible(&UnnamedX{})
|
||||
|
||||
// FromStackItem retrieves fields of UnnamedX from the given
|
||||
// [stackitem.Item] or returns an error if it's not possible to do to so.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *UnnamedX) FromStackItem(item stackitem.Item) error {
|
||||
arr, ok := item.Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
|
@ -438,3 +506,58 @@ func (res *UnnamedX) FromStackItem(item stackitem.Item) error {
|
|||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ToStackItem creates [stackitem.Item] representing UnnamedX.
|
||||
// It implements [stackitem.Convertible] interface.
|
||||
func (res *UnnamedX) ToStackItem() (stackitem.Item, error) {
|
||||
if res == nil {
|
||||
return stackitem.Null{}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
itm stackitem.Item
|
||||
items = make([]stackitem.Item, 0, 2)
|
||||
)
|
||||
itm, err = (*stackitem.BigInteger)(res.I), error(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field I: %w", err)
|
||||
}
|
||||
items = append(items, itm)
|
||||
|
||||
itm, err = stackitem.NewBool(res.B), error(nil)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("field B: %w", err)
|
||||
}
|
||||
items = append(items, itm)
|
||||
|
||||
return stackitem.NewStruct(items), nil
|
||||
}
|
||||
|
||||
// ToSCParameter creates [smartcontract.Parameter] representing UnnamedX.
|
||||
// It implements [smartcontract.Convertible] interface so that UnnamedX
|
||||
// could be used with invokers.
|
||||
func (res *UnnamedX) ToSCParameter() (smartcontract.Parameter, error) {
|
||||
if res == nil {
|
||||
return smartcontract.Parameter{Type: smartcontract.AnyType}, nil
|
||||
}
|
||||
|
||||
var (
|
||||
err error
|
||||
prm smartcontract.Parameter
|
||||
prms = make([]smartcontract.Parameter, 0, 2)
|
||||
)
|
||||
prm, err = smartcontract.NewParameterFromValue(res.I)
|
||||
if err != nil {
|
||||
return smartcontract.Parameter{}, fmt.Errorf("field I: %w", err)
|
||||
}
|
||||
prms = append(prms, prm)
|
||||
|
||||
prm, err = smartcontract.NewParameterFromValue(res.B)
|
||||
if err != nil {
|
||||
return smartcontract.Parameter{}, fmt.Errorf("field B: %w", err)
|
||||
}
|
||||
prms = append(prms, prm)
|
||||
|
||||
return smartcontract.Parameter{Type: smartcontract.ArrayType, Value: prms}, nil
|
||||
}
|
||||
|
|
|
@ -1 +1 @@
|
|||
{"name":"verify","abi":{"methods":[{"name":"verify","offset":0,"parameters":[],"returntype":"Boolean","safe":false},{"name":"onNEP17Payment","offset":5,"parameters":[{"name":"from","type":"ByteArray"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false}],"events":[{"name":"Hello world!","parameters":[{"name":"args","type":"Array"}]}]},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null}
|
||||
{"name":"verify","abi":{"methods":[{"name":"verify","offset":0,"parameters":[],"returntype":"Boolean","safe":false},{"name":"onNEP17Payment","offset":5,"parameters":[{"name":"from","type":"ByteArray"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false}],"events":[{"name":"Hello world!","parameters":[{"name":"args","type":"Array"}]}]},"groups":[],"permissions":[{"contract":"*","methods":"*"}],"supportedstandards":[],"trusts":[],"extra":null,"features":{}}
|
||||
|
|
|
@ -4,6 +4,7 @@
|
|||
"supportedstandards" : [],
|
||||
"name" : "verify",
|
||||
"trusts" : [],
|
||||
"features": {},
|
||||
"permissions" : [
|
||||
{
|
||||
"methods" : "*",
|
||||
|
|
1
cli/testdata/wallet1_solo.json
vendored
1
cli/testdata/wallet1_solo.json
vendored
|
@ -65,7 +65,6 @@
|
|||
"key": "6PYSATFztBa3CHjSR6sLAKungUEAbQUCVE16KzmaQQ38gLeYGZ9Knd5mGv",
|
||||
"label": "verify",
|
||||
"contract": {
|
||||
"script": "VwEAEUBXAANA",
|
||||
"parameters": [],
|
||||
"deployed": true
|
||||
},
|
||||
|
|
|
@ -16,34 +16,36 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var (
|
||||
// GasFlag is a flag used for the additional network fee.
|
||||
GasFlag = flags.Fixed8Flag{
|
||||
Name: "gas, g",
|
||||
Usage: "network fee to add to the transaction (prioritizing it)",
|
||||
GasFlag = &flags.Fixed8Flag{
|
||||
Name: "gas",
|
||||
Aliases: []string{"g"},
|
||||
Usage: "Network fee to add to the transaction (prioritizing it)",
|
||||
}
|
||||
// SysGasFlag is a flag used for the additional system fee.
|
||||
SysGasFlag = flags.Fixed8Flag{
|
||||
Name: "sysgas, e",
|
||||
Usage: "system fee to add to the transaction (compensating for execution)",
|
||||
SysGasFlag = &flags.Fixed8Flag{
|
||||
Name: "sysgas",
|
||||
Aliases: []string{"e"},
|
||||
Usage: "System fee to add to the transaction (compensating for execution)",
|
||||
}
|
||||
// OutFlag is a flag used for file output.
|
||||
OutFlag = cli.StringFlag{
|
||||
OutFlag = &cli.StringFlag{
|
||||
Name: "out",
|
||||
Usage: "file (JSON) to put signature context with a transaction to",
|
||||
Usage: "File (JSON) to put signature context with a transaction to",
|
||||
}
|
||||
// ForceFlag is a flag used to force transaction send.
|
||||
ForceFlag = cli.BoolFlag{
|
||||
ForceFlag = &cli.BoolFlag{
|
||||
Name: "force",
|
||||
Usage: "Do not ask for a confirmation (and ignore errors)",
|
||||
}
|
||||
// AwaitFlag is a flag used to wait for the transaction to be included in a block.
|
||||
AwaitFlag = cli.BoolFlag{
|
||||
AwaitFlag = &cli.BoolFlag{
|
||||
Name: "await",
|
||||
Usage: "wait for the transaction to be included in a block",
|
||||
Usage: "Wait for the transaction to be included in a block",
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -71,7 +73,7 @@ func SignAndSend(ctx *cli.Context, act *actor.Actor, acc *wallet.Account, tx *tr
|
|||
promptTime := time.Now()
|
||||
err := input.ConfirmTx(ctx.App.Writer, tx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
waitTime := time.Since(promptTime)
|
||||
// Compensate for confirmation waiting.
|
||||
|
@ -83,17 +85,17 @@ func SignAndSend(ctx *cli.Context, act *actor.Actor, acc *wallet.Account, tx *tr
|
|||
)
|
||||
resTx, vub, err = act.SignAndSend(tx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
if ctx.Bool("await") {
|
||||
aer, err = act.Wait(resTx, vub, err)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to await transaction %s: %w", resTx.StringLE(), err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to await transaction %s: %w", resTx.StringLE(), err), 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
DumpTransactionInfo(ctx.App.Writer, tx.Hash(), aer)
|
||||
|
|
|
@ -16,20 +16,20 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func cancelTx(ctx *cli.Context) error {
|
||||
args := ctx.Args()
|
||||
args := ctx.Args().Slice()
|
||||
if len(args) == 0 {
|
||||
return cli.NewExitError("transaction hash is missing", 1)
|
||||
return cli.Exit("transaction hash is missing", 1)
|
||||
} else if len(args) > 1 {
|
||||
return cli.NewExitError("only one transaction hash is accepted", 1)
|
||||
return cli.Exit("only one transaction hash is accepted", 1)
|
||||
}
|
||||
|
||||
txHash, err := util.Uint256DecodeStringLE(strings.TrimPrefix(args[0], "0x"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("invalid tx hash: %s", args[0]), 1)
|
||||
return cli.Exit(fmt.Sprintf("invalid tx hash: %s", args[0]), 1)
|
||||
}
|
||||
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
|
@ -37,13 +37,13 @@ func cancelTx(ctx *cli.Context) error {
|
|||
|
||||
acc, w, err := options.GetAccFromContext(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to get account from context to sign the conflicting transaction: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to get account from context to sign the conflicting transaction: %w", err), 1)
|
||||
}
|
||||
defer w.Close()
|
||||
|
||||
signers, err := cmdargs.GetSignersAccounts(acc, w, nil, transaction.CalledByEntry)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("invalid signers: %w", err), 1)
|
||||
}
|
||||
c, a, exitErr := options.GetRPCWithActor(gctx, ctx, signers)
|
||||
if exitErr != nil {
|
||||
|
@ -52,11 +52,11 @@ func cancelTx(ctx *cli.Context) error {
|
|||
|
||||
mainTx, _ := c.GetRawTransactionVerbose(txHash)
|
||||
if mainTx != nil && !mainTx.Blockhash.Equals(util.Uint256{}) {
|
||||
return cli.NewExitError(fmt.Errorf("target transaction %s is accepted at block %s", txHash, mainTx.Blockhash.StringLE()), 1)
|
||||
return cli.Exit(fmt.Errorf("target transaction %s is accepted at block %s", txHash, mainTx.Blockhash.StringLE()), 1)
|
||||
}
|
||||
|
||||
if mainTx != nil && !mainTx.HasSigner(acc.ScriptHash()) {
|
||||
return cli.NewExitError(fmt.Errorf("account %s is not a signer of the conflicting transaction", acc.Address), 1)
|
||||
return cli.Exit(fmt.Errorf("account %s is not a signer of the conflicting transaction", acc.Address), 1)
|
||||
}
|
||||
|
||||
resHash, resVub, err := a.SendTunedRun([]byte{byte(opcode.RET)}, []transaction.Attribute{{Type: transaction.ConflictsT, Value: &transaction.Conflicts{Hash: txHash}}}, func(r *result.Invoke, t *transaction.Transaction) error {
|
||||
|
@ -64,8 +64,8 @@ func cancelTx(ctx *cli.Context) error {
|
|||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if mainTx != nil && t.NetworkFee < mainTx.NetworkFee+1 {
|
||||
t.NetworkFee = mainTx.NetworkFee + 1
|
||||
if mainTx != nil {
|
||||
t.NetworkFee = max(t.NetworkFee, mainTx.NetworkFee+1)
|
||||
}
|
||||
t.NetworkFee += int64(flags.Fixed8FromContext(ctx, "gas"))
|
||||
if mainTx != nil {
|
||||
|
@ -74,7 +74,7 @@ func cancelTx(ctx *cli.Context) error {
|
|||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to send conflicting transaction: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to send conflicting transaction: %w", err), 1)
|
||||
}
|
||||
var res *state.AppExecResult
|
||||
if ctx.Bool("await") {
|
||||
|
@ -82,19 +82,19 @@ func cancelTx(ctx *cli.Context) error {
|
|||
if err != nil {
|
||||
if errors.Is(err, waiter.ErrTxNotAccepted) {
|
||||
if mainTx == nil {
|
||||
return cli.NewExitError(fmt.Errorf("neither target nor conflicting transaction is accepted before the current height %d (ValidUntilBlock value of conlicting transaction). Main transaction is unknown to the provided RPC node, thus still has chances to be accepted, you may try cancellation again", resVub), 1)
|
||||
return cli.Exit(fmt.Errorf("neither target nor conflicting transaction is accepted before the current height %d (ValidUntilBlock value of conlicting transaction). Main transaction is unknown to the provided RPC node, thus still has chances to be accepted, you may try cancellation again", resVub), 1)
|
||||
}
|
||||
fmt.Fprintf(ctx.App.Writer, "Neither target nor conflicting transaction is accepted before the current height %d (ValidUntilBlock value of both target and conflicting transactions). Main transaction is not valid anymore, cancellation is successful\n", resVub)
|
||||
return nil
|
||||
}
|
||||
return cli.NewExitError(fmt.Errorf("failed to await target/ conflicting transaction %s/ %s: %w", txHash.StringLE(), resHash.StringLE(), err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to await target/ conflicting transaction %s/ %s: %w", txHash.StringLE(), resHash.StringLE(), err), 1)
|
||||
}
|
||||
if txHash.Equals(res.Container) {
|
||||
tx, err := c.GetRawTransactionVerbose(txHash)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("target transaction %s is accepted", txHash), 1)
|
||||
return cli.Exit(fmt.Errorf("target transaction %s is accepted", txHash), 1)
|
||||
}
|
||||
return cli.NewExitError(fmt.Errorf("target transaction %s is accepted at block %s", txHash, tx.Blockhash.StringLE()), 1)
|
||||
return cli.Exit(fmt.Errorf("target transaction %s is accepted at block %s", txHash, tx.Blockhash.StringLE()), 1)
|
||||
}
|
||||
fmt.Fprintln(ctx.App.Writer, "Conflicting transaction accepted")
|
||||
}
|
||||
|
|
|
@ -6,32 +6,107 @@ import (
|
|||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||
"github.com/nspcc-dev/neo-go/cli/options"
|
||||
"github.com/nspcc-dev/neo-go/cli/txctx"
|
||||
vmcli "github.com/nspcc-dev/neo-go/cli/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/services/helpers/neofs"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
var neoFSFlags = append([]cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "container",
|
||||
Aliases: []string{"cid"},
|
||||
Usage: "NeoFS container ID to upload objects to",
|
||||
Required: true,
|
||||
Action: cmdargs.EnsureNotEmpty("container"),
|
||||
},
|
||||
&flags.AddressFlag{
|
||||
Name: "address",
|
||||
Usage: "Address to use for signing the uploading and searching transactions in NeoFS",
|
||||
},
|
||||
&cli.UintFlag{
|
||||
Name: "retries",
|
||||
Usage: "Maximum number of NeoFS node request retries",
|
||||
Value: neofs.MaxRetries,
|
||||
Action: func(context *cli.Context, u uint) error {
|
||||
if u < 1 {
|
||||
return cli.Exit("retries should be greater than 0", 1)
|
||||
}
|
||||
return nil
|
||||
},
|
||||
},
|
||||
&cli.UintFlag{
|
||||
Name: "searchers",
|
||||
Usage: "Number of concurrent searches for objects",
|
||||
Value: 100,
|
||||
}}, options.NeoFSRPC...)
|
||||
|
||||
// NewCommands returns util commands for neo-go CLI.
|
||||
func NewCommands() []cli.Command {
|
||||
txDumpFlags := append([]cli.Flag{}, options.RPC...)
|
||||
func NewCommands() []*cli.Command {
|
||||
// By default, RPC flag is required. sendtx and txdump may be called without provided rpc-endpoint.
|
||||
rpcFlagOriginal, _ := options.RPC[0].(*cli.StringFlag)
|
||||
rpcFlag := *rpcFlagOriginal
|
||||
rpcFlag.Required = false
|
||||
txDumpFlags := append([]cli.Flag{&rpcFlag}, options.RPC[1:]...)
|
||||
txSendFlags := append(txDumpFlags, txctx.AwaitFlag)
|
||||
txCancelFlags := append([]cli.Flag{
|
||||
flags.AddressFlag{
|
||||
Name: "address, a",
|
||||
Usage: "address to use as conflicting transaction signee (and gas source)",
|
||||
&flags.AddressFlag{
|
||||
Name: "address",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "Address to use as conflicting transaction signee (and gas source)",
|
||||
},
|
||||
txctx.GasFlag,
|
||||
txctx.AwaitFlag,
|
||||
}, options.RPC...)
|
||||
txCancelFlags = append(txCancelFlags, options.Wallet...)
|
||||
return []cli.Command{
|
||||
uploadBinFlags := append([]cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "block-attribute",
|
||||
Usage: "Attribute key of the block object",
|
||||
Value: neofs.DefaultBlockAttribute,
|
||||
Action: cmdargs.EnsureNotEmpty("block-attribute"),
|
||||
},
|
||||
&cli.StringFlag{
|
||||
Name: "index-attribute",
|
||||
Usage: "Attribute key of the index file object",
|
||||
Value: neofs.DefaultIndexFileAttribute,
|
||||
Action: cmdargs.EnsureNotEmpty("index-attribute"),
|
||||
},
|
||||
&cli.UintFlag{
|
||||
Name: "index-file-size",
|
||||
Usage: "Size of index file",
|
||||
Value: neofs.DefaultIndexFileSize,
|
||||
},
|
||||
&cli.UintFlag{
|
||||
Name: "workers",
|
||||
Usage: "Number of workers to fetch and upload blocks concurrently",
|
||||
Value: 20,
|
||||
},
|
||||
options.Debug,
|
||||
}, options.RPC...)
|
||||
uploadBinFlags = append(uploadBinFlags, options.Wallet...)
|
||||
uploadBinFlags = append(uploadBinFlags, neoFSFlags...)
|
||||
|
||||
uploadStateFlags := append([]cli.Flag{
|
||||
&cli.StringFlag{
|
||||
Name: "state-attribute",
|
||||
Usage: "Attribute key of the state object",
|
||||
Value: neofs.DefaultStateAttribute,
|
||||
Action: cmdargs.EnsureNotEmpty("state-attribute"),
|
||||
},
|
||||
options.Debug, options.Config, options.ConfigFile, options.RelativePath,
|
||||
}, options.Wallet...)
|
||||
uploadStateFlags = append(uploadStateFlags, options.Network...)
|
||||
uploadStateFlags = append(uploadStateFlags, neoFSFlags...)
|
||||
return []*cli.Command{
|
||||
{
|
||||
Name: "util",
|
||||
Usage: "Various helper commands",
|
||||
Subcommands: []cli.Command{
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "convert",
|
||||
Usage: "Convert provided argument into other possible formats",
|
||||
|
@ -44,7 +119,7 @@ func NewCommands() []cli.Command {
|
|||
{
|
||||
Name: "sendtx",
|
||||
Usage: "Send complete transaction stored in a context file",
|
||||
UsageText: "sendtx [-r <endpoint>] <file.in> [--await]",
|
||||
UsageText: "sendtx [-r <endpoint>] [--await] <file.in>",
|
||||
Description: `Sends the transaction from the given context file to the given RPC node if it's
|
||||
completely signed and ready. This command expects a ContractParametersContext
|
||||
JSON file for input, it can't handle binary (or hex- or base64-encoded)
|
||||
|
@ -57,7 +132,7 @@ func NewCommands() []cli.Command {
|
|||
{
|
||||
Name: "canceltx",
|
||||
Usage: "Cancel transaction by sending conflicting transaction",
|
||||
UsageText: "canceltx <txid> -r <endpoint> --wallet <wallet> [--account <account>] [--wallet-config <path>] [--gas <gas>] [--await]",
|
||||
UsageText: "canceltx -r <endpoint> --wallet <wallet> [--address <account>] [--wallet-config <path>] [--gas <gas>] [--await] <txid>",
|
||||
Description: `Aims to prevent a transaction from being added to the blockchain by dispatching a more
|
||||
prioritized conflicting transaction to the specified RPC node. The input for this command should
|
||||
be the transaction hash. If another account is not specified, the conflicting transaction is
|
||||
|
@ -90,28 +165,43 @@ func NewCommands() []cli.Command {
|
|||
{
|
||||
Name: "ops",
|
||||
Usage: "Pretty-print VM opcodes of the given base64- or hex- encoded script (base64 is checked first). If the input file is specified, then the script is taken from the file.",
|
||||
UsageText: "ops <base64/hex-encoded script> [-i path-to-file] [--hex]",
|
||||
UsageText: "ops [-i path-to-file] [--hex] <base64/hex-encoded script>",
|
||||
Action: handleOps,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
Name: "in, i",
|
||||
Usage: "input file containing base64- or hex- encoded script representation",
|
||||
&cli.StringFlag{
|
||||
Name: "in",
|
||||
Aliases: []string{"i"},
|
||||
Usage: "Input file containing base64- or hex- encoded script representation",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
&cli.BoolFlag{
|
||||
Name: "hex",
|
||||
Usage: "use hex encoding and do not check base64",
|
||||
Usage: "Use hex encoding and do not check base64",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "upload-bin",
|
||||
Usage: "Fetch blocks from RPC node and upload them to the NeoFS container",
|
||||
UsageText: "neo-go util upload-bin --fs-rpc-endpoint <address1>[,<address2>[...]] --container <cid> --block-attribute block --index-attribute index --rpc-endpoint <node> [--timeout <time>] --wallet <wallet> [--wallet-config <config>] [--address <address>] [--workers <num>] [--searchers <num>] [--index-file-size <size>] [--retries <num>] [--debug]",
|
||||
Action: uploadBin,
|
||||
Flags: uploadBinFlags,
|
||||
},
|
||||
{
|
||||
Name: "upload-state",
|
||||
Usage: "Start the node, traverse MPT and upload MPT nodes to the NeoFS container at every StateSyncInterval number of blocks",
|
||||
UsageText: "neo-go util upload-state --fs-rpc-endpoint <address1>[,<address2>[...]] --container <cid> --state-attribute state --wallet <wallet> [--wallet-config <config>] [--address <address>] [--searchers <num>] [--retries <num>] [--debug] [--config-path path] [-p/-m/-t] [--config-file file]",
|
||||
Action: uploadState,
|
||||
Flags: uploadStateFlags,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
func handleParse(ctx *cli.Context) error {
|
||||
res, err := vmcli.Parse(ctx.Args())
|
||||
res, err := vmcli.Parse(ctx.Args().Slice())
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
fmt.Fprint(ctx.App.Writer, res)
|
||||
return nil
|
||||
|
@ -127,21 +217,21 @@ func handleOps(ctx *cli.Context) error {
|
|||
if len(in) != 0 {
|
||||
b, err := os.ReadFile(in)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to read file: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to read file: %w", err), 1)
|
||||
}
|
||||
s = string(b)
|
||||
} else {
|
||||
if !ctx.Args().Present() {
|
||||
return cli.NewExitError("missing script", 1)
|
||||
return cli.Exit("missing script", 1)
|
||||
}
|
||||
s = ctx.Args()[0]
|
||||
s = ctx.Args().Slice()[0]
|
||||
}
|
||||
b, err = base64.StdEncoding.DecodeString(s)
|
||||
if err != nil || ctx.Bool("hex") {
|
||||
b, err = hex.DecodeString(s)
|
||||
}
|
||||
if err != nil {
|
||||
return cli.NewExitError("unknown encoding: base64 or hex are supported", 1)
|
||||
return cli.Exit("unknown encoding: base64 or hex are supported", 1)
|
||||
}
|
||||
v := vm.New()
|
||||
v.LoadScript(b)
|
||||
|
|
|
@ -8,30 +8,30 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/cli/paramcontext"
|
||||
"github.com/nspcc-dev/neo-go/cli/query"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func txDump(ctx *cli.Context) error {
|
||||
args := ctx.Args()
|
||||
args := ctx.Args().Slice()
|
||||
if len(args) == 0 {
|
||||
return cli.NewExitError("missing input file", 1)
|
||||
return cli.Exit("missing input file", 1)
|
||||
} else if len(args) > 1 {
|
||||
return cli.NewExitError("only one input file is accepted", 1)
|
||||
return cli.Exit("only one input file is accepted", 1)
|
||||
}
|
||||
|
||||
c, err := paramcontext.Read(args[0])
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
tx, ok := c.Verifiable.(*transaction.Transaction)
|
||||
if !ok {
|
||||
return cli.NewExitError("verifiable item is not a transaction", 1)
|
||||
return cli.Exit("verifiable item is not a transaction", 1)
|
||||
}
|
||||
|
||||
err = query.DumpApplicationLog(ctx, nil, tx, nil, true)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
if ctx.String(options.RPCEndpointFlag) != "" {
|
||||
|
@ -41,15 +41,15 @@ func txDump(ctx *cli.Context) error {
|
|||
var err error
|
||||
cl, err := options.GetRPCClient(gctx, ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
res, err := cl.InvokeScript(tx.Script, tx.Signers)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
resS, err := json.MarshalIndent(res, "", " ")
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
fmt.Fprintln(ctx.App.Writer, string(resS))
|
||||
}
|
||||
|
|
|
@ -8,25 +8,25 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/cli/txctx"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func sendTx(ctx *cli.Context) error {
|
||||
args := ctx.Args()
|
||||
args := ctx.Args().Slice()
|
||||
if len(args) == 0 {
|
||||
return cli.NewExitError("missing input file", 1)
|
||||
return cli.Exit("missing input file", 1)
|
||||
} else if len(args) > 1 {
|
||||
return cli.NewExitError("only one input file is accepted", 1)
|
||||
return cli.Exit("only one input file is accepted", 1)
|
||||
}
|
||||
|
||||
pc, err := paramcontext.Read(args[0])
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
tx, err := pc.GetCompleteTransaction()
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to complete transaction: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to complete transaction: %w", err), 1)
|
||||
}
|
||||
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
|
@ -34,18 +34,18 @@ func sendTx(ctx *cli.Context) error {
|
|||
|
||||
c, err := options.GetRPCClient(gctx, ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to create RPC client: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to create RPC client: %w", err), 1)
|
||||
}
|
||||
res, err := c.SendRawTransaction(tx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to submit transaction to RPC node: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to submit transaction to RPC node: %w", err), 1)
|
||||
}
|
||||
var aer *state.AppExecResult
|
||||
if ctx.Bool("await") {
|
||||
version, err := c.GetVersion()
|
||||
aer, err = waiter.New(c, version).Wait(res, tx.ValidUntilBlock, err)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to await transaction %s: %w", res.StringLE(), err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to await transaction %s: %w", res.StringLE(), err), 1)
|
||||
}
|
||||
}
|
||||
txctx.DumpTransactionInfo(ctx.App.Writer, res, aer)
|
||||
|
|
449
cli/util/upload_bin.go
Normal file
449
cli/util/upload_bin.go
Normal file
|
@ -0,0 +1,449 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strconv"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||
"github.com/nspcc-dev/neo-go/cli/options"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/services/helpers/neofs"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/client"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/container"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/object"
|
||||
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/user"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func uploadBin(ctx *cli.Context) error {
|
||||
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
attr := ctx.String("block-attribute")
|
||||
numWorkers := ctx.Uint("workers")
|
||||
maxParallelSearches := ctx.Uint("searchers")
|
||||
maxRetries := ctx.Uint("retries")
|
||||
debug := ctx.Bool("debug")
|
||||
indexFileSize := ctx.Uint("index-file-size")
|
||||
indexAttrKey := ctx.String("index-attribute")
|
||||
acc, _, err := options.GetAccFromContext(ctx)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("failed to load account: %v", err), 1)
|
||||
}
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
defer cancel()
|
||||
rpc, err := options.GetRPCClient(gctx, ctx)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("failed to create RPC client: %v", err), 1)
|
||||
}
|
||||
|
||||
signer, pWrapper, err := options.GetNeoFSClientPool(ctx, acc)
|
||||
if err != nil {
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer pWrapper.Close()
|
||||
|
||||
v, err := rpc.GetVersion()
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("failed to get version from RPC: %v", err), 1)
|
||||
}
|
||||
magic := strconv.Itoa(int(v.Protocol.Network))
|
||||
containerID, err := getContainer(ctx, pWrapper, magic, maxRetries, debug)
|
||||
if err != nil {
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
currentBlockHeight, err := rpc.GetBlockCount()
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("failed to get current block height from RPC: %v", err), 1)
|
||||
}
|
||||
fmt.Fprintln(ctx.App.Writer, "Chain block height:", currentBlockHeight)
|
||||
i, buf, err := searchIndexFile(ctx, pWrapper, containerID, acc.PrivateKey(), signer, indexFileSize, attr, indexAttrKey, maxParallelSearches, maxRetries, debug)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Errorf("failed to find objects: %w", err), 1)
|
||||
}
|
||||
|
||||
err = uploadBlocksAndIndexFiles(ctx, pWrapper, rpc, signer, containerID, attr, indexAttrKey, buf, i, indexFileSize, uint(currentBlockHeight), numWorkers, maxRetries, debug)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Errorf("failed to upload objects: %w", err), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// retry function with exponential backoff.
|
||||
func retry(action func() error, maxRetries uint, debug bool) error {
|
||||
var err error
|
||||
backoff := neofs.InitialBackoff
|
||||
for i := range maxRetries {
|
||||
if err = action(); err == nil {
|
||||
return nil // Success, no retry needed.
|
||||
}
|
||||
if debug {
|
||||
fmt.Printf("Retry %d: %v\n", i, err)
|
||||
}
|
||||
time.Sleep(backoff) // Backoff before retrying.
|
||||
backoff *= time.Duration(neofs.BackoffFactor)
|
||||
if backoff > neofs.MaxBackoff {
|
||||
backoff = neofs.MaxBackoff
|
||||
}
|
||||
}
|
||||
return err // Return the last error after exhausting retries.
|
||||
}
|
||||
|
||||
// uploadBlocksAndIndexFiles uploads the blocks and index files to the container using the pool.
|
||||
func uploadBlocksAndIndexFiles(ctx *cli.Context, p neofs.PoolWrapper, rpc *rpcclient.Client, signer user.Signer, containerID cid.ID, attr, indexAttributeKey string, buf []byte, currentIndexFileID, indexFileSize, currentBlockHeight uint, numWorkers, maxRetries uint, debug bool) error {
|
||||
if currentIndexFileID*indexFileSize >= currentBlockHeight {
|
||||
fmt.Fprintf(ctx.App.Writer, "No new blocks to upload. Need to upload starting from %d, current height %d\n", currentIndexFileID*indexFileSize, currentBlockHeight)
|
||||
return nil
|
||||
}
|
||||
fmt.Fprintln(ctx.App.Writer, "Uploading blocks and index files...")
|
||||
for indexFileStart := currentIndexFileID * indexFileSize; indexFileStart < currentBlockHeight; indexFileStart += indexFileSize {
|
||||
var (
|
||||
indexFileEnd = min(indexFileStart+indexFileSize, currentBlockHeight)
|
||||
errCh = make(chan error)
|
||||
doneCh = make(chan struct{})
|
||||
wg sync.WaitGroup
|
||||
)
|
||||
fmt.Fprintf(ctx.App.Writer, "Processing batch from %d to %d\n", indexFileStart, indexFileEnd-1)
|
||||
wg.Add(int(numWorkers))
|
||||
for i := range numWorkers {
|
||||
go func(i uint) {
|
||||
defer wg.Done()
|
||||
for blockIndex := indexFileStart + i; blockIndex < indexFileEnd; blockIndex += numWorkers {
|
||||
if !oid.ID(buf[blockIndex%indexFileSize*oid.Size : blockIndex%indexFileSize*oid.Size+oid.Size]).IsZero() {
|
||||
if debug {
|
||||
fmt.Fprintf(ctx.App.Writer, "Block %d is already uploaded\n", blockIndex)
|
||||
}
|
||||
continue
|
||||
}
|
||||
var blk *block.Block
|
||||
errGet := retry(func() error {
|
||||
var errGetBlock error
|
||||
blk, errGetBlock = rpc.GetBlockByIndex(uint32(blockIndex))
|
||||
if errGetBlock != nil {
|
||||
return fmt.Errorf("failed to fetch block %d: %w", blockIndex, errGetBlock)
|
||||
}
|
||||
return nil
|
||||
}, maxRetries, debug)
|
||||
if errGet != nil {
|
||||
select {
|
||||
case errCh <- errGet:
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
bw := io.NewBufBinWriter()
|
||||
blk.EncodeBinary(bw.BinWriter)
|
||||
if bw.Err != nil {
|
||||
select {
|
||||
case errCh <- fmt.Errorf("failed to encode block %d: %w", blockIndex, bw.Err):
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
attrs := []object.Attribute{
|
||||
*object.NewAttribute(attr, strconv.Itoa(int(blk.Index))),
|
||||
*object.NewAttribute("Primary", strconv.Itoa(int(blk.PrimaryIndex))),
|
||||
*object.NewAttribute("Hash", blk.Hash().StringLE()),
|
||||
*object.NewAttribute("PrevHash", blk.PrevHash.StringLE()),
|
||||
*object.NewAttribute("BlockTime", strconv.FormatUint(blk.Timestamp, 10)),
|
||||
*object.NewAttribute("Timestamp", strconv.FormatInt(time.Now().Unix(), 10)),
|
||||
}
|
||||
|
||||
var (
|
||||
objBytes = bw.Bytes()
|
||||
resOid oid.ID
|
||||
)
|
||||
errRetr := retry(func() error {
|
||||
var errUpload error
|
||||
resOid, errUpload = uploadObj(ctx.Context, p, signer, containerID, objBytes, attrs)
|
||||
if errUpload != nil {
|
||||
return errUpload
|
||||
}
|
||||
if debug {
|
||||
fmt.Fprintf(ctx.App.Writer, "Uploaded block %d with object ID: %s\n", blockIndex, resOid.String())
|
||||
}
|
||||
return errUpload
|
||||
}, maxRetries, debug)
|
||||
if errRetr != nil {
|
||||
select {
|
||||
case errCh <- errRetr:
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
copy(buf[blockIndex%indexFileSize*oid.Size:], resOid[:])
|
||||
}
|
||||
}(i)
|
||||
}
|
||||
|
||||
go func() {
|
||||
wg.Wait()
|
||||
close(doneCh)
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-errCh:
|
||||
return fmt.Errorf("upload error: %w", err)
|
||||
case <-doneCh:
|
||||
}
|
||||
fmt.Fprintf(ctx.App.Writer, "Successfully processed batch of blocks: from %d to %d\n", indexFileStart, indexFileEnd-1)
|
||||
|
||||
// Additional check for empty OIDs in the buffer.
|
||||
for k := uint(0); k < (indexFileEnd-indexFileStart)*oid.Size; k += oid.Size {
|
||||
if oid.ID(buf[k : k+oid.Size]).IsZero() {
|
||||
return fmt.Errorf("empty OID found in index file %d at position %d (block index %d)", indexFileStart/indexFileSize, k/oid.Size, indexFileStart/indexFileSize*indexFileSize+k/oid.Size)
|
||||
}
|
||||
}
|
||||
if indexFileEnd-indexFileStart == indexFileSize {
|
||||
attrs := []object.Attribute{
|
||||
*object.NewAttribute(indexAttributeKey, strconv.Itoa(int(indexFileStart/indexFileSize))),
|
||||
*object.NewAttribute("IndexSize", strconv.Itoa(int(indexFileSize))),
|
||||
*object.NewAttribute("Timestamp", strconv.FormatInt(time.Now().Unix(), 10)),
|
||||
}
|
||||
err := retry(func() error {
|
||||
var errUpload error
|
||||
_, errUpload = uploadObj(ctx.Context, p, signer, containerID, buf, attrs)
|
||||
return errUpload
|
||||
}, maxRetries, debug)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to upload index file: %w", err)
|
||||
}
|
||||
fmt.Fprintln(ctx.App.Writer, "Successfully uploaded index file ", indexFileStart/indexFileSize)
|
||||
}
|
||||
clear(buf)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// searchIndexFile returns the ID and buffer for the next index file to be uploaded.
|
||||
func searchIndexFile(ctx *cli.Context, p neofs.PoolWrapper, containerID cid.ID, privKeys *keys.PrivateKey, signer user.Signer, indexFileSize uint, blockAttributeKey, attributeKey string, maxParallelSearches, maxRetries uint, debug bool) (uint, []byte, error) {
|
||||
var (
|
||||
// buf is used to store OIDs of the uploaded blocks.
|
||||
buf = make([]byte, indexFileSize*oid.Size)
|
||||
doneCh = make(chan struct{})
|
||||
errCh = make(chan error)
|
||||
|
||||
existingIndexCount = uint(0)
|
||||
filters = object.NewSearchFilters()
|
||||
)
|
||||
go func() {
|
||||
defer close(doneCh)
|
||||
// Search for existing index files.
|
||||
filters.AddFilter("IndexSize", fmt.Sprintf("%d", indexFileSize), object.MatchStringEqual)
|
||||
for i := 0; ; i++ {
|
||||
indexIDs := searchObjects(ctx.Context, p, containerID, privKeys, attributeKey, uint(i), uint(i+1), 1, maxRetries, debug, errCh, filters)
|
||||
resOIDs := make([]oid.ID, 0, 1)
|
||||
for id := range indexIDs {
|
||||
resOIDs = append(resOIDs, id)
|
||||
}
|
||||
if len(resOIDs) == 0 {
|
||||
break
|
||||
}
|
||||
if len(resOIDs) > 1 {
|
||||
fmt.Fprintf(ctx.App.Writer, "WARN: %d duplicated index files with index %d found: %s\n", len(resOIDs), i, resOIDs)
|
||||
}
|
||||
existingIndexCount++
|
||||
}
|
||||
fmt.Fprintf(ctx.App.Writer, "Current index files count: %d\n", existingIndexCount)
|
||||
|
||||
// Start block parsing goroutines.
|
||||
var (
|
||||
// processedIndices is a mapping from position in buffer to the block index.
|
||||
// It prevents duplicates.
|
||||
processedIndices sync.Map
|
||||
wg sync.WaitGroup
|
||||
oidCh = make(chan oid.ID, 2*maxParallelSearches)
|
||||
)
|
||||
wg.Add(int(maxParallelSearches))
|
||||
for range maxParallelSearches {
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for id := range oidCh {
|
||||
var obj object.Object
|
||||
errRetr := retry(func() error {
|
||||
var errGet error
|
||||
obj, _, errGet = p.ObjectGetInit(ctx.Context, containerID, id, signer, client.PrmObjectGet{})
|
||||
return errGet
|
||||
}, maxRetries, debug)
|
||||
if errRetr != nil {
|
||||
select {
|
||||
case errCh <- fmt.Errorf("failed to fetch object %s: %w", id.String(), errRetr):
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
blockIndex, err := getBlockIndex(obj, blockAttributeKey)
|
||||
if err != nil {
|
||||
select {
|
||||
case errCh <- fmt.Errorf("failed to get block index from object %s: %w", id.String(), err):
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
pos := uint(blockIndex) % indexFileSize
|
||||
if _, ok := processedIndices.LoadOrStore(pos, blockIndex); !ok {
|
||||
copy(buf[pos*oid.Size:], id[:])
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// Search for blocks within the index file range.
|
||||
objIDs := searchObjects(ctx.Context, p, containerID, privKeys, blockAttributeKey, existingIndexCount*indexFileSize, (existingIndexCount+1)*indexFileSize, maxParallelSearches, maxRetries, debug, errCh)
|
||||
for id := range objIDs {
|
||||
oidCh <- id
|
||||
}
|
||||
close(oidCh)
|
||||
wg.Wait()
|
||||
}()
|
||||
|
||||
select {
|
||||
case err := <-errCh:
|
||||
return existingIndexCount, nil, err
|
||||
case <-doneCh:
|
||||
return existingIndexCount, buf, nil
|
||||
}
|
||||
}
|
||||
|
||||
// searchObjects searches in parallel for objects with attribute GE startIndex and LT
|
||||
// endIndex. It returns a buffered channel of resulting object IDs and closes it once
|
||||
// OID search is finished. Errors are sent to errCh in a non-blocking way.
|
||||
func searchObjects(ctx context.Context, p neofs.PoolWrapper, containerID cid.ID, privKeys *keys.PrivateKey, blockAttributeKey string, startIndex, endIndex, maxParallelSearches, maxRetries uint, debug bool, errCh chan error, additionalFilters ...object.SearchFilters) chan oid.ID {
|
||||
var res = make(chan oid.ID, 2*neofs.DefaultSearchBatchSize)
|
||||
go func() {
|
||||
var wg sync.WaitGroup
|
||||
defer close(res)
|
||||
|
||||
for i := startIndex; i < endIndex; i += neofs.DefaultSearchBatchSize * maxParallelSearches {
|
||||
for j := range maxParallelSearches {
|
||||
start := i + j*neofs.DefaultSearchBatchSize
|
||||
end := start + neofs.DefaultSearchBatchSize
|
||||
|
||||
if start >= endIndex {
|
||||
break
|
||||
}
|
||||
if end > endIndex {
|
||||
end = endIndex
|
||||
}
|
||||
|
||||
wg.Add(1)
|
||||
go func(start, end uint) {
|
||||
defer wg.Done()
|
||||
|
||||
prm := client.PrmObjectSearch{}
|
||||
filters := object.NewSearchFilters()
|
||||
if len(additionalFilters) != 0 {
|
||||
filters = additionalFilters[0]
|
||||
}
|
||||
if end == start+1 {
|
||||
filters.AddFilter(blockAttributeKey, fmt.Sprintf("%d", start), object.MatchStringEqual)
|
||||
} else {
|
||||
filters.AddFilter(blockAttributeKey, fmt.Sprintf("%d", start), object.MatchNumGE)
|
||||
filters.AddFilter(blockAttributeKey, fmt.Sprintf("%d", end), object.MatchNumLT)
|
||||
}
|
||||
prm.SetFilters(filters)
|
||||
|
||||
var objIDs []oid.ID
|
||||
err := retry(func() error {
|
||||
var errBlockSearch error
|
||||
objIDs, errBlockSearch = neofs.ObjectSearch(ctx, p, privKeys, containerID.String(), prm)
|
||||
return errBlockSearch
|
||||
}, maxRetries, debug)
|
||||
if err != nil {
|
||||
select {
|
||||
case errCh <- fmt.Errorf("failed to search for block(s) from %d to %d: %w", start, end, err):
|
||||
default:
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
for _, id := range objIDs {
|
||||
res <- id
|
||||
}
|
||||
}(start, end)
|
||||
}
|
||||
wg.Wait()
|
||||
}
|
||||
}()
|
||||
|
||||
return res
|
||||
}
|
||||
|
||||
// uploadObj uploads object to the container using provided settings.
|
||||
func uploadObj(ctx context.Context, p neofs.PoolWrapper, signer user.Signer, containerID cid.ID, objData []byte, attrs []object.Attribute) (oid.ID, error) {
|
||||
var (
|
||||
hdr object.Object
|
||||
prmObjectPutInit client.PrmObjectPutInit
|
||||
resOID = oid.ID{}
|
||||
)
|
||||
|
||||
hdr.SetContainerID(containerID)
|
||||
hdr.SetOwner(signer.UserID())
|
||||
hdr.SetAttributes(attrs...)
|
||||
|
||||
writer, err := p.ObjectPutInit(ctx, hdr, signer, prmObjectPutInit)
|
||||
if err != nil {
|
||||
return resOID, fmt.Errorf("failed to initiate object upload: %w", err)
|
||||
}
|
||||
_, err = writer.Write(objData)
|
||||
if err != nil {
|
||||
_ = writer.Close()
|
||||
return resOID, fmt.Errorf("failed to write object data: %w", err)
|
||||
}
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
return resOID, fmt.Errorf("failed to close object writer: %w", err)
|
||||
}
|
||||
res := writer.GetResult()
|
||||
resOID = res.StoredObjectID()
|
||||
return resOID, nil
|
||||
}
|
||||
|
||||
func getBlockIndex(header object.Object, attribute string) (int, error) {
|
||||
for _, attr := range header.UserAttributes() {
|
||||
if attr.Key() == attribute {
|
||||
value := attr.Value()
|
||||
blockIndex, err := strconv.Atoi(value)
|
||||
if err != nil {
|
||||
return -1, fmt.Errorf("attribute %s has invalid value: %s, error: %w", attribute, value, err)
|
||||
}
|
||||
return blockIndex, nil
|
||||
}
|
||||
}
|
||||
return -1, fmt.Errorf("attribute %s not found", attribute)
|
||||
}
|
||||
|
||||
// getContainer gets container by ID and checks its magic.
|
||||
func getContainer(ctx *cli.Context, p neofs.PoolWrapper, expectedMagic string, maxRetries uint, debug bool) (cid.ID, error) {
|
||||
var (
|
||||
containerObj container.Container
|
||||
err error
|
||||
containerIDStr = ctx.String("container")
|
||||
)
|
||||
var containerID cid.ID
|
||||
if err = containerID.DecodeString(containerIDStr); err != nil {
|
||||
return containerID, fmt.Errorf("failed to decode container ID: %w", err)
|
||||
}
|
||||
err = retry(func() error {
|
||||
containerObj, err = p.ContainerGet(ctx.Context, containerID, client.PrmContainerGet{})
|
||||
return err
|
||||
}, maxRetries, debug)
|
||||
if err != nil {
|
||||
return containerID, fmt.Errorf("failed to get container: %w", err)
|
||||
}
|
||||
containerMagic := containerObj.Attribute("Magic")
|
||||
if containerMagic != expectedMagic {
|
||||
return containerID, fmt.Errorf("container magic mismatch: expected %s, got %s", expectedMagic, containerMagic)
|
||||
}
|
||||
return containerID, nil
|
||||
}
|
206
cli/util/upload_state.go
Normal file
206
cli/util/upload_state.go
Normal file
|
@ -0,0 +1,206 @@
|
|||
package util
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"time"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||
"github.com/nspcc-dev/neo-go/cli/options"
|
||||
"github.com/nspcc-dev/neo-go/cli/server"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/mpt"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
gio "github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/services/helpers/neofs"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/client"
|
||||
cid "github.com/nspcc-dev/neofs-sdk-go/container/id"
|
||||
"github.com/nspcc-dev/neofs-sdk-go/object"
|
||||
oid "github.com/nspcc-dev/neofs-sdk-go/object/id"
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.uber.org/zap"
|
||||
)
|
||||
|
||||
func uploadState(ctx *cli.Context) error {
|
||||
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
cfg, err := options.GetConfigFromContext(ctx)
|
||||
if err != nil {
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
attr := ctx.String("state-attribute")
|
||||
maxRetries := ctx.Uint("retries")
|
||||
debug := ctx.Bool("debug")
|
||||
|
||||
acc, _, err := options.GetAccFromContext(ctx)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("failed to load account: %v", err), 1)
|
||||
}
|
||||
|
||||
signer, p, err := options.GetNeoFSClientPool(ctx, acc)
|
||||
if err != nil {
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer p.Close()
|
||||
log, _, logCloser, err := options.HandleLoggingParams(debug, cfg.ApplicationConfiguration)
|
||||
if err != nil {
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
if logCloser != nil {
|
||||
defer func() { _ = logCloser() }()
|
||||
}
|
||||
|
||||
chain, store, prometheus, pprof, err := server.InitBCWithMetrics(cfg, log)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
pprof.ShutDown()
|
||||
prometheus.ShutDown()
|
||||
chain.Close()
|
||||
}()
|
||||
|
||||
if chain.GetConfig().Ledger.KeepOnlyLatestState || chain.GetConfig().Ledger.RemoveUntraceableBlocks {
|
||||
return cli.Exit("only full-state node is supported: disable KeepOnlyLatestState and RemoveUntraceableBlocks", 1)
|
||||
}
|
||||
syncInterval := cfg.ProtocolConfiguration.StateSyncInterval
|
||||
if syncInterval == 0 {
|
||||
syncInterval = core.DefaultStateSyncInterval
|
||||
}
|
||||
|
||||
containerID, err := getContainer(ctx, p, strconv.Itoa(int(chain.GetConfig().Magic)), maxRetries, debug)
|
||||
if err != nil {
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
stateObjCount, err := searchStateIndex(ctx, p, containerID, acc.PrivateKey(), attr, syncInterval, maxRetries, debug)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("failed searching existing states: %v", err), 1)
|
||||
}
|
||||
stateModule := chain.GetStateModule()
|
||||
currentHeight := int(stateModule.CurrentLocalHeight())
|
||||
currentStateIndex := currentHeight / syncInterval
|
||||
if currentStateIndex <= stateObjCount {
|
||||
log.Info("no new states to upload",
|
||||
zap.Int("number of uploaded state objects", stateObjCount),
|
||||
zap.Int("latest state is uploaded for block", stateObjCount*syncInterval),
|
||||
zap.Int("current height", currentHeight),
|
||||
zap.Int("StateSyncInterval", syncInterval))
|
||||
return nil
|
||||
}
|
||||
log.Info("starting uploading",
|
||||
zap.Int("number of uploaded state objects", stateObjCount),
|
||||
zap.Int("next state to upload for block", stateObjCount*syncInterval),
|
||||
zap.Int("current height", currentHeight),
|
||||
zap.Int("StateSyncInterval", syncInterval),
|
||||
zap.Int("number of states to upload", currentStateIndex-stateObjCount))
|
||||
for state := stateObjCount; state < currentStateIndex; state++ {
|
||||
height := uint32(state * syncInterval)
|
||||
stateRoot, err := stateModule.GetStateRoot(height)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("failed to get state root for height %d: %v", height, err), 1)
|
||||
}
|
||||
h, err := chain.GetHeader(chain.GetHeaderHash(height))
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("failed to get header %d: %v", height, err), 1)
|
||||
}
|
||||
|
||||
var (
|
||||
hdr object.Object
|
||||
prmObjectPutInit client.PrmObjectPutInit
|
||||
attrs = []object.Attribute{
|
||||
*object.NewAttribute(attr, strconv.Itoa(int(height))),
|
||||
*object.NewAttribute("Timestamp", strconv.FormatInt(time.Now().Unix(), 10)),
|
||||
*object.NewAttribute("StateRoot", stateRoot.Root.StringLE()),
|
||||
*object.NewAttribute("StateSyncInterval", strconv.Itoa(syncInterval)),
|
||||
*object.NewAttribute("BlockTime", strconv.FormatUint(h.Timestamp, 10)),
|
||||
}
|
||||
)
|
||||
hdr.SetContainerID(containerID)
|
||||
hdr.SetOwner(signer.UserID())
|
||||
hdr.SetAttributes(attrs...)
|
||||
err = retry(func() error {
|
||||
writer, err := p.ObjectPutInit(ctx.Context, hdr, signer, prmObjectPutInit)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
start := time.Now()
|
||||
wrt := gio.NewBinWriterFromIO(writer)
|
||||
wrt.WriteB(byte(0))
|
||||
wrt.WriteU32LE(uint32(chain.GetConfig().Magic))
|
||||
wrt.WriteU32LE(height)
|
||||
wrt.WriteBytes(stateRoot.Root[:])
|
||||
err = traverseMPT(stateRoot.Root, store, wrt)
|
||||
if err != nil {
|
||||
_ = writer.Close()
|
||||
return err
|
||||
}
|
||||
err = writer.Close()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
duration := time.Since(start)
|
||||
res := writer.GetResult()
|
||||
log.Info("uploaded state object",
|
||||
zap.String("object ID", res.StoredObjectID().String()),
|
||||
zap.Uint32("height", height),
|
||||
zap.Duration("time spent", duration))
|
||||
return nil
|
||||
}, maxRetries, debug)
|
||||
if err != nil {
|
||||
return cli.Exit(fmt.Sprintf("failed to upload object at height %d: %v", height, err), 1)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func searchStateIndex(ctx *cli.Context, p neofs.PoolWrapper, containerID cid.ID, privKeys *keys.PrivateKey,
|
||||
attributeKey string, syncInterval int, maxRetries uint, debug bool,
|
||||
) (int, error) {
|
||||
var (
|
||||
doneCh = make(chan struct{})
|
||||
errCh = make(chan error)
|
||||
objCount = 0
|
||||
)
|
||||
|
||||
go func() {
|
||||
defer close(doneCh)
|
||||
for i := 0; ; i++ {
|
||||
indexIDs := searchObjects(ctx.Context, p, containerID, privKeys,
|
||||
attributeKey, uint(i*syncInterval), uint(i*syncInterval)+1, 1, maxRetries, debug, errCh)
|
||||
resOIDs := make([]oid.ID, 0, 1)
|
||||
for id := range indexIDs {
|
||||
resOIDs = append(resOIDs, id)
|
||||
}
|
||||
if len(resOIDs) == 0 {
|
||||
break
|
||||
}
|
||||
if len(resOIDs) > 1 {
|
||||
fmt.Fprintf(ctx.App.Writer, "WARN: %d duplicated state objects with %s: %d found: %s\n", len(resOIDs), attributeKey, i, resOIDs)
|
||||
}
|
||||
objCount++
|
||||
}
|
||||
}()
|
||||
select {
|
||||
case err := <-errCh:
|
||||
return objCount, err
|
||||
case <-doneCh:
|
||||
return objCount, nil
|
||||
}
|
||||
}
|
||||
|
||||
func traverseMPT(root util.Uint256, store storage.Store, writer *gio.BinWriter) error {
|
||||
cache := storage.NewMemCachedStore(store)
|
||||
billet := mpt.NewBillet(root, mpt.ModeAll, mpt.DummySTTempStoragePrefix, cache)
|
||||
err := billet.Traverse(func(pathToNode []byte, node mpt.Node, nodeBytes []byte) bool {
|
||||
writer.WriteVarBytes(nodeBytes)
|
||||
return writer.Err != nil
|
||||
}, false)
|
||||
if err != nil {
|
||||
return fmt.Errorf("billet traversal error: %w", err)
|
||||
}
|
||||
return nil
|
||||
}
|
|
@ -190,3 +190,21 @@ func TestAwaitUtilCancelTx(t *testing.T) {
|
|||
t.Fatal(fmt.Errorf("unexpected error: %w", err))
|
||||
}
|
||||
}
|
||||
|
||||
func TestUploadBin(t *testing.T) {
|
||||
e := testcli.NewExecutor(t, true)
|
||||
args := []string{
|
||||
"neo-go", "util", "upload-bin",
|
||||
"--cid", "test",
|
||||
"--wallet", "./not-exist.json",
|
||||
"--block-attribute", "block",
|
||||
"--index-attribute", "oid-index",
|
||||
"--fsr", "st1.local.fs.neo.org:8080",
|
||||
}
|
||||
e.In.WriteString("one\r")
|
||||
e.RunWithErrorCheckExit(t, "failed to load account", append(args, "--cid", "test", "--wallet", "./not-exist.json", "--rpc-endpoint", "https://test")...)
|
||||
e.In.WriteString("one\r")
|
||||
e.RunWithErrorCheckExit(t, "failed to create RPC client", append(args, "--cid", "9iVfUg8aDHKjPC4LhQXEkVUM4HDkR7UCXYLs8NQwYfSG", "--wallet", testcli.ValidatorWallet, "--rpc-endpoint", "https://test")...)
|
||||
e.In.WriteString("one\r")
|
||||
e.RunWithErrorCheckExit(t, "failed to dial NeoFS pool", append(args, "--cid", "9iVfUg8aDHKjPC4LhQXEkVUM4HDkR7UCXYLs8NQwYfSG", "--wallet", testcli.ValidatorWallet, "--rpc-endpoint", "http://"+e.RPC.Addresses()[0])...)
|
||||
}
|
||||
|
|
139
cli/vm/cli.go
139
cli/vm/cli.go
|
@ -12,6 +12,7 @@ import (
|
|||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/tabwriter"
|
||||
|
@ -41,10 +42,9 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util/slice"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zapcore"
|
||||
)
|
||||
|
@ -70,22 +70,22 @@ const (
|
|||
)
|
||||
|
||||
var (
|
||||
historicFlag = cli.IntFlag{
|
||||
historicFlag = &cli.IntFlag{
|
||||
Name: historicFlagFullName,
|
||||
Usage: "Height for historic script invocation (for MPT-enabled blockchain configuration with KeepOnlyLatestState setting disabled). " +
|
||||
"Assuming that block N-th is specified as an argument, the historic invocation is based on the storage state of height N and fake currently-accepting block with index N+1.",
|
||||
}
|
||||
gasFlag = cli.Int64Flag{
|
||||
gasFlag = &cli.Int64Flag{
|
||||
Name: gasFlagFullName,
|
||||
Usage: "GAS limit for this execution (integer number, satoshi).",
|
||||
}
|
||||
hashFlag = cli.StringFlag{
|
||||
hashFlag = &flags.AddressFlag{
|
||||
Name: hashFlagFullName,
|
||||
Usage: "Smart-contract hash in LE form or address",
|
||||
}
|
||||
)
|
||||
|
||||
var commands = []cli.Command{
|
||||
var commands = []*cli.Command{
|
||||
{
|
||||
Name: "exit",
|
||||
Usage: "Exit the VM prompt",
|
||||
|
@ -110,6 +110,26 @@ Example:
|
|||
> break 12`,
|
||||
Action: handleBreak,
|
||||
},
|
||||
{
|
||||
Name: "delete",
|
||||
Usage: "Remove a breakpoint",
|
||||
UsageText: `delete <ip>`,
|
||||
Description: `<ip> is mandatory parameter.
|
||||
|
||||
Example:
|
||||
> delete 12`,
|
||||
Action: handleRemoveBreak,
|
||||
},
|
||||
{
|
||||
Name: "ib",
|
||||
Usage: "List breakpoints",
|
||||
UsageText: `ib`,
|
||||
Description: `List breakpoints.
|
||||
|
||||
Example:
|
||||
> ib`,
|
||||
Action: handleListBreak,
|
||||
},
|
||||
{
|
||||
Name: "jump",
|
||||
Usage: "Jump to the specified instruction (absolute IP value)",
|
||||
|
@ -339,9 +359,10 @@ Example:
|
|||
Usage: "Dump state of the chain that is used for VM CLI invocations (use -v for verbose node configuration)",
|
||||
UsageText: `env [-v]`,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: verboseFlagFullName + ",v",
|
||||
Usage: "Print the whole blockchain node configuration.",
|
||||
&cli.BoolFlag{
|
||||
Name: verboseFlagFullName,
|
||||
Aliases: []string{"v"},
|
||||
Usage: "Print the whole blockchain node configuration.",
|
||||
},
|
||||
},
|
||||
Description: `Dump state of the chain that is used for VM CLI invocations (use -v for verbose node configuration).
|
||||
|
@ -353,15 +374,17 @@ Example:
|
|||
{
|
||||
Name: "storage",
|
||||
Usage: "Dump storage of the contract with the specified hash, address or ID as is at the current stage of script invocation",
|
||||
UsageText: `storage <hash-or-address-or-id> [<prefix>] [--backwards] [--diff]`,
|
||||
UsageText: `storage [--backwards] [--diff] <hash-or-address-or-id> [<prefix>]`,
|
||||
Flags: []cli.Flag{
|
||||
cli.BoolFlag{
|
||||
Name: backwardsFlagFullName + ",b",
|
||||
Usage: "Backwards traversal direction",
|
||||
&cli.BoolFlag{
|
||||
Name: backwardsFlagFullName,
|
||||
Aliases: []string{"b"},
|
||||
Usage: "Backwards traversal direction",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: diffFlagFullName + ",d",
|
||||
Usage: "Dump only those storage items that were added or changed during the current script invocation. Note that this call won't show removed storage items, use 'changes' command for that.",
|
||||
&cli.BoolFlag{
|
||||
Name: diffFlagFullName,
|
||||
Aliases: []string{"d"},
|
||||
Usage: "Dump only those storage items that were added or changed during the current script invocation. Note that this call won't show removed storage items, use 'changes' command for that.",
|
||||
},
|
||||
},
|
||||
Description: `Dump storage of the contract with the specified hash, address or ID as is at the current stage of script invocation.
|
||||
|
@ -400,7 +423,7 @@ func init() {
|
|||
if !c.Hidden {
|
||||
var flagsItems []readline.PrefixCompleterInterface
|
||||
for _, f := range c.Flags {
|
||||
names := strings.SplitN(f.GetName(), ", ", 2) // only long name will be offered
|
||||
names := strings.SplitN(f.Names()[0], ", ", 2) // only long name will be offered
|
||||
flagsItems = append(flagsItems, readline.PcItem("--"+names[0]))
|
||||
}
|
||||
pcItems = append(pcItems, readline.PcItem(c.Name, flagsItems...))
|
||||
|
@ -459,7 +482,7 @@ func NewWithConfig(printLogotype bool, onExit func(int), c *readline.Config, cfg
|
|||
|
||||
log, _, logCloser, err := options.HandleLoggingParams(false, cfg.ApplicationConfiguration)
|
||||
if err != nil {
|
||||
return nil, cli.NewExitError(fmt.Errorf("failed to init logger: %w", err), 1)
|
||||
return nil, cli.Exit(fmt.Errorf("failed to init logger: %w", err), 1)
|
||||
}
|
||||
filter := zap.WrapCore(func(z zapcore.Core) zapcore.Core {
|
||||
return options.NewFilteringCore(z, func(entry zapcore.Entry) bool {
|
||||
|
@ -479,12 +502,12 @@ func NewWithConfig(printLogotype bool, onExit func(int), c *readline.Config, cfg
|
|||
|
||||
chain, err := core.NewBlockchain(store, cfg.Blockchain(), fLog)
|
||||
if err != nil {
|
||||
return nil, cli.NewExitError(fmt.Errorf("could not initialize blockchain: %w", err), 1)
|
||||
return nil, cli.Exit(fmt.Errorf("could not initialize blockchain: %w", err), 1)
|
||||
}
|
||||
// Do not run chain, we need only state-related functionality from it.
|
||||
ic, err := chain.GetTestVM(trigger.Application, nil, nil)
|
||||
if err != nil {
|
||||
return nil, cli.NewExitError(fmt.Errorf("failed to create test VM: %w", err), 1)
|
||||
return nil, cli.Exit(fmt.Errorf("failed to create test VM: %w", err), 1)
|
||||
}
|
||||
|
||||
vmcli := CLI{
|
||||
|
@ -594,6 +617,33 @@ func handleBreak(c *cli.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func handleRemoveBreak(c *cli.Context) error {
|
||||
if !checkVMIsReady(c.App) {
|
||||
return nil
|
||||
}
|
||||
n, err := getInstructionParameter(c)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
v := getVMFromContext(c.App)
|
||||
v.RemoveBreakPoint(n)
|
||||
fmt.Fprintf(c.App.Writer, "breakpoint removed at instruction %d\n", n)
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleListBreak(c *cli.Context) error {
|
||||
if !checkVMIsReady(c.App) {
|
||||
return nil
|
||||
}
|
||||
|
||||
v := getVMFromContext(c.App)
|
||||
for _, bp := range v.Context().BreakPoints() {
|
||||
fmt.Fprintf(c.App.Writer, "%d\n", bp)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func handleJump(c *cli.Context) error {
|
||||
if !checkVMIsReady(c.App) {
|
||||
return nil
|
||||
|
@ -610,7 +660,7 @@ func handleJump(c *cli.Context) error {
|
|||
}
|
||||
|
||||
func getInstructionParameter(c *cli.Context) (int, error) {
|
||||
args := c.Args()
|
||||
args := c.Args().Slice()
|
||||
if len(args) != 1 {
|
||||
return 0, fmt.Errorf("%w: <ip>", ErrMissingParameter)
|
||||
}
|
||||
|
@ -645,11 +695,11 @@ func handleSlots(c *cli.Context) error {
|
|||
var rawSlot string
|
||||
switch c.Command.Name {
|
||||
case "sslot":
|
||||
rawSlot = vmCtx.DumpStaticSlot()
|
||||
rawSlot = dumpSlot(vmCtx.StaticsSlot())
|
||||
case "lslot":
|
||||
rawSlot = vmCtx.DumpLocalSlot()
|
||||
rawSlot = dumpSlot(vmCtx.LocalsSlot())
|
||||
case "aslot":
|
||||
rawSlot = vmCtx.DumpArgumentsSlot()
|
||||
rawSlot = dumpSlot(vmCtx.ArgumentsSlot())
|
||||
default:
|
||||
return errors.New("unknown slot")
|
||||
}
|
||||
|
@ -657,6 +707,14 @@ func handleSlots(c *cli.Context) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func dumpSlot(s *vm.Slot) string {
|
||||
if s == nil {
|
||||
return "[]"
|
||||
}
|
||||
b, _ := json.MarshalIndent(s, "", " ")
|
||||
return string(b)
|
||||
}
|
||||
|
||||
// prepareVM retrieves --historic flag from context (if set) and resets app state
|
||||
// (to the specified historic height if given).
|
||||
func prepareVM(c *cli.Context, tx *transaction.Transaction) error {
|
||||
|
@ -682,15 +740,12 @@ func getHashFlag(c *cli.Context) (util.Uint160, error) {
|
|||
if !c.IsSet(hashFlagFullName) {
|
||||
return util.Uint160{}, nil
|
||||
}
|
||||
h, err := flags.ParseAddress(c.String(hashFlagFullName))
|
||||
if err != nil {
|
||||
return util.Uint160{}, fmt.Errorf("failed to parse contract hash: %w", err)
|
||||
}
|
||||
return h, nil
|
||||
h := c.Generic(hashFlagFullName).(*flags.Address)
|
||||
return h.Uint160(), nil
|
||||
}
|
||||
|
||||
func handleLoadNEF(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
args := c.Args().Slice()
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("%w: <nef> is required", ErrMissingParameter)
|
||||
}
|
||||
|
@ -734,7 +789,7 @@ func handleLoadNEF(c *cli.Context) error {
|
|||
}
|
||||
var signers []transaction.Signer
|
||||
if signersStartOffset != 0 && len(args) > signersStartOffset {
|
||||
signers, err = cmdargs.ParseSigners(c.Args()[signersStartOffset:])
|
||||
signers, err = cmdargs.ParseSigners(args[signersStartOffset:])
|
||||
if err != nil {
|
||||
return fmt.Errorf("%w: failed to parse signers: %w", ErrInvalidParameter, err)
|
||||
}
|
||||
|
@ -761,7 +816,7 @@ func handleLoadNEF(c *cli.Context) error {
|
|||
}
|
||||
|
||||
func handleLoadBase64(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
args := c.Args().Slice()
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("%w: <string>", ErrMissingParameter)
|
||||
}
|
||||
|
@ -801,7 +856,7 @@ func createFakeTransaction(script []byte, signers []transaction.Signer) *transac
|
|||
}
|
||||
|
||||
func handleLoadHex(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
args := c.Args().Slice()
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("%w: <string>", ErrMissingParameter)
|
||||
}
|
||||
|
@ -833,7 +888,7 @@ func handleLoadHex(c *cli.Context) error {
|
|||
}
|
||||
|
||||
func handleLoadGo(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
args := c.Args().Slice()
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("%w: <file>", ErrMissingParameter)
|
||||
}
|
||||
|
@ -885,7 +940,7 @@ func handleLoadGo(c *cli.Context) error {
|
|||
}
|
||||
|
||||
func handleLoadTx(c *cli.Context) error {
|
||||
args := c.Args()
|
||||
args := c.Args().Slice()
|
||||
if len(args) < 1 {
|
||||
return fmt.Errorf("%w: <file-or-hash>", ErrMissingParameter)
|
||||
}
|
||||
|
@ -933,7 +988,7 @@ func handleLoadDeployed(c *cli.Context) error {
|
|||
if !c.Args().Present() {
|
||||
return errors.New("contract hash, address or ID is mandatory argument")
|
||||
}
|
||||
args := c.Args()
|
||||
args := c.Args().Slice()
|
||||
hashOrID := args[0]
|
||||
ic := getInteropContextFromContext(c.App)
|
||||
h, err := flags.ParseAddress(hashOrID)
|
||||
|
@ -1062,7 +1117,7 @@ func getManifestFromFile(name string) (*manifest.Manifest, error) {
|
|||
func handleRun(c *cli.Context) error {
|
||||
v := getVMFromContext(c.App)
|
||||
cs := getContractStateFromContext(c.App)
|
||||
args := c.Args()
|
||||
args := c.Args().Slice()
|
||||
if len(args) != 0 {
|
||||
var (
|
||||
params []stackitem.Item
|
||||
|
@ -1105,7 +1160,7 @@ func handleRun(c *cli.Context) error {
|
|||
breaks := v.Context().BreakPoints() // We ensure that there's a context loaded.
|
||||
ic.ReuseVM(v)
|
||||
v.GasLimit = gasLimit
|
||||
v.LoadNEFMethod(&cs.NEF, util.Uint160{}, cs.Hash, callflag.All, hasRet, offset, initOff, nil)
|
||||
v.LoadNEFMethod(&cs.NEF, &cs.Manifest, util.Uint160{}, cs.Hash, callflag.All, hasRet, offset, initOff, nil)
|
||||
for _, bp := range breaks {
|
||||
v.AddBreakPoint(bp)
|
||||
}
|
||||
|
@ -1181,7 +1236,7 @@ func handleStep(c *cli.Context) error {
|
|||
return nil
|
||||
}
|
||||
v := getVMFromContext(c.App)
|
||||
args := c.Args()
|
||||
args := c.Args().Slice()
|
||||
if len(args) > 0 {
|
||||
n, err = strconv.Atoi(args[0])
|
||||
if err != nil {
|
||||
|
@ -1422,7 +1477,7 @@ func (c *CLI) Run() error {
|
|||
}
|
||||
|
||||
func handleParse(c *cli.Context) error {
|
||||
res, err := Parse(c.Args())
|
||||
res, err := Parse(c.Args().Slice())
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -1456,7 +1511,9 @@ func Parse(args []string) (string, error) {
|
|||
}
|
||||
buf = fmt.Appendf(buf, "Hex to String\t%s\n", fmt.Sprintf("%q", string(rawStr)))
|
||||
buf = fmt.Appendf(buf, "Hex to Integer\t%s\n", bigint.FromBytes(rawStr))
|
||||
buf = fmt.Appendf(buf, "Swap Endianness\t%s\n", hex.EncodeToString(slice.CopyReverse(rawStr)))
|
||||
var clonedStr = slices.Clone(rawStr)
|
||||
slices.Reverse(clonedStr)
|
||||
buf = fmt.Appendf(buf, "Swap Endianness\t%s\n", hex.EncodeToString(clonedStr))
|
||||
}
|
||||
if addr, err := address.StringToUint160(arg); err == nil {
|
||||
buf = fmt.Appendf(buf, "Address to BE ScriptHash\t%s\n", addr)
|
||||
|
|
|
@ -339,6 +339,31 @@ func TestRun_WithNewVMContextAndBreakpoints(t *testing.T) {
|
|||
e.checkNextLine(t, "at breakpoint 10 (ADD)*")
|
||||
e.checkStack(t, 13)
|
||||
})
|
||||
t.Run("contract breakpoints", func(t *testing.T) {
|
||||
src := `package kek
|
||||
func Main(a, b int) int {
|
||||
var c = a + b
|
||||
return c + 5
|
||||
}`
|
||||
tmpDir := t.TempDir()
|
||||
filename := prepareLoadgoSrc(t, tmpDir, src)
|
||||
|
||||
e := newTestVMCLI(t)
|
||||
e.runProgWithTimeout(t, 10*time.Second,
|
||||
"loadgo "+filename,
|
||||
"break 7",
|
||||
"break 8",
|
||||
"ib",
|
||||
"delete 7",
|
||||
)
|
||||
|
||||
e.checkNextLine(t, "READY: loaded \\d* instructions")
|
||||
e.checkNextLine(t, "breakpoint added at instruction 7")
|
||||
e.checkNextLine(t, "breakpoint added at instruction 8")
|
||||
e.checkNextLine(t, "7")
|
||||
e.checkNextLine(t, "8")
|
||||
e.checkNextLine(t, "breakpoint removed at instruction 7")
|
||||
})
|
||||
}
|
||||
|
||||
// prepareLoadgoSrc prepares provided SC source file for loading into VM via `loadgo` command.
|
||||
|
@ -353,7 +378,7 @@ require (
|
|||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0
|
||||
)
|
||||
replace github.com/nspcc-dev/neo-go/pkg/interop => ` + filepath.Join(wd, "../../pkg/interop") + `
|
||||
go 1.20`)
|
||||
go 1.22`)
|
||||
require.NoError(t, os.WriteFile(filepath.Join(tmpDir, "go.mod"), goMod, os.ModePerm))
|
||||
return filename
|
||||
}
|
||||
|
@ -1188,7 +1213,7 @@ func TestDumpStorage(t *testing.T) {
|
|||
"storage "+address.Uint160ToString(h),
|
||||
"storage 1",
|
||||
"storage 1 "+hex.EncodeToString(expected[0].Key),
|
||||
"storage 1 --backwards",
|
||||
"storage --backwards 1",
|
||||
"exit",
|
||||
)
|
||||
e.checkStorage(t, expected...)
|
||||
|
@ -1214,11 +1239,11 @@ func TestDumpStorageDiff(t *testing.T) {
|
|||
diff := storage.KeyValue{Key: []byte{3}, Value: []byte{3}}
|
||||
e.runProg(t,
|
||||
"storage 1",
|
||||
"storage 1 --diff",
|
||||
"storage --diff 1",
|
||||
"loadhex "+hex.EncodeToString(script.Bytes()),
|
||||
"run",
|
||||
"storage 1",
|
||||
"storage 1 --diff",
|
||||
"storage --diff 1",
|
||||
"exit",
|
||||
)
|
||||
|
||||
|
|
12
cli/vm/vm.go
12
cli/vm/vm.go
|
@ -8,16 +8,16 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||
"github.com/nspcc-dev/neo-go/cli/options"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/storage/dbconfig"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// NewCommands returns 'vm' command.
|
||||
func NewCommands() []cli.Command {
|
||||
func NewCommands() []*cli.Command {
|
||||
cfgFlags := []cli.Flag{options.Config, options.ConfigFile, options.RelativePath}
|
||||
cfgFlags = append(cfgFlags, options.Network...)
|
||||
return []cli.Command{{
|
||||
return []*cli.Command{{
|
||||
Name: "vm",
|
||||
Usage: "start the virtual machine",
|
||||
Usage: "Start the virtual machine",
|
||||
Action: startVMPrompt,
|
||||
Flags: cfgFlags,
|
||||
}}
|
||||
|
@ -30,7 +30,7 @@ func startVMPrompt(ctx *cli.Context) error {
|
|||
|
||||
cfg, err := options.GetConfigFromContext(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
if ctx.NumFlags() == 0 {
|
||||
cfg.ApplicationConfiguration.DBConfiguration.Type = dbconfig.InMemoryDB
|
||||
|
@ -42,7 +42,7 @@ func startVMPrompt(ctx *cli.Context) error {
|
|||
|
||||
p, err := NewWithConfig(true, os.Exit, &readline.Config{}, cfg)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to create VM CLI: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to create VM CLI: %w", err), 1)
|
||||
}
|
||||
return p.Run()
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@ func TestRegisterCandidate(t *testing.T) {
|
|||
e.CheckEOF(t)
|
||||
|
||||
// missing address
|
||||
e.RunWithError(t, "neo-go", "wallet", "candidate", "register",
|
||||
e.RunWithErrorCheck(t, `Required flag "address" not set`, "neo-go", "wallet", "candidate", "register",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||
"--wallet", testcli.ValidatorWallet)
|
||||
|
||||
|
@ -131,7 +131,7 @@ func TestRegisterCandidate(t *testing.T) {
|
|||
})
|
||||
|
||||
// missing address
|
||||
e.RunWithError(t, "neo-go", "wallet", "candidate", "unregister",
|
||||
e.RunWithErrorCheck(t, `Required flag "address" not set`, "neo-go", "wallet", "candidate", "unregister",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||
"--wallet", testcli.ValidatorWallet)
|
||||
// additional argument
|
||||
|
@ -153,7 +153,7 @@ func TestRegisterCandidate(t *testing.T) {
|
|||
require.Equal(t, 0, len(vs))
|
||||
|
||||
// query voter: missing address
|
||||
e.RunWithError(t, "neo-go", "query", "voter")
|
||||
e.RunWithError(t, "neo-go", "query", "voter", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0])
|
||||
// Excessive parameters.
|
||||
e.RunWithError(t, "neo-go", "query", "voter", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], validatorAddress, validatorAddress)
|
||||
e.RunWithError(t, "neo-go", "query", "committee", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0], "something")
|
||||
|
|
|
@ -12,7 +12,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/waiter"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func signStoredTransaction(ctx *cli.Context) error {
|
||||
|
@ -28,52 +28,52 @@ func signStoredTransaction(ctx *cli.Context) error {
|
|||
|
||||
pc, err := paramcontext.Read(ctx.String("in"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
if !addrFlag.IsSet {
|
||||
return cli.NewExitError("address was not provided", 1)
|
||||
return cli.Exit("address was not provided", 1)
|
||||
}
|
||||
acc, _, err := options.GetAccFromContext(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
tx, ok := pc.Verifiable.(*transaction.Transaction)
|
||||
if !ok {
|
||||
return cli.NewExitError("verifiable item is not a transaction", 1)
|
||||
return cli.Exit("verifiable item is not a transaction", 1)
|
||||
}
|
||||
|
||||
if !tx.HasSigner(acc.ScriptHash()) {
|
||||
return cli.NewExitError("tx signers don't contain provided account", 1)
|
||||
return cli.Exit("tx signers don't contain provided account", 1)
|
||||
}
|
||||
|
||||
if acc.CanSign() {
|
||||
sign := acc.SignHashable(pc.Network, pc.Verifiable)
|
||||
if err := pc.AddSignature(acc.ScriptHash(), acc.Contract, acc.PublicKey(), sign); err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't add signature: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't add signature: %w", err), 1)
|
||||
}
|
||||
} else if rpcNode == "" {
|
||||
return cli.NewExitError(fmt.Errorf("can't sign transactions with the given account and no RPC endpoing given to send anything signed"), 1)
|
||||
return cli.Exit(fmt.Errorf("can't sign transactions with the given account and no RPC endpoing given to send anything signed"), 1)
|
||||
}
|
||||
// Not saving and not sending, print.
|
||||
if out == "" && rpcNode == "" {
|
||||
txt, err := json.MarshalIndent(pc, " ", " ")
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't display resulting context: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't display resulting context: %w", err), 1)
|
||||
}
|
||||
fmt.Fprintln(ctx.App.Writer, string(txt))
|
||||
return nil
|
||||
}
|
||||
if out != "" {
|
||||
if err := paramcontext.Save(pc, out); err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't save resulting context: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't save resulting context: %w", err), 1)
|
||||
}
|
||||
}
|
||||
if rpcNode != "" {
|
||||
tx, err = pc.GetCompleteTransaction()
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to complete transaction: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to complete transaction: %w", err), 1)
|
||||
}
|
||||
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
|
@ -82,17 +82,17 @@ func signStoredTransaction(ctx *cli.Context) error {
|
|||
var err error // `GetRPCClient` returns specialized type.
|
||||
c, err := options.GetRPCClient(gctx, ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to create RPC client: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to create RPC client: %w", err), 1)
|
||||
}
|
||||
res, err := c.SendRawTransaction(tx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to submit transaction to RPC node: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to submit transaction to RPC node: %w", err), 1)
|
||||
}
|
||||
if ctx.Bool("await") {
|
||||
version, err := c.GetVersion()
|
||||
aer, err = waiter.New(c, version).Wait(res, tx.ValidUntilBlock, err)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to await transaction %s: %w", res.StringLE(), err), 1)
|
||||
return cli.Exit(fmt.Errorf("failed to await transaction %s: %w", res.StringLE(), err), 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -79,10 +79,10 @@ func TestSignMultisigTx(t *testing.T) {
|
|||
"--out", txPath)
|
||||
|
||||
// missing wallet
|
||||
e.RunWithError(t, "neo-go", "wallet", "sign")
|
||||
e.RunWithErrorCheck(t, `Required flag "in" not set`, "neo-go", "wallet", "sign")
|
||||
|
||||
// missing in
|
||||
e.RunWithError(t, "neo-go", "wallet", "sign",
|
||||
e.RunWithErrorCheck(t, `Required flag "in" not set`, "neo-go", "wallet", "sign",
|
||||
"--wallet", wallet2Path)
|
||||
|
||||
// missing address
|
||||
|
|
|
@ -4,6 +4,7 @@ import (
|
|||
"encoding/hex"
|
||||
"errors"
|
||||
"fmt"
|
||||
"slices"
|
||||
"strconv"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||
|
@ -19,39 +20,41 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func newNEP11Commands() []cli.Command {
|
||||
func newNEP11Commands() []*cli.Command {
|
||||
maxIters := strconv.Itoa(config.DefaultMaxIteratorResultItems)
|
||||
tokenAddressFlag := flags.AddressFlag{
|
||||
Name: "token",
|
||||
Usage: "Token contract address or hash in LE",
|
||||
tokenAddressFlag := &flags.AddressFlag{
|
||||
Name: "token",
|
||||
Usage: "Token contract address or hash in LE",
|
||||
Required: true,
|
||||
}
|
||||
ownerAddressFlag := flags.AddressFlag{
|
||||
Name: "address",
|
||||
Usage: "NFT owner address or hash in LE",
|
||||
ownerAddressFlag := &flags.AddressFlag{
|
||||
Name: "address",
|
||||
Usage: "NFT owner address or hash in LE",
|
||||
Required: true,
|
||||
}
|
||||
tokenID := cli.StringFlag{
|
||||
tokenID := &cli.StringFlag{
|
||||
Name: "id",
|
||||
Usage: "Hex-encoded token ID",
|
||||
}
|
||||
|
||||
balanceFlags := make([]cli.Flag, len(baseBalanceFlags))
|
||||
copy(balanceFlags, baseBalanceFlags)
|
||||
balanceFlags := slices.Clone(baseBalanceFlags)
|
||||
balanceFlags = append(balanceFlags, tokenID)
|
||||
balanceFlags = append(balanceFlags, options.RPC...)
|
||||
transferFlags := make([]cli.Flag, len(baseTransferFlags))
|
||||
copy(transferFlags, baseTransferFlags)
|
||||
|
||||
transferFlags := slices.Clone(baseTransferFlags)
|
||||
transferFlags = append(transferFlags, tokenID)
|
||||
transferFlags = append(transferFlags, options.RPC...)
|
||||
return []cli.Command{
|
||||
return []*cli.Command{
|
||||
{
|
||||
Name: "balance",
|
||||
Usage: "get address balance",
|
||||
UsageText: "balance -w wallet [--wallet-config path] --rpc-endpoint <node> [--timeout <time>] [--address <address>] [--token <hash-or-name>] [--id <token-id>]",
|
||||
Description: `Prints NEP-11 balances for address and assets/IDs specified. By default (no
|
||||
address or token parameter) all tokens (NFT contracts) for all accounts in
|
||||
Usage: "Get address balance",
|
||||
UsageText: "balance [-w wallet] [--wallet-config path] --rpc-endpoint <node> [--timeout <time>] [--address <address>] [--token <hash-or-name>] [--id <token-id>]",
|
||||
Description: `Prints NEP-11 balances for address and assets/IDs specified. One of wallet
|
||||
or address must be specified, passing both is valid too. If a wallet is
|
||||
given without an address all tokens (NFT contracts) for all accounts in
|
||||
the specified wallet are listed with all tokens (actual NFTs) insied. A
|
||||
single account can be chosen with the address option and/or a single NFT
|
||||
contract can be selected with the token option. Further, you can specify a
|
||||
|
@ -70,14 +73,14 @@ func newNEP11Commands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "import",
|
||||
Usage: "import NEP-11 token to a wallet",
|
||||
UsageText: "import -w wallet [--wallet-config path] --rpc-endpoint <node> --timeout <time> --token <hash>",
|
||||
Usage: "Import NEP-11 token to a wallet",
|
||||
UsageText: "import -w wallet [--wallet-config path] --rpc-endpoint <node> [--timeout <time>] --token <hash>",
|
||||
Action: importNEP11Token,
|
||||
Flags: importFlags,
|
||||
},
|
||||
{
|
||||
Name: "info",
|
||||
Usage: "print imported NEP-11 token info",
|
||||
Usage: "Print imported NEP-11 token info",
|
||||
UsageText: "print -w wallet [--wallet-config path] [--token <hash-or-name>]",
|
||||
Action: printNEP11Info,
|
||||
Flags: []cli.Flag{
|
||||
|
@ -88,7 +91,7 @@ func newNEP11Commands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Usage: "remove NEP-11 token from the wallet",
|
||||
Usage: "Remove NEP-11 token from the wallet",
|
||||
UsageText: "remove -w wallet [--wallet-config path] --token <hash-or-name>",
|
||||
Action: removeNEP11Token,
|
||||
Flags: []cli.Flag{
|
||||
|
@ -100,8 +103,8 @@ func newNEP11Commands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "transfer",
|
||||
Usage: "transfer NEP-11 tokens",
|
||||
UsageText: "transfer -w wallet [--wallet-config path] --rpc-endpoint <node> --timeout <time> --from <addr> --to <addr> --token <hash-or-name> --id <token-id> [--amount string] [--await] [data] [-- <cosigner1:Scope> [<cosigner2> [...]]]",
|
||||
Usage: "Transfer NEP-11 tokens",
|
||||
UsageText: "transfer -w wallet [--wallet-config path] --rpc-endpoint <node> [--timeout <time>] --from <addr> --to <addr> --token <hash-or-name> --id <token-id> [--amount string] [--await] [data] [-- <cosigner1:Scope> [<cosigner2> [...]]]",
|
||||
Action: transferNEP11,
|
||||
Flags: transferFlags,
|
||||
Description: `Transfers specified NEP-11 token with optional cosigners list attached to
|
||||
|
@ -116,7 +119,7 @@ func newNEP11Commands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "properties",
|
||||
Usage: "print properties of NEP-11 token",
|
||||
Usage: "Print properties of NEP-11 token",
|
||||
UsageText: "properties --rpc-endpoint <node> [--timeout <time>] --token <hash> --id <token-id> [--historic <block/hash>]",
|
||||
Action: printNEP11Properties,
|
||||
Flags: append([]cli.Flag{
|
||||
|
@ -127,7 +130,7 @@ func newNEP11Commands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "ownerOf",
|
||||
Usage: "print owner of non-divisible NEP-11 token with the specified ID",
|
||||
Usage: "Print owner of non-divisible NEP-11 token with the specified ID",
|
||||
UsageText: "ownerOf --rpc-endpoint <node> [--timeout <time>] --token <hash> --id <token-id> [--historic <block/hash>]",
|
||||
Action: printNEP11NDOwner,
|
||||
Flags: append([]cli.Flag{
|
||||
|
@ -138,7 +141,7 @@ func newNEP11Commands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "ownerOfD",
|
||||
Usage: "print set of owners of divisible NEP-11 token with the specified ID (" + maxIters + " will be printed at max)",
|
||||
Usage: "Print set of owners of divisible NEP-11 token with the specified ID (" + maxIters + " will be printed at max)",
|
||||
UsageText: "ownerOfD --rpc-endpoint <node> [--timeout <time>] --token <hash> --id <token-id> [--historic <block/hash>]",
|
||||
Action: printNEP11DOwner,
|
||||
Flags: append([]cli.Flag{
|
||||
|
@ -149,7 +152,7 @@ func newNEP11Commands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "tokensOf",
|
||||
Usage: "print list of tokens IDs for the specified NFT owner (" + maxIters + " will be printed at max)",
|
||||
Usage: "Print list of tokens IDs for the specified NFT owner (" + maxIters + " will be printed at max)",
|
||||
UsageText: "tokensOf --rpc-endpoint <node> [--timeout <time>] --token <hash> --address <addr> [--historic <block/hash>]",
|
||||
Action: printNEP11TokensOf,
|
||||
Flags: append([]cli.Flag{
|
||||
|
@ -160,7 +163,7 @@ func newNEP11Commands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "tokens",
|
||||
Usage: "print list of tokens IDs minted by the specified NFT (optional method; " + maxIters + " will be printed at max)",
|
||||
Usage: "Print list of tokens IDs minted by the specified NFT (optional method; " + maxIters + " will be printed at max)",
|
||||
UsageText: "tokens --rpc-endpoint <node> [--timeout <time>] --token <hash> [--historic <block/hash>]",
|
||||
Action: printNEP11Tokens,
|
||||
Flags: append([]cli.Flag{
|
||||
|
@ -246,17 +249,13 @@ func printNEP11Owner(ctx *cli.Context, divisible bool) error {
|
|||
return err
|
||||
}
|
||||
tokenHash := ctx.Generic("token").(*flags.Address)
|
||||
if !tokenHash.IsSet {
|
||||
return cli.NewExitError("token contract hash was not set", 1)
|
||||
}
|
||||
|
||||
tokenID := ctx.String("id")
|
||||
if tokenID == "" {
|
||||
return cli.NewExitError(errors.New("token ID should be specified"), 1)
|
||||
return cli.Exit(errors.New("token ID should be specified"), 1)
|
||||
}
|
||||
tokenIDBytes, err := hex.DecodeString(tokenID)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid tokenID bytes: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("invalid tokenID bytes: %w", err), 1)
|
||||
}
|
||||
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
|
@ -271,7 +270,7 @@ func printNEP11Owner(ctx *cli.Context, divisible bool) error {
|
|||
n11 := nep11.NewDivisibleReader(inv, tokenHash.Uint160())
|
||||
result, err := n11.OwnerOfExpanded(tokenIDBytes, config.DefaultMaxIteratorResultItems)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 divisible `ownerOf` method: %s", err.Error()), 1)
|
||||
return cli.Exit(fmt.Sprintf("failed to call NEP-11 divisible `ownerOf` method: %s", err.Error()), 1)
|
||||
}
|
||||
for _, h := range result {
|
||||
fmt.Fprintln(ctx.App.Writer, address.Uint160ToString(h))
|
||||
|
@ -280,7 +279,7 @@ func printNEP11Owner(ctx *cli.Context, divisible bool) error {
|
|||
n11 := nep11.NewNonDivisibleReader(inv, tokenHash.Uint160())
|
||||
result, err := n11.OwnerOf(tokenIDBytes)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 non-divisible `ownerOf` method: %s", err.Error()), 1)
|
||||
return cli.Exit(fmt.Sprintf("failed to call NEP-11 non-divisible `ownerOf` method: %s", err.Error()), 1)
|
||||
}
|
||||
fmt.Fprintln(ctx.App.Writer, address.Uint160ToString(result))
|
||||
}
|
||||
|
@ -291,15 +290,7 @@ func printNEP11Owner(ctx *cli.Context, divisible bool) error {
|
|||
func printNEP11TokensOf(ctx *cli.Context) error {
|
||||
var err error
|
||||
tokenHash := ctx.Generic("token").(*flags.Address)
|
||||
if !tokenHash.IsSet {
|
||||
return cli.NewExitError("token contract hash was not set", 1)
|
||||
}
|
||||
|
||||
acc := ctx.Generic("address").(*flags.Address)
|
||||
if !acc.IsSet {
|
||||
return cli.NewExitError("owner address flag was not set", 1)
|
||||
}
|
||||
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
defer cancel()
|
||||
|
||||
|
@ -311,7 +302,7 @@ func printNEP11TokensOf(ctx *cli.Context) error {
|
|||
n11 := nep11.NewBaseReader(inv, tokenHash.Uint160())
|
||||
result, err := n11.TokensOfExpanded(acc.Uint160(), config.DefaultMaxIteratorResultItems)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 `tokensOf` method: %s", err.Error()), 1)
|
||||
return cli.Exit(fmt.Sprintf("failed to call NEP-11 `tokensOf` method: %s", err.Error()), 1)
|
||||
}
|
||||
|
||||
for i := range result {
|
||||
|
@ -326,10 +317,6 @@ func printNEP11Tokens(ctx *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
tokenHash := ctx.Generic("token").(*flags.Address)
|
||||
if !tokenHash.IsSet {
|
||||
return cli.NewExitError("token contract hash was not set", 1)
|
||||
}
|
||||
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
defer cancel()
|
||||
|
||||
|
@ -341,7 +328,7 @@ func printNEP11Tokens(ctx *cli.Context) error {
|
|||
n11 := nep11.NewBaseReader(inv, tokenHash.Uint160())
|
||||
result, err := n11.TokensExpanded(config.DefaultMaxIteratorResultItems)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("failed to call optional NEP-11 `tokens` method: %s", err.Error()), 1)
|
||||
return cli.Exit(fmt.Sprintf("failed to call optional NEP-11 `tokens` method: %s", err.Error()), 1)
|
||||
}
|
||||
|
||||
for i := range result {
|
||||
|
@ -356,17 +343,13 @@ func printNEP11Properties(ctx *cli.Context) error {
|
|||
return err
|
||||
}
|
||||
tokenHash := ctx.Generic("token").(*flags.Address)
|
||||
if !tokenHash.IsSet {
|
||||
return cli.NewExitError("token contract hash was not set", 1)
|
||||
}
|
||||
|
||||
tokenID := ctx.String("id")
|
||||
if tokenID == "" {
|
||||
return cli.NewExitError(errors.New("token ID should be specified"), 1)
|
||||
return cli.Exit(errors.New("token ID should be specified"), 1)
|
||||
}
|
||||
tokenIDBytes, err := hex.DecodeString(tokenID)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid tokenID bytes: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("invalid tokenID bytes: %w", err), 1)
|
||||
}
|
||||
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
|
@ -380,12 +363,12 @@ func printNEP11Properties(ctx *cli.Context) error {
|
|||
n11 := nep11.NewBaseReader(inv, tokenHash.Uint160())
|
||||
result, err := n11.Properties(tokenIDBytes)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("failed to call NEP-11 `properties` method: %s", err.Error()), 1)
|
||||
return cli.Exit(fmt.Sprintf("failed to call NEP-11 `properties` method: %s", err.Error()), 1)
|
||||
}
|
||||
|
||||
bytes, err := stackitem.ToJSON(result)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Sprintf("failed to convert result to JSON: %s", err), 1)
|
||||
return cli.Exit(fmt.Sprintf("failed to convert result to JSON: %s", err), 1)
|
||||
}
|
||||
fmt.Fprintln(ctx.App.Writer, string(bytes))
|
||||
return nil
|
||||
|
|
|
@ -5,6 +5,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"math/big"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||
|
@ -27,7 +28,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
// transferTarget represents target address, token amount and data for transfer.
|
||||
|
@ -39,7 +40,7 @@ type transferTarget struct {
|
|||
}
|
||||
|
||||
var (
|
||||
tokenFlag = cli.StringFlag{
|
||||
tokenFlag = &cli.StringFlag{
|
||||
Name: "token",
|
||||
Usage: "Token to use (hash or name (for NEO/GAS or imported tokens))",
|
||||
}
|
||||
|
@ -47,17 +48,19 @@ var (
|
|||
walletPathFlag,
|
||||
walletConfigFlag,
|
||||
tokenFlag,
|
||||
flags.AddressFlag{
|
||||
Name: "address, a",
|
||||
Usage: "Address to use",
|
||||
&flags.AddressFlag{
|
||||
Name: "address",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "Address to use",
|
||||
},
|
||||
}
|
||||
importFlags = append([]cli.Flag{
|
||||
walletPathFlag,
|
||||
walletConfigFlag,
|
||||
flags.AddressFlag{
|
||||
Name: "token",
|
||||
Usage: "Token contract address or hash in LE",
|
||||
&flags.AddressFlag{
|
||||
Name: "token",
|
||||
Usage: "Token contract address or hash in LE",
|
||||
Required: true,
|
||||
},
|
||||
}, options.RPC...)
|
||||
baseTransferFlags = []cli.Flag{
|
||||
|
@ -71,7 +74,7 @@ var (
|
|||
txctx.SysGasFlag,
|
||||
txctx.ForceFlag,
|
||||
txctx.AwaitFlag,
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "amount",
|
||||
Usage: "Amount of asset to send",
|
||||
},
|
||||
|
@ -88,21 +91,21 @@ var (
|
|||
}, options.RPC...)
|
||||
)
|
||||
|
||||
func newNEP17Commands() []cli.Command {
|
||||
balanceFlags := make([]cli.Flag, len(baseBalanceFlags))
|
||||
copy(balanceFlags, baseBalanceFlags)
|
||||
func newNEP17Commands() []*cli.Command {
|
||||
balanceFlags := slices.Clone(baseBalanceFlags)
|
||||
balanceFlags = append(balanceFlags, options.RPC...)
|
||||
transferFlags := make([]cli.Flag, len(baseTransferFlags))
|
||||
copy(transferFlags, baseTransferFlags)
|
||||
|
||||
transferFlags := slices.Clone(baseTransferFlags)
|
||||
transferFlags = append(transferFlags, options.RPC...)
|
||||
return []cli.Command{
|
||||
return []*cli.Command{
|
||||
{
|
||||
Name: "balance",
|
||||
Usage: "get address balance",
|
||||
UsageText: "balance -w wallet [--wallet-config path] --rpc-endpoint <node> [--timeout <time>] [--address <address>] [--token <hash-or-name>]",
|
||||
Description: `Prints NEP-17 balances for address and tokens specified. By default (no
|
||||
address or token parameter) all tokens for all accounts in the specified wallet
|
||||
are listed. A single account can be chosen with the address option and/or a
|
||||
Usage: "Get address balance",
|
||||
UsageText: "balance [-w wallet] [--wallet-config path] --rpc-endpoint <node> [--timeout <time>] [--address <address>] [--token <hash-or-name>]",
|
||||
Description: `Prints NEP-17 balances for address and tokens specified. One of wallet
|
||||
or address must be specified, passing both is valid too. If a wallet is
|
||||
given without an address all tokens for all accounts in this wallet are
|
||||
listed. A single account can be chosen with the address option and/or a
|
||||
single token can be selected with the token option. Tokens can be specified
|
||||
by hash, address, name or symbol. Hashes and addresses always work (as long
|
||||
as they belong to a correct NEP-17 contract), while names or symbols (if
|
||||
|
@ -118,14 +121,14 @@ func newNEP17Commands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "import",
|
||||
Usage: "import NEP-17 token to a wallet",
|
||||
UsageText: "import -w wallet [--wallet-config path] --rpc-endpoint <node> --timeout <time> --token <hash>",
|
||||
Usage: "Import NEP-17 token to a wallet",
|
||||
UsageText: "import -w wallet [--wallet-config path] --rpc-endpoint <node> [--timeout <time>] --token <hash>",
|
||||
Action: importNEP17Token,
|
||||
Flags: importFlags,
|
||||
},
|
||||
{
|
||||
Name: "info",
|
||||
Usage: "print imported NEP-17 token info",
|
||||
Usage: "Print imported NEP-17 token info",
|
||||
UsageText: "print -w wallet [--wallet-config path] [--token <hash-or-name>]",
|
||||
Action: printNEP17Info,
|
||||
Flags: []cli.Flag{
|
||||
|
@ -136,7 +139,7 @@ func newNEP17Commands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Usage: "remove NEP-17 token from the wallet",
|
||||
Usage: "Remove NEP-17 token from the wallet",
|
||||
UsageText: "remove -w wallet [--wallet-config path] --token <hash-or-name>",
|
||||
Action: removeNEP17Token,
|
||||
Flags: []cli.Flag{
|
||||
|
@ -148,8 +151,8 @@ func newNEP17Commands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "transfer",
|
||||
Usage: "transfer NEP-17 tokens",
|
||||
UsageText: "transfer -w wallet [--wallet-config path] [--await] --rpc-endpoint <node> --timeout <time> --from <addr> --to <addr> --token <hash-or-name> --amount string [data] [-- <cosigner1:Scope> [<cosigner2> [...]]]",
|
||||
Usage: "Transfer NEP-17 tokens",
|
||||
UsageText: "transfer -w wallet [--wallet-config path] [--await] --rpc-endpoint <node> [--timeout <time>] --from <addr> --to <addr> --token <hash-or-name> --amount string [data] [-- <cosigner1:Scope> [<cosigner2> [...]]]",
|
||||
Action: transferNEP17,
|
||||
Flags: transferFlags,
|
||||
Description: `Transfers specified NEP-17 token amount with optional 'data' parameter and cosigners
|
||||
|
@ -163,7 +166,7 @@ func newNEP17Commands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "multitransfer",
|
||||
Usage: "transfer NEP-17 tokens to multiple recipients",
|
||||
Usage: "Transfer NEP-17 tokens to multiple recipients",
|
||||
UsageText: `multitransfer -w wallet [--wallet-config path] [--await] --rpc-endpoint <node> --timeout <time> --from <addr>` +
|
||||
` <token1>:<addr1>:<amount1> [<token2>:<addr2>:<amount2> [...]] [-- <cosigner1:Scope> [<cosigner2> [...]]]`,
|
||||
Action: multiTransferNEP17,
|
||||
|
@ -215,30 +218,40 @@ func getNEP17Balance(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
func getNEPBalance(ctx *cli.Context, standard string, accHandler func(*cli.Context, *rpcclient.Client, util.Uint160, string, *wallet.Token, string) error) error {
|
||||
var accounts []*wallet.Account
|
||||
var addresses []util.Uint160
|
||||
|
||||
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||
return err
|
||||
}
|
||||
wall, _, err := readWallet(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("bad wallet: %w", err), 1)
|
||||
if !errors.Is(err, errNoPath) {
|
||||
return cli.Exit(fmt.Errorf("bad wallet: %w", err), 1)
|
||||
}
|
||||
} else {
|
||||
defer wall.Close()
|
||||
}
|
||||
defer wall.Close()
|
||||
|
||||
addrFlag := ctx.Generic("address").(*flags.Address)
|
||||
if addrFlag.IsSet {
|
||||
addrHash := addrFlag.Uint160()
|
||||
acc := wall.GetAccount(addrHash)
|
||||
if acc == nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't find account for the address: %s", address.Uint160ToString(addrHash)), 1)
|
||||
if wall != nil {
|
||||
acc := wall.GetAccount(addrHash)
|
||||
if acc == nil {
|
||||
return cli.Exit(fmt.Errorf("can't find account for the address: %s", address.Uint160ToString(addrHash)), 1)
|
||||
}
|
||||
}
|
||||
accounts = append(accounts, acc)
|
||||
addresses = append(addresses, addrHash)
|
||||
} else {
|
||||
if len(wall.Accounts) == 0 {
|
||||
return cli.NewExitError(errors.New("no accounts in the wallet"), 1)
|
||||
if wall == nil {
|
||||
return cli.Exit(errors.New("neither wallet nor address specified"), 1)
|
||||
}
|
||||
if len(wall.Accounts) == 0 {
|
||||
return cli.Exit(errors.New("no accounts in the wallet"), 1)
|
||||
}
|
||||
for _, acc := range wall.Accounts {
|
||||
addresses = append(addresses, acc.ScriptHash())
|
||||
}
|
||||
accounts = wall.Accounts
|
||||
}
|
||||
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
|
@ -246,7 +259,7 @@ func getNEPBalance(ctx *cli.Context, standard string, accHandler func(*cli.Conte
|
|||
|
||||
c, err := options.GetRPCClient(gctx, ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
name := ctx.String("token")
|
||||
|
@ -274,7 +287,7 @@ func getNEPBalance(ctx *cli.Context, standard string, accHandler func(*cli.Conte
|
|||
// But if we have an exact hash, it must be correct.
|
||||
token, err = getTokenWithStandard(c, h, standard)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("%q is not a valid %s token: %w", name, standard, err), 1)
|
||||
return cli.Exit(fmt.Errorf("%q is not a valid %s token: %w", name, standard, err), 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -284,19 +297,19 @@ func getNEPBalance(ctx *cli.Context, standard string, accHandler func(*cli.Conte
|
|||
if len(tokenID) > 0 {
|
||||
_, err = hex.DecodeString(tokenID)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid token ID: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("invalid token ID: %w", err), 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
for k, acc := range accounts {
|
||||
for k, addr := range addresses {
|
||||
if k != 0 {
|
||||
fmt.Fprintln(ctx.App.Writer)
|
||||
}
|
||||
fmt.Fprintf(ctx.App.Writer, "Account %s\n", acc.Address)
|
||||
fmt.Fprintf(ctx.App.Writer, "Account %s\n", address.Uint160ToString(addr))
|
||||
|
||||
err = accHandler(ctx, c, acc.ScriptHash(), name, token, tokenID)
|
||||
err = accHandler(ctx, c, addr, name, token, tokenID)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -319,6 +332,9 @@ func printAssetBalance(ctx *cli.Context, balance result.NEP17Balance) {
|
|||
}
|
||||
|
||||
func getMatchingToken(ctx *cli.Context, w *wallet.Wallet, name string, standard string) (*wallet.Token, error) {
|
||||
if w == nil {
|
||||
return getMatchingTokenAux(ctx, nil, 0, name, standard)
|
||||
}
|
||||
return getMatchingTokenAux(ctx, func(i int) *wallet.Token {
|
||||
return w.Extra.Tokens[i]
|
||||
}, len(w.Extra.Tokens), name, standard)
|
||||
|
@ -360,7 +376,7 @@ func getMatchingTokenRPC(ctx *cli.Context, c *rpcclient.Client, addr util.Uint16
|
|||
func getMatchingTokenAux(ctx *cli.Context, get func(i int) *wallet.Token, n int, name string, standard string) (*wallet.Token, error) {
|
||||
var token *wallet.Token
|
||||
var count int
|
||||
for i := 0; i < n; i++ {
|
||||
for i := range n {
|
||||
t := get(i)
|
||||
if t != nil && (t.Hash.StringLE() == name || t.Address() == name || t.Symbol == name || t.Name == name) && t.Standard == standard {
|
||||
if count == 1 {
|
||||
|
@ -388,20 +404,17 @@ func importNEPToken(ctx *cli.Context, standard string) error {
|
|||
}
|
||||
wall, _, err := openWallet(ctx, true)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
|
||||
tokenHashFlag := ctx.Generic("token").(*flags.Address)
|
||||
if !tokenHashFlag.IsSet {
|
||||
return cli.NewExitError("token contract hash was not set", 1)
|
||||
}
|
||||
tokenHash := tokenHashFlag.Uint160()
|
||||
|
||||
for _, t := range wall.Extra.Tokens {
|
||||
if t.Hash.Equals(tokenHash) && t.Standard == standard {
|
||||
printTokenInfo(ctx, t)
|
||||
return cli.NewExitError(fmt.Errorf("%s token already exists", standard), 1)
|
||||
return cli.Exit(fmt.Errorf("%s token already exists", standard), 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -410,17 +423,17 @@ func importNEPToken(ctx *cli.Context, standard string) error {
|
|||
|
||||
c, err := options.GetRPCClient(gctx, ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
tok, err := getTokenWithStandard(c, tokenHash, standard)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't receive token info: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't receive token info: %w", err), 1)
|
||||
}
|
||||
|
||||
wall.AddToken(tok)
|
||||
if err := wall.Save(); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
printTokenInfo(ctx, tok)
|
||||
return nil
|
||||
|
@ -457,14 +470,14 @@ func printNEPInfo(ctx *cli.Context, standard string) error {
|
|||
}
|
||||
wall, _, err := readWallet(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
|
||||
if name := ctx.String("token"); name != "" {
|
||||
token, err := getMatchingToken(ctx, wall, name, standard)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
printTokenInfo(ctx, token)
|
||||
return nil
|
||||
|
@ -493,13 +506,13 @@ func removeNEPToken(ctx *cli.Context, standard string) error {
|
|||
}
|
||||
wall, _, err := openWallet(ctx, true)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
|
||||
token, err := getMatchingToken(ctx, wall, ctx.String("token"), standard)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
if !ctx.Bool("force") {
|
||||
if ok := askForConsent(ctx.App.Writer); !ok {
|
||||
|
@ -507,9 +520,9 @@ func removeNEPToken(ctx *cli.Context, standard string) error {
|
|||
}
|
||||
}
|
||||
if err := wall.RemoveToken(token.Hash); err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't remove token: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't remove token: %w", err), 1)
|
||||
} else if err := wall.Save(); err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("error while saving wallet: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("error while saving wallet: %w", err), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -517,31 +530,31 @@ func removeNEPToken(ctx *cli.Context, standard string) error {
|
|||
func multiTransferNEP17(ctx *cli.Context) error {
|
||||
wall, pass, err := readWallet(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
|
||||
fromFlag := ctx.Generic("from").(*flags.Address)
|
||||
from, err := getDefaultAddress(fromFlag, wall)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
acc, err := options.GetUnlockedAccount(wall, from, pass)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
defer cancel()
|
||||
|
||||
if ctx.NArg() == 0 {
|
||||
return cli.NewExitError("empty recipients list", 1)
|
||||
return cli.Exit("empty recipients list", 1)
|
||||
}
|
||||
var (
|
||||
recipients []transferTarget
|
||||
cosignersSepPos = ctx.NArg() // `--` position.
|
||||
)
|
||||
for i := 0; i < ctx.NArg(); i++ {
|
||||
for i := range ctx.NArg() {
|
||||
arg := ctx.Args().Get(i)
|
||||
if arg == cmdargs.CosignersSeparator {
|
||||
cosignersSepPos = i
|
||||
|
@ -554,7 +567,7 @@ func multiTransferNEP17(ctx *cli.Context) error {
|
|||
}
|
||||
signersAccounts, err := cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.CalledByEntry)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("invalid signers: %w", err), 1)
|
||||
}
|
||||
c, act, exitErr := options.GetRPCWithActor(gctx, ctx, signersAccounts)
|
||||
if exitErr != nil {
|
||||
|
@ -562,11 +575,11 @@ func multiTransferNEP17(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
cache := make(map[string]*wallet.Token)
|
||||
for i := 0; i < cosignersSepPos; i++ {
|
||||
for i := range cosignersSepPos {
|
||||
arg := ctx.Args().Get(i)
|
||||
ss := strings.SplitN(arg, ":", 3)
|
||||
if len(ss) != 3 {
|
||||
return cli.NewExitError("send format must be '<token>:<addr>:<amount>", 1)
|
||||
return cli.Exit("send format must be '<token>:<addr>:<amount>", 1)
|
||||
}
|
||||
token, ok := cache[ss[0]]
|
||||
if !ok {
|
||||
|
@ -574,18 +587,18 @@ func multiTransferNEP17(ctx *cli.Context) error {
|
|||
if err != nil {
|
||||
token, err = getMatchingTokenRPC(ctx, c, from, ss[0], manifest.NEP17StandardName)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't fetch matching token from RPC-node: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't fetch matching token from RPC-node: %w", err), 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
cache[ss[0]] = token
|
||||
addr, err := address.StringToUint160(ss[1])
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid address: '%s'", ss[1]), 1)
|
||||
return cli.Exit(fmt.Errorf("invalid address: '%s'", ss[1]), 1)
|
||||
}
|
||||
amount, err := fixedn.FromString(ss[2], int(token.Decimals))
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid amount: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("invalid amount: %w", err), 1)
|
||||
}
|
||||
recipients = append(recipients, transferTarget{
|
||||
Token: token.Hash,
|
||||
|
@ -597,7 +610,7 @@ func multiTransferNEP17(ctx *cli.Context) error {
|
|||
|
||||
tx, err := makeMultiTransferNEP17(act, recipients)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't make transaction: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't make transaction: %w", err), 1)
|
||||
}
|
||||
return txctx.SignAndSend(ctx, act, acc, tx)
|
||||
}
|
||||
|
@ -611,18 +624,18 @@ func transferNEP(ctx *cli.Context, standard string) error {
|
|||
|
||||
wall, pass, err := readWallet(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
|
||||
fromFlag := ctx.Generic("from").(*flags.Address)
|
||||
from, err := getDefaultAddress(fromFlag, wall)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
acc, err := options.GetUnlockedAccount(wall, from, pass)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
|
@ -638,7 +651,7 @@ func transferNEP(ctx *cli.Context, standard string) error {
|
|||
}
|
||||
signersAccounts, err := cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.CalledByEntry)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("invalid signers: %w", err), 1)
|
||||
}
|
||||
|
||||
c, act, exitErr := options.GetRPCWithActor(gctx, ctx, signersAccounts)
|
||||
|
@ -647,15 +660,12 @@ func transferNEP(ctx *cli.Context, standard string) error {
|
|||
}
|
||||
|
||||
toFlag := ctx.Generic("to").(*flags.Address)
|
||||
if !toFlag.IsSet {
|
||||
return cli.NewExitError(errors.New("missing receiver address (--to)"), 1)
|
||||
}
|
||||
to := toFlag.Uint160()
|
||||
token, err := getMatchingToken(ctx, wall, ctx.String("token"), standard)
|
||||
if err != nil {
|
||||
token, err = getMatchingTokenRPC(ctx, c, from, ctx.String("token"), standard)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't fetch matching token from RPC-node: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't fetch matching token from RPC-node: %w", err), 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -663,7 +673,7 @@ func transferNEP(ctx *cli.Context, standard string) error {
|
|||
amount, err := fixedn.FromString(amountArg, int(token.Decimals))
|
||||
// It's OK for NEP-11 transfer to not have amount set.
|
||||
if err != nil && (standard == manifest.NEP17StandardName || amountArg != "") {
|
||||
return cli.NewExitError(fmt.Errorf("invalid amount: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("invalid amount: %w", err), 1)
|
||||
}
|
||||
switch standard {
|
||||
case manifest.NEP17StandardName:
|
||||
|
@ -672,11 +682,11 @@ func transferNEP(ctx *cli.Context, standard string) error {
|
|||
case manifest.NEP11StandardName:
|
||||
tokenID := ctx.String("id")
|
||||
if tokenID == "" {
|
||||
return cli.NewExitError(errors.New("token ID should be specified"), 1)
|
||||
return cli.Exit(errors.New("token ID should be specified"), 1)
|
||||
}
|
||||
tokenIDBytes, terr := hex.DecodeString(tokenID)
|
||||
if terr != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid token ID: %w", terr), 1)
|
||||
return cli.Exit(fmt.Errorf("invalid token ID: %w", terr), 1)
|
||||
}
|
||||
if amountArg == "" {
|
||||
n11 := nep11.NewNonDivisible(act, token.Hash)
|
||||
|
@ -686,10 +696,10 @@ func transferNEP(ctx *cli.Context, standard string) error {
|
|||
tx, err = n11.TransferDUnsigned(act.Sender(), to, amount, tokenIDBytes, data)
|
||||
}
|
||||
default:
|
||||
return cli.NewExitError(fmt.Errorf("unsupported token standard %s", standard), 1)
|
||||
return cli.Exit(fmt.Errorf("unsupported token standard %s", standard), 1)
|
||||
}
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't make transaction: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't make transaction: %w", err), 1)
|
||||
}
|
||||
|
||||
return txctx.SignAndSend(ctx, act, acc, tx)
|
||||
|
|
|
@ -12,15 +12,15 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
func newValidatorCommands() []cli.Command {
|
||||
return []cli.Command{
|
||||
func newValidatorCommands() []*cli.Command {
|
||||
return []*cli.Command{
|
||||
{
|
||||
Name: "register",
|
||||
Usage: "register as a new candidate",
|
||||
UsageText: "register -w <path> -r <rpc> -a <addr> [-g gas] [-e sysgas] [--out file] [--force] [--await]",
|
||||
Usage: "Register as a new candidate",
|
||||
UsageText: "register -w <path> -r <rpc> [-s timeout] -a <addr> [-g gas] [-e sysgas] [--out file] [--force] [--await]",
|
||||
Action: handleRegister,
|
||||
Flags: append([]cli.Flag{
|
||||
walletPathFlag,
|
||||
|
@ -30,16 +30,18 @@ func newValidatorCommands() []cli.Command {
|
|||
txctx.OutFlag,
|
||||
txctx.ForceFlag,
|
||||
txctx.AwaitFlag,
|
||||
flags.AddressFlag{
|
||||
Name: "address, a",
|
||||
Usage: "Address to register",
|
||||
&flags.AddressFlag{
|
||||
Name: "address",
|
||||
Aliases: []string{"a"},
|
||||
Required: true,
|
||||
Usage: "Address to register",
|
||||
},
|
||||
}, options.RPC...),
|
||||
},
|
||||
{
|
||||
Name: "unregister",
|
||||
Usage: "unregister self as a candidate",
|
||||
UsageText: "unregister -w <path> -r <rpc> -a <addr> [-g gas] [-e sysgas] [--out file] [--force] [--await]",
|
||||
Usage: "Unregister self as a candidate",
|
||||
UsageText: "unregister -w <path> -r <rpc> [-s timeout] -a <addr> [-g gas] [-e sysgas] [--out file] [--force] [--await]",
|
||||
Action: handleUnregister,
|
||||
Flags: append([]cli.Flag{
|
||||
walletPathFlag,
|
||||
|
@ -49,15 +51,17 @@ func newValidatorCommands() []cli.Command {
|
|||
txctx.OutFlag,
|
||||
txctx.ForceFlag,
|
||||
txctx.AwaitFlag,
|
||||
flags.AddressFlag{
|
||||
Name: "address, a",
|
||||
Usage: "Address to unregister",
|
||||
&flags.AddressFlag{
|
||||
Name: "address",
|
||||
Required: true,
|
||||
Aliases: []string{"a"},
|
||||
Usage: "Address to unregister",
|
||||
},
|
||||
}, options.RPC...),
|
||||
},
|
||||
{
|
||||
Name: "vote",
|
||||
Usage: "vote for a validator",
|
||||
Usage: "Vote for a validator",
|
||||
UsageText: "vote -w <path> -r <rpc> [-s <timeout>] [-g gas] [-e sysgas] -a <addr> [-c <public key>] [--out file] [--force] [--await]",
|
||||
Description: `Votes for a validator by calling "vote" method of a NEO native
|
||||
contract. Do not provide candidate argument to perform unvoting. If --await flag is
|
||||
|
@ -72,13 +76,16 @@ func newValidatorCommands() []cli.Command {
|
|||
txctx.OutFlag,
|
||||
txctx.ForceFlag,
|
||||
txctx.AwaitFlag,
|
||||
flags.AddressFlag{
|
||||
Name: "address, a",
|
||||
Usage: "Address to vote from",
|
||||
&flags.AddressFlag{
|
||||
Name: "address",
|
||||
Required: true,
|
||||
Aliases: []string{"a"},
|
||||
Usage: "Address to vote from",
|
||||
},
|
||||
cli.StringFlag{
|
||||
Name: "candidate, c",
|
||||
Usage: "Public key of candidate to vote for",
|
||||
&cli.StringFlag{
|
||||
Name: "candidate",
|
||||
Aliases: []string{"c"},
|
||||
Usage: "Public key of candidate to vote for",
|
||||
},
|
||||
}, options.RPC...),
|
||||
},
|
||||
|
@ -103,18 +110,15 @@ func handleNeoAction(ctx *cli.Context, mkTx func(*neo.Contract, util.Uint160, *w
|
|||
}
|
||||
wall, pass, err := readWallet(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
|
||||
addrFlag := ctx.Generic("address").(*flags.Address)
|
||||
if !addrFlag.IsSet {
|
||||
return cli.NewExitError("address was not provided", 1)
|
||||
}
|
||||
addr := addrFlag.Uint160()
|
||||
acc, err := options.GetUnlockedAccount(wall, addr, pass)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
|
@ -122,7 +126,7 @@ func handleNeoAction(ctx *cli.Context, mkTx func(*neo.Contract, util.Uint160, *w
|
|||
|
||||
signers, err := cmdargs.GetSignersAccounts(acc, wall, nil, transaction.CalledByEntry)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("invalid signers: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("invalid signers: %w", err), 1)
|
||||
}
|
||||
_, act, exitErr := options.GetRPCWithActor(gctx, ctx, signers)
|
||||
if exitErr != nil {
|
||||
|
@ -132,7 +136,7 @@ func handleNeoAction(ctx *cli.Context, mkTx func(*neo.Contract, util.Uint160, *w
|
|||
contract := neo.New(act)
|
||||
tx, err := mkTx(contract, addr, acc)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
return txctx.SignAndSend(ctx, act, acc, tx)
|
||||
}
|
||||
|
|
|
@ -8,6 +8,7 @@ import (
|
|||
"io"
|
||||
"math/big"
|
||||
"os"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||
|
@ -24,7 +25,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/urfave/cli"
|
||||
"github.com/urfave/cli/v2"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -47,38 +48,43 @@ var (
|
|||
)
|
||||
|
||||
var (
|
||||
walletPathFlag = cli.StringFlag{
|
||||
Name: "wallet, w",
|
||||
Usage: "Path to the wallet file ('-' to read from stdin); conflicts with --wallet-config flag.",
|
||||
walletPathFlag = &cli.StringFlag{
|
||||
Name: "wallet",
|
||||
Aliases: []string{"w"},
|
||||
Usage: "Path to the wallet file ('-' to read from stdin); conflicts with --wallet-config flag.",
|
||||
}
|
||||
walletConfigFlag = cli.StringFlag{
|
||||
walletConfigFlag = &cli.StringFlag{
|
||||
Name: "wallet-config",
|
||||
Usage: "Path to the wallet config file; conflicts with --wallet flag.",
|
||||
}
|
||||
wifFlag = cli.StringFlag{
|
||||
wifFlag = &cli.StringFlag{
|
||||
Name: "wif",
|
||||
Usage: "WIF to import",
|
||||
}
|
||||
decryptFlag = cli.BoolFlag{
|
||||
Name: "decrypt, d",
|
||||
Usage: "Decrypt encrypted keys.",
|
||||
decryptFlag = &cli.BoolFlag{
|
||||
Name: "decrypt",
|
||||
Aliases: []string{"d"},
|
||||
Usage: "Decrypt encrypted keys.",
|
||||
}
|
||||
inFlag = cli.StringFlag{
|
||||
Name: "in",
|
||||
Usage: "file with JSON transaction",
|
||||
inFlag = &cli.StringFlag{
|
||||
Name: "in",
|
||||
Required: true,
|
||||
Usage: "File with JSON transaction",
|
||||
Action: cmdargs.EnsureNotEmpty("in"),
|
||||
}
|
||||
fromAddrFlag = flags.AddressFlag{
|
||||
fromAddrFlag = &flags.AddressFlag{
|
||||
Name: "from",
|
||||
Usage: "Address to send an asset from",
|
||||
}
|
||||
toAddrFlag = flags.AddressFlag{
|
||||
Name: "to",
|
||||
Usage: "Address to send an asset to",
|
||||
toAddrFlag = &flags.AddressFlag{
|
||||
Name: "to",
|
||||
Usage: "Address to send an asset to",
|
||||
Required: true,
|
||||
}
|
||||
)
|
||||
|
||||
// NewCommands returns 'wallet' command.
|
||||
func NewCommands() []cli.Command {
|
||||
func NewCommands() []*cli.Command {
|
||||
claimFlags := []cli.Flag{
|
||||
walletPathFlag,
|
||||
walletConfigFlag,
|
||||
|
@ -87,9 +93,11 @@ func NewCommands() []cli.Command {
|
|||
txctx.OutFlag,
|
||||
txctx.ForceFlag,
|
||||
txctx.AwaitFlag,
|
||||
flags.AddressFlag{
|
||||
Name: "address, a",
|
||||
Usage: "Address to claim GAS for",
|
||||
&flags.AddressFlag{
|
||||
Name: "address",
|
||||
Aliases: []string{"a"},
|
||||
Required: true,
|
||||
Usage: "Address to claim GAS for",
|
||||
},
|
||||
}
|
||||
claimFlags = append(claimFlags, options.RPC...)
|
||||
|
@ -99,67 +107,78 @@ func NewCommands() []cli.Command {
|
|||
txctx.OutFlag,
|
||||
txctx.AwaitFlag,
|
||||
inFlag,
|
||||
flags.AddressFlag{
|
||||
Name: "address, a",
|
||||
Usage: "Address to use",
|
||||
&flags.AddressFlag{
|
||||
Name: "address",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "Address to use",
|
||||
},
|
||||
}
|
||||
signFlags = append(signFlags, options.RPC...)
|
||||
return []cli.Command{{
|
||||
// By default, RPC flag is required. signtx may be called without provided rpc-endpoint.
|
||||
rpcFlagOriginal, _ := options.RPC[0].(*cli.StringFlag)
|
||||
rpcFlag := *rpcFlagOriginal
|
||||
rpcFlag.Required = false
|
||||
signFlags = append(signFlags, &rpcFlag)
|
||||
signFlags = append(signFlags, options.RPC[1:]...)
|
||||
return []*cli.Command{{
|
||||
Name: "wallet",
|
||||
Usage: "create, open and manage a Neo wallet",
|
||||
Subcommands: []cli.Command{
|
||||
Usage: "Create, open and manage a Neo wallet",
|
||||
Subcommands: []*cli.Command{
|
||||
{
|
||||
Name: "claim",
|
||||
Usage: "claim GAS",
|
||||
Usage: "Claim GAS",
|
||||
UsageText: "neo-go wallet claim -w wallet [--wallet-config path] [-g gas] [-e sysgas] -a address -r endpoint [-s timeout] [--out file] [--force] [--await]",
|
||||
Action: claimGas,
|
||||
Flags: claimFlags,
|
||||
},
|
||||
{
|
||||
Name: "init",
|
||||
Usage: "create a new wallet",
|
||||
Usage: "Create a new wallet",
|
||||
UsageText: "neo-go wallet init -w wallet [--wallet-config path] [-a]",
|
||||
Action: createWallet,
|
||||
Flags: []cli.Flag{
|
||||
walletPathFlag,
|
||||
walletConfigFlag,
|
||||
cli.BoolFlag{
|
||||
Name: "account, a",
|
||||
Usage: "Create a new account",
|
||||
&cli.BoolFlag{
|
||||
Name: "account",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "Create a new account",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "change-password",
|
||||
Usage: "change password for accounts",
|
||||
UsageText: "neo-go wallet change-password -w wallet -a address",
|
||||
Usage: "Change password for accounts",
|
||||
UsageText: "neo-go wallet change-password -w wallet [-a address]",
|
||||
Action: changePassword,
|
||||
Flags: []cli.Flag{
|
||||
walletPathFlag,
|
||||
flags.AddressFlag{
|
||||
Name: "address, a",
|
||||
Usage: "address to change password for",
|
||||
&flags.AddressFlag{
|
||||
Name: "address",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "Address to change password for",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "convert",
|
||||
Usage: "convert addresses from existing Neo Legacy NEP6-wallet to Neo N3 format",
|
||||
Usage: "Convert addresses from existing Neo Legacy NEP6-wallet to Neo N3 format",
|
||||
UsageText: "neo-go wallet convert -w legacywallet [--wallet-config path] -o n3wallet",
|
||||
Action: convertWallet,
|
||||
Flags: []cli.Flag{
|
||||
walletPathFlag,
|
||||
walletConfigFlag,
|
||||
cli.StringFlag{
|
||||
Name: "out, o",
|
||||
Usage: "where to write converted wallet",
|
||||
&cli.StringFlag{
|
||||
Name: "out",
|
||||
Aliases: []string{"o"},
|
||||
Required: true,
|
||||
Usage: "Where to write converted wallet",
|
||||
Action: cmdargs.EnsureNotEmpty("out"),
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "create",
|
||||
Usage: "add an account to the existing wallet",
|
||||
Usage: "Add an account to the existing wallet",
|
||||
UsageText: "neo-go wallet create -w wallet [--wallet-config path]",
|
||||
Action: addAccount,
|
||||
Flags: []cli.Flag{
|
||||
|
@ -169,7 +188,7 @@ func NewCommands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "dump",
|
||||
Usage: "check and dump an existing Neo wallet",
|
||||
Usage: "Check and dump an existing Neo wallet",
|
||||
UsageText: "neo-go wallet dump -w wallet [--wallet-config path] [-d]",
|
||||
Description: `Prints the given wallet (via -w option or via wallet configuration file) in JSON
|
||||
format to the standard output. If -d is given, private keys are unencrypted and
|
||||
|
@ -185,21 +204,22 @@ func NewCommands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "dump-keys",
|
||||
Usage: "dump public keys for account",
|
||||
Usage: "Dump public keys for account",
|
||||
UsageText: "neo-go wallet dump-keys -w wallet [--wallet-config path] [-a address]",
|
||||
Action: dumpKeys,
|
||||
Flags: []cli.Flag{
|
||||
walletPathFlag,
|
||||
walletConfigFlag,
|
||||
flags.AddressFlag{
|
||||
Name: "address, a",
|
||||
Usage: "address to print public keys for",
|
||||
&flags.AddressFlag{
|
||||
Name: "address",
|
||||
Aliases: []string{"a"},
|
||||
Usage: "Address to print public keys for",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "export",
|
||||
Usage: "export keys for address",
|
||||
Usage: "Export keys for address",
|
||||
UsageText: "export -w wallet [--wallet-config path] [--decrypt] [<address>]",
|
||||
Description: `Prints the key for the given account to the standard output. It uses NEP-2
|
||||
encrypted format by default (the way NEP-6 wallets store it) or WIF format if
|
||||
|
@ -216,18 +236,19 @@ func NewCommands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "import",
|
||||
Usage: "import WIF of a standard signature contract",
|
||||
Usage: "Import WIF of a standard signature contract",
|
||||
UsageText: "import -w wallet [--wallet-config path] --wif <wif> [--name <account_name>]",
|
||||
Action: importWallet,
|
||||
Flags: []cli.Flag{
|
||||
walletPathFlag,
|
||||
walletConfigFlag,
|
||||
wifFlag,
|
||||
cli.StringFlag{
|
||||
Name: "name, n",
|
||||
Usage: "Optional account name",
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Aliases: []string{"n"},
|
||||
Usage: "Optional account name",
|
||||
},
|
||||
cli.StringFlag{
|
||||
&cli.StringFlag{
|
||||
Name: "contract",
|
||||
Usage: "Verification script for custom contracts",
|
||||
},
|
||||
|
@ -235,7 +256,7 @@ func NewCommands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "import-multisig",
|
||||
Usage: "import multisig contract",
|
||||
Usage: "Import multisig contract",
|
||||
UsageText: "import-multisig -w wallet [--wallet-config path] [--wif <wif>] [--name <account_name>] --min <m>" +
|
||||
" [<pubkey1> [<pubkey2> [...]]]",
|
||||
Description: `Imports a standard multisignature contract with "m out of n" signatures required where "m" is
|
||||
|
@ -250,53 +271,60 @@ func NewCommands() []cli.Command {
|
|||
walletPathFlag,
|
||||
walletConfigFlag,
|
||||
wifFlag,
|
||||
cli.StringFlag{
|
||||
Name: "name, n",
|
||||
Usage: "Optional account name",
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Aliases: []string{"n"},
|
||||
Usage: "Optional account name",
|
||||
},
|
||||
cli.IntFlag{
|
||||
Name: "min, m",
|
||||
Usage: "Minimal number of signatures",
|
||||
&cli.IntFlag{
|
||||
Name: "min",
|
||||
Aliases: []string{"m"},
|
||||
Usage: "Minimal number of signatures",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "import-deployed",
|
||||
Usage: "import deployed contract",
|
||||
UsageText: "import-deployed -w wallet [--wallet-config path] --wif <wif> --contract <hash> [--name <account_name>]",
|
||||
Usage: "Import deployed contract",
|
||||
UsageText: "import-deployed -w wallet [--wallet-config path] --wif <wif> --contract <hash> --rpc-endpoint <endpoint> [-s <timeout>] [--name <account_name>]",
|
||||
Action: importDeployed,
|
||||
Flags: append([]cli.Flag{
|
||||
walletPathFlag,
|
||||
walletConfigFlag,
|
||||
wifFlag,
|
||||
cli.StringFlag{
|
||||
Name: "name, n",
|
||||
Usage: "Optional account name",
|
||||
&cli.StringFlag{
|
||||
Name: "name",
|
||||
Aliases: []string{"n"},
|
||||
Usage: "Optional account name",
|
||||
},
|
||||
flags.AddressFlag{
|
||||
Name: "contract, c",
|
||||
Usage: "Contract hash or address",
|
||||
&flags.AddressFlag{
|
||||
Name: "contract",
|
||||
Aliases: []string{"c"},
|
||||
Required: true,
|
||||
Usage: "Contract hash or address",
|
||||
},
|
||||
}, options.RPC...),
|
||||
},
|
||||
{
|
||||
Name: "remove",
|
||||
Usage: "remove an account from the wallet",
|
||||
Usage: "Remove an account from the wallet",
|
||||
UsageText: "remove -w wallet [--wallet-config path] [--force] --address <addr>",
|
||||
Action: removeAccount,
|
||||
Flags: []cli.Flag{
|
||||
walletPathFlag,
|
||||
walletConfigFlag,
|
||||
txctx.ForceFlag,
|
||||
flags.AddressFlag{
|
||||
Name: "address, a",
|
||||
Usage: "Account address or hash in LE form to be removed",
|
||||
&flags.AddressFlag{
|
||||
Name: "address",
|
||||
Aliases: []string{"a"},
|
||||
Required: true,
|
||||
Usage: "Account address or hash in LE form to be removed",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "sign",
|
||||
Usage: "cosign transaction with multisig/contract/additional account",
|
||||
Usage: "Cosign transaction with multisig/contract/additional account",
|
||||
UsageText: "sign -w wallet [--wallet-config path] --address <address> --in <file.in> [--out <file.out>] [-r <endpoint>] [--await]",
|
||||
Description: `Signs the given (in file.in) context (which must be a transaction
|
||||
signing context) for the given address using the given wallet. This command can
|
||||
|
@ -312,7 +340,7 @@ func NewCommands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "strip-keys",
|
||||
Usage: "remove private keys for all accounts",
|
||||
Usage: "Remove private keys for all accounts",
|
||||
UsageText: "neo-go wallet strip-keys -w wallet [--wallet-config path] [--force]",
|
||||
Description: `Removes private keys for all accounts from the given wallet. Notice,
|
||||
this is a very dangerous action (you can lose keys if you don't have a wallet
|
||||
|
@ -329,17 +357,17 @@ func NewCommands() []cli.Command {
|
|||
},
|
||||
{
|
||||
Name: "nep17",
|
||||
Usage: "work with NEP-17 contracts",
|
||||
Usage: "Work with NEP-17 contracts",
|
||||
Subcommands: newNEP17Commands(),
|
||||
},
|
||||
{
|
||||
Name: "nep11",
|
||||
Usage: "work with NEP-11 contracts",
|
||||
Usage: "Work with NEP-11 contracts",
|
||||
Subcommands: newNEP11Commands(),
|
||||
},
|
||||
{
|
||||
Name: "candidate",
|
||||
Usage: "work with candidates",
|
||||
Usage: "Work with candidates",
|
||||
Subcommands: newValidatorCommands(),
|
||||
},
|
||||
},
|
||||
|
@ -358,24 +386,24 @@ func changePassword(ctx *cli.Context) error {
|
|||
}
|
||||
wall, _, err := openWallet(ctx, false)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
if len(wall.Accounts) == 0 {
|
||||
return cli.NewExitError("wallet has no accounts", 1)
|
||||
return cli.Exit("wallet has no accounts", 1)
|
||||
}
|
||||
addrFlag := ctx.Generic("address").(*flags.Address)
|
||||
if addrFlag.IsSet {
|
||||
// Check for account presence first before asking for password.
|
||||
acc := wall.GetAccount(addrFlag.Uint160())
|
||||
if acc == nil {
|
||||
return cli.NewExitError("account is missing", 1)
|
||||
return cli.Exit("account is missing", 1)
|
||||
}
|
||||
}
|
||||
|
||||
oldPass, err := input.ReadPassword(EnterOldPasswordPrompt)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("Error reading old password: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("error reading old password: %w", err), 1)
|
||||
}
|
||||
|
||||
for i := range wall.Accounts {
|
||||
|
@ -384,13 +412,13 @@ func changePassword(ctx *cli.Context) error {
|
|||
}
|
||||
err := wall.Accounts[i].Decrypt(oldPass, wall.Scrypt)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("unable to decrypt account %s: %w", wall.Accounts[i].Address, err), 1)
|
||||
return cli.Exit(fmt.Errorf("unable to decrypt account %s: %w", wall.Accounts[i].Address, err), 1)
|
||||
}
|
||||
}
|
||||
|
||||
pass, err := readNewPassword()
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("Error reading new password: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("error reading new password: %w", err), 1)
|
||||
}
|
||||
for i := range wall.Accounts {
|
||||
if addrFlag.IsSet && wall.Accounts[i].Address != addrFlag.String() {
|
||||
|
@ -398,12 +426,12 @@ func changePassword(ctx *cli.Context) error {
|
|||
}
|
||||
err := wall.Accounts[i].Encrypt(pass, wall.Scrypt)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
}
|
||||
err = wall.Save()
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("Error saving the wallet: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("error saving the wallet: %w", err), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -414,16 +442,13 @@ func convertWallet(ctx *cli.Context) error {
|
|||
}
|
||||
wall, pass, err := newWalletV2FromFile(ctx.String("wallet"), ctx.String("wallet-config"))
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
out := ctx.String("out")
|
||||
if len(out) == 0 {
|
||||
return cli.NewExitError("missing out path", 1)
|
||||
}
|
||||
newWallet, err := wallet.NewWallet(out)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
newWallet.Scrypt = wall.Scrypt
|
||||
|
||||
|
@ -431,19 +456,19 @@ func convertWallet(ctx *cli.Context) error {
|
|||
if len(wall.Accounts) != 1 || pass == nil {
|
||||
password, err := input.ReadPassword(fmt.Sprintf("Enter password for account %s (label '%s') > ", acc.Address, acc.Label))
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("Error reading password: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("error reading password: %w", err), 1)
|
||||
}
|
||||
pass = &password
|
||||
}
|
||||
|
||||
newAcc, err := acc.convert(*pass, wall.Scrypt)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
newWallet.AddAccount(newAcc)
|
||||
}
|
||||
if err := newWallet.Save(); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -454,12 +479,12 @@ func addAccount(ctx *cli.Context) error {
|
|||
}
|
||||
wall, pass, err := openWallet(ctx, true)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
|
||||
if err := createAccount(wall, pass); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -468,7 +493,7 @@ func addAccount(ctx *cli.Context) error {
|
|||
func exportKeys(ctx *cli.Context) error {
|
||||
wall, pass, err := readWallet(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
|
||||
|
@ -476,28 +501,25 @@ func exportKeys(ctx *cli.Context) error {
|
|||
|
||||
decrypt := ctx.Bool("decrypt")
|
||||
if ctx.NArg() == 0 && decrypt {
|
||||
return cli.NewExitError(errors.New("address must be provided if '--decrypt' flag is used"), 1)
|
||||
return cli.Exit(errors.New("address must be provided if '--decrypt' flag is used"), 1)
|
||||
} else if ctx.NArg() > 0 {
|
||||
// check address format just to catch possible typos
|
||||
addr = ctx.Args().First()
|
||||
_, err := address.StringToUint160(addr)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't parse address: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't parse address: %w", err), 1)
|
||||
}
|
||||
}
|
||||
|
||||
var wifs []string
|
||||
|
||||
loop:
|
||||
for _, a := range wall.Accounts {
|
||||
if addr != "" && a.Address != addr {
|
||||
continue
|
||||
}
|
||||
|
||||
for i := range wifs {
|
||||
if a.EncryptedWIF == wifs[i] {
|
||||
continue loop
|
||||
}
|
||||
if slices.Contains(wifs, a.EncryptedWIF) {
|
||||
continue
|
||||
}
|
||||
|
||||
wifs = append(wifs, a.EncryptedWIF)
|
||||
|
@ -508,14 +530,14 @@ loop:
|
|||
if pass == nil {
|
||||
password, err := input.ReadPassword(EnterPasswordPrompt)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("Error reading password: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("error reading password: %w", err), 1)
|
||||
}
|
||||
pass = &password
|
||||
}
|
||||
|
||||
pk, err := keys.NEP2Decrypt(wif, *pass, wall.Scrypt)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
wif = pk.WIF()
|
||||
|
@ -536,22 +558,22 @@ func importMultisig(ctx *cli.Context) error {
|
|||
|
||||
wall, pass, err := openWallet(ctx, true)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
|
||||
m := ctx.Int("min")
|
||||
if ctx.NArg() < m {
|
||||
return cli.NewExitError(errors.New("insufficient number of public keys"), 1)
|
||||
return cli.Exit(errors.New("insufficient number of public keys"), 1)
|
||||
}
|
||||
|
||||
args := []string(ctx.Args())
|
||||
args := ctx.Args().Slice()
|
||||
pubs := make([]*keys.PublicKey, len(args))
|
||||
|
||||
for i := range args {
|
||||
pubs[i], err = keys.NewPublicKeyFromString(args[i])
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't decode public key %d: %w", i, err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't decode public key %d: %w", i, err), 1)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -579,31 +601,31 @@ loop:
|
|||
if acc != nil {
|
||||
err = acc.ConvertMultisigEncrypted(accPub, m, pubs)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
if label != nil {
|
||||
acc.Label = *label
|
||||
}
|
||||
if err := addAccountAndSave(wall, acc); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
if !ctx.IsSet("wif") {
|
||||
return cli.NewExitError(errors.New("none of the provided public keys correspond to an existing key in the wallet or multiple matching accounts found in the wallet, and no WIF is provided"), 1)
|
||||
return cli.Exit(errors.New("none of the provided public keys correspond to an existing key in the wallet or multiple matching accounts found in the wallet, and no WIF is provided"), 1)
|
||||
}
|
||||
acc, err = newAccountFromWIF(ctx.App.Writer, ctx.String("wif"), wall.Scrypt, label, pass)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
if err := acc.ConvertMultisig(m, pubs); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
if err := addAccountAndSave(wall, acc); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -615,14 +637,11 @@ func importDeployed(ctx *cli.Context) error {
|
|||
}
|
||||
wall, pass, err := openWallet(ctx, true)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
|
||||
rawHash := ctx.Generic("contract").(*flags.Address)
|
||||
if !rawHash.IsSet {
|
||||
return cli.NewExitError("contract hash was not provided", 1)
|
||||
}
|
||||
|
||||
var label *string
|
||||
if ctx.IsSet("name") {
|
||||
|
@ -631,7 +650,7 @@ func importDeployed(ctx *cli.Context) error {
|
|||
}
|
||||
acc, err := newAccountFromWIF(ctx.App.Writer, ctx.String("wif"), wall.Scrypt, label, pass)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
|
@ -639,19 +658,20 @@ func importDeployed(ctx *cli.Context) error {
|
|||
|
||||
c, err := options.GetRPCClient(gctx, ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
cs, err := c.GetContractStateByHash(rawHash.Uint160())
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't fetch contract info: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("can't fetch contract info: %w", err), 1)
|
||||
}
|
||||
md := cs.Manifest.ABI.GetMethod(manifest.MethodVerify, -1)
|
||||
if md == nil || md.ReturnType != smartcontract.BoolType {
|
||||
return cli.NewExitError("contract has no `verify` method with boolean return", 1)
|
||||
return cli.Exit("contract has no `verify` method with boolean return", 1)
|
||||
}
|
||||
acc.Address = address.Uint160ToString(cs.Hash)
|
||||
acc.Contract.Script = cs.NEF.Script
|
||||
// Explicitly overwrite single signature script of the provided WIF since the contract is known to be deployed.
|
||||
acc.Contract.Script = nil
|
||||
acc.Contract.Parameters = acc.Contract.Parameters[:0]
|
||||
for _, p := range md.Parameters {
|
||||
acc.Contract.Parameters = append(acc.Contract.Parameters, wallet.ContractParam{
|
||||
|
@ -662,7 +682,7 @@ func importDeployed(ctx *cli.Context) error {
|
|||
acc.Contract.Deployed = true
|
||||
|
||||
if err := addAccountAndSave(wall, acc); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -674,7 +694,7 @@ func importWallet(ctx *cli.Context) error {
|
|||
}
|
||||
wall, pass, err := openWallet(ctx, true)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
|
||||
|
@ -686,19 +706,19 @@ func importWallet(ctx *cli.Context) error {
|
|||
|
||||
acc, err := newAccountFromWIF(ctx.App.Writer, ctx.String("wif"), wall.Scrypt, label, pass)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
if ctrFlag := ctx.String("contract"); ctrFlag != "" {
|
||||
ctr, err := hex.DecodeString(ctrFlag)
|
||||
if err != nil {
|
||||
return cli.NewExitError("invalid contract", 1)
|
||||
return cli.Exit("invalid contract", 1)
|
||||
}
|
||||
acc.Contract.Script = ctr
|
||||
}
|
||||
|
||||
if err := addAccountAndSave(wall, acc); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -710,17 +730,14 @@ func removeAccount(ctx *cli.Context) error {
|
|||
}
|
||||
wall, _, err := openWallet(ctx, true)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
|
||||
addr := ctx.Generic("address").(*flags.Address)
|
||||
if !addr.IsSet {
|
||||
return cli.NewExitError("valid account address must be provided", 1)
|
||||
}
|
||||
acc := wall.GetAccount(addr.Uint160())
|
||||
if acc == nil {
|
||||
return cli.NewExitError("account wasn't found", 1)
|
||||
return cli.Exit("account wasn't found", 1)
|
||||
}
|
||||
|
||||
if !ctx.Bool("force") {
|
||||
|
@ -731,10 +748,10 @@ func removeAccount(ctx *cli.Context) error {
|
|||
}
|
||||
|
||||
if err := wall.RemoveAccount(acc.Address); err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("error on remove: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("error on remove: %w", err), 1)
|
||||
}
|
||||
if err := wall.Save(); err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("error while saving wallet: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("error while saving wallet: %w", err), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -757,14 +774,14 @@ func dumpWallet(ctx *cli.Context) error {
|
|||
}
|
||||
wall, pass, err := readWallet(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
if ctx.Bool("decrypt") {
|
||||
if pass == nil {
|
||||
password, err := input.ReadPassword(EnterPasswordPrompt)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("Error reading password: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("error reading password: %w", err), 1)
|
||||
}
|
||||
pass = &password
|
||||
}
|
||||
|
@ -772,7 +789,7 @@ func dumpWallet(ctx *cli.Context) error {
|
|||
// Just testing the decryption here.
|
||||
err := wall.Accounts[i].Decrypt(*pass, wall.Scrypt)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -786,7 +803,7 @@ func dumpKeys(ctx *cli.Context) error {
|
|||
}
|
||||
wall, _, err := readWallet(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
accounts := wall.Accounts
|
||||
|
@ -795,7 +812,7 @@ func dumpKeys(ctx *cli.Context) error {
|
|||
if addrFlag.IsSet {
|
||||
acc := wall.GetAccount(addrFlag.Uint160())
|
||||
if acc == nil {
|
||||
return cli.NewExitError("account is missing", 1)
|
||||
return cli.Exit("account is missing", 1)
|
||||
}
|
||||
accounts = []*wallet.Account{acc}
|
||||
}
|
||||
|
@ -825,7 +842,7 @@ func dumpKeys(ctx *cli.Context) error {
|
|||
continue
|
||||
}
|
||||
if addrFlag.IsSet {
|
||||
return cli.NewExitError(fmt.Errorf("unknown script type for address %s", address.Uint160ToString(addrFlag.Uint160())), 1)
|
||||
return cli.Exit(fmt.Errorf("unknown script type for address %s", address.Uint160ToString(addrFlag.Uint160())), 1)
|
||||
}
|
||||
}
|
||||
return nil
|
||||
|
@ -837,7 +854,7 @@ func stripKeys(ctx *cli.Context) error {
|
|||
}
|
||||
wall, _, err := readWallet(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
if !ctx.Bool("force") {
|
||||
|
@ -850,7 +867,7 @@ func stripKeys(ctx *cli.Context) error {
|
|||
a.EncryptedWIF = ""
|
||||
}
|
||||
if err := wall.Save(); err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("error while saving wallet: %w", err), 1)
|
||||
return cli.Exit(fmt.Errorf("error while saving wallet: %w", err), 1)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
@ -866,28 +883,28 @@ func createWallet(ctx *cli.Context) error {
|
|||
return errConflictingWalletFlags
|
||||
}
|
||||
if len(path) == 0 && len(configPath) == 0 {
|
||||
return cli.NewExitError(errNoPath, 1)
|
||||
return cli.Exit(errNoPath, 1)
|
||||
}
|
||||
var pass *string
|
||||
if len(configPath) != 0 {
|
||||
cfg, err := options.ReadWalletConfig(configPath)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
path = cfg.Path
|
||||
pass = &cfg.Password
|
||||
}
|
||||
wall, err := wallet.NewWallet(path)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
if err := wall.Save(); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
|
||||
if ctx.Bool("account") {
|
||||
if err := createAccount(wall, pass); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
return cli.Exit(err, 1)
|
||||
}
|
||||
defer wall.Close()
|
||||
}
|
||||
|
@ -948,14 +965,14 @@ func createAccount(wall *wallet.Wallet, pass *string) error {
|
|||
func openWallet(ctx *cli.Context, canUseWalletConfig bool) (*wallet.Wallet, *string, error) {
|
||||
path, pass, err := getWalletPathAndPass(ctx, canUseWalletConfig)
|
||||
if err != nil {
|
||||
return nil, nil, cli.NewExitError(fmt.Errorf("failed to get wallet path or password: %w", err), 1)
|
||||
return nil, nil, cli.Exit(fmt.Errorf("failed to get wallet path or password: %w", err), 1)
|
||||
}
|
||||
if path == "-" {
|
||||
return nil, nil, errNoStdin
|
||||
}
|
||||
w, err := wallet.NewWalletFromFile(path)
|
||||
if err != nil {
|
||||
return nil, nil, cli.NewExitError(fmt.Errorf("failed to read wallet: %w", err), 1)
|
||||
return nil, nil, cli.Exit(fmt.Errorf("failed to read wallet: %w", err), 1)
|
||||
}
|
||||
return w, pass, nil
|
||||
}
|
||||
|
|
|
@ -40,10 +40,10 @@ func TestWalletAccountRemove(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
t.Run("missing wallet", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "wallet", "remove")
|
||||
e.RunWithErrorCheck(t, `Required flag "address" not set`, "neo-go", "wallet", "remove")
|
||||
})
|
||||
t.Run("missing address", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "wallet", "remove", "--wallet", walletPath)
|
||||
e.RunWithErrorCheck(t, `Required flag "address" not set`, "neo-go", "wallet", "remove", "--wallet", walletPath)
|
||||
})
|
||||
t.Run("invalid address", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "wallet", "remove", "--wallet", walletPath,
|
||||
|
@ -109,7 +109,7 @@ func TestWalletChangePassword(t *testing.T) {
|
|||
e.In.WriteString("pass\r")
|
||||
e.In.WriteString("pass1\r")
|
||||
e.In.WriteString("pass2\r")
|
||||
e.RunWithError(t, "neo-go", "wallet", "change-password", "--wallet", walletPath)
|
||||
e.RunWithError(t, "neo-go", "wallet", "change-password", "--wallet", walletPath, "--address", addr1)
|
||||
})
|
||||
t.Run("good, multiaccount", func(t *testing.T) {
|
||||
e.In.WriteString("pass\r")
|
||||
|
@ -593,7 +593,7 @@ func TestWalletClaimGas(t *testing.T) {
|
|||
"--address", testcli.TestWalletAccount)
|
||||
})
|
||||
t.Run("missing address", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "wallet", "claim",
|
||||
e.RunWithErrorCheck(t, `Required flag "address" not set`, "neo-go", "wallet", "claim",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||
"--wallet", testcli.TestWalletPath)
|
||||
})
|
||||
|
@ -605,7 +605,7 @@ func TestWalletClaimGas(t *testing.T) {
|
|||
})
|
||||
t.Run("missing endpoint", func(t *testing.T) {
|
||||
e.In.WriteString("testpass\r")
|
||||
e.RunWithError(t, "neo-go", "wallet", "claim",
|
||||
e.RunWithErrorCheck(t, `Required flag "rpc-endpoint" not set`, "neo-go", "wallet", "claim",
|
||||
"--wallet", testcli.TestWalletPath,
|
||||
"--address", testcli.TestWalletAccount)
|
||||
})
|
||||
|
@ -711,19 +711,19 @@ func TestWalletImportDeployed(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
|
||||
t.Run("missing wallet", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "wallet", "import-deployed")
|
||||
e.RunWithErrorCheck(t, `Required flag "contract" not set`, "neo-go", "wallet", "import-deployed", "--rpc-endpoint", "http://"+e.RPC.Addresses()[0])
|
||||
})
|
||||
t.Run("missing contract sh", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "wallet", "import-deployed",
|
||||
"--wallet", walletPath)
|
||||
e.RunWithErrorCheck(t, `Required flag "contract" not set`, "neo-go", "wallet", "import-deployed",
|
||||
"--wallet", walletPath, "--rpc-endpoint", "http://"+e.RPC.Addresses()[0])
|
||||
})
|
||||
t.Run("missing WIF", func(t *testing.T) {
|
||||
e.RunWithError(t, "neo-go", "wallet", "import-deployed",
|
||||
"--wallet", walletPath, "--contract", h.StringLE())
|
||||
"--wallet", walletPath, "--contract", h.StringLE(), "--rpc-endpoint", "http://"+e.RPC.Addresses()[0])
|
||||
})
|
||||
t.Run("missing endpoint", func(t *testing.T) {
|
||||
e.In.WriteString("acc\rpass\rpass\r")
|
||||
e.RunWithError(t, "neo-go", "wallet", "import-deployed",
|
||||
e.RunWithErrorCheck(t, `Required flag "rpc-endpoint" not set`, "neo-go", "wallet", "import-deployed",
|
||||
"--wallet", walletPath, "--contract", h.StringLE(),
|
||||
"--wif", priv.WIF())
|
||||
})
|
||||
|
@ -992,7 +992,7 @@ func TestOfflineSigning(t *testing.T) {
|
|||
|
||||
e.Run(t, "neo-go", "util", "sendtx",
|
||||
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||
txPath, "--await")
|
||||
"--await", txPath)
|
||||
e.CheckAwaitableTxPersisted(t)
|
||||
})
|
||||
}
|
||||
|
@ -1062,7 +1062,7 @@ func TestWalletDumpKeys(t *testing.T) {
|
|||
e.CheckNextLine(t, pubRegex)
|
||||
e.CheckNextLine(t, "^\\s*$")
|
||||
e.CheckNextLine(t, "NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq")
|
||||
for i := 0; i < 4; i++ {
|
||||
for range 4 {
|
||||
e.CheckNextLine(t, pubRegex)
|
||||
}
|
||||
e.CheckNextLine(t, "^\\s*$")
|
||||
|
@ -1085,7 +1085,7 @@ func TestWalletDumpKeys(t *testing.T) {
|
|||
cmd := append(cmd, "-a", "NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq")
|
||||
e.Run(t, cmd...)
|
||||
e.CheckNextLine(t, "3 out of 4 multisig contract")
|
||||
for i := 0; i < 4; i++ {
|
||||
for range 4 {
|
||||
e.CheckNextLine(t, pubRegex)
|
||||
}
|
||||
e.CheckEOF(t)
|
||||
|
@ -1107,11 +1107,11 @@ func TestWalletConvert(t *testing.T) {
|
|||
outPath := filepath.Join(tmpDir, "wallet.json")
|
||||
cmd := []string{"neo-go", "wallet", "convert"}
|
||||
t.Run("missing wallet", func(t *testing.T) {
|
||||
e.RunWithError(t, cmd...)
|
||||
e.RunWithErrorCheck(t, `Required flag "out" not set`, cmd...)
|
||||
})
|
||||
cmd = append(cmd, "--wallet", "testdata/testwallet_NEO2.json")
|
||||
t.Run("missing out path", func(t *testing.T) {
|
||||
e.RunWithError(t, cmd...)
|
||||
e.RunWithErrorCheck(t, `Required flag "out" not set`, cmd...)
|
||||
})
|
||||
t.Run("invalid out path", func(t *testing.T) {
|
||||
dir := t.TempDir()
|
||||
|
|
37
config/config_embed.go
Normal file
37
config/config_embed.go
Normal file
|
@ -0,0 +1,37 @@
|
|||
// Package config contains embedded YAML configuration files for different network modes
|
||||
// of the Neo N3 blockchain and for NeoFS mainnet and testnet networks.
|
||||
package config
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
)
|
||||
|
||||
// MainNet is the Neo N3 mainnet configuration.
|
||||
//
|
||||
//go:embed protocol.mainnet.yml
|
||||
var MainNet []byte
|
||||
|
||||
// TestNet is the Neo N3 testnet configuration.
|
||||
//
|
||||
//go:embed protocol.testnet.yml
|
||||
var TestNet []byte
|
||||
|
||||
// PrivNet is the private network configuration.
|
||||
//
|
||||
//go:embed protocol.privnet.yml
|
||||
var PrivNet []byte
|
||||
|
||||
// MainNetNeoFS is the mainnet NeoFS configuration.
|
||||
//
|
||||
//go:embed protocol.mainnet.neofs.yml
|
||||
var MainNetNeoFS []byte
|
||||
|
||||
// TestNetNeoFS is the testnet NeoFS configuration.
|
||||
//
|
||||
//go:embed protocol.testnet.neofs.yml
|
||||
var TestNetNeoFS []byte
|
||||
|
||||
// UnitTestNet is the unit test network configuration.
|
||||
//
|
||||
//go:embed protocol.unit_testnet.yml
|
||||
var UnitTestNet []byte
|
|
@ -1,6 +1,6 @@
|
|||
ProtocolConfiguration:
|
||||
Magic: 91414437
|
||||
MaxTraceableBlocks: 2102400
|
||||
MaxTraceableBlocks: 17280
|
||||
InitialGASSupply: 52000000
|
||||
TimePerBlock: 15s
|
||||
MemPoolSize: 50000
|
||||
|
@ -27,6 +27,7 @@ ProtocolConfiguration:
|
|||
Aspidochelone: 3000000
|
||||
Basilisk: 4500000
|
||||
Cockatrice: 5800000
|
||||
Domovoi: 5800000
|
||||
|
||||
ApplicationConfiguration:
|
||||
SkipBlockVerification: false
|
||||
|
@ -79,3 +80,19 @@ ApplicationConfiguration:
|
|||
Enabled: false
|
||||
Addresses:
|
||||
- ":2113"
|
||||
NeoFSBlockFetcher:
|
||||
Enabled: true
|
||||
Addresses:
|
||||
- st1.storage.fs.neo.org:8080
|
||||
- st2.storage.fs.neo.org:8080
|
||||
- st3.storage.fs.neo.org:8080
|
||||
- st4.storage.fs.neo.org:8080
|
||||
Timeout: 10m
|
||||
DownloaderWorkersCount: 500
|
||||
OIDBatchSize: 8000
|
||||
BQueueSize: 16000 # must be larger than OIDBatchSize; recommended to be 2*OIDBatchSize or 3*OIDBatchSize
|
||||
SkipIndexFilesSearch: false
|
||||
IndexFileSize: 128000
|
||||
ContainerID: "BP71MqY7nJhpuHfdQU3infRSjMgVmSFFt9GfG2GGMZJj"
|
||||
BlockAttribute: "Block"
|
||||
IndexFileAttribute: "Index"
|
||||
|
|
|
@ -39,6 +39,7 @@ ProtocolConfiguration:
|
|||
Aspidochelone: 1730000
|
||||
Basilisk: 4120000
|
||||
Cockatrice: 5450000
|
||||
Domovoi: 5570000
|
||||
|
||||
ApplicationConfiguration:
|
||||
SkipBlockVerification: false
|
||||
|
@ -97,3 +98,19 @@ ApplicationConfiguration:
|
|||
Enabled: false
|
||||
Addresses:
|
||||
- ":2113"
|
||||
NeoFSBlockFetcher:
|
||||
Enabled: true
|
||||
Addresses:
|
||||
- st1.storage.fs.neo.org:8080
|
||||
- st2.storage.fs.neo.org:8080
|
||||
- st3.storage.fs.neo.org:8080
|
||||
- st4.storage.fs.neo.org:8080
|
||||
Timeout: 10m
|
||||
DownloaderWorkersCount: 500
|
||||
OIDBatchSize: 8000
|
||||
BQueueSize: 16000 # must be larger than OIDBatchSize; recommended to be 2*OIDBatchSize or 3*OIDBatchSize
|
||||
SkipIndexFilesSearch: false
|
||||
IndexFileSize: 128000
|
||||
ContainerID: "3RCdP3ZubyKyo8qFeo7EJPryidTZaGCMdUjqFJaaEKBV"
|
||||
BlockAttribute: "Block"
|
||||
IndexFileAttribute: "Index"
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
ProtocolConfiguration:
|
||||
Magic: 735783775
|
||||
MaxTraceableBlocks: 2102400
|
||||
MaxTraceableBlocks: 17280
|
||||
InitialGASSupply: 52000000
|
||||
TimePerBlock: 15s
|
||||
MemPoolSize: 50000
|
||||
|
@ -24,6 +24,11 @@ ProtocolConfiguration:
|
|||
- morph7.t5.fs.neo.org:50333
|
||||
VerifyTransactions: true
|
||||
P2PSigExtensions: true
|
||||
Hardforks:
|
||||
Aspidochelone: 0
|
||||
Basilisk: 0
|
||||
Cockatrice: 0
|
||||
Domovoi: 0
|
||||
|
||||
ApplicationConfiguration:
|
||||
SkipBlockVerification: false
|
||||
|
@ -82,3 +87,19 @@ ApplicationConfiguration:
|
|||
Enabled: false
|
||||
Addresses:
|
||||
- ":2113"
|
||||
NeoFSBlockFetcher:
|
||||
Enabled: true
|
||||
Addresses:
|
||||
- st1.storage.fs.neo.org:8080
|
||||
- st2.storage.fs.neo.org:8080
|
||||
- st3.storage.fs.neo.org:8080
|
||||
- st4.storage.fs.neo.org:8080
|
||||
Timeout: 10m
|
||||
DownloaderWorkersCount: 500
|
||||
OIDBatchSize: 8000
|
||||
BQueueSize: 16000 # must be larger than OIDBatchSize; recommended to be 2*OIDBatchSize or 3*OIDBatchSize
|
||||
SkipIndexFilesSearch: false
|
||||
IndexFileSize: 128000
|
||||
ContainerID: "98xz5YeanzxRCpH6EfUhECVm2MynGYchDN4naJViHT9M"
|
||||
BlockAttribute: "Block"
|
||||
IndexFileAttribute: "Index"
|
||||
|
|
|
@ -42,6 +42,7 @@ ProtocolConfiguration:
|
|||
Aspidochelone: 210000
|
||||
Basilisk: 2680000
|
||||
Cockatrice: 3967000
|
||||
Domovoi: 4144000
|
||||
|
||||
ApplicationConfiguration:
|
||||
SkipBlockVerification: false
|
||||
|
@ -99,3 +100,19 @@ ApplicationConfiguration:
|
|||
Enabled: false
|
||||
Addresses:
|
||||
- ":2113"
|
||||
NeoFSBlockFetcher:
|
||||
Enabled: true
|
||||
Addresses:
|
||||
- st1.storage.fs.neo.org:8080
|
||||
- st2.storage.fs.neo.org:8080
|
||||
- st3.storage.fs.neo.org:8080
|
||||
- st4.storage.fs.neo.org:8080
|
||||
Timeout: 10m
|
||||
DownloaderWorkersCount: 500
|
||||
OIDBatchSize: 8000
|
||||
BQueueSize: 16000 # must be larger than OIDBatchSize; recommended to be 2*OIDBatchSize or 3*OIDBatchSize
|
||||
SkipIndexFilesSearch: false
|
||||
IndexFileSize: 128000
|
||||
ContainerID: "A8nGtDemWrm2SjfcGAG6wvrxmXwqc5fwr8ezNDm6FraT"
|
||||
BlockAttribute: "Block"
|
||||
IndexFileAttribute: "Index"
|
||||
|
|
|
@ -18,7 +18,11 @@ ProtocolConfiguration:
|
|||
VerifyTransactions: true
|
||||
P2PSigExtensions: true
|
||||
Hardforks:
|
||||
Aspidochelone: 25
|
||||
Aspidochelone: 3
|
||||
Basilisk: 6
|
||||
Cockatrice: 9
|
||||
Domovoi: 12
|
||||
Echidna: 13
|
||||
|
||||
ApplicationConfiguration:
|
||||
SkipBlockVerification: false
|
||||
|
|
|
@ -33,6 +33,13 @@ a dialect of Go rather than a complete port of the language:
|
|||
it's up to the programmer whether assert can be performed successfully.
|
||||
* type aliases including the built-in `any` alias are supported.
|
||||
* generics are not supported, but eventually will be (at least, partially), ref. https://github.com/nspcc-dev/neo-go/issues/2376.
|
||||
* `~` token is not supported
|
||||
* `comparable` is not supported
|
||||
* arrays (`[4]byte`) are not supported (https://github.com/nspcc-dev/neo-go/issues/3524)
|
||||
* `min()` and `max()` are not supported (https://github.com/nspcc-dev/neo-go/issues/3090)
|
||||
* `clear()` is not supported (https://github.com/nspcc-dev/neo-go/issues/3091)
|
||||
* ranging over integers in `for` is not supported (https://github.com/nspcc-dev/neo-go/issues/3525)
|
||||
* `for` loop variables are treated in pre-Go 1.22 way: a single instance is created for the whole loop
|
||||
|
||||
## VM API (interop layer)
|
||||
Compiler translates interop function calls into Neo VM syscalls or (for custom
|
||||
|
|
|
@ -155,7 +155,7 @@ four-node setup.
|
|||
|
||||
#### Prerequisites
|
||||
- `docker` of version >= 20.10.0
|
||||
- `docker-compose`
|
||||
- `docker compose` V2
|
||||
- `go` compiler
|
||||
|
||||
#### Instructions
|
||||
|
@ -166,7 +166,7 @@ make env_up # start containers, use "make env_single" for single CN
|
|||
```
|
||||
To monitor logs:
|
||||
```bash
|
||||
docker-compose -f .docker/docker-compose.yml logs -f
|
||||
docker compose -f .docker/docker-compose.yml logs -f
|
||||
```
|
||||
|
||||
To stop:
|
||||
|
|
126
docs/neofs-blockstorage.md
Normal file
126
docs/neofs-blockstorage.md
Normal file
|
@ -0,0 +1,126 @@
|
|||
# NeoFS block storage
|
||||
|
||||
Using NeoFS to store chain's blocks and snapshots was proposed in
|
||||
[#3463](https://github.com/neo-project/neo/issues/3463). NeoGo contains several
|
||||
extensions utilizing NeoFS block storage aimed to improve node synchronization
|
||||
efficiency and reduce node storage size.
|
||||
|
||||
## Components and functionality
|
||||
|
||||
### Block storage schema
|
||||
|
||||
A single NeoFS container is used to store blocks and index files. Each container
|
||||
has network magic attribute (`Magic:56753`). Each block is stored in a binary
|
||||
form as a separate object with a unique OID and a set of attributes:
|
||||
- block object identifier with block index value (`Block:1`)
|
||||
- primary node index (`Primary:0`)
|
||||
- block hash in the LE form (`Hash:5412a781caf278c0736556c0e544c7cfdbb6e3c62ae221ef53646be89364566b`)
|
||||
- previous block hash in the LE form (`PrevHash:3654a054d82a8178c7dfacecc2c57282e23468a42ee407f14506368afe22d929`)
|
||||
- millisecond-precision block creation timestamp (`BlockTime:1627894840919`)
|
||||
- second-precision block uploading timestamp (`Timestamp:1627894840`)
|
||||
|
||||
Each index file is an object containing a constant-sized batch of raw block object
|
||||
IDs in binary form ordered by block index. Each index file is marked with the
|
||||
following attributes:
|
||||
- index file identifier with consecutive file index value (`Index:0`)
|
||||
- the number of OIDs included into index file (`IndexSize:128000`)
|
||||
- second-precision index file uploading timestamp (`Timestamp:1627894840`)
|
||||
|
||||
### NeoFS BlockFetcher
|
||||
|
||||
NeoFS BlockFetcher service is designed as an alternative to P2P synchronisation
|
||||
protocol. It allows to download blocks from a trusted container in the NeoFS network
|
||||
and persist them to database using standard verification flow. NeoFS BlockFetcher
|
||||
service primarily used during the node's bootstrap, providing a fast alternative to
|
||||
P2P blocks synchronisation.
|
||||
|
||||
NeoFS BlockFetcher service has two modes of operation:
|
||||
- Index File Search: Search for index files, which contain batches of block object
|
||||
IDs and fetch blocks from NeoFS by retrieved OIDs.
|
||||
- Direct Block Search: Search and fetch blocks directly from NeoFS container via
|
||||
built-in NeoFS object search mechanism.
|
||||
|
||||
Operation mode of BlockFetcher can be configured via `SkipIndexFilesSearch`
|
||||
parameter.
|
||||
|
||||
#### Operation flow
|
||||
|
||||
1. **OID Fetching**:
|
||||
Depending on the mode, the service either:
|
||||
- Searches for index files by index file attribute and reads block OIDs from index
|
||||
file object-by-object.
|
||||
- Searches blocks one by one directly by block attribute.
|
||||
|
||||
Once the OIDs are retrieved, they are immediately redirected to the
|
||||
block downloading routines for further processing. The channel that
|
||||
is used to redirect block OIDs to downloading routines is buffered
|
||||
to provide smooth OIDs delivery without delays. The size of this channel
|
||||
can be configured via `OIDBatchSize` parameter and equals to `2*OIDBatchSize`.
|
||||
2. **Parallel Block Downloading**:
|
||||
The number of downloading routines can be configured via
|
||||
`DownloaderWorkersCount` parameter. It's up to the user to find the
|
||||
balance between the downloading speed and blocks persist speed for every
|
||||
node that uses NeoFS BlockFetcher. Downloaded blocks are placed to the
|
||||
block queue directly.
|
||||
3. **Block Insertion**:
|
||||
Downloaded blocks are inserted into the blockchain using the same logic
|
||||
as in the P2P synchronisation protocol. The block queue is used to order
|
||||
downloaded blocks before they are inserted into the blockchain. The
|
||||
size of the queue can be configured via the `BQueueSize` parameter
|
||||
and should be larger than the `OIDBatchSize` parameter to avoid blocking
|
||||
the downloading routines.
|
||||
|
||||
Once all blocks available in the NeoFS container are processed, the service
|
||||
shuts down automatically.
|
||||
|
||||
### NeoFS block uploading command
|
||||
The `util upload-bin` command is designed to fetch blocks from the RPC node and upload
|
||||
them to the NeoFS container. It also creates and uploads index files. Below is an
|
||||
example usage of the command:
|
||||
|
||||
```shell
|
||||
./bin/neo-go util upload-bin --cid 9iVfUg8aDHKjPC4LhQXEkVUM4HDkR7UCXYLs8NQwYfSG --wallet-config ./wallet-config.yml --block-attribute Block --index-attribute Index --rpc-endpoint https://rpc.t5.n3.nspcc.ru:20331 -fsr st1.t5.fs.neo.org:8080 -fsr st2.t5.fs.neo.org:8080 -fsr st3.t5.fs.neo.org:8080
|
||||
```
|
||||
|
||||
Run `./bin/neo-go util upload-bin --help` to see the full list of supported options.
|
||||
|
||||
This command works as follows:
|
||||
1. Fetches the current block height from the RPC node.
|
||||
2. Searches for the index files stored in NeoFS.
|
||||
3. Searches for the stored blocks from the latest incomplete index file.
|
||||
4. Fetches missing blocks from the RPC node and uploads them to the NeoFS container.
|
||||
5. After uploading the blocks, it creates index file based on the uploaded block OIDs.
|
||||
6. Uploads the created index file to the NeoFS container.
|
||||
7. Repeats steps 4-6 until the current block height is reached.
|
||||
|
||||
If the command is interrupted, it can be resumed. It starts the uploading process
|
||||
from the last uploaded index file.
|
||||
|
||||
For a given block sequence, only one type of index file is supported. If new index
|
||||
files are needed (different `index-file-size` or `index-attribute`), `upload-bin`
|
||||
will upload the entire block sequence starting from genesis since no migration is
|
||||
supported yet by this command. Please, add a comment to the
|
||||
[#3744](https://github.com/nspcc-dev/neo-go/issues/3744) issue if you need this
|
||||
functionality.
|
||||
|
||||
### NeoFS state uploading command
|
||||
The `util upload-state` command is used to start a node, traverse the MPT over the
|
||||
smart contract storage, and upload MPT nodes to a NeoFS container at every
|
||||
`StateSyncInterval` number of blocks. Below is an example usage of the command:
|
||||
|
||||
```shell
|
||||
./bin/neo-go util upload-state --cid 9iVfUg8aDHKjPC4LhQXEkVUM4HDkR7UCXYLs8NQwYfSG --wallet-config ./wallet-config.yml --state-attribute State -m -fsr st1.t5.fs.neo.org:8080 -fsr st2.t5.fs.neo.org:8080 -fsr st3.t5.fs.neo.org:8080
|
||||
```
|
||||
|
||||
Run `./bin/neo-go util upload-state --help` to see the full list of supported options.
|
||||
|
||||
This command works as follows:
|
||||
1. Searches for the state objects stored in NeoFS to find the latest uploaded object.
|
||||
2. Checks if new state objects could be uploaded given the current local state height.
|
||||
3. Traverses the MPT nodes (pre-order) starting from the stateroot at the height of the
|
||||
latest uploaded state object down to its children.
|
||||
4. Uploads the MPT nodes to the NeoFS container.
|
||||
5. Repeats steps 3-4 with a step equal to the `StateSyncInterval` number of blocks.
|
||||
|
||||
If the command is interrupted, it can be resumed. It starts the uploading process
|
||||
from the last uploaded state object.
|
|
@ -18,9 +18,10 @@ node-related settings described in the table below.
|
|||
| --- | --- | --- | --- |
|
||||
| DBConfiguration | [DB Configuration](#DB-Configuration) | | Describes configuration for database. See the [DB Configuration](#DB-Configuration) section for details. |
|
||||
| LogLevel | `string` | "info" | Minimal logged messages level (can be "debug", "info", "warn", "error", "dpanic", "panic" or "fatal"). |
|
||||
| GarbageCollectionPeriod | `uint32` | 10000 | Controls MPT garbage collection interval (in blocks) for configurations with `RemoveUntraceableBlocks` enabled and `KeepOnlyLatestState` disabled. In this mode the node stores a number of MPT trees (corresponding to `MaxTraceableBlocks` and `StateSyncInterval`), but the DB needs to be clean from old entries from time to time. Doing it too often will cause too much processing overhead, doing it too rarely will leave more useless data in the DB. |
|
||||
| GarbageCollectionPeriod | `uint32` | 10000 | Controls MPT garbage collection interval (in blocks) for configurations with `RemoveUntraceableBlocks` enabled and `KeepOnlyLatestState` disabled. In this mode the node stores a number of MPT trees (corresponding to `MaxTraceableBlocks` and `StateSyncInterval`), but the DB needs to be clean from old entries from time to time. Doing it too often will cause too much processing overhead (it requires going through the whole DB which can take minutes), doing it too rarely will leave more useless data in the DB. Always compare this to `MaxTraceableBlocks`, values lower than 10% of it are likely too low, values higher than 50% are likely to leave more garbage than is possible to collect. The default value is more aligned with NeoFS networks that have low MTB values, but for N3 mainnet it's too low. |
|
||||
| KeepOnlyLatestState | `bool` | `false` | Specifies if MPT should only store the latest state (or a set of latest states, see `P2PStateExchangeExtensions` section in the ProtocolConfiguration for details). If true, DB size will be smaller, but older roots won't be accessible. This value should remain the same for the same database. | |
|
||||
| LogPath | `string` | "", so only console logging | File path where to store node logs. |
|
||||
| NeoFSBlockFetcher | [NeoFS BlockFetcher Configuration](#NeoFS-BlockFetcher-Configuration) | | NeoFS BlockFetcher module configuration. See the [NeoFS BlockFetcher Configuration](#NeoFS-BlockFetcher-Configuration) section for details. |
|
||||
| Oracle | [Oracle Configuration](#Oracle-Configuration) | | Oracle module configuration. See the [Oracle Configuration](#Oracle-Configuration) section for details. |
|
||||
| P2P | [P2P Configuration](#P2P-Configuration) | | Configuration values for P2P network interaction. See the [P2P Configuration](#P2P-Configuration) section for details. |
|
||||
| P2PNotary | [P2P Notary Configuration](#P2P-Notary-Configuration) | | P2P Notary module configuration. See the [P2P Notary Configuration](#P2P-Notary-Configuration) section for details. |
|
||||
|
@ -29,10 +30,12 @@ node-related settings described in the table below.
|
|||
| Relay | `bool` | `true` | Determines whether the server is forwarding its inventory. |
|
||||
| Consensus | [Consensus Configuration](#Consensus-Configuration) | | Describes consensus (dBFT) configuration. See the [Consensus Configuration](#Consensus-Configuration) for details. |
|
||||
| RemoveUntraceableBlocks | `bool`| `false` | Denotes whether old blocks should be removed from cache and database. If enabled, then only the last `MaxTraceableBlocks` are stored and accessible to smart contracts. Old MPT data is also deleted in accordance with `GarbageCollectionPeriod` setting. If enabled along with `P2PStateExchangeExtensions` protocol extension, then old blocks and MPT states will be removed up to the second latest state synchronisation point (see `StateSyncInterval`). |
|
||||
| RemoveUntraceableHeaders | `bool`| `false` | Used only with RemoveUntraceableBlocks and makes node delete untraceable block headers as well. Notice that this is an experimental option, not recommended for production use. |
|
||||
| RPC | [RPC Configuration](#RPC-Configuration) | | Describes [RPC subsystem](rpc.md) configuration. See the [RPC Configuration](#RPC-Configuration) for details. |
|
||||
| SaveStorageBatch | `bool` | `false` | Enables storage batch saving before every persist. It is similar to StorageDump plugin for C# node. |
|
||||
| SkipBlockVerification | `bool` | `false` | Allows to disable verification of received/processed blocks (including cryptographic checks). |
|
||||
| StateRoot | [State Root Configuration](#State-Root-Configuration) | | State root module configuration. See the [State Root Configuration](#State-Root-Configuration) section for details. |
|
||||
| SaveInvocations | `bool` | `false` | Determines if additional smart contract invocation details are stored. If enabled, the `getapplicationlog` RPC method will return a new field with invocation details for the transaction. See the [RPC](rpc.md#applicationlog-invocations) documentation for more information. |
|
||||
|
||||
### P2P Configuration
|
||||
|
||||
|
@ -98,7 +101,12 @@ DBConfiguration:
|
|||
```
|
||||
where:
|
||||
- `Type` is the database type (string value). Supported types: `leveldb`, `boltdb` and
|
||||
`inmemory` (not recommended for production usage).
|
||||
`inmemory` (not recommended for production usage). LevelDB is better for archive nodes
|
||||
that store all data, it better deals with writes in general, but any tail-cutting node
|
||||
options seriously degrade its performance. BoltDB works much better in various
|
||||
performance tests, however it can seriously degrade GC in case the DB size is bigger
|
||||
than the amount of available memory. BoltDB is also more memory-demanding for some
|
||||
operations, so GC can be problematic from that angle as well.
|
||||
- `LevelDBOptions` are settings for LevelDB. Includes the DB files path and ReadOnly mode toggle.
|
||||
If ReadOnly mode is on, then an error will be returned on attempt to connect to unexisting or empty
|
||||
database. Database doesn't allow changes in this mode, a warning will be logged on DB persist attempts.
|
||||
|
@ -153,6 +161,59 @@ where:
|
|||
Please, refer to the [Notary module documentation](./notary.md#Notary node module) for
|
||||
details on module features.
|
||||
|
||||
### NeoFS BlockFetcher Configuration
|
||||
|
||||
`NeoFSBlockFetcher` configuration section contains settings for NeoFS
|
||||
BlockFetcher module and has the following structure:
|
||||
```
|
||||
NeoFSBlockFetcher:
|
||||
Enabled: true
|
||||
UnlockWallet:
|
||||
Path: "./wallet.json"
|
||||
Password: "pass"
|
||||
Addresses:
|
||||
- st1.storage.fs.neo.org:8080
|
||||
- st2.storage.fs.neo.org:8080
|
||||
- st3.storage.fs.neo.org:8080
|
||||
- st4.storage.fs.neo.org:8080
|
||||
Timeout: 10m
|
||||
DownloaderWorkersCount: 500
|
||||
OIDBatchSize: 8000
|
||||
BQueueSize: 16000
|
||||
SkipIndexFilesSearch: false
|
||||
ContainerID: "7a1cn9LNmAcHjESKWxRGG7RSZ55YHJF6z2xDLTCuTZ6c"
|
||||
BlockAttribute: "Block"
|
||||
IndexFileAttribute: "Index"
|
||||
IndexFileSize: 128000
|
||||
```
|
||||
where:
|
||||
- `Enabled` enables NeoFS BlockFetcher module.
|
||||
- `UnlockWallet` contains wallet settings to retrieve account to sign requests to
|
||||
NeoFS. Without this setting, the module will use randomly generated private key.
|
||||
For configuration details see [Unlock Wallet Configuration](#Unlock-Wallet-Configuration)
|
||||
- `Addresses` is a list of NeoFS storage nodes addresses. This parameter is required.
|
||||
- `Timeout` is a timeout for a single request to NeoFS storage node (10 minutes by
|
||||
default).
|
||||
- `ContainerID` is a container ID to fetch blocks from. This parameter is required.
|
||||
- `BlockAttribute` is an attribute name of NeoFS object that contains block
|
||||
data. It's set to `Block` by default.
|
||||
- `IndexFileAttribute` is an attribute name of NeoFS index object that contains block
|
||||
object IDs. It's set to `Index` by default.
|
||||
- `DownloaderWorkersCount` is a number of workers that download blocks from
|
||||
NeoFS in parallel (500 by default).
|
||||
- `OIDBatchSize` is the number of blocks to search per a single request to NeoFS
|
||||
in case of disabled index files search. Also, for both modes of BlockFetcher
|
||||
operation this setting manages the buffer size of OIDs and blocks transferring
|
||||
channels. By default, it's set to a half of `BQueueSize` parameter.
|
||||
- `BQueueSize` is a size of the block queue used to manage consecutive blocks
|
||||
addition to the chain. It must be larger than `OIDBatchSize` and highly recommended
|
||||
to be `2*OIDBatchSize` or `3*OIDBatchSize`. By default, it's set to 16000.
|
||||
- `SkipIndexFilesSearch` is a flag that allows to skip index files search and search
|
||||
for blocks directly. It is set to `false` by default.
|
||||
- `IndexFileSize` is the number of OID objects stored in the index files. This
|
||||
setting depends on the NeoFS block storage configuration and is applicable only if
|
||||
`SkipIndexFilesSearch` is set to `false`. It's set to 128000 by default.
|
||||
|
||||
### Metrics Services Configuration
|
||||
|
||||
Metrics services configuration describes options for metrics services (pprof,
|
||||
|
@ -190,6 +251,7 @@ RPC:
|
|||
MaxRequestBodyBytes: 5242880
|
||||
MaxRequestHeaderBytes: 1048576
|
||||
MaxWebSocketClients: 64
|
||||
MaxWebSocketFeeds: 16
|
||||
SessionEnabled: false
|
||||
SessionExpirationTime: 15
|
||||
SessionBackedByMPT: false
|
||||
|
@ -235,6 +297,9 @@ where:
|
|||
number (64 by default). Attempts to establish additional connections will
|
||||
lead to websocket handshake failures. Use "-1" to disable websocket
|
||||
connections (0 will lead to using the default value).
|
||||
- `MaxWebSocketFeeds` -- the maximum simultaneous event subscriptions number
|
||||
for a single client (16 by default). Attemps to create additional subscriptions
|
||||
will lead to error.
|
||||
- `SessionEnabled` denotes whether session-based iterator JSON-RPC API is enabled.
|
||||
If true, then all iterators got from `invoke*` calls will be stored as sessions
|
||||
on the server side available for further traverse. `traverseiterator` and
|
||||
|
@ -250,8 +315,8 @@ where:
|
|||
enable `SessionBackedByMPT`, see `SessionBackedByMPT` documentation for more
|
||||
details.
|
||||
- `SessionExpirationTime` is a lifetime of iterator session in seconds. It is set
|
||||
to `TimePerBlock` seconds by default and is relevant only if `SessionEnabled`
|
||||
is set to `true`.
|
||||
to `TimePerBlock` seconds (but not less than 5s) by default and is relevant
|
||||
only if `SessionEnabled` is set to `true`.
|
||||
- `SessionBackedByMPT` is a flag forcing JSON-RPC server into using MPT-backed
|
||||
storage for delayed iterator traversal. If `true`, then iterator resources got
|
||||
after `invoke*` calls will be released immediately. Further iterator traversing
|
||||
|
@ -332,7 +397,7 @@ protocol-related settings described in the table below.
|
|||
| --- | --- | --- | --- | --- |
|
||||
| CommitteeHistory | map[uint32]uint32 | none | Number of committee members after the given height, for example `{0: 1, 20: 4}` sets up a chain with one committee member since the genesis and then changes the setting to 4 committee members at the height of 20. `StandbyCommittee` committee setting must have the number of keys equal or exceeding the highest value in this option. Blocks numbers where the change happens must be divisible by the old and by the new values simultaneously. If not set, committee size is derived from the `StandbyCommittee` setting and never changes. |
|
||||
| Genesis | [Genesis](#Genesis-Configuration) | none | The set of genesis block settings including NeoGo-specific protocol extensions that should be enabled at the genesis block or during native contracts initialisation. |
|
||||
| Hardforks | `map[string]uint32` | [] | The set of incompatible changes that affect node behaviour starting from the specified height. The default value is an empty set which should be interpreted as "each known hard-fork is applied from the zero blockchain height". The list of valid hard-fork names:<br>• `Aspidochelone` represents hard-fork introduced in [#2469](https://github.com/nspcc-dev/neo-go/pull/2469) (ported from the [reference](https://github.com/neo-project/neo/pull/2712)). It adjusts the prices of `System.Contract.CreateStandardAccount` and `System.Contract.CreateMultisigAccount` interops so that the resulting prices are in accordance with `sha256` method of native `CryptoLib` contract. It also includes [#2519](https://github.com/nspcc-dev/neo-go/pull/2519) (ported from the [reference](https://github.com/neo-project/neo/pull/2749)) that adjusts the price of `System.Runtime.GetRandom` interop and fixes its vulnerability. A special NeoGo-specific change is included as well for ContractManagement's update/deploy call flags behaviour to be compatible with pre-0.99.0 behaviour that was changed because of the [3.2.0 protocol change](https://github.com/neo-project/neo/pull/2653).<br>• `Basilisk` represents hard-fork introduced in [#3056](https://github.com/nspcc-dev/neo-go/pull/3056) (ported from the [reference](https://github.com/neo-project/neo/pull/2881)). It enables strict smart contract script check against a set of JMP instructions and against method boundaries enabled on contract deploy or update. It also includes [#3080](https://github.com/nspcc-dev/neo-go/pull/3080) (ported from the [reference](https://github.com/neo-project/neo/pull/2883)) that increases `stackitem.Integer` JSON parsing precision up to the maximum value supported by the NeoVM. It also includes [#3085](https://github.com/nspcc-dev/neo-go/pull/3085) (ported from the [reference](https://github.com/neo-project/neo/pull/2810)) that enables strict check for notifications emitted by a contract to precisely match the events specified in the contract manifest. <br>• `Cockatrice` represents hard-fork introduced in [#3402](https://github.com/nspcc-dev/neo-go/pull/3402) (ported from the [reference](https://github.com/neo-project/neo/pull/2942)). Initially it is introduced along with the ability to update native contracts. This hard-fork also includes a couple of new native smart contract APIs: `keccak256` of native CryptoLib contract introduced in [#3301](https://github.com/nspcc-dev/neo-go/pull/3301) (ported from the [reference](https://github.com/neo-project/neo/pull/2925)) and `getCommitteeAddress` of native NeoToken contract inctroduced in [#3362](https://github.com/nspcc-dev/neo-go/pull/3362) (ported from the [reference](https://github.com/neo-project/neo/pull/3154)). |
|
||||
| Hardforks | `map[string]uint32` | [] | The set of incompatible changes that affect node behaviour starting from the specified height. The default value is an empty set which should be interpreted as "each known stable hard-fork is applied from the zero blockchain height". See [Hardforks](#Hardforks) section for a list of supported keys. |
|
||||
| Magic | `uint32` | `0` | Magic number which uniquely identifies Neo network. |
|
||||
| MaxBlockSize | `uint32` | `262144` | Maximum block size in bytes. |
|
||||
| MaxBlockSystemFee | `int64` | `900000000000` | Maximum overall transactions system fee per block. |
|
||||
|
@ -403,3 +468,40 @@ where:
|
|||
|
||||
Note that `Transaction` is a NeoGo extension that isn't supported by the NeoC#
|
||||
node and must be disabled on the public Neo N3 networks.
|
||||
|
||||
### Hardforks
|
||||
|
||||
The latest stable hardfork as per 0.107.1 release is Domovoi. Echidna is still
|
||||
in development and can change in an incompatible way.
|
||||
|
||||
| Name | Changes | References |
|
||||
| --- | --- | --- |
|
||||
| `Aspidochelone` | Adjusts the price of `System.Contract.CreateStandardAccount` and `System.Contract.CreateMultisigAccount` interops so that the resulting prices are in accordance with `sha256` method of native `CryptoLib` contract. Also adjusts the price of `System.Runtime.GetRandom` interop and fixes its vulnerability. A special NeoGo-specific change is included as well for ContractManagement's update/deploy call flags behaviour to be compatible with pre-0.99.0 behaviour that was changed because of the 3.2.0 protocol change | https://github.com/nspcc-dev/neo-go/pull/2469 <br> https://github.com/neo-project/neo/pull/2712 <br> https://github.com/nspcc-dev/neo-go/pull/2519 <br> https://github.com/neo-project/neo/pull/2749 <br> https://github.com/neo-project/neo/pull/2653 |
|
||||
| `Basilisk` | Enables strict smart contract script check against a set of JMP instructions and against method boundaries enabled on contract deploy or update. Increases `stackitem.Integer` JSON parsing precision up to the maximum value supported by the NeoVM. Enables strict check for notifications emitted by a contract to precisely match the events specified in the contract manifest. | https://github.com/nspcc-dev/neo-go/pull/3056 <br> https://github.com/neo-project/neo/pull/2881 <br> https://github.com/nspcc-dev/neo-go/pull/3080 <br> https://github.com/neo-project/neo/pull/2883 <br> https://github.com/nspcc-dev/neo-go/pull/3085 <br> https://github.com/neo-project/neo/pull/2810 |
|
||||
| `Cockatrice` | Introduces the ability to update native contracts. Includes a couple of new native smart contract APIs: `keccak256` of native CryptoLib contract and `getCommitteeAddress` of native NeoToken contract. | https://github.com/nspcc-dev/neo-go/pull/3402 <br> https://github.com/neo-project/neo/pull/2942 <br> https://github.com/nspcc-dev/neo-go/pull/3301 <br> https://github.com/neo-project/neo/pull/2925 <br> https://github.com/nspcc-dev/neo-go/pull/3362 <br> https://github.com/neo-project/neo/pull/3154 |
|
||||
| `Domovoi` | Makes node use executing contract state for the contract call permissions check instead of the state stored in the native Management contract. In C# also makes System.Runtime.GetNotifications interop properly count stack references of notification parameters which prevents users from creating objects that exceed MaxStackSize constraint, but NeoGo has never had this bug, thus proper behaviour is preserved even before HFDomovoi. It results in the fact that some T5 testnet transactions have different ApplicationLogs compared to the C# node, but the node states match. | https://github.com/nspcc-dev/neo-go/pull/3476 <br> https://github.com/neo-project/neo/pull/3290 <br> https://github.com/nspcc-dev/neo-go/pull/3473 <br> https://github.com/neo-project/neo/pull/3290 <br> https://github.com/neo-project/neo/pull/3301 <br> https://github.com/nspcc-dev/neo-go/pull/3485 |
|
||||
| `Echidna` | Introduces `Designation` event extension with `Old` and `New` roles data to native RoleManagement contract. Adds support for `base64UrlEncode` and `base64UrlDecode` methods to native StdLib contract. Extends the list of required call flags for `registerCandidate`, `unregisterCandidate`and `vote` methods of native NeoToken contract with AllowNotify flag. Enables `onNEP17Payment` method of NEO contract for candidate registration. Introduces constraint for maximum number of execution notifications. | https://github.com/nspcc-dev/neo-go/pull/3554 <br> https://github.com/nspcc-dev/neo-go/pull/3761 <br> https://github.com/nspcc-dev/neo-go/pull/3554 <br> https://github.com/neo-project/neo/pull/3597 <br> https://github.com/nspcc-dev/neo-go/pull/3700 <br> https://github.com/nspcc-dev/neo-go/pull/3640 <br> https://github.com/neo-project/neo/pull/3548 |
|
||||
|
||||
|
||||
## DB compatibility
|
||||
|
||||
Real networks with large number of blocks require a substantial amount of time
|
||||
to synchronize. When operating a number of node instances with similar
|
||||
configurations you may want to save some resources by performing synchronization
|
||||
on one node and then copying the DB over to other instances. In general, this
|
||||
can be done and this is supported, but NeoGo has a lot of options that may
|
||||
affect this:
|
||||
- any differences in `ProtocolConfiguration` section make (or may make) databases
|
||||
incompatible, except for `MemPoolSize`, `P2PNotaryRequestPayloadPoolSize`,
|
||||
`SeedList`, `TimePerBlock`.
|
||||
Protocol configuration is expected to be the same on all nodes of the same
|
||||
network, so don't touch it unless you know what you're doing.
|
||||
- DB types (Level/Bolt) must be the same
|
||||
- `GarbageCollectionPeriod` must be the same
|
||||
- `KeepOnlyLatestState` must be the same
|
||||
- `RemoveUntraceableBlocks` must be the same
|
||||
- `SaveInvocations` must be the same
|
||||
|
||||
BotlDB is also known to be incompatible between machines with different
|
||||
endianness. Nothing is known for LevelDB wrt this, so it's not recommended
|
||||
to copy it this way too.
|
||||
|
|
|
@ -16,7 +16,8 @@ Currently supported events:
|
|||
Contents: transaction. Filters: sender and signer.
|
||||
* notification generated during execution
|
||||
|
||||
Contents: container hash, contract hash, notification name, stack item. Filters: contract hash, notification name.
|
||||
Contents: container hash, contract hash, notification name, stack item.
|
||||
Filters: contract hash, notification name, notification parameters.
|
||||
* transaction/persisting script executed
|
||||
|
||||
Contents: application execution result. Filters: VM state, script container hash.
|
||||
|
@ -57,6 +58,9 @@ method. Upon successful subscription, clients receive subscription ID for
|
|||
subsequent management of this subscription. Subscription is only valid for
|
||||
connection lifetime, no long-term client identification is being made.
|
||||
|
||||
The maximum number of simultaneous subscriptions can be set server-side
|
||||
via `MaxWebSocketFeeds` setting.
|
||||
|
||||
Errors are not described down below, but they can be returned as standard
|
||||
JSON-RPC errors (most often caused by invalid parameters).
|
||||
|
||||
|
@ -84,9 +88,15 @@ Recognized stream names:
|
|||
format for one of transaction's `Signers`.
|
||||
* `notification_from_execution`
|
||||
Filter: `contract` field containing a string with hex-encoded Uint160 (LE
|
||||
representation) and/or `name` field containing a string with execution
|
||||
representation), `name` field containing a string with execution
|
||||
notification name which should be a valid UTF-8 string not longer than
|
||||
32 bytes.
|
||||
32 bytes and/or `parameters` field containing an ordered array of structs
|
||||
with `type` and `value` fields. Not more than 16 parameters are accepted.
|
||||
Parameter's `type` must be not-a-complex type from the list: `Any`,
|
||||
`Boolean`, `Integer`, `ByteArray`, `String`, `Hash160`, `Hash256`, `PublicKey`
|
||||
or `Signature`. Filter that allows any parameter must be omitted or must
|
||||
be `Any` typed with zero value. It is prohibited to have `parameters` be
|
||||
filled with `Any` types only.
|
||||
* `transaction_executed`
|
||||
Filter: `state` field containing `HALT` or `FAULT` string for successful
|
||||
and failed executions respectively and/or `container` field containing
|
||||
|
|
74
docs/rpc.md
74
docs/rpc.md
|
@ -207,6 +207,15 @@ the error-free C# response that provides a default result.
|
|||
NeoGo can generate an error in response to an invalid proof, unlike
|
||||
the error-free C# implementation.
|
||||
|
||||
##### `getPeers`
|
||||
|
||||
NeoGo extends the `getpeers` RPC call to return the user agent
|
||||
(`useragent` JSON field) and last known block height
|
||||
(`lastknownheight` JSON field) for each connected peer where available.
|
||||
The last known block height field may be stale depending on the
|
||||
PingInterval node config and the time since the last ping.
|
||||
Ping behavior may also differ between node implementations.
|
||||
|
||||
### Unsupported methods
|
||||
|
||||
Methods listed below are not going to be supported for various reasons
|
||||
|
@ -239,6 +248,15 @@ block. It can be removed in future versions, but at the moment you can use it
|
|||
to see how much GAS is burned with a particular block (because system fees are
|
||||
burned).
|
||||
|
||||
#### `getblocknotifications` call
|
||||
|
||||
This method returns notifications from a block organized by trigger type.
|
||||
Supports filtering by contract and event name (the same filter as provided
|
||||
for subscriptions to execution results, see [notifications specification](notifications.md).
|
||||
The resulting JSON is an object with three (if matched) field: "onpersist",
|
||||
"application" and "postpersist" containing arrays of notifications (same JSON
|
||||
as used in notification service) for the respective triggers.
|
||||
|
||||
#### Historic calls
|
||||
|
||||
A set of `*historic` extension methods provide the ability of interacting with
|
||||
|
@ -347,6 +365,62 @@ to various blockchain events (with simple event filtering) and receive them on
|
|||
the client as JSON-RPC notifications. More details on that are written in the
|
||||
[notifications specification](notifications.md).
|
||||
|
||||
#### `applicationlog` call invocations
|
||||
|
||||
The `SaveInvocations` configuration setting causes the RPC server to store smart contract
|
||||
invocation details as part of the application logs. This feature is specifically useful to
|
||||
capture information in the absence of `System.Runtime.Notify` calls for the given smart
|
||||
contract method. Other use-cases are described in [this issue](https://github.com/neo-project/neo/issues/3386).
|
||||
|
||||
Example transaction on Testnet which interacts with the native PolicyContract:
|
||||
```json
|
||||
{
|
||||
"id": 1,
|
||||
"jsonrpc": "2.0",
|
||||
"result": {
|
||||
"txid": "0xd6fe5f61d9cb34d6324db1be42c056d02ba1f1f6cd0bd3f3c6bb24faaaeef2a9",
|
||||
"executions": [
|
||||
{
|
||||
"trigger": "Application",
|
||||
"vmstate": "HALT",
|
||||
"gasconsumed": "2028120",
|
||||
"stack": [
|
||||
{
|
||||
"type": "Any"
|
||||
}
|
||||
],
|
||||
"notifications": [],
|
||||
"exception": null,
|
||||
"invocations": [
|
||||
{
|
||||
"hash": "0xcc5e4edd9f5f8dba8bb65734541df7a1c081c67b",
|
||||
"method": "setFeePerByte",
|
||||
"arguments": {
|
||||
"type": "Array",
|
||||
"value": [
|
||||
{
|
||||
"type": "Integer",
|
||||
"value": "100"
|
||||
}
|
||||
]
|
||||
},
|
||||
"argumentscount": 1,
|
||||
"truncated": false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
For security reasons the `arguments` field data may result in `null` if the count exceeds 2048.
|
||||
In such case the `Truncated` field will be set to `true`.
|
||||
|
||||
The invocation records are presented in a flat structure in the order as how they were executed.
|
||||
Note that invocation records for faulted transactions are kept and are present in the
|
||||
applicationlog. This behaviour differs from notifications which are omitted for faulted transactions.
|
||||
|
||||
## Reference
|
||||
|
||||
* [JSON-RPC 2.0 Specification](http://www.jsonrpc.org/specification)
|
||||
|
|
10
docs/vm.md
10
docs/vm.md
|
@ -105,13 +105,13 @@ READY: loaded 36 instructions
|
|||
To make it even more complete, you can directly load hex or base64 strings into the VM:
|
||||
|
||||
```
|
||||
NEO-GO-VM > loadhex 54c56b006c766b00527ac46c766b00c391640b006203005a616c756662030000616c7566
|
||||
READY: loaded 36 instructions
|
||||
NEO-GO-VM > run
|
||||
NEO-GO-VM > loadhex 0c0c48656c6c6f20776f726c6421
|
||||
READY: loaded 14 instructions
|
||||
NEO-GO-VM 0 > run
|
||||
[
|
||||
{
|
||||
"value": 10,
|
||||
"type": "BigInteger"
|
||||
"type": "ByteString",
|
||||
"value": "SGVsbG8gd29ybGQh"
|
||||
}
|
||||
]
|
||||
|
||||
|
|
|
@ -25,8 +25,8 @@ See the table below for the detailed examples description.
|
|||
| [engine](engine) | This contract demonstrates how to use `runtime` interop package which implements an API for `System.Runtime.*` Neo system calls. Please, refer to the `runtime` [package documentation](../pkg/interop/doc.go) for details. |
|
||||
| [events](events) | The contract shows how execution notifications with the different arguments types can be sent with the help of `runtime.Notify` function of the `runtime` interop package. Please, refer to the `runtime.Notify` [function documentation](../pkg/interop/runtime/runtime.go) for details. |
|
||||
| [iterator](iterator) | This example describes a way to work with Neo iterators. Please, refer to the `iterator` [package documentation](../pkg/interop/iterator/iterator.go) for details. |
|
||||
| [nft-d](nft-d) | NEP-11 divisible NFT. See NEP-11 token standard [specification](https://github.com/neo-project/proposals/blob/master/nep-11.mediawiki) for details. |
|
||||
| [nft-nd](nft-nd) | NEP-11 non-divisible NFT. See NEP-11 token standard [specification](https://github.com/neo-project/proposals/blob/master/nep-11.mediawiki) for details. |
|
||||
| [nft-d](nft-d) | NEP-11 divisible NFT. This contract implements the NEP-11 and the NEP-24 token standards. See NEP-11 token standard [specification](https://github.com/neo-project/proposals/blob/master/nep-11.mediawiki) and NEP-24 [specification](https://github.com/neo-project/proposals/blob/master/nep-24.mediawiki) for details. |
|
||||
| [nft-nd](nft-nd) | NEP-11 non-divisible NFT. This contract implements the NEP-11 and the NEP-24 token standards. See NEP-11 token standard [specification](https://github.com/neo-project/proposals/blob/master/nep-11.mediawiki) and NEP-24 [specification](https://github.com/neo-project/proposals/blob/master/nep-24.mediawiki) for details. |
|
||||
| [nft-nd-nns](nft-nd-nns) | Neo Name Service contract which is NEP-11 non-divisible NFT. The contract implements methods for Neo domain name system managing such as domains registration/transferring, records addition and names resolving. The package also contains tests implemented with [neotest](https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/neotest). |
|
||||
| [oracle](oracle) | Oracle demo contract exposing two methods that you can use to process URLs. It uses oracle native contract, see [interop package documentation](../pkg/interop/native/oracle/oracle.go) also. |
|
||||
| [runtime](runtime) | This contract demonstrates how to use special `_initialize` and `_deploy` methods. See the [compiler documentation](../docs/compiler.md#vm-api-interop-layer ) for methods details. It also shows the pattern for checking owner witness inside the contract with the help of `runtime.CheckWitness` interop [function](../pkg/interop/runtime/runtime.go). |
|
||||
|
|
|
@ -4,25 +4,25 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||
)
|
||||
|
||||
// NotifyScriptContainer sends runtime notification with script container hash
|
||||
// NotifyScriptContainer sends runtime notification with script container hash.
|
||||
func NotifyScriptContainer() {
|
||||
tx := runtime.GetScriptContainer()
|
||||
runtime.Notify("Tx", tx.Hash)
|
||||
}
|
||||
|
||||
// NotifyCallingScriptHash sends runtime notification with calling script hash
|
||||
// NotifyCallingScriptHash sends runtime notification with calling script hash.
|
||||
func NotifyCallingScriptHash() {
|
||||
callingScriptHash := runtime.GetCallingScriptHash()
|
||||
runtime.Notify("Calling", callingScriptHash)
|
||||
}
|
||||
|
||||
// NotifyExecutingScriptHash sends runtime notification about executing script hash
|
||||
// NotifyExecutingScriptHash sends runtime notification about executing script hash.
|
||||
func NotifyExecutingScriptHash() {
|
||||
execScriptHash := runtime.GetExecutingScriptHash()
|
||||
runtime.Notify("Executing", execScriptHash)
|
||||
}
|
||||
|
||||
// NotifyEntryScriptHash sends notification about entry script hash
|
||||
// NotifyEntryScriptHash sends notification about entry script hash.
|
||||
func NotifyEntryScriptHash() {
|
||||
entryScriptHash := runtime.GetEntryScriptHash()
|
||||
runtime.Notify("Entry", entryScriptHash)
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
module github.com/nspcc-dev/neo-go/examples/engine
|
||||
|
||||
go 1.20
|
||||
go 1.22
|
||||
|
||||
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240521091047-78685785716d
|
||||
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20241223145456-80e18222bca2
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240521091047-78685785716d h1:Vcb7YkZuUSSIC+WF/xV3UDfHbAxZgyT2zGleJP3Ig5k=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240521091047-78685785716d/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20241223145456-80e18222bca2 h1:4Bfi6A1kPpaTDuwbDVc6x+R4WXgoNN9wIq6XobDlXHs=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20241223145456-80e18222bca2/go.mod h1:kVLzmbeJJdbIPF2bUYhD8YppIiLXnRQj5yqNZvzbOL0=
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
module github.com/nspcc-dev/neo-go/examples/events
|
||||
|
||||
go 1.20
|
||||
go 1.22
|
||||
|
||||
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240521091047-78685785716d
|
||||
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20241223145456-80e18222bca2
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240521091047-78685785716d h1:Vcb7YkZuUSSIC+WF/xV3UDfHbAxZgyT2zGleJP3Ig5k=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240521091047-78685785716d/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20241223145456-80e18222bca2 h1:4Bfi6A1kPpaTDuwbDVc6x+R4WXgoNN9wIq6XobDlXHs=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20241223145456-80e18222bca2/go.mod h1:kVLzmbeJJdbIPF2bUYhD8YppIiLXnRQj5yqNZvzbOL0=
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
module github.com/nspcc-dev/neo-go/examples/iterator
|
||||
|
||||
go 1.20
|
||||
go 1.22
|
||||
|
||||
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240521091047-78685785716d
|
||||
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20241223145456-80e18222bca2
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240521091047-78685785716d h1:Vcb7YkZuUSSIC+WF/xV3UDfHbAxZgyT2zGleJP3Ig5k=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240521091047-78685785716d/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20241223145456-80e18222bca2 h1:4Bfi6A1kPpaTDuwbDVc6x+R4WXgoNN9wIq6XobDlXHs=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20241223145456-80e18222bca2/go.mod h1:kVLzmbeJJdbIPF2bUYhD8YppIiLXnRQj5yqNZvzbOL0=
|
||||
|
|
|
@ -7,7 +7,7 @@ import (
|
|||
)
|
||||
|
||||
// _deploy primes contract's storage with some data to be used later.
|
||||
func _deploy(_ any, _ bool) {
|
||||
func _deploy(_ any, _ bool) { // nolint: unused
|
||||
ctx := storage.GetContext() // RW context.
|
||||
storage.Put(ctx, "foo1", "1")
|
||||
storage.Put(ctx, "foo2", "2")
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
module github.com/nspcc-dev/neo-go/examples/nft
|
||||
|
||||
go 1.20
|
||||
go 1.22
|
||||
|
||||
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240521091047-78685785716d
|
||||
require github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20241223145456-80e18222bca2
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240521091047-78685785716d h1:Vcb7YkZuUSSIC+WF/xV3UDfHbAxZgyT2zGleJP3Ig5k=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20240521091047-78685785716d/go.mod h1:/vrbWSHc7YS1KSYhVOyyeucXW/e+1DkVBOgnBEXUCeY=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20241223145456-80e18222bca2 h1:4Bfi6A1kPpaTDuwbDVc6x+R4WXgoNN9wIq6XobDlXHs=
|
||||
github.com/nspcc-dev/neo-go/pkg/interop v0.0.0-20241223145456-80e18222bca2/go.mod h1:kVLzmbeJJdbIPF2bUYhD8YppIiLXnRQj5yqNZvzbOL0=
|
||||
|
|
|
@ -425,3 +425,43 @@ func Update(nef, manifest []byte) {
|
|||
}
|
||||
management.Update(nef, manifest)
|
||||
}
|
||||
|
||||
// RoyaltyRecipient contains information about the recipient and the royalty amount.
|
||||
type RoyaltyRecipient struct {
|
||||
Address interop.Hash160
|
||||
Amount int
|
||||
}
|
||||
|
||||
// RoyaltyInfo returns a list of royalty recipients and the corresponding royalty amounts.
|
||||
func RoyaltyInfo(tokenID []byte, royaltyToken interop.Hash160, salePrice int) []RoyaltyRecipient {
|
||||
if salePrice <= 0 {
|
||||
panic("sale price must be positive")
|
||||
}
|
||||
|
||||
executingHash := runtime.GetExecutingScriptHash()
|
||||
if !royaltyToken.Equals(executingHash) {
|
||||
panic("invalid royalty token")
|
||||
}
|
||||
|
||||
ctx := storage.GetReadOnlyContext()
|
||||
if !isTokenValid(ctx, tokenID) {
|
||||
panic("unknown token")
|
||||
}
|
||||
ownerIter := ownersOf(ctx, tokenID)
|
||||
var owners []interop.Hash160
|
||||
for iterator.Next(ownerIter) {
|
||||
owners = append(owners, iterator.Value(ownerIter).(interop.Hash160))
|
||||
}
|
||||
|
||||
var (
|
||||
recipients []RoyaltyRecipient
|
||||
amount = salePrice / 10 / len(owners)
|
||||
)
|
||||
for _, owner := range owners {
|
||||
recipients = append(recipients, RoyaltyRecipient{
|
||||
Address: owner,
|
||||
Amount: amount,
|
||||
})
|
||||
}
|
||||
return recipients
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
name: "NeoFS Object NFT"
|
||||
sourceurl: https://github.com/nspcc-dev/neo-go/
|
||||
supportedstandards: ["NEP-11"]
|
||||
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf", "properties", "tokens"]
|
||||
supportedstandards: ["NEP-11", "NEP-24", "NEP-27"]
|
||||
safemethods: ["balanceOf", "decimals", "symbol", "totalSupply", "tokensOf", "ownerOf", "properties", "tokens", "royaltyInfo"]
|
||||
events:
|
||||
- name: Transfer
|
||||
parameters:
|
||||
|
|
Some files were not shown because too many files have changed in this diff Show more
Loading…
Add table
Reference in a new issue