Initial Tutus chain fork from NeoGo
- Renamed module to github.com/tutus-one/tutus-chain - Created Tutus network configurations - Updated Makefile for tutus binary - Updated service templates - Added Tutus README
This commit is contained in:
commit
62bd7bb153
|
|
@ -0,0 +1,74 @@
|
||||||
|
networks:
|
||||||
|
default:
|
||||||
|
name: neo_go_network
|
||||||
|
ipam:
|
||||||
|
config:
|
||||||
|
- subnet: 172.200.0.0/24
|
||||||
|
gateway: 172.200.0.254
|
||||||
|
|
||||||
|
volumes:
|
||||||
|
volume_chain:
|
||||||
|
driver: local
|
||||||
|
|
||||||
|
services:
|
||||||
|
node_one:
|
||||||
|
container_name: neo_go_node_one
|
||||||
|
image: env_neo_go_image
|
||||||
|
command: "node --config-path /config --privnet --force-timestamp-logs"
|
||||||
|
volumes:
|
||||||
|
- ../config/protocol.privnet.docker.one.yml:/config/protocol.privnet.yml
|
||||||
|
- ./wallets/wallet1.json:/wallet1.json
|
||||||
|
- volume_chain:/chains
|
||||||
|
ports:
|
||||||
|
- 20333:20333
|
||||||
|
- 30333:30333
|
||||||
|
- 20001:20001
|
||||||
|
node_two:
|
||||||
|
container_name: neo_go_node_two
|
||||||
|
image: env_neo_go_image
|
||||||
|
command: "node --config-path /config --privnet --force-timestamp-logs"
|
||||||
|
volumes:
|
||||||
|
- ../config/protocol.privnet.docker.two.yml:/config/protocol.privnet.yml
|
||||||
|
- ./wallets/wallet2.json:/wallet2.json
|
||||||
|
- volume_chain:/chains
|
||||||
|
ports:
|
||||||
|
- 20334:20334
|
||||||
|
- 30334:30334
|
||||||
|
- 20002:20002
|
||||||
|
node_three:
|
||||||
|
container_name: neo_go_node_three
|
||||||
|
image: env_neo_go_image
|
||||||
|
command: "node --config-path /config --privnet --force-timestamp-logs"
|
||||||
|
volumes:
|
||||||
|
- ../config/protocol.privnet.docker.three.yml:/config/protocol.privnet.yml
|
||||||
|
- ./wallets/wallet3.json:/wallet3.json
|
||||||
|
- volume_chain:/chains
|
||||||
|
ports:
|
||||||
|
- 20335:20335
|
||||||
|
- 30335:30335
|
||||||
|
- 20003:20003
|
||||||
|
node_four:
|
||||||
|
container_name: neo_go_node_four
|
||||||
|
image: env_neo_go_image
|
||||||
|
command: "node --config-path /config --privnet --force-timestamp-logs"
|
||||||
|
volumes:
|
||||||
|
- ../config/protocol.privnet.docker.four.yml:/config/protocol.privnet.yml
|
||||||
|
- ./wallets/wallet4.json:/wallet4.json
|
||||||
|
- volume_chain:/chains
|
||||||
|
ports:
|
||||||
|
- 20336:20336
|
||||||
|
- 30336:30336
|
||||||
|
- 20004:20004
|
||||||
|
node_single:
|
||||||
|
container_name: neo_go_node_single
|
||||||
|
image: env_neo_go_image
|
||||||
|
command: "node --config-path /config --privnet --force-timestamp-logs"
|
||||||
|
volumes:
|
||||||
|
- ../config/protocol.privnet.docker.single.yml:/config/protocol.privnet.yml
|
||||||
|
- ./wallets/wallet1_solo.json:/wallet1.json
|
||||||
|
- volume_chain:/chains
|
||||||
|
ports:
|
||||||
|
- 20333:20333
|
||||||
|
- 30333:30333
|
||||||
|
- 20001:20001
|
||||||
|
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!C:\Program Files\PowerShell\7\pwsh.EXE -File
|
||||||
|
|
||||||
|
$bin = '/usr/bin/neo-go.exe'
|
||||||
|
|
||||||
|
for ( $i = 0; $i -lt $args.count; $i++ ) {
|
||||||
|
if ($args[$i] -eq "node"){
|
||||||
|
Write-Host "=> Try to restore blocks before running node"
|
||||||
|
if (($Env:ACC -ne $null) -and (Test-Path $Env:ACC -PathType Leaf)) {
|
||||||
|
& $bin db restore -p --config-path /config -i $Env:ACC
|
||||||
|
}
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
& $bin $args
|
||||||
|
|
@ -0,0 +1,15 @@
|
||||||
|
#!/bin/sh
|
||||||
|
|
||||||
|
BIN=/usr/bin/neo-go
|
||||||
|
|
||||||
|
case $@ in
|
||||||
|
"node"*)
|
||||||
|
echo "=> Try to restore blocks before running node"
|
||||||
|
if [ -f "$ACC" ]; then
|
||||||
|
gunzip --stdout "$ACC" >/privnet.acc
|
||||||
|
${BIN} db restore -p --config-path /config -i /privnet.acc
|
||||||
|
fi
|
||||||
|
;;
|
||||||
|
esac
|
||||||
|
|
||||||
|
${BIN} "$@"
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"address": "Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn",
|
||||||
|
"key": "6PYM8VdX2BSm7BSXKzV4Fz6S3R9cDLLWNrD9nMjxW352jEv3fsC8N3wNLY",
|
||||||
|
"label": "",
|
||||||
|
"contract": {
|
||||||
|
"script": "DCECs2Ir9AF73+MXxYrtX0x1PyBrfbiWBG+n13S7xL9/jcJBVuezJw==",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "parameter0",
|
||||||
|
"type": "Signature"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deployed": false
|
||||||
|
},
|
||||||
|
"lock": false,
|
||||||
|
"isDefault": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq",
|
||||||
|
"key": "6PYM8VdX2BSm7BSXKzV4Fz6S3R9cDLLWNrD9nMjxW352jEv3fsC8N3wNLY",
|
||||||
|
"label": "",
|
||||||
|
"contract": {
|
||||||
|
"script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEGe0Nw6",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "parameter0",
|
||||||
|
"type": "Signature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "parameter1",
|
||||||
|
"type": "Signature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "parameter2",
|
||||||
|
"type": "Signature"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deployed": false
|
||||||
|
},
|
||||||
|
"lock": false,
|
||||||
|
"isDefault": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scrypt": {
|
||||||
|
"n": 16384,
|
||||||
|
"r": 8,
|
||||||
|
"p": 8
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"Tokens": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,72 @@
|
||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"address": "Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn",
|
||||||
|
"key": "6PYM8VdX2BSm7BSXKzV4Fz6S3R9cDLLWNrD9nMjxW352jEv3fsC8N3wNLY",
|
||||||
|
"label": "",
|
||||||
|
"contract": {
|
||||||
|
"script": "DCECs2Ir9AF73+MXxYrtX0x1PyBrfbiWBG+n13S7xL9/jcJBVuezJw==",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "parameter0",
|
||||||
|
"type": "Signature"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deployed": false
|
||||||
|
},
|
||||||
|
"lock": false,
|
||||||
|
"isDefault": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq",
|
||||||
|
"key": "6PYM8VdX2BSm7BSXKzV4Fz6S3R9cDLLWNrD9nMjxW352jEv3fsC8N3wNLY",
|
||||||
|
"label": "",
|
||||||
|
"contract": {
|
||||||
|
"script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEGe0Nw6",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "parameter0",
|
||||||
|
"type": "Signature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "parameter1",
|
||||||
|
"type": "Signature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "parameter2",
|
||||||
|
"type": "Signature"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deployed": false
|
||||||
|
},
|
||||||
|
"lock": false,
|
||||||
|
"isDefault": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "NfgHwwTi3wHAS8aFAN243C5vGbkYDpqLHP",
|
||||||
|
"key": "6PYM8VdX2BSm7BSXKzV4Fz6S3R9cDLLWNrD9nMjxW352jEv3fsC8N3wNLY",
|
||||||
|
"label": "",
|
||||||
|
"contract": {
|
||||||
|
"script": "EQwhArNiK/QBe9/jF8WK7V9MdT8ga324lgRvp9d0u8S/f43CEUGe0Nw6",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "parameter0",
|
||||||
|
"type": "Signature"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deployed": false
|
||||||
|
},
|
||||||
|
"lock": false,
|
||||||
|
"isDefault": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scrypt": {
|
||||||
|
"n": 16384,
|
||||||
|
"r": 8,
|
||||||
|
"p": 8
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"Tokens": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"address": "NMUedC8TSV2rE17wGguSvPk9XcmHSaT275",
|
||||||
|
"key": "6PYSYoZaxqDu5vqvm7yUFT3sPJJFwyLyYDnp8zwj1YVPcBWxacz64bNX59",
|
||||||
|
"label": "",
|
||||||
|
"contract": {
|
||||||
|
"script": "DCECEDp/fdAWVYWX95YNJ8UWpDlP2Wi55lFV60sBPkBAQG5BVuezJw==",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "parameter0",
|
||||||
|
"type": "Signature"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deployed": false
|
||||||
|
},
|
||||||
|
"lock": false,
|
||||||
|
"isDefault": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq",
|
||||||
|
"key": "6PYSYoZaxqDu5vqvm7yUFT3sPJJFwyLyYDnp8zwj1YVPcBWxacz64bNX59",
|
||||||
|
"label": "",
|
||||||
|
"contract": {
|
||||||
|
"script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEGe0Nw6",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "parameter0",
|
||||||
|
"type": "Signature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "parameter1",
|
||||||
|
"type": "Signature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "parameter2",
|
||||||
|
"type": "Signature"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deployed": false
|
||||||
|
},
|
||||||
|
"lock": false,
|
||||||
|
"isDefault": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scrypt": {
|
||||||
|
"n": 16384,
|
||||||
|
"r": 8,
|
||||||
|
"p": 8
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"Tokens": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"address": "NdypBhqkz2CMMnwxBgvoC9X2XjKF5axgKo",
|
||||||
|
"key": "6PYVxMnzPMFTYY16xRvXm2SJcvaChabLzaARAb1Mmej9U7rYLYWMSPtfam",
|
||||||
|
"label": "",
|
||||||
|
"contract": {
|
||||||
|
"script": "DCED2QwH32PmkM53kS4Qq1GsyUS2aGAje2CMT4+DCece5plBVuezJw==",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "parameter0",
|
||||||
|
"type": "Signature"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deployed": false
|
||||||
|
},
|
||||||
|
"lock": false,
|
||||||
|
"isDefault": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq",
|
||||||
|
"key": "6PYVxMnzPMFTYY16xRvXm2SJcvaChabLzaARAb1Mmej9U7rYLYWMSPtfam",
|
||||||
|
"label": "",
|
||||||
|
"contract": {
|
||||||
|
"script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEGe0Nw6",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "parameter0",
|
||||||
|
"type": "Signature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "parameter1",
|
||||||
|
"type": "Signature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "parameter2",
|
||||||
|
"type": "Signature"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deployed": false
|
||||||
|
},
|
||||||
|
"lock": false,
|
||||||
|
"isDefault": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scrypt": {
|
||||||
|
"n": 16384,
|
||||||
|
"r": 8,
|
||||||
|
"p": 8
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"Tokens": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,55 @@
|
||||||
|
{
|
||||||
|
"version": "1.0",
|
||||||
|
"accounts": [
|
||||||
|
{
|
||||||
|
"address": "NPrB7BmTMYxf9UVroJp4RQExM9tqKmsHTz",
|
||||||
|
"key": "6PYX8eELiDduPW3RGiZxZNmG6KtWtXkyRFi47f8w6quEBpRkpBPxH5u5AP",
|
||||||
|
"label": "",
|
||||||
|
"contract": {
|
||||||
|
"script": "DCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWJBVuezJw==",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "parameter0",
|
||||||
|
"type": "Signature"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deployed": false
|
||||||
|
},
|
||||||
|
"lock": false,
|
||||||
|
"isDefault": false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"address": "NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq",
|
||||||
|
"key": "6PYX8eELiDduPW3RGiZxZNmG6KtWtXkyRFi47f8w6quEBpRkpBPxH5u5AP",
|
||||||
|
"label": "",
|
||||||
|
"contract": {
|
||||||
|
"script": "EwwhAhA6f33QFlWFl/eWDSfFFqQ5T9loueZRVetLAT5AQEBuDCECp7xV/oaE4BGXaNEEujB5W9zIZhnoZK3SYVZyPtGFzWIMIQKzYiv0AXvf4xfFiu1fTHU/IGt9uJYEb6fXdLvEv3+NwgwhA9kMB99j5pDOd5EuEKtRrMlEtmhgI3tgjE+PgwnnHuaZFEGe0Nw6",
|
||||||
|
"parameters": [
|
||||||
|
{
|
||||||
|
"name": "parameter0",
|
||||||
|
"type": "Signature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "parameter1",
|
||||||
|
"type": "Signature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "parameter2",
|
||||||
|
"type": "Signature"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"deployed": false
|
||||||
|
},
|
||||||
|
"lock": false,
|
||||||
|
"isDefault": false
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"scrypt": {
|
||||||
|
"n": 16384,
|
||||||
|
"r": 8,
|
||||||
|
"p": 8
|
||||||
|
},
|
||||||
|
"extra": {
|
||||||
|
"Tokens": null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
.git
|
||||||
|
chains
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
* @AnnaShaleva @roman-khimov
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
### Problem
|
||||||
|
|
||||||
|
...
|
||||||
|
|
||||||
|
### Solution
|
||||||
|
|
||||||
|
...
|
||||||
Binary file not shown.
|
After Width: | Height: | Size: 41 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 54 KiB |
|
|
@ -0,0 +1,152 @@
|
||||||
|
name: Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
types: [opened, synchronize]
|
||||||
|
paths-ignore:
|
||||||
|
- 'scripts/**'
|
||||||
|
- '**/*.md'
|
||||||
|
push:
|
||||||
|
# Build for the master branch.
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
release:
|
||||||
|
# Publish released commit as Docker `latest` and `git_revision` images.
|
||||||
|
types:
|
||||||
|
- published
|
||||||
|
workflow_dispatch:
|
||||||
|
inputs:
|
||||||
|
ref:
|
||||||
|
description: 'Ref to build CLI for Ubuntu and Windows Server Core [default: latest master; examples: v0.92.0, 0a4ff9d3e4a9ab432fd5812eb18c98e03b5a7432]'
|
||||||
|
required: false
|
||||||
|
default: ''
|
||||||
|
push_image:
|
||||||
|
description: 'Push images to DockerHub [default: false; examples: true, false]'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
|
use_latest_tag:
|
||||||
|
description: 'Use `latest` tag while pushing images to DockerHub (applied to Ubuntu image only) [default: false; examples: true, false]'
|
||||||
|
required: false
|
||||||
|
default: 'false'
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build_cli:
|
||||||
|
name: Build CLI
|
||||||
|
runs-on: ${{matrix.os.name}}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [{ name: ubuntu-latest, bin-name: linux }, { name: windows-latest, bin-name: windows }, { name: macos-latest, bin-name: darwin }]
|
||||||
|
arch: [amd64, arm64]
|
||||||
|
exclude:
|
||||||
|
- os: { name: windows-latest, bin-name: windows }
|
||||||
|
arch: 'arm64'
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.inputs.ref }}
|
||||||
|
# Allows to fetch all history for all branches and tags. Need this for proper versioning.
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.25'
|
||||||
|
|
||||||
|
- name: Build CLI
|
||||||
|
run: make build
|
||||||
|
env:
|
||||||
|
GOARCH: ${{ matrix.arch }}
|
||||||
|
|
||||||
|
- name: Rename CLI binary
|
||||||
|
run: mv ./bin/neo-go* ./bin/neo-go-${{ matrix.os.bin-name }}-${{ matrix.arch }}${{ (matrix.os.bin-name == 'windows' && '.exe') || '' }}
|
||||||
|
|
||||||
|
- name: Upload artifact
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: neo-go-${{ matrix.os.bin-name }}-${{ matrix.arch }}
|
||||||
|
path: ./bin/neo-go*
|
||||||
|
if-no-files-found: error
|
||||||
|
|
||||||
|
- name: Attach binary to the release as an asset
|
||||||
|
if: ${{ github.event_name == 'release' }}
|
||||||
|
run: gh release upload ${{ github.event.release.tag_name }} ./bin/neo-go-${{ matrix.os.bin-name }}-${{ matrix.arch }}${{ (matrix.os.bin-name == 'windows' && '.exe') || '' }}
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
|
||||||
|
build_image:
|
||||||
|
needs: build_cli
|
||||||
|
name: Build and push docker image
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.inputs.ref }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: Set up QEMU
|
||||||
|
uses: docker/setup-qemu-action@v3
|
||||||
|
|
||||||
|
- name: Set up Docker Buildx
|
||||||
|
uses: docker/setup-buildx-action@v3
|
||||||
|
|
||||||
|
- name: Login to DockerHub
|
||||||
|
if: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_image == 'true') }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Set vars
|
||||||
|
id: setvars
|
||||||
|
run: make gh-docker-vars >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Set latest tag
|
||||||
|
id: setlatest
|
||||||
|
if: ${{ (github.event_name == 'release' && github.event.release.target_commitish == 'master') || (github.event_name == 'workflow_dispatch' && github.event.inputs.use_latest_tag == 'true') }}
|
||||||
|
run: echo "latest=,${{ steps.setvars.outputs.repo }}:latest" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- name: Build and push
|
||||||
|
uses: docker/build-push-action@v5
|
||||||
|
with:
|
||||||
|
context: .
|
||||||
|
push: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_image == 'true') }}
|
||||||
|
platforms: linux/amd64,linux/arm64
|
||||||
|
build-args: |
|
||||||
|
REPO=github.com/${{ github.repository }}
|
||||||
|
VERSION=${{ steps.setvars.outputs.version }}
|
||||||
|
tags: ${{ steps.setvars.outputs.repo }}:${{ steps.setvars.outputs.version }}${{ steps.setvars.outputs.suffix }}${{ steps.setlatest.outputs.latest }}
|
||||||
|
|
||||||
|
build_image_wsc:
|
||||||
|
needs: build_cli
|
||||||
|
name: Build and push docker image (Windows Server Core)
|
||||||
|
runs-on: windows-latest
|
||||||
|
if: ${{ github.event_name != 'pull_request' }}
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
ref: ${{ github.event.inputs.ref }}
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
# For proper `deps` make target execution.
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.25'
|
||||||
|
|
||||||
|
- name: Login to DockerHub
|
||||||
|
if: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_image == 'true') }}
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
||||||
|
|
||||||
|
- name: Build Docker image
|
||||||
|
run: make image
|
||||||
|
|
||||||
|
- name: Push image to registry
|
||||||
|
if: ${{ github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.push_image == 'true') }}
|
||||||
|
run: make image-push
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
name: Contribution guidelines
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
commits_check_job:
|
||||||
|
name: DCO check
|
||||||
|
uses: nspcc-dev/.github/.github/workflows/dco.yml@master
|
||||||
|
|
@ -0,0 +1,197 @@
|
||||||
|
name: Tests
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches: [ master ]
|
||||||
|
pull_request:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
types: [opened, synchronize]
|
||||||
|
paths-ignore:
|
||||||
|
- 'scripts/*.sh'
|
||||||
|
- '**/*.md'
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
lint:
|
||||||
|
name: 'Lint: NeoGo'
|
||||||
|
uses: nspcc-dev/.github/.github/workflows/go-linter.yml@master
|
||||||
|
|
||||||
|
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
|
||||||
|
runs-on: ubuntu-slim
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- name: Check dependencies
|
||||||
|
run: |
|
||||||
|
./scripts/check_deps.sh
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: 'go.mod'
|
||||||
|
- 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
|
||||||
|
runs-on: ubuntu-slim
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version-file: 'go.mod'
|
||||||
|
|
||||||
|
- name: Install stringer
|
||||||
|
run: go install golang.org/x/tools/cmd/stringer@latest
|
||||||
|
|
||||||
|
- name: Run go generate
|
||||||
|
run: go generate ./...
|
||||||
|
|
||||||
|
- name: Check that autogenerated code is up-to-date
|
||||||
|
run: |
|
||||||
|
if [[ $(git diff --name-only | grep '' -c) != 0 ]]; then
|
||||||
|
echo "Fresh version of autogenerated code should be committed for the following files:";
|
||||||
|
git diff --name-only;
|
||||||
|
exit 1;
|
||||||
|
fi
|
||||||
|
|
||||||
|
codeql:
|
||||||
|
name: CodeQL
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
language: [ 'go' ]
|
||||||
|
# CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ]
|
||||||
|
# Learn more:
|
||||||
|
# https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout repository
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
|
||||||
|
# Initializes the CodeQL tools for scanning.
|
||||||
|
- name: Initialize CodeQL
|
||||||
|
uses: github/codeql-action/init@v3
|
||||||
|
with:
|
||||||
|
languages: ${{ matrix.language }}
|
||||||
|
# If you wish to specify custom queries, you can do so here or in a config file.
|
||||||
|
# By default, queries listed here will override any specified in a config file.
|
||||||
|
# Prefix the list here with "+" to use these queries and those in the config file.
|
||||||
|
# queries: ./path/to/local/query, your-org/your-repo/queries@main
|
||||||
|
|
||||||
|
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||||
|
# If this step fails, then you should remove it and run the build manually (see below)
|
||||||
|
- name: Autobuild
|
||||||
|
uses: github/codeql-action/autobuild@v3
|
||||||
|
|
||||||
|
# ℹ️ Command-line programs to run using the OS shell.
|
||||||
|
# 📚 https://git.io/JvXDl
|
||||||
|
|
||||||
|
# ✏️ If the Autobuild fails above, remove it and uncomment the following three lines
|
||||||
|
# and modify them (or add more) to build your code if your project
|
||||||
|
# uses a compiled language
|
||||||
|
|
||||||
|
#- run: |
|
||||||
|
# make bootstrap
|
||||||
|
# make release
|
||||||
|
|
||||||
|
- name: Perform CodeQL Analysis
|
||||||
|
uses: github/codeql-action/analyze@v3
|
||||||
|
|
||||||
|
test_cover:
|
||||||
|
name: Coverage
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
|
env:
|
||||||
|
CGO_ENABLED: 0
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
submodules: 'true'
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.25'
|
||||||
|
cache: true
|
||||||
|
|
||||||
|
- name: Write coverage profile
|
||||||
|
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
|
||||||
|
with:
|
||||||
|
fail_ci_if_error: true # if something is wrong on uploading codecov results, then this job will fail
|
||||||
|
files: ./coverage.txt
|
||||||
|
slug: nspcc-dev/neo-go
|
||||||
|
token: ${{ secrets.CODECOV_TOKEN }}
|
||||||
|
verbose: true
|
||||||
|
|
||||||
|
tests:
|
||||||
|
name: Run tests
|
||||||
|
runs-on: ${{ matrix.os }}
|
||||||
|
strategy:
|
||||||
|
matrix:
|
||||||
|
os: [ubuntu-latest, windows-latest, macos-latest]
|
||||||
|
go_versions: [ '1.24', '1.25' ]
|
||||||
|
exclude:
|
||||||
|
# Only latest Go version for Windows and MacOS.
|
||||||
|
- os: windows-latest
|
||||||
|
go_versions: '1.24'
|
||||||
|
- os: macos-latest
|
||||||
|
go_versions: '1.24'
|
||||||
|
# Exclude latest Go version for Ubuntu as Coverage uses it.
|
||||||
|
- os: ubuntu-latest
|
||||||
|
go_versions: '1.25'
|
||||||
|
fail-fast: false
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
submodules: 'true'
|
||||||
|
|
||||||
|
- name: Set up Go
|
||||||
|
uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '${{ matrix.go_versions }}'
|
||||||
|
|
||||||
|
- name: Run tests
|
||||||
|
run: go test -timeout 15m -v -race ./...
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
*.exe
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, build with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Added by CoZ developers
|
||||||
|
vendor/
|
||||||
|
bin/
|
||||||
|
!examples/**/vendor
|
||||||
|
|
||||||
|
# text editors
|
||||||
|
# vscode
|
||||||
|
.vscode/*
|
||||||
|
!.vscode/settings.json
|
||||||
|
!.vscode/tasks.json
|
||||||
|
!.vscode/launch.json
|
||||||
|
!.vscode/extensions.json
|
||||||
|
# goland
|
||||||
|
.idea/*
|
||||||
|
# emacs
|
||||||
|
*~
|
||||||
|
TAGS
|
||||||
|
|
||||||
|
# storage
|
||||||
|
/chains
|
||||||
|
|
||||||
|
# patch
|
||||||
|
*.orig
|
||||||
|
*.rej
|
||||||
|
|
||||||
|
# Coverage
|
||||||
|
coverage.txt
|
||||||
|
coverage.html
|
||||||
|
|
||||||
|
# Compiler output
|
||||||
|
examples/*/*.nef
|
||||||
|
examples/*/*.json
|
||||||
|
|
||||||
|
# Fuzzing testdata.
|
||||||
|
testdata/
|
||||||
|
!cli/testdata
|
||||||
|
!internal/basicchain/testdata
|
||||||
|
!pkg/compiler/testdata
|
||||||
|
!pkg/config/testdata
|
||||||
|
!pkg/consensus/testdata
|
||||||
|
!pkg/services/rpcsrv/testdata
|
||||||
|
!pkg/services/notary/testdata
|
||||||
|
!pkg/services/oracle/testdata
|
||||||
|
!pkg/smartcontract/testdata
|
||||||
|
!cli/smartcontract/testdata
|
||||||
|
pkg/vm/testdata/fuzz
|
||||||
|
!pkg/vm/testdata
|
||||||
|
!pkg/wallet/testdata
|
||||||
|
|
||||||
|
# Linter
|
||||||
|
.golangci.yml
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
[submodule "pkg/vm/testdata/neo-vm"]
|
||||||
|
path = pkg/vm/testdata/neo-vm
|
||||||
|
url = https://github.com/neo-project/neo.git
|
||||||
|
branch = master
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,25 @@
|
||||||
|
# Contributing
|
||||||
|
|
||||||
|
First, thank you for contributing! We love and encourage pull requests from everyone. Please
|
||||||
|
follow the guidelines:
|
||||||
|
|
||||||
|
1. Check open [issues](https://github.com/nspcc-dev/neo-go/issues) and
|
||||||
|
[pull requests](https://github.com/nspcc-dev/neo-go/pulls) for existing discussions.
|
||||||
|
1. Open an issue first to discuss a new feature or enhancement.
|
||||||
|
1. Write tests and make sure the test suite passes locally and on CI.
|
||||||
|
1. When optimizing something, write benchmarks and attach the results:
|
||||||
|
```
|
||||||
|
go test -run - -bench BenchmarkYourFeature -count=10 ./... >old // on master
|
||||||
|
go test -run - -bench BenchmarkYourFeature -count=10 ./... >new // on your branch
|
||||||
|
benchstat old new
|
||||||
|
```
|
||||||
|
`benchstat` is described here https://godocs.io/golang.org/x/perf/cmd/benchstat.
|
||||||
|
|
||||||
|
1. Open a pull request and reference the relevant issue(s).
|
||||||
|
1. Make sure your commits are logically separated and have good comments
|
||||||
|
explaining the details of your change. Add a package/file prefix to your
|
||||||
|
commit if that's applicable (like 'vm: fix ADD miscalculation on full
|
||||||
|
moon').
|
||||||
|
1. After receiving a feedback, amend your commits or add new ones as
|
||||||
|
appropriate.
|
||||||
|
1. **Have fun!**
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Builder image
|
||||||
|
# Keep go version in sync with Build GA job.
|
||||||
|
FROM golang:1.25-alpine AS builder
|
||||||
|
|
||||||
|
# Display go version for information purposes.
|
||||||
|
RUN go version
|
||||||
|
|
||||||
|
RUN set -x \
|
||||||
|
&& apk add --no-cache git make \
|
||||||
|
&& mkdir -p /tmp
|
||||||
|
|
||||||
|
COPY . /neo-go
|
||||||
|
|
||||||
|
WORKDIR /neo-go
|
||||||
|
|
||||||
|
ARG REPO=repository
|
||||||
|
ARG VERSION=dev
|
||||||
|
|
||||||
|
RUN VERSION=$VERSION REPO=$REPO make build
|
||||||
|
|
||||||
|
# Executable image
|
||||||
|
FROM alpine
|
||||||
|
|
||||||
|
ARG VERSION=dev
|
||||||
|
LABEL version=$VERSION
|
||||||
|
|
||||||
|
WORKDIR /
|
||||||
|
|
||||||
|
COPY --from=builder /neo-go/config /config
|
||||||
|
COPY --from=builder /neo-go/.docker/privnet-entrypoint.sh /usr/bin/privnet-entrypoint.sh
|
||||||
|
COPY --from=builder /neo-go/bin/neo-go /usr/bin/neo-go
|
||||||
|
COPY --from=builder /etc/ssl/certs/ca-certificates.crt /etc/ssl/certs/
|
||||||
|
|
||||||
|
ENTRYPOINT ["/usr/bin/privnet-entrypoint.sh"]
|
||||||
|
|
||||||
|
CMD ["node", "--config-path", "/config", "--privnet"]
|
||||||
|
|
@ -0,0 +1,37 @@
|
||||||
|
# Builder image
|
||||||
|
# Keep go version in sync with Build GA job.
|
||||||
|
FROM golang:1.25.0-windowsservercore-ltsc2022 AS builder
|
||||||
|
|
||||||
|
COPY . /neo-go
|
||||||
|
|
||||||
|
WORKDIR /neo-go
|
||||||
|
|
||||||
|
ARG REPO=repository
|
||||||
|
ARG VERSION=dev
|
||||||
|
|
||||||
|
SHELL ["cmd", "/S", "/C"]
|
||||||
|
RUN go env -w CGO_ENABLED=0
|
||||||
|
ENV GOGC=off
|
||||||
|
|
||||||
|
RUN go build -trimpath -v -o ./bin/neo-go.exe -ldflags="-X %REPO%/pkg/config.Version=%VERSION%" ./cli/main.go
|
||||||
|
|
||||||
|
# Executable image
|
||||||
|
FROM mcr.microsoft.com/windows/servercore:ltsc2022
|
||||||
|
|
||||||
|
ARG VERSION
|
||||||
|
LABEL version=%VERSION%
|
||||||
|
|
||||||
|
WORKDIR /
|
||||||
|
|
||||||
|
COPY --from=builder /neo-go/config /config
|
||||||
|
COPY --from=builder /neo-go/.docker/privnet-entrypoint.ps1 /usr/bin/privnet-entrypoint.ps1
|
||||||
|
COPY --from=builder /neo-go/bin/neo-go.exe /usr/bin/neo-go.exe
|
||||||
|
|
||||||
|
SHELL ["powershell", "-Command", "$ErrorActionPreference = 'Stop';", "$ProgressPreference = 'SilentlyContinue';"]
|
||||||
|
|
||||||
|
# Check executable version.
|
||||||
|
RUN /usr/bin/neo-go.exe --version
|
||||||
|
|
||||||
|
ENTRYPOINT ["powershell", "-File", "/usr/bin/privnet-entrypoint.ps1"]
|
||||||
|
|
||||||
|
CMD ["node", "--config-path", "/config", "--privnet"]
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2018-2023 NeoSPCC (@nspcc-dev), Anthony De Meulemeester (@anthdm), City of Zion community (@CityOfZion)
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
||||||
|
|
@ -0,0 +1,159 @@
|
||||||
|
BRANCH = "master"
|
||||||
|
REPONAME = "tutus-chain"
|
||||||
|
NETMODE ?= "tutus-testnet"
|
||||||
|
BINARY=tutus
|
||||||
|
BINARY_PATH=./bin/$(BINARY)$(shell go env GOEXE)
|
||||||
|
GO_VERSION ?= 1.25
|
||||||
|
DESTDIR = ""
|
||||||
|
SYSCONFIGDIR = "/etc"
|
||||||
|
BINDIR = "/usr/bin"
|
||||||
|
SYSTEMDUNIT_DIR = "/lib/systemd/system"
|
||||||
|
UNITWORKDIR = "/var/lib/tutus"
|
||||||
|
|
||||||
|
IMAGE_SUFFIX="$(shell if [ "$(OS)" = Windows_NT ]; then echo "_WindowsServerCore"; fi)"
|
||||||
|
D_FILE ?= "$(shell if [ "$(OS)" = Windows_NT ]; then echo "Dockerfile.wsc"; else echo "Dockerfile"; fi)"
|
||||||
|
DC_FILE ?= ".docker/docker-compose.yml" # Single docker-compose for Ubuntu/WSC, should be kept in sync with ENV_IMAGE_TAG.
|
||||||
|
ENV_IMAGE_TAG="env_neo_go_image"
|
||||||
|
|
||||||
|
REPO ?= "$(shell go list -m)"
|
||||||
|
VERSION ?= "$(shell git describe --tags --match "v*" --abbrev=8 2>/dev/null | sed -r 's,^v([0-9]+\.[0-9]+)\.([0-9]+)(-.*)?$$,\1 \2 \3,' | while read mm patch suffix; do if [ -z "$$suffix" ]; then echo $$mm.$$patch; else patch=`expr $$patch + 1`; echo $$mm.$${patch}-pre$$suffix; fi; done)"
|
||||||
|
MODVERSION ?= "$(shell cat go.mod | cat go.mod | sed -r -n -e 's|.*pkg/interop (.*)|\1|p')"
|
||||||
|
BUILD_FLAGS = "-X '$(REPO)/pkg/config.Version=$(VERSION)' -X '$(REPO)/cli/smartcontract.ModVersion=$(MODVERSION)'"
|
||||||
|
|
||||||
|
IMAGE_REPO=tutus-one/tutus-chain
|
||||||
|
|
||||||
|
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 \
|
||||||
|
test vet lint fmt cover version gh-docker-vars
|
||||||
|
|
||||||
|
build: deps
|
||||||
|
@echo "=> Building binary"
|
||||||
|
@set -x \
|
||||||
|
&& export GOGC=off \
|
||||||
|
&& export CGO_ENABLED=0 \
|
||||||
|
&& go build -trimpath -v -ldflags $(BUILD_FLAGS) -o ${BINARY_PATH} ./cli/main.go
|
||||||
|
|
||||||
|
$(BINARY): build
|
||||||
|
|
||||||
|
docker/$(BINARY):
|
||||||
|
@echo "=> Building binary using clean Docker environment"
|
||||||
|
@docker run --rm -t \
|
||||||
|
-v `pwd`:/src \
|
||||||
|
-w /src \
|
||||||
|
-u "$$(id -u):$$(id -g)" \
|
||||||
|
--env HOME=/src \
|
||||||
|
golang:$(GO_VERSION) make $(BINARY)
|
||||||
|
|
||||||
|
tutus.service: tutus.service.template
|
||||||
|
@sed -r -e 's_BINDIR_$(BINDIR)_' -e 's_UNITWORKDIR_$(UNITWORKDIR)_' -e 's_SYSCONFIGDIR_$(SYSCONFIGDIR)_' $< >$@
|
||||||
|
|
||||||
|
install: build tutus.service
|
||||||
|
@echo "=> Installing systemd service"
|
||||||
|
@mkdir -p $(DESTDIR)$(SYSCONFIGDIR)/tutus \
|
||||||
|
&& mkdir -p $(SYSTEMDUNIT_DIR) \
|
||||||
|
&& cp ./tutus.service $(SYSTEMDUNIT_DIR) \
|
||||||
|
&& cp ./config/protocol.tutus.yml $(DESTDIR)$(SYSCONFIGDIR)/tutus \
|
||||||
|
&& cp ./config/protocol.tutus.testnet.yml $(DESTDIR)$(SYSCONFIGDIR)/tutus \
|
||||||
|
&& install -m 0755 -t $(BINDIR) $(BINARY_PATH) \
|
||||||
|
|
||||||
|
postinst: install
|
||||||
|
@echo "=> Preparing directories and configs"
|
||||||
|
@id tutus || useradd -s /usr/sbin/nologin -d $(UNITWORKDIR) tutus \
|
||||||
|
&& mkdir -p $(UNITWORKDIR) \
|
||||||
|
&& chown -R tutus:tutus $(UNITWORKDIR) $(BINDIR)/tutus \
|
||||||
|
&& systemctl enable tutus.service
|
||||||
|
|
||||||
|
image: deps
|
||||||
|
@echo "=> Building image"
|
||||||
|
@echo " Dockerfile: $(D_FILE)"
|
||||||
|
@echo " Tag: $(IMAGE_REPO):$(VERSION)$(IMAGE_SUFFIX)"
|
||||||
|
@docker build -f $(D_FILE) -t $(IMAGE_REPO):$(VERSION)$(IMAGE_SUFFIX) --build-arg REPO=$(REPO) --build-arg VERSION=$(VERSION) .
|
||||||
|
|
||||||
|
image-latest: deps
|
||||||
|
@echo "=> Building image with 'latest' tag"
|
||||||
|
@echo " Dockerfile: Dockerfile" # Always use default Dockerfile for Ubuntu as `latest`.
|
||||||
|
@echo " Tag: $(IMAGE_REPO):latest"
|
||||||
|
@docker build -t $(IMAGE_REPO):latest --build-arg REPO=$(REPO) --build-arg VERSION=$(VERSION) .
|
||||||
|
|
||||||
|
image-push:
|
||||||
|
@echo "=> Publish image"
|
||||||
|
@echo " Tag: $(IMAGE_REPO):$(VERSION)$(IMAGE_SUFFIX)"
|
||||||
|
@docker push $(IMAGE_REPO):$(VERSION)$(IMAGE_SUFFIX)
|
||||||
|
|
||||||
|
image-push-latest:
|
||||||
|
@echo "=> Publish image for Ubuntu with 'latest' tag"
|
||||||
|
@docker push $(IMAGE_REPO):latest
|
||||||
|
|
||||||
|
deps:
|
||||||
|
@CGO_ENABLED=0 \
|
||||||
|
go mod download
|
||||||
|
@CGO_ENABLED=0 \
|
||||||
|
go mod tidy -v
|
||||||
|
|
||||||
|
version:
|
||||||
|
@echo $(VERSION)
|
||||||
|
|
||||||
|
gh-docker-vars:
|
||||||
|
@echo "file=$(D_FILE)"
|
||||||
|
@echo "version=$(VERSION)"
|
||||||
|
@echo "repo=$(IMAGE_REPO)"
|
||||||
|
@echo "suffix=$(IMAGE_SUFFIX)"
|
||||||
|
|
||||||
|
test:
|
||||||
|
@go test ./... -cover
|
||||||
|
|
||||||
|
vet:
|
||||||
|
@go vet ./...
|
||||||
|
|
||||||
|
.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/")
|
||||||
|
|
||||||
|
cover:
|
||||||
|
@go test -v -race ./... -coverprofile=coverage.txt -covermode=atomic -coverpkg=./pkg/...,./cli/...
|
||||||
|
@go tool cover -html=coverage.txt -o coverage.html
|
||||||
|
|
||||||
|
# --- Ubuntu/Windows environment ---
|
||||||
|
env_image:
|
||||||
|
@echo "=> Building env image"
|
||||||
|
@echo " Dockerfile: $(D_FILE)"
|
||||||
|
@echo " Tag: $(ENV_IMAGE_TAG)"
|
||||||
|
@docker build \
|
||||||
|
-f $(D_FILE) \
|
||||||
|
-t $(ENV_IMAGE_TAG) \
|
||||||
|
--build-arg REPO=$(REPO) \
|
||||||
|
--build-arg VERSION=$(VERSION) .
|
||||||
|
|
||||||
|
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
|
||||||
|
|
||||||
|
env_single:
|
||||||
|
@echo "=> Bootup environment"
|
||||||
|
@docker compose -f $(DC_FILE) up -d node_single
|
||||||
|
|
||||||
|
env_down:
|
||||||
|
@echo "=> Stop environment"
|
||||||
|
@docker compose -f $(DC_FILE) down
|
||||||
|
|
||||||
|
env_restart:
|
||||||
|
@echo "=> Stop and start environment"
|
||||||
|
@docker compose -f $(DC_FILE) restart
|
||||||
|
|
||||||
|
env_clean: env_down
|
||||||
|
@echo "=> Cleanup environment"
|
||||||
|
@docker volume rm docker_volume_chain
|
||||||
|
|
@ -0,0 +1,88 @@
|
||||||
|
# Tutus Chain
|
||||||
|
|
||||||
|
Sovereign blockchain platform for government deployments. Forked from [NeoGo](https://github.com/nspcc-dev/neo-go).
|
||||||
|
|
||||||
|
**Website:** tutus.one
|
||||||
|
**License:** Apache 2.0
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Tutus is an independent blockchain designed for government-grade deployments with:
|
||||||
|
|
||||||
|
- **Sovereign instances** - Each government runs their own chain
|
||||||
|
- **1-second blocks** - Fast finality with dBFT consensus
|
||||||
|
- **7 validators** - Government-controlled consensus nodes
|
||||||
|
- **Zero gas for citizens** - Native contracts subsidize operations
|
||||||
|
- **Inter-government bridge** - Optional cross-border interoperability
|
||||||
|
|
||||||
|
## Building
|
||||||
|
|
||||||
|
Requires Go 1.24+ and `make`:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
make build
|
||||||
|
```
|
||||||
|
|
||||||
|
The resulting binary is `bin/tutus`.
|
||||||
|
|
||||||
|
## Running a Node
|
||||||
|
|
||||||
|
Start a Tutus node:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/tutus node --config-file ./config/protocol.tutus.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
For testnet:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./bin/tutus node --config-file ./config/protocol.tutus.testnet.yml
|
||||||
|
```
|
||||||
|
|
||||||
|
## Network Flags
|
||||||
|
|
||||||
|
- `--tutus` - Tutus mainnet (government deployment)
|
||||||
|
- `--tutus-testnet` - Tutus testnet
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Network configurations are in `./config/`:
|
||||||
|
|
||||||
|
| File | Purpose |
|
||||||
|
|------|---------|
|
||||||
|
| `protocol.tutus.yml` | Production government deployment |
|
||||||
|
| `protocol.tutus.testnet.yml` | Development testnet |
|
||||||
|
|
||||||
|
## Docker
|
||||||
|
|
||||||
|
```bash
|
||||||
|
docker build -t tutus-chain .
|
||||||
|
docker run -d --name tutus -p 10333:10333 -p 10332:10332 tutus-chain
|
||||||
|
```
|
||||||
|
|
||||||
|
## Native Contracts (Planned)
|
||||||
|
|
||||||
|
The following will be built into the Tutus protocol:
|
||||||
|
|
||||||
|
| Contract | Purpose |
|
||||||
|
|----------|---------|
|
||||||
|
| PersonToken | Soul-bound identity |
|
||||||
|
| Scire | Universal education |
|
||||||
|
| Salus | Universal healthcare |
|
||||||
|
| Sese | Life planning |
|
||||||
|
| Tribute | Anti-hoarding economics |
|
||||||
|
| VTS | Government stablecoin |
|
||||||
|
| Eligere | Democratic voting |
|
||||||
|
|
||||||
|
## Validator Setup
|
||||||
|
|
||||||
|
See [docs/validator.md](docs/validator.md) for government validator deployment.
|
||||||
|
|
||||||
|
## Origin
|
||||||
|
|
||||||
|
Tutus is forked from NeoGo, the Go implementation of the Neo N3 blockchain.
|
||||||
|
We maintain compatibility with the Neo VM while extending the protocol for government use cases.
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
Apache 2.0 - See [LICENSE.md](LICENSE.md)
|
||||||
|
|
@ -0,0 +1,36 @@
|
||||||
|
# Roadmap for neo-go
|
||||||
|
|
||||||
|
This defines approximate plan of neo-go releases and key features planned for
|
||||||
|
them. Things can change if there is a need to push a bugfix or some critical
|
||||||
|
functionality.
|
||||||
|
|
||||||
|
## Versions 0.7X.Y (as needed)
|
||||||
|
* Neo 2.0 support (bug fixes, minor functionality additions)
|
||||||
|
|
||||||
|
## Version 0.115.0 (~Dec 2025)
|
||||||
|
* protocol updates
|
||||||
|
* bug fixes
|
||||||
|
|
||||||
|
## Version 1.0 (2026, TBD)
|
||||||
|
* stable version
|
||||||
|
|
||||||
|
# Deprecated functionality
|
||||||
|
|
||||||
|
As the node and the protocol evolve some external APIs can change. Usually we
|
||||||
|
try keeping backwards compatibility for some time (like half a year) unless
|
||||||
|
it's impossible to do for some reason. But eventually old
|
||||||
|
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.
|
||||||
|
|
||||||
|
## Candidate registration via `registerCandidate` method of native NeoToken contract
|
||||||
|
|
||||||
|
The original way of Neo candidate registration via `wallet candidate register` CLI
|
||||||
|
command using `registerCandidate` method of native NeoToken contract is deprecated
|
||||||
|
and has been superseded by the GAS transfer approach. Deprecated candidate
|
||||||
|
registration way via `registerCandidate` method call is supported via
|
||||||
|
`--useRegisterCall` flag.
|
||||||
|
|
||||||
|
Removal of `registerCandidate`–based support of candidate registration will be
|
||||||
|
done once `registerCandidate` method is officially deprecated and removed from
|
||||||
|
the NeoToken manifest with the subsequent hardfork.
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
cli
|
||||||
|
|
@ -0,0 +1,41 @@
|
||||||
|
package app
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/cli/query"
|
||||||
|
"github.com/nspcc-dev/neo-go/cli/server"
|
||||||
|
"github.com/nspcc-dev/neo-go/cli/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/cli/util"
|
||||||
|
"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/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func versionPrinter(c *cli.Context) {
|
||||||
|
_, _ = fmt.Fprintf(c.App.Writer, "NeoGo\nVersion: %s\nGoVersion: %s\n",
|
||||||
|
config.Version,
|
||||||
|
runtime.Version(),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
// New creates a NeoGo instance of [cli.App] with all commands included.
|
||||||
|
func New() *cli.App {
|
||||||
|
cli.VersionPrinter = versionPrinter
|
||||||
|
ctl := cli.NewApp()
|
||||||
|
ctl.Name = "neo-go"
|
||||||
|
ctl.Version = config.Version
|
||||||
|
ctl.Usage = "Official Go client for Neo"
|
||||||
|
ctl.ErrWriter = os.Stdout
|
||||||
|
|
||||||
|
ctl.Commands = append(ctl.Commands, server.NewCommands()...)
|
||||||
|
ctl.Commands = append(ctl.Commands, smartcontract.NewCommands()...)
|
||||||
|
ctl.Commands = append(ctl.Commands, wallet.NewCommands()...)
|
||||||
|
ctl.Commands = append(ctl.Commands, vm.NewCommands()...)
|
||||||
|
ctl.Commands = append(ctl.Commands, util.NewCommands()...)
|
||||||
|
ctl.Commands = append(ctl.Commands, query.NewCommands()...)
|
||||||
|
return ctl
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,19 @@
|
||||||
|
package app_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/testcli"
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/versionutil"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestCLIVersion(t *testing.T) {
|
||||||
|
config.Version = versionutil.TestVersion // Zero-length version string disables '--version' completely.
|
||||||
|
e := testcli.NewExecutor(t, false)
|
||||||
|
e.Run(t, "neo-go", "--version")
|
||||||
|
e.CheckNextLine(t, "^NeoGo")
|
||||||
|
e.CheckNextLine(t, "^Version:")
|
||||||
|
e.CheckNextLine(t, "^GoVersion:")
|
||||||
|
e.CheckEOF(t)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,360 @@
|
||||||
|
package cmdargs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
|
"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/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// CosignersSeparator marks the start of cosigners cli args.
|
||||||
|
CosignersSeparator = "--"
|
||||||
|
// ArrayStartSeparator marks the start of array cli arg.
|
||||||
|
ArrayStartSeparator = "["
|
||||||
|
// ArrayEndSeparator marks the end of array cli arg.
|
||||||
|
ArrayEndSeparator = "]"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// ParamsParsingDoc is a documentation for parameters parsing.
|
||||||
|
ParamsParsingDoc = ` Arguments always do have regular Neo smart contract parameter types, either
|
||||||
|
specified explicitly or being inferred from the value. To specify the type
|
||||||
|
manually use "type:value" syntax where the type is one of the following:
|
||||||
|
'signature', 'bool', 'int', 'hash160', 'hash256', 'bytes', 'key' or 'string'.
|
||||||
|
Array types are also supported: use special space-separated '[' and ']'
|
||||||
|
symbols around array values to denote array bounds. Nested arrays are also
|
||||||
|
supported. Null parameter is supported via 'nil' keyword without additional
|
||||||
|
type specification.
|
||||||
|
|
||||||
|
There is ability to provide an argument of 'bytearray' type via file. Use a
|
||||||
|
special 'filebytes' argument type for this with a filepath specified after
|
||||||
|
the colon, e.g. 'filebytes:my_file.txt'.
|
||||||
|
|
||||||
|
Given values are type-checked against given types with the following
|
||||||
|
restrictions applied:
|
||||||
|
* 'signature' type values should be hex-encoded and have a (decoded)
|
||||||
|
length of 64 bytes.
|
||||||
|
* 'bool' type values are 'true' and 'false'.
|
||||||
|
* 'int' values are decimal integers that can be successfully converted
|
||||||
|
from the string.
|
||||||
|
* 'hash160' values are Neo addresses and hex-encoded 20-bytes long (after
|
||||||
|
decoding) strings.
|
||||||
|
* 'hash256' type values should be hex-encoded and have a (decoded)
|
||||||
|
length of 32 bytes.
|
||||||
|
* 'bytes' type values are any hex-encoded things.
|
||||||
|
* 'filebytes' type values are filenames with the argument value inside.
|
||||||
|
* 'key' type values are hex-encoded marshalled public keys.
|
||||||
|
* 'string' type values are any valid UTF-8 strings. In the value's part of
|
||||||
|
the string the colon looses it's special meaning as a separator between
|
||||||
|
type and value and is taken literally.
|
||||||
|
|
||||||
|
If no type is explicitly specified, it is inferred from the value using the
|
||||||
|
following logic:
|
||||||
|
- anything that can be interpreted as a decimal integer gets
|
||||||
|
an 'int' type
|
||||||
|
- 'nil' string gets 'Any' NEP-14 parameter type and nil value which corresponds
|
||||||
|
to Null stackitem
|
||||||
|
- 'true' and 'false' strings get 'bool' type
|
||||||
|
- valid Neo addresses and 20 bytes long hex-encoded strings get 'hash160'
|
||||||
|
type
|
||||||
|
- valid hex-encoded public keys get 'key' type
|
||||||
|
- 32 bytes long hex-encoded values get 'hash256' type
|
||||||
|
- 64 bytes long hex-encoded values get 'signature' type
|
||||||
|
- any other valid hex-encoded values get 'bytes' type
|
||||||
|
- anything else is a 'string'
|
||||||
|
|
||||||
|
Backslash character is used as an escape character and allows to use colon in
|
||||||
|
an implicitly typed string. For any other characters it has no special
|
||||||
|
meaning, to get a literal backslash in the string use the '\\' sequence.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
* 'int:42' is an integer with a value of 42
|
||||||
|
* '42' is an integer with a value of 42
|
||||||
|
* 'nil' is a parameter with Any NEP-14 type and nil value (corresponds to Null stackitem)
|
||||||
|
* 'bad' is a string with a value of 'bad'
|
||||||
|
* 'dead' is a byte array with a value of 'dead'
|
||||||
|
* 'string:dead' is a string with a value of 'dead'
|
||||||
|
* 'filebytes:my_data.txt' is bytes decoded from a content of my_data.txt
|
||||||
|
* 'NSiVJYZej4XsxG5CUpdwn7VRQk8iiiDMPM' is a hash160 with a value
|
||||||
|
of '682cca3ebdc66210e5847d7f8115846586079d4a'
|
||||||
|
* '\4\2' is an integer with a value of 42
|
||||||
|
* '\\4\2' is a string with a value of '\42'
|
||||||
|
* 'string:string' is a string with a value of 'string'
|
||||||
|
* 'string\:string' is a string with a value of 'string:string'
|
||||||
|
* '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c' is a
|
||||||
|
key with a value of '03b209fd4f53a7170ea4444e0cb0a6bb6a53c2bd016926989cf85f9b0fba17a70c'
|
||||||
|
* '[ a b c ]' is an array with strings values 'a', 'b' and 'c'
|
||||||
|
* '[ a b [ c d ] e ]' is an array with 4 values: string 'a', string 'b',
|
||||||
|
array of two strings 'c' and 'd', string 'e'
|
||||||
|
* '[ ]' is an empty array`
|
||||||
|
|
||||||
|
// SignersParsingDoc is a documentation for signers parsing.
|
||||||
|
SignersParsingDoc = ` Signers represent a set of Uint160 hashes with witness scopes and are used
|
||||||
|
to verify hashes in System.Runtime.CheckWitness syscall. First signer is treated
|
||||||
|
as a sender. To specify signers use signer[:scope] syntax where
|
||||||
|
* 'signer' is a signer's address (as Neo address or hex-encoded 160 bit (20 byte)
|
||||||
|
LE value with or without '0x' prefix).
|
||||||
|
* 'scope' is a comma-separated set of cosigner's scopes, which could be:
|
||||||
|
- 'None' - default witness scope which may be used for the sender
|
||||||
|
to only pay fee for the transaction.
|
||||||
|
- 'Global' - allows this witness in all contexts. This cannot be combined
|
||||||
|
with other flags.
|
||||||
|
- 'CalledByEntry' - means that this condition must hold: EntryScriptHash
|
||||||
|
== CallingScriptHash. The witness/permission/signature
|
||||||
|
given on first invocation will automatically expire if
|
||||||
|
entering deeper internal invokes. This can be default
|
||||||
|
safe choice for native NEO/GAS.
|
||||||
|
- 'CustomContracts' - define valid custom contract hashes for witness check.
|
||||||
|
Hashes are be provided as hex-encoded LE value string.
|
||||||
|
At lest one hash must be provided. Multiple hashes
|
||||||
|
are separated by ':'.
|
||||||
|
- 'CustomGroups' - define custom public keys for group members. Public keys are
|
||||||
|
provided as short-form (1-byte prefix + 32 bytes) hex-encoded
|
||||||
|
values. At least one key must be provided. Multiple keys
|
||||||
|
are separated by ':'.
|
||||||
|
|
||||||
|
If no scopes were specified, 'CalledByEntry' used as default. If no signers were
|
||||||
|
specified, no array is passed. Note that scopes are properly handled by
|
||||||
|
neo-go RPC server only. C# implementation does not support scopes capability.
|
||||||
|
|
||||||
|
Examples:
|
||||||
|
* 'NNQk4QXsxvsrr3GSozoWBUxEmfag7B6hz5'
|
||||||
|
* 'NVquyZHoPirw6zAEPvY1ZezxM493zMWQqs:Global'
|
||||||
|
* '0x0000000009070e030d0f0e020d0c06050e030c02'
|
||||||
|
* '0000000009070e030d0f0e020d0c06050e030c02:CalledByEntry,` +
|
||||||
|
`CustomGroups:0206d7495ceb34c197093b5fc1cccf1996ada05e69ef67e765462a7f5d88ee14d0'
|
||||||
|
* '0000000009070e030d0f0e020d0c06050e030c02:CalledByEntry,` +
|
||||||
|
`CustomContracts:1011120009070e030d0f0e020d0c06050e030c02:0x1211100009070e030d0f0e020d0c06050e030c02'`
|
||||||
|
)
|
||||||
|
|
||||||
|
// GetSignersFromContext returns signers parsed from context args starting
|
||||||
|
// from the specified offset.
|
||||||
|
func GetSignersFromContext(ctx *cli.Context, offset int) ([]transaction.Signer, cli.ExitCoder) {
|
||||||
|
args := ctx.Args()
|
||||||
|
var (
|
||||||
|
signers []transaction.Signer
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if args.Present() && args.Len() > offset {
|
||||||
|
signers, err = ParseSigners(args.Slice()[offset:])
|
||||||
|
if err != nil {
|
||||||
|
return nil, cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return signers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseSigners returns array of signers parsed from their string representation.
|
||||||
|
func ParseSigners(args []string) ([]transaction.Signer, error) {
|
||||||
|
var signers []transaction.Signer
|
||||||
|
for i, c := range args {
|
||||||
|
cosigner, err := parseCosigner(c)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to parse signer #%d: %w", i, err)
|
||||||
|
}
|
||||||
|
signers = append(signers, cosigner)
|
||||||
|
}
|
||||||
|
return signers, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func parseCosigner(c string) (transaction.Signer, error) {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
res = transaction.Signer{
|
||||||
|
Scopes: transaction.CalledByEntry,
|
||||||
|
}
|
||||||
|
)
|
||||||
|
address, scopes, _ := strings.Cut(c, ":")
|
||||||
|
res.Account, err = flags.ParseAddress(address)
|
||||||
|
if err != nil {
|
||||||
|
return res, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if scopes == "" {
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Scopes = 0
|
||||||
|
for s := range strings.SplitSeq(scopes, ",") {
|
||||||
|
sub := strings.Split(s, ":")
|
||||||
|
scope, err := transaction.ScopesFromString(sub[0])
|
||||||
|
if err != nil {
|
||||||
|
return transaction.Signer{}, err
|
||||||
|
}
|
||||||
|
if scope == transaction.Global && res.Scopes&^transaction.Global != 0 ||
|
||||||
|
scope != transaction.Global && res.Scopes&transaction.Global != 0 {
|
||||||
|
return transaction.Signer{}, errors.New("'Global' scope can not be combined with other scopes")
|
||||||
|
}
|
||||||
|
|
||||||
|
res.Scopes |= scope
|
||||||
|
|
||||||
|
switch scope {
|
||||||
|
case transaction.CustomContracts:
|
||||||
|
if len(sub) == 1 {
|
||||||
|
return transaction.Signer{}, errors.New("CustomContracts scope must refer to at least one contract")
|
||||||
|
}
|
||||||
|
for _, s := range sub[1:] {
|
||||||
|
addr, err := flags.ParseAddress(s)
|
||||||
|
if err != nil {
|
||||||
|
return transaction.Signer{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
res.AllowedContracts = append(res.AllowedContracts, addr)
|
||||||
|
}
|
||||||
|
case transaction.CustomGroups:
|
||||||
|
if len(sub) == 1 {
|
||||||
|
return transaction.Signer{}, errors.New("CustomGroups scope must refer to at least one group")
|
||||||
|
}
|
||||||
|
for _, s := range sub[1:] {
|
||||||
|
pub, err := keys.NewPublicKeyFromString(s)
|
||||||
|
if err != nil {
|
||||||
|
return transaction.Signer{}, err
|
||||||
|
}
|
||||||
|
|
||||||
|
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.ExitCoder) {
|
||||||
|
var (
|
||||||
|
data any
|
||||||
|
offset int
|
||||||
|
params []smartcontract.Parameter
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
args := ctx.Args()
|
||||||
|
if args.Present() {
|
||||||
|
offset, params, err = ParseParams(args.Slice(), true)
|
||||||
|
if err != nil {
|
||||||
|
return offset, nil, cli.Exit(fmt.Errorf("unable to parse 'data' parameter: %w", err), 1)
|
||||||
|
}
|
||||||
|
if len(params) > 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.Exit(fmt.Sprintf("failed to convert 'data' to emitable type: %s", err.Error()), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return offset, data, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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.ExitCoder {
|
||||||
|
if ctx.Args().Present() {
|
||||||
|
return cli.Exit(fmt.Errorf("additional arguments given while this command expects none"), 1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseParams extracts array of smartcontract.Parameter from the given args and
|
||||||
|
// returns the number of handled words, the array itself and an error.
|
||||||
|
// `calledFromMain` denotes whether the method was called from the outside or
|
||||||
|
// recursively and used to check if CosignersSeparator and ArrayEndSeparator are
|
||||||
|
// allowed to be in `args` sequence.
|
||||||
|
func ParseParams(args []string, calledFromMain bool) (int, []smartcontract.Parameter, error) {
|
||||||
|
res := []smartcontract.Parameter{}
|
||||||
|
for k := 0; k < len(args); {
|
||||||
|
s := args[k]
|
||||||
|
switch s {
|
||||||
|
case CosignersSeparator:
|
||||||
|
if calledFromMain {
|
||||||
|
return k + 1, res, nil // `1` to convert index to numWordsRead
|
||||||
|
}
|
||||||
|
return 0, []smartcontract.Parameter{}, errors.New("invalid array syntax: missing closing bracket")
|
||||||
|
case ArrayStartSeparator:
|
||||||
|
numWordsRead, array, err := ParseParams(args[k+1:], false)
|
||||||
|
if err != nil {
|
||||||
|
return 0, nil, fmt.Errorf("failed to parse array: %w", err)
|
||||||
|
}
|
||||||
|
res = append(res, smartcontract.Parameter{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: array,
|
||||||
|
})
|
||||||
|
k += 1 + numWordsRead // `1` for opening bracket
|
||||||
|
case ArrayEndSeparator:
|
||||||
|
if calledFromMain {
|
||||||
|
return 0, nil, errors.New("invalid array syntax: missing opening bracket")
|
||||||
|
}
|
||||||
|
return k + 1, res, nil // `1`to convert index to numWordsRead
|
||||||
|
default:
|
||||||
|
param, err := smartcontract.NewParameterFromString(s)
|
||||||
|
if err != nil {
|
||||||
|
// '--' argument is skipped by urfave/cli library, which leads
|
||||||
|
// to [--, addr:scope] being transformed to [addr:scope] and
|
||||||
|
// interpreted as a parameter if other positional arguments are not present.
|
||||||
|
// Here we fallback to parsing cosigners in this specific case to
|
||||||
|
// create a better user experience ('-- addr:scope' vs '-- -- addr:scope').
|
||||||
|
if k == 0 {
|
||||||
|
if _, err := parseCosigner(s); err == nil {
|
||||||
|
return 0, nil, nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return 0, nil, fmt.Errorf("failed to parse argument #%d: %w", k+1, err)
|
||||||
|
}
|
||||||
|
res = append(res, *param)
|
||||||
|
k++
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if calledFromMain {
|
||||||
|
return len(args), res, nil
|
||||||
|
}
|
||||||
|
return 0, []smartcontract.Parameter{}, errors.New("invalid array syntax: missing closing bracket")
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetSignersAccounts returns the list of signers combined with the corresponding
|
||||||
|
// accounts from the provided wallet.
|
||||||
|
func GetSignersAccounts(senderAcc *wallet.Account, wall *wallet.Wallet, signers []transaction.Signer, accScope transaction.WitnessScope) ([]actor.SignerAccount, error) {
|
||||||
|
signersAccounts := make([]actor.SignerAccount, 0, len(signers)+1)
|
||||||
|
sender := senderAcc.ScriptHash()
|
||||||
|
signersAccounts = append(signersAccounts, actor.SignerAccount{
|
||||||
|
Signer: transaction.Signer{
|
||||||
|
Account: sender,
|
||||||
|
Scopes: accScope,
|
||||||
|
},
|
||||||
|
Account: senderAcc,
|
||||||
|
})
|
||||||
|
for i, s := range signers {
|
||||||
|
if s.Account == sender {
|
||||||
|
signersAccounts[0].Signer = s
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
signerAcc := wall.GetAccount(s.Account)
|
||||||
|
if signerAcc == nil {
|
||||||
|
return nil, fmt.Errorf("no account was found in the wallet for signer #%d (%s)", i, address.Uint160ToString(s.Account))
|
||||||
|
}
|
||||||
|
signersAccounts = append(signersAccounts, actor.SignerAccount{
|
||||||
|
Signer: s,
|
||||||
|
Account: signerAcc,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,379 @@
|
||||||
|
package cmdargs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/random"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestParseCosigner(t *testing.T) {
|
||||||
|
acc := util.Uint160{1, 3, 5, 7}
|
||||||
|
c1, c2 := random.Uint160(), random.Uint160()
|
||||||
|
priv, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
testCases := map[string]transaction.Signer{
|
||||||
|
acc.StringLE(): {
|
||||||
|
Account: acc,
|
||||||
|
Scopes: transaction.CalledByEntry,
|
||||||
|
},
|
||||||
|
"0x" + acc.StringLE(): {
|
||||||
|
Account: acc,
|
||||||
|
Scopes: transaction.CalledByEntry,
|
||||||
|
},
|
||||||
|
acc.StringLE() + ":Global": {
|
||||||
|
Account: acc,
|
||||||
|
Scopes: transaction.Global,
|
||||||
|
},
|
||||||
|
acc.StringLE() + ":CalledByEntry": {
|
||||||
|
Account: acc,
|
||||||
|
Scopes: transaction.CalledByEntry,
|
||||||
|
},
|
||||||
|
acc.StringLE() + ":None": {
|
||||||
|
Account: acc,
|
||||||
|
Scopes: transaction.None,
|
||||||
|
},
|
||||||
|
acc.StringLE() + ":CalledByEntry,CustomContracts:" + c1.StringLE() + ":0x" + c2.StringLE(): {
|
||||||
|
Account: acc,
|
||||||
|
Scopes: transaction.CalledByEntry | transaction.CustomContracts,
|
||||||
|
AllowedContracts: []util.Uint160{c1, c2},
|
||||||
|
},
|
||||||
|
acc.StringLE() + ":CustomGroups:" + priv.PublicKey().StringCompressed(): {
|
||||||
|
Account: acc,
|
||||||
|
Scopes: transaction.CustomGroups,
|
||||||
|
AllowedGroups: keys.PublicKeys{priv.PublicKey()},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for s, expected := range testCases {
|
||||||
|
actual, err := parseCosigner(s)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, actual, s)
|
||||||
|
}
|
||||||
|
errorCases := []string{
|
||||||
|
acc.StringLE() + "0",
|
||||||
|
acc.StringLE() + ":Unknown",
|
||||||
|
acc.StringLE() + ":Global,CustomContracts",
|
||||||
|
acc.StringLE() + ":Global,None",
|
||||||
|
acc.StringLE() + ":CustomContracts:" + acc.StringLE() + ",Global",
|
||||||
|
acc.StringLE() + ":CustomContracts",
|
||||||
|
acc.StringLE() + ":CustomContracts:xxx",
|
||||||
|
acc.StringLE() + ":CustomGroups",
|
||||||
|
acc.StringLE() + ":CustomGroups:xxx",
|
||||||
|
}
|
||||||
|
for _, s := range errorCases {
|
||||||
|
_, err := parseCosigner(s)
|
||||||
|
require.Error(t, err, s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseParams_CalledFromItself(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
WordsRead int
|
||||||
|
Value []smartcontract.Parameter
|
||||||
|
}{
|
||||||
|
"]": {
|
||||||
|
WordsRead: 1,
|
||||||
|
Value: []smartcontract.Parameter{},
|
||||||
|
},
|
||||||
|
"[ [ ] ] ]": {
|
||||||
|
WordsRead: 5,
|
||||||
|
Value: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: []smartcontract.Parameter{},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"a b c ]": {
|
||||||
|
WordsRead: 4,
|
||||||
|
Value: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"a [ b [ [ c d ] e ] ] f ] extra items": {
|
||||||
|
WordsRead: 13, // the method should return right after the last bracket, as calledFromMain == false
|
||||||
|
Value: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "c",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "d",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "e",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "f",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for str, expected := range testCases {
|
||||||
|
input := strings.Split(str, " ")
|
||||||
|
offset, actual, err := ParseParams(input, false)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected.WordsRead, offset)
|
||||||
|
require.Equal(t, expected.Value, actual)
|
||||||
|
}
|
||||||
|
|
||||||
|
errorCases := []string{
|
||||||
|
"[ ]",
|
||||||
|
"[ a b [ c ] d ]",
|
||||||
|
"[ ] --",
|
||||||
|
"--",
|
||||||
|
"not-int:integer ]",
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, str := range errorCases {
|
||||||
|
input := strings.Split(str, " ")
|
||||||
|
_, _, err := ParseParams(input, false)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestParseParams_CalledFromOutside(t *testing.T) {
|
||||||
|
testCases := map[string]struct {
|
||||||
|
WordsRead int
|
||||||
|
Parameters []smartcontract.Parameter
|
||||||
|
}{
|
||||||
|
"-- cosigner1": {
|
||||||
|
WordsRead: 1, // the `--` only
|
||||||
|
Parameters: []smartcontract.Parameter{},
|
||||||
|
},
|
||||||
|
"a b c": {
|
||||||
|
WordsRead: 3,
|
||||||
|
Parameters: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"a b c -- cosigner1": {
|
||||||
|
WordsRead: 4,
|
||||||
|
Parameters: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"a [ b [ [ c d ] e ] ] f": {
|
||||||
|
WordsRead: 12,
|
||||||
|
Parameters: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "c",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "d",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "e",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "f",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"a [ b ] -- cosigner1 cosigner2": {
|
||||||
|
WordsRead: 5,
|
||||||
|
Parameters: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"a [ b ]": {
|
||||||
|
WordsRead: 4,
|
||||||
|
Parameters: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
"a [ b ] [ [ c ] ] [ [ [ d ] ] ]": {
|
||||||
|
WordsRead: 16,
|
||||||
|
Parameters: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "a",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "b",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "c",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
Value: []smartcontract.Parameter{
|
||||||
|
{
|
||||||
|
Type: smartcontract.StringType,
|
||||||
|
Value: "d",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for str, expected := range testCases {
|
||||||
|
input := strings.Split(str, " ")
|
||||||
|
offset, arr, err := ParseParams(input, true)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected.WordsRead, offset)
|
||||||
|
require.Equal(t, expected.Parameters, arr)
|
||||||
|
}
|
||||||
|
|
||||||
|
errorCases := []string{
|
||||||
|
"[",
|
||||||
|
"]",
|
||||||
|
"[ [ ]",
|
||||||
|
"[ [ ] --",
|
||||||
|
"[ -- ]",
|
||||||
|
}
|
||||||
|
for _, str := range errorCases {
|
||||||
|
input := strings.Split(str, " ")
|
||||||
|
_, _, err := ParseParams(input, true)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,146 @@
|
||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"fmt"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Address is a wrapper for a Uint160 with flag.Value methods.
|
||||||
|
type Address struct {
|
||||||
|
IsSet bool
|
||||||
|
Value util.Uint160
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddressFlag is a flag with type Uint160.
|
||||||
|
type AddressFlag struct {
|
||||||
|
Name string
|
||||||
|
Usage string
|
||||||
|
Value Address
|
||||||
|
Aliases []string
|
||||||
|
Required bool
|
||||||
|
Hidden bool
|
||||||
|
Action func(*cli.Context, string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ flag.Value = (*Address)(nil)
|
||||||
|
_ cli.Flag = AddressFlag{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// String implements the fmt.Stringer interface.
|
||||||
|
func (a Address) String() string {
|
||||||
|
return address.Uint160ToString(a.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set implements the flag.Value interface.
|
||||||
|
func (a *Address) Set(s string) error {
|
||||||
|
addr, err := ParseAddress(s)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
a.IsSet = true
|
||||||
|
a.Value = addr
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Uint160 casts an address to Uint160.
|
||||||
|
func (a *Address) Uint160() (u util.Uint160) {
|
||||||
|
if !a.IsSet {
|
||||||
|
// It is a programmer error to call this method without
|
||||||
|
// checking if the value was provided.
|
||||||
|
panic("address was not set")
|
||||||
|
}
|
||||||
|
return a.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsSet checks if flag was set to a non-default value.
|
||||||
|
func (f AddressFlag) IsSet() bool {
|
||||||
|
return f.Value.IsSet
|
||||||
|
}
|
||||||
|
|
||||||
|
// String returns a readable representation of this value
|
||||||
|
// (for usage defaults).
|
||||||
|
func (f AddressFlag) String() string {
|
||||||
|
var names []string
|
||||||
|
for _, name := range f.Names() {
|
||||||
|
names = append(names, getNameHelp(name))
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.Join(names, ", ") + "\t" + f.Usage
|
||||||
|
}
|
||||||
|
|
||||||
|
func getNameHelp(name string) string {
|
||||||
|
if len(name) == 1 {
|
||||||
|
return fmt.Sprintf("-%s value", name)
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("--%s value", 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) 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.
|
||||||
|
func ParseAddress(s string) (util.Uint160, error) {
|
||||||
|
const uint160size = 2 * util.Uint160Size
|
||||||
|
switch len(s) {
|
||||||
|
case uint160size, uint160size + 2:
|
||||||
|
return util.Uint160DecodeStringLE(strings.TrimPrefix(s, "0x"))
|
||||||
|
default:
|
||||||
|
return address.StringToUint160(s)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,211 @@
|
||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/random"
|
||||||
|
"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) {
|
||||||
|
expected := random.Uint160()
|
||||||
|
t.Run("simple LE", func(t *testing.T) {
|
||||||
|
u, err := ParseAddress(expected.StringLE())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, u)
|
||||||
|
})
|
||||||
|
t.Run("with prefix", func(t *testing.T) {
|
||||||
|
u, err := ParseAddress("0x" + expected.StringLE())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, u)
|
||||||
|
|
||||||
|
t.Run("bad", func(t *testing.T) {
|
||||||
|
_, err := ParseAddress("0s" + expected.StringLE())
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
t.Run("address", func(t *testing.T) {
|
||||||
|
addr := address.Uint160ToString(expected)
|
||||||
|
u, err := ParseAddress(addr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, u)
|
||||||
|
|
||||||
|
t.Run("bad", func(t *testing.T) {
|
||||||
|
_, err := ParseAddress(addr[1:])
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddress_String(t *testing.T) {
|
||||||
|
value := util.Uint160{1, 2, 3}
|
||||||
|
addr := Address{
|
||||||
|
IsSet: true,
|
||||||
|
Value: value,
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, address.Uint160ToString(value), addr.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddress_Set(t *testing.T) {
|
||||||
|
value := util.Uint160{1, 2, 3}
|
||||||
|
addr := Address{}
|
||||||
|
|
||||||
|
t.Run("bad address", func(t *testing.T) {
|
||||||
|
require.Error(t, addr.Set("not an address"))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("positive", func(t *testing.T) {
|
||||||
|
require.NoError(t, addr.Set(address.Uint160ToString(value)))
|
||||||
|
require.Equal(t, true, addr.IsSet)
|
||||||
|
require.Equal(t, value, addr.Value)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddress_Uint160(t *testing.T) {
|
||||||
|
value := util.Uint160{4, 5, 6}
|
||||||
|
addr := Address{}
|
||||||
|
|
||||||
|
t.Run("not set", func(t *testing.T) {
|
||||||
|
require.Panics(t, func() { addr.Uint160() })
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
addr.IsSet = true
|
||||||
|
addr.Value = value
|
||||||
|
require.Equal(t, value, addr.Uint160())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddressFlag_IsSet(t *testing.T) {
|
||||||
|
flag := AddressFlag{}
|
||||||
|
|
||||||
|
t.Run("not set", func(t *testing.T) {
|
||||||
|
require.False(t, flag.IsSet())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("set", func(t *testing.T) {
|
||||||
|
flag.Value.IsSet = true
|
||||||
|
require.True(t, flag.IsSet())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddressFlag_String(t *testing.T) {
|
||||||
|
flag := AddressFlag{
|
||||||
|
Name: "myFlag",
|
||||||
|
Usage: "Address to pass",
|
||||||
|
Value: Address{},
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, "--myFlag value\tAddress to pass", flag.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddress_getNameHelp(t *testing.T) {
|
||||||
|
require.Equal(t, "-f value", getNameHelp("f"))
|
||||||
|
require.Equal(t, "--flag value", getNameHelp("flag"))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddressFlag_Names(t *testing.T) {
|
||||||
|
flag := AddressFlag{
|
||||||
|
Name: "flag",
|
||||||
|
Aliases: []string{"my"},
|
||||||
|
}
|
||||||
|
|
||||||
|
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", 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)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,123 @@
|
||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Fixed8 is a wrapper for a Uint160 with flag.Value methods.
|
||||||
|
type Fixed8 struct {
|
||||||
|
Value fixedn.Fixed8
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixed8Flag is a flag with type string.
|
||||||
|
type Fixed8Flag struct {
|
||||||
|
Name string
|
||||||
|
Usage string
|
||||||
|
Value Fixed8
|
||||||
|
Aliases []string
|
||||||
|
Required bool
|
||||||
|
Hidden bool
|
||||||
|
Action func(*cli.Context, string) error
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
_ flag.Value = (*Fixed8)(nil)
|
||||||
|
_ cli.Flag = Fixed8Flag{}
|
||||||
|
)
|
||||||
|
|
||||||
|
// String implements the fmt.Stringer interface.
|
||||||
|
func (a Fixed8) String() string {
|
||||||
|
return a.Value.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set implements the flag.Value interface.
|
||||||
|
func (a *Fixed8) Set(s string) error {
|
||||||
|
f, err := fixedn.Fixed8FromString(s)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
a.Value = f
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fixed8 casts the address to util.Fixed8.
|
||||||
|
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
|
||||||
|
for _, name := range f.Names() {
|
||||||
|
names = append(names, getNameHelp(name))
|
||||||
|
}
|
||||||
|
return strings.Join(names, ", ") + "\t" + f.Usage
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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) 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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,128 @@
|
||||||
|
package flags
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"io"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"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) {
|
||||||
|
value := fixedn.Fixed8(123)
|
||||||
|
f := Fixed8{
|
||||||
|
Value: value,
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, "0.00000123", f.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFixed8_Set(t *testing.T) {
|
||||||
|
value := fixedn.Fixed8(123)
|
||||||
|
f := Fixed8{}
|
||||||
|
|
||||||
|
require.Error(t, f.Set("not-a-fixed8"))
|
||||||
|
|
||||||
|
require.NoError(t, f.Set("0.00000123"))
|
||||||
|
require.Equal(t, value, f.Value)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFixed8_Fixed8(t *testing.T) {
|
||||||
|
f := Fixed8{
|
||||||
|
Value: fixedn.Fixed8(123),
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, fixedn.Fixed8(123), f.Fixed8())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFixed8Flag_String(t *testing.T) {
|
||||||
|
flag := Fixed8Flag{
|
||||||
|
Name: "myFlag",
|
||||||
|
Usage: "Gas amount",
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, "--myFlag value\tGas amount", flag.String())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestFixed8Flag_Names(t *testing.T) {
|
||||||
|
flag := Fixed8Flag{
|
||||||
|
Name: "myFlag",
|
||||||
|
}
|
||||||
|
|
||||||
|
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", 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())
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,71 @@
|
||||||
|
package input
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Terminal is a terminal used for input. If `nil`, stdin is used.
|
||||||
|
var Terminal *term.Terminal
|
||||||
|
|
||||||
|
// ReadWriter combiner reader and writer.
|
||||||
|
type ReadWriter struct {
|
||||||
|
io.Reader
|
||||||
|
io.Writer
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadLine reads a line from the input without trailing '\n'.
|
||||||
|
func ReadLine(prompt string) (string, error) {
|
||||||
|
trm := Terminal
|
||||||
|
if trm == nil {
|
||||||
|
s, err := term.MakeRaw(int(syscall.Stdin))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer func() { _ = term.Restore(int(syscall.Stdin), s) }()
|
||||||
|
trm = term.NewTerminal(ReadWriter{
|
||||||
|
Reader: os.Stdin,
|
||||||
|
Writer: os.Stdout,
|
||||||
|
}, "")
|
||||||
|
}
|
||||||
|
return readLine(trm, prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
func readLine(trm *term.Terminal, prompt string) (string, error) {
|
||||||
|
_, err := trm.Write([]byte(prompt))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return trm.ReadLine()
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadPassword reads the user's password with prompt.
|
||||||
|
func ReadPassword(prompt string) (string, error) {
|
||||||
|
trm := Terminal
|
||||||
|
if trm != nil {
|
||||||
|
return trm.ReadPassword(prompt)
|
||||||
|
}
|
||||||
|
return readSecurePassword(prompt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ConfirmTx asks for a confirmation to send the tx.
|
||||||
|
func ConfirmTx(w io.Writer, tx *transaction.Transaction) error {
|
||||||
|
fmt.Fprintf(w, "Network fee: %s\n", fixedn.Fixed8(tx.NetworkFee))
|
||||||
|
fmt.Fprintf(w, "System fee: %s\n", fixedn.Fixed8(tx.SystemFee))
|
||||||
|
fmt.Fprintf(w, "Total fee: %s\n", fixedn.Fixed8(tx.NetworkFee+tx.SystemFee))
|
||||||
|
ln, err := ReadLine("Relay transaction (y|N)> ")
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
if 0 < len(ln) && (ln[0] == 'y' || ln[0] == 'Y') {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return errors.New("cancelled")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,29 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package input
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
// readSecurePassword reads the user's password with prompt directly from /dev/tty.
|
||||||
|
func readSecurePassword(prompt string) (string, error) {
|
||||||
|
f, err := os.OpenFile("/dev/tty", os.O_RDWR, 0)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
_, err = f.WriteString(prompt)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
pass, err := term.ReadPassword(int(f.Fd()))
|
||||||
|
if err != nil {
|
||||||
|
return "", fmt.Errorf("failed to read password: %w", err)
|
||||||
|
}
|
||||||
|
_, err = f.WriteString("\n")
|
||||||
|
return string(pass), err
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package input
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"golang.org/x/term"
|
||||||
|
)
|
||||||
|
|
||||||
|
// readSecurePassword reads the user's password with prompt.
|
||||||
|
func readSecurePassword(prompt string) (string, error) {
|
||||||
|
s, err := term.MakeRaw(int(syscall.Stdin))
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
defer func() { _ = term.Restore(int(syscall.Stdin), s) }()
|
||||||
|
trm := term.NewTerminal(ReadWriter{os.Stdin, os.Stdout}, prompt)
|
||||||
|
return trm.ReadPassword(prompt)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,17 @@
|
||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/cli/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
ctl := app.New()
|
||||||
|
|
||||||
|
if err := ctl.Run(os.Args); err != nil {
|
||||||
|
fmt.Fprintln(ctl.ErrWriter, err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,682 @@
|
||||||
|
package nep_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/base64"
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/testcli"
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/versionutil"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// nftOwnerAddr is the owner of NFT-ND HASHY token (../examples/nft-nd/nft.go).
|
||||||
|
nftOwnerAddr = "NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB"
|
||||||
|
nftOwnerWallet = "../../examples/my_wallet.json"
|
||||||
|
nftOwnerPass = "qwerty"
|
||||||
|
|
||||||
|
// Keep contract NEFs consistent between runs.
|
||||||
|
_ = versionutil.TestVersion
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNEP11Import(t *testing.T) {
|
||||||
|
e := testcli.NewExecutor(t, true)
|
||||||
|
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
walletPath := filepath.Join(tmpDir, "walletForImport.json")
|
||||||
|
|
||||||
|
// deploy NFT NeoNameService contract
|
||||||
|
nnsContractHash := deployNNSContract(t, e)
|
||||||
|
// deploy NFT-D NeoFS Object contract
|
||||||
|
nfsContractHash := deployNFSContract(t, e)
|
||||||
|
neoContractHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Neo)
|
||||||
|
require.NoError(t, err)
|
||||||
|
e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath)
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"neo-go", "wallet", "nep11", "import",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
"--wallet", walletPath,
|
||||||
|
}
|
||||||
|
// missing token hash
|
||||||
|
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())...)
|
||||||
|
|
||||||
|
// good: divisible
|
||||||
|
e.Run(t, append(args, "--token", nfsContractHash.StringLE())...)
|
||||||
|
|
||||||
|
// already exists
|
||||||
|
e.RunWithError(t, append(args, "--token", nnsContractHash.StringLE())...)
|
||||||
|
|
||||||
|
// not a NEP-11 token
|
||||||
|
e.RunWithError(t, append(args, "--token", neoContractHash.StringLE())...)
|
||||||
|
|
||||||
|
checkInfo := func(t *testing.T, h util.Uint160, name string, symbol string, decimals int) {
|
||||||
|
e.CheckNextLine(t, "^Name:\\s*"+name)
|
||||||
|
e.CheckNextLine(t, "^Symbol:\\s*"+symbol)
|
||||||
|
e.CheckNextLine(t, "^Hash:\\s*"+h.StringLE())
|
||||||
|
e.CheckNextLine(t, "^Decimals:\\s*"+strconv.Itoa(decimals))
|
||||||
|
e.CheckNextLine(t, "^Address:\\s*"+address.Uint160ToString(h))
|
||||||
|
e.CheckNextLine(t, "^Standard:\\s*"+string(manifest.NEP11StandardName))
|
||||||
|
}
|
||||||
|
t.Run("Info", func(t *testing.T) {
|
||||||
|
t.Run("excessive parameters", func(t *testing.T) {
|
||||||
|
e.RunWithError(t, "neo-go", "wallet", "nep11", "info",
|
||||||
|
"--wallet", walletPath, "--token", nnsContractHash.StringLE(), "qwerty")
|
||||||
|
})
|
||||||
|
t.Run("WithToken", func(t *testing.T) {
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep11", "info",
|
||||||
|
"--wallet", walletPath, "--token", nnsContractHash.StringLE())
|
||||||
|
checkInfo(t, nnsContractHash, "NameService", "NNS", 0)
|
||||||
|
})
|
||||||
|
t.Run("NoToken", func(t *testing.T) {
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep11", "info",
|
||||||
|
"--wallet", walletPath)
|
||||||
|
checkInfo(t, nnsContractHash, "NameService", "NNS", 0)
|
||||||
|
e.CheckNextLine(t, "")
|
||||||
|
checkInfo(t, nfsContractHash, "NeoFS Object NFT", "NFSO", 2)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("Remove", func(t *testing.T) {
|
||||||
|
e.RunWithError(t, "neo-go", "wallet", "nep11", "remove",
|
||||||
|
"--wallet", walletPath, "--token", nnsContractHash.StringLE(), "parameter")
|
||||||
|
e.In.WriteString("y\r")
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep11", "remove",
|
||||||
|
"--wallet", walletPath, "--token", nnsContractHash.StringLE())
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep11", "info",
|
||||||
|
"--wallet", walletPath)
|
||||||
|
checkInfo(t, nfsContractHash, "NeoFS Object NFT", "NFSO", 2)
|
||||||
|
_, err := e.Out.ReadString('\n')
|
||||||
|
require.Equal(t, err, io.EOF)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNEP11_ND_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
||||||
|
e := testcli.NewExecutor(t, true)
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
// copy wallet to temp dir in order not to overwrite the original file
|
||||||
|
bytesRead, err := os.ReadFile(nftOwnerWallet)
|
||||||
|
require.NoError(t, err)
|
||||||
|
wall := filepath.Join(tmpDir, "my_wallet.json")
|
||||||
|
err = os.WriteFile(wall, bytesRead, 0755)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// transfer funds to contract owner
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep17", "transfer",
|
||||||
|
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||||
|
"--wallet", testcli.ValidatorWallet,
|
||||||
|
"--to", nftOwnerAddr,
|
||||||
|
"--token", "GAS",
|
||||||
|
"--amount", "10000",
|
||||||
|
"--force",
|
||||||
|
"--from", testcli.ValidatorAddr)
|
||||||
|
e.CheckTxPersisted(t)
|
||||||
|
|
||||||
|
// deploy NFT HASHY contract
|
||||||
|
h := deployNFTContract(t, e)
|
||||||
|
|
||||||
|
mint := func(t *testing.T) []byte {
|
||||||
|
// mint 1 HASHY token by transferring 10 GAS to HASHY contract
|
||||||
|
e.In.WriteString(nftOwnerPass + "\r")
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep17", "transfer",
|
||||||
|
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||||
|
"--wallet", wall,
|
||||||
|
"--to", h.StringLE(),
|
||||||
|
"--token", "GAS",
|
||||||
|
"--amount", "10",
|
||||||
|
"--force",
|
||||||
|
"--from", nftOwnerAddr)
|
||||||
|
txMint, _ := e.CheckTxPersisted(t)
|
||||||
|
|
||||||
|
// get NFT ID from AER
|
||||||
|
aer, err := e.Chain.GetAppExecResults(txMint.Hash(), trigger.Application)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(aer))
|
||||||
|
require.Equal(t, 2, len(aer[0].Events))
|
||||||
|
hashyMintEvent := aer[0].Events[1]
|
||||||
|
require.Equal(t, "Transfer", hashyMintEvent.Name)
|
||||||
|
tokenID, err := hashyMintEvent.Item.Value().([]stackitem.Item)[3].TryBytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, tokenID)
|
||||||
|
return tokenID
|
||||||
|
}
|
||||||
|
|
||||||
|
tokenID := mint(t)
|
||||||
|
var hashBeforeTransfer = e.Chain.CurrentHeaderHash()
|
||||||
|
|
||||||
|
// check the balance
|
||||||
|
cmdCheckBalance := []string{"neo-go", "wallet", "nep11", "balance",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
"--wallet", wall,
|
||||||
|
"--address", nftOwnerAddr}
|
||||||
|
checkBalanceResult := func(t *testing.T, acc string, ids ...[]byte) {
|
||||||
|
e.CheckNextLine(t, "^\\s*Account\\s+"+acc)
|
||||||
|
e.CheckNextLine(t, "^\\s*HASHY:\\s+HASHY NFT \\("+h.StringLE()+"\\)")
|
||||||
|
|
||||||
|
// Hashes can be ordered in any way, so make a regexp for them.
|
||||||
|
var tokstring strings.Builder
|
||||||
|
tokstring.WriteString("(")
|
||||||
|
for i, id := range ids {
|
||||||
|
if i > 0 {
|
||||||
|
tokstring.WriteString("|")
|
||||||
|
}
|
||||||
|
tokstring.WriteString(hex.EncodeToString(id))
|
||||||
|
}
|
||||||
|
tokstring.WriteString(")")
|
||||||
|
|
||||||
|
for range ids {
|
||||||
|
e.CheckNextLine(t, "^\\s*Token: "+tokstring.String()+"\\s*$")
|
||||||
|
e.CheckNextLine(t, "^\\s*Amount: 1\\s*$")
|
||||||
|
e.CheckNextLine(t, "^\\s*Updated: [0-9]+\\s*$")
|
||||||
|
}
|
||||||
|
e.CheckEOF(t)
|
||||||
|
}
|
||||||
|
// balance check: by symbol, token is not imported
|
||||||
|
e.Run(t, append(cmdCheckBalance, "--token", "HASHY")...)
|
||||||
|
checkBalanceResult(t, nftOwnerAddr, tokenID)
|
||||||
|
// balance check: excessive parameters
|
||||||
|
e.RunWithError(t, append(cmdCheckBalance, "--token", h.StringLE(), "neo-go")...)
|
||||||
|
// balance check: by hash, ok
|
||||||
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||||
|
checkBalanceResult(t, nftOwnerAddr, tokenID)
|
||||||
|
|
||||||
|
// import token
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep11", "import",
|
||||||
|
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||||
|
"--wallet", wall,
|
||||||
|
"--token", h.StringLE())
|
||||||
|
|
||||||
|
// balance check: by symbol, ok
|
||||||
|
e.Run(t, append(cmdCheckBalance, "--token", "HASHY")...)
|
||||||
|
checkBalanceResult(t, nftOwnerAddr, tokenID)
|
||||||
|
|
||||||
|
// balance check: all accounts
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep11", "balance",
|
||||||
|
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||||
|
"--wallet", wall,
|
||||||
|
"--token", h.StringLE())
|
||||||
|
checkBalanceResult(t, nftOwnerAddr, tokenID)
|
||||||
|
|
||||||
|
// remove token from wallet
|
||||||
|
e.In.WriteString("y\r")
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep11", "remove",
|
||||||
|
"--wallet", wall, "--token", h.StringLE())
|
||||||
|
|
||||||
|
// ownerOf: missing contract hash
|
||||||
|
cmdOwnerOf := []string{"neo-go", "wallet", "nep11", "ownerOf",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
}
|
||||||
|
e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdOwnerOf...)
|
||||||
|
cmdOwnerOf = append(cmdOwnerOf, "--token", h.StringLE())
|
||||||
|
|
||||||
|
// ownerOf: missing token ID
|
||||||
|
e.RunWithError(t, cmdOwnerOf...)
|
||||||
|
cmdOwnerOf = append(cmdOwnerOf, "--id", hex.EncodeToString(tokenID))
|
||||||
|
|
||||||
|
// ownerOf: good
|
||||||
|
e.Run(t, cmdOwnerOf...)
|
||||||
|
e.CheckNextLine(t, nftOwnerAddr)
|
||||||
|
|
||||||
|
// tokensOf: missing contract hash
|
||||||
|
cmdTokensOf := []string{"neo-go", "wallet", "nep11", "tokensOf",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
}
|
||||||
|
e.RunWithErrorCheck(t, `Required flags "token, address" not set`, cmdTokensOf...)
|
||||||
|
cmdTokensOf = append(cmdTokensOf, "--token", h.StringLE())
|
||||||
|
|
||||||
|
// tokensOf: missing owner address
|
||||||
|
e.RunWithErrorCheck(t, `Required flag "address" not set`, cmdTokensOf...)
|
||||||
|
cmdTokensOf = append(cmdTokensOf, "--address", nftOwnerAddr)
|
||||||
|
|
||||||
|
// tokensOf: good
|
||||||
|
e.Run(t, cmdTokensOf...)
|
||||||
|
require.Equal(t, hex.EncodeToString(tokenID), e.GetNextLine(t))
|
||||||
|
|
||||||
|
// properties: no contract
|
||||||
|
cmdProperties := []string{
|
||||||
|
"neo-go", "wallet", "nep11", "properties",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
}
|
||||||
|
e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdProperties...)
|
||||||
|
cmdProperties = append(cmdProperties, "--token", h.StringLE())
|
||||||
|
|
||||||
|
// properties: no token ID
|
||||||
|
e.RunWithError(t, cmdProperties...)
|
||||||
|
cmdProperties = append(cmdProperties, "--id", hex.EncodeToString(tokenID))
|
||||||
|
|
||||||
|
// properties: ok
|
||||||
|
e.Run(t, cmdProperties...)
|
||||||
|
require.Equal(t, fmt.Sprintf(`{"name":"HASHY %s"}`, base64.StdEncoding.EncodeToString(tokenID)), e.GetNextLine(t))
|
||||||
|
|
||||||
|
// tokensOf: good, several tokens
|
||||||
|
tokenID1 := mint(t)
|
||||||
|
e.Run(t, cmdTokensOf...)
|
||||||
|
fst, snd := tokenID, tokenID1
|
||||||
|
if bytes.Compare(tokenID, tokenID1) == 1 {
|
||||||
|
fst, snd = snd, fst
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, hex.EncodeToString(fst), e.GetNextLine(t))
|
||||||
|
require.Equal(t, hex.EncodeToString(snd), e.GetNextLine(t))
|
||||||
|
|
||||||
|
// tokens: missing contract hash
|
||||||
|
cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
}
|
||||||
|
e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdTokens...)
|
||||||
|
cmdTokens = append(cmdTokens, "--token", h.StringLE())
|
||||||
|
|
||||||
|
// tokens: excessive parameters
|
||||||
|
e.RunWithError(t, append(cmdTokens, "additional")...)
|
||||||
|
// tokens: good, several tokens
|
||||||
|
e.Run(t, cmdTokens...)
|
||||||
|
require.Equal(t, hex.EncodeToString(fst), e.GetNextLine(t))
|
||||||
|
require.Equal(t, hex.EncodeToString(snd), e.GetNextLine(t))
|
||||||
|
|
||||||
|
// balance check: several tokens, ok
|
||||||
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||||
|
checkBalanceResult(t, nftOwnerAddr, tokenID, tokenID1)
|
||||||
|
|
||||||
|
cmdTransfer := []string{
|
||||||
|
"neo-go", "wallet", "nep11", "transfer",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
"--wallet", wall,
|
||||||
|
"--to", testcli.ValidatorAddr,
|
||||||
|
"--from", nftOwnerAddr,
|
||||||
|
"--force",
|
||||||
|
}
|
||||||
|
|
||||||
|
// transfer: unimported token with symbol id specified
|
||||||
|
e.In.WriteString(nftOwnerPass + "\r")
|
||||||
|
e.RunWithError(t, append(cmdTransfer,
|
||||||
|
"--token", "HASHY")...)
|
||||||
|
cmdTransfer = append(cmdTransfer, "--token", h.StringLE())
|
||||||
|
|
||||||
|
// transfer: no id specified
|
||||||
|
e.In.WriteString(nftOwnerPass + "\r")
|
||||||
|
e.RunWithError(t, cmdTransfer...)
|
||||||
|
|
||||||
|
// transfer: good
|
||||||
|
e.In.WriteString(nftOwnerPass + "\r")
|
||||||
|
e.Run(t, append(cmdTransfer, "--id", hex.EncodeToString(tokenID))...)
|
||||||
|
e.CheckTxPersisted(t)
|
||||||
|
|
||||||
|
// check balance after transfer
|
||||||
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||||
|
checkBalanceResult(t, nftOwnerAddr, tokenID1)
|
||||||
|
|
||||||
|
// check --await flag
|
||||||
|
tokenID2 := mint(t)
|
||||||
|
e.In.WriteString(nftOwnerPass + "\r")
|
||||||
|
e.Run(t, append(cmdTransfer, "--await", "--id", hex.EncodeToString(tokenID2))...)
|
||||||
|
e.CheckAwaitableTxPersisted(t)
|
||||||
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||||
|
checkBalanceResult(t, nftOwnerAddr, tokenID1)
|
||||||
|
|
||||||
|
// transfer: good, to NEP-11-Payable contract, with data
|
||||||
|
verifyH := deployVerifyContract(t, e)
|
||||||
|
cmdTransfer = []string{
|
||||||
|
"neo-go", "wallet", "nep11", "transfer",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
"--wallet", wall,
|
||||||
|
"--to", verifyH.StringLE(),
|
||||||
|
"--from", nftOwnerAddr,
|
||||||
|
"--token", h.StringLE(),
|
||||||
|
"--id", hex.EncodeToString(tokenID1),
|
||||||
|
"--force",
|
||||||
|
"string:some_data",
|
||||||
|
}
|
||||||
|
e.In.WriteString(nftOwnerPass + "\r")
|
||||||
|
e.Run(t, cmdTransfer...)
|
||||||
|
tx, _ := e.CheckTxPersisted(t)
|
||||||
|
// check OnNEP11Payment event
|
||||||
|
aer, err := e.Chain.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(aer[0].Events))
|
||||||
|
nftOwnerHash, err := address.StringToUint160(nftOwnerAddr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, state.NotificationEvent{
|
||||||
|
ScriptHash: verifyH,
|
||||||
|
Name: "OnNEP11Payment",
|
||||||
|
Item: stackitem.NewArray([]stackitem.Item{
|
||||||
|
stackitem.NewByteArray(nftOwnerHash.BytesBE()),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(1)),
|
||||||
|
stackitem.NewByteArray(tokenID1),
|
||||||
|
stackitem.NewByteArray([]byte("some_data")),
|
||||||
|
}),
|
||||||
|
}, aer[0].Events[1])
|
||||||
|
|
||||||
|
// check balance after transfer
|
||||||
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||||
|
checkBalanceResult(t, nftOwnerAddr)
|
||||||
|
|
||||||
|
// historic calls still remember the good old days.
|
||||||
|
cmdOwnerOf = append(cmdOwnerOf, "--historic", hashBeforeTransfer.StringLE())
|
||||||
|
e.Run(t, cmdOwnerOf...)
|
||||||
|
e.CheckNextLine(t, nftOwnerAddr)
|
||||||
|
|
||||||
|
cmdTokensOf = append(cmdTokensOf, "--historic", hashBeforeTransfer.StringLE())
|
||||||
|
e.Run(t, cmdTokensOf...)
|
||||||
|
require.Equal(t, hex.EncodeToString(tokenID), e.GetNextLine(t))
|
||||||
|
|
||||||
|
cmdTokens = append(cmdTokens, "--historic", hashBeforeTransfer.StringLE())
|
||||||
|
e.Run(t, cmdTokens...)
|
||||||
|
require.Equal(t, hex.EncodeToString(tokenID), e.GetNextLine(t))
|
||||||
|
|
||||||
|
// this one is not affected by transfer, but anyway
|
||||||
|
cmdProperties = append(cmdProperties, "--historic", hashBeforeTransfer.StringLE())
|
||||||
|
e.Run(t, cmdProperties...)
|
||||||
|
require.Equal(t, fmt.Sprintf(`{"name":"HASHY %s"}`, base64.StdEncoding.EncodeToString(tokenID)), e.GetNextLine(t))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNEP11_D_OwnerOf_BalanceOf_Transfer(t *testing.T) {
|
||||||
|
e := testcli.NewExecutor(t, true)
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
|
||||||
|
// copy wallet to temp dir in order not to overwrite the original file
|
||||||
|
bytesRead, err := os.ReadFile(testcli.ValidatorWallet)
|
||||||
|
require.NoError(t, err)
|
||||||
|
wall := filepath.Join(tmpDir, "my_wallet.json")
|
||||||
|
err = os.WriteFile(wall, bytesRead, 0755)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// deploy NeoFS Object contract
|
||||||
|
h := deployNFSContract(t, e)
|
||||||
|
|
||||||
|
mint := func(t *testing.T, containerID, objectID util.Uint256) []byte {
|
||||||
|
// mint 1.00 NFSO token by transferring 10 GAS to NFSO contract
|
||||||
|
e.In.WriteString(testcli.ValidatorPass + "\r")
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep17", "transfer",
|
||||||
|
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||||
|
"--wallet", wall,
|
||||||
|
"--to", h.StringLE(),
|
||||||
|
"--token", "GAS",
|
||||||
|
"--amount", "10",
|
||||||
|
"--force",
|
||||||
|
"--from", testcli.ValidatorAddr,
|
||||||
|
"--", "[", "hash256:"+containerID.StringLE(), "hash256:"+objectID.StringLE(), "]",
|
||||||
|
)
|
||||||
|
txMint, _ := e.CheckTxPersisted(t)
|
||||||
|
|
||||||
|
// get NFT ID from AER
|
||||||
|
aer, err := e.Chain.GetAppExecResults(txMint.Hash(), trigger.Application)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(aer))
|
||||||
|
require.Equal(t, 2, len(aer[0].Events))
|
||||||
|
nfsoMintEvent := aer[0].Events[1]
|
||||||
|
require.Equal(t, "Transfer", nfsoMintEvent.Name)
|
||||||
|
tokenID, err := nfsoMintEvent.Item.Value().([]stackitem.Item)[3].TryBytes()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NotNil(t, tokenID)
|
||||||
|
return tokenID
|
||||||
|
}
|
||||||
|
|
||||||
|
container1ID := util.Uint256{1, 2, 3}
|
||||||
|
object1ID := util.Uint256{4, 5, 6}
|
||||||
|
token1ID := mint(t, container1ID, object1ID)
|
||||||
|
|
||||||
|
container2ID := util.Uint256{7, 8, 9}
|
||||||
|
object2ID := util.Uint256{10, 11, 12}
|
||||||
|
token2ID := mint(t, container2ID, object2ID)
|
||||||
|
|
||||||
|
// check properties
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep11", "properties",
|
||||||
|
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||||
|
"--token", h.StringLE(),
|
||||||
|
"--id", hex.EncodeToString(token1ID))
|
||||||
|
jProps := e.GetNextLine(t)
|
||||||
|
props := make(map[string]string)
|
||||||
|
require.NoError(t, json.Unmarshal([]byte(jProps), &props))
|
||||||
|
require.Equal(t, base64.StdEncoding.EncodeToString(container1ID.BytesBE()), props["containerID"])
|
||||||
|
require.Equal(t, base64.StdEncoding.EncodeToString(object1ID.BytesBE()), props["objectID"])
|
||||||
|
e.CheckEOF(t)
|
||||||
|
|
||||||
|
type idAmount struct {
|
||||||
|
id string
|
||||||
|
amount string
|
||||||
|
}
|
||||||
|
|
||||||
|
// check the balance
|
||||||
|
cmdCheckBalance := []string{"neo-go", "wallet", "nep11", "balance",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
"--wallet", wall,
|
||||||
|
"--address", testcli.ValidatorAddr}
|
||||||
|
checkBalanceResult := func(t *testing.T, acc string, objs ...idAmount) {
|
||||||
|
e.CheckNextLine(t, "^\\s*Account\\s+"+acc)
|
||||||
|
e.CheckNextLine(t, "^\\s*NFSO:\\s+NeoFS Object NFT \\("+h.StringLE()+"\\)")
|
||||||
|
|
||||||
|
for _, o := range objs {
|
||||||
|
e.CheckNextLine(t, "^\\s*Token: "+o.id+"\\s*$")
|
||||||
|
e.CheckNextLine(t, "^\\s*Amount: "+o.amount+"\\s*$")
|
||||||
|
e.CheckNextLine(t, "^\\s*Updated: [0-9]+\\s*$")
|
||||||
|
}
|
||||||
|
e.CheckEOF(t)
|
||||||
|
}
|
||||||
|
tokz := []idAmount{
|
||||||
|
{hex.EncodeToString(token1ID), "1"},
|
||||||
|
{hex.EncodeToString(token2ID), "1"},
|
||||||
|
}
|
||||||
|
// balance check: by symbol, token is not imported
|
||||||
|
e.Run(t, append(cmdCheckBalance, "--token", "NFSO")...)
|
||||||
|
checkBalanceResult(t, testcli.ValidatorAddr, tokz...)
|
||||||
|
|
||||||
|
// overall NFSO balance check: by hash, ok
|
||||||
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||||
|
checkBalanceResult(t, testcli.ValidatorAddr, tokz...)
|
||||||
|
|
||||||
|
// particular NFSO balance check: by hash, ok
|
||||||
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE(), "--id", hex.EncodeToString(token2ID))...)
|
||||||
|
checkBalanceResult(t, testcli.ValidatorAddr, tokz[1])
|
||||||
|
|
||||||
|
// import token
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep11", "import",
|
||||||
|
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||||
|
"--wallet", wall,
|
||||||
|
"--token", h.StringLE())
|
||||||
|
|
||||||
|
// overall balance check: by symbol, ok
|
||||||
|
e.Run(t, append(cmdCheckBalance, "--token", "NFSO")...)
|
||||||
|
checkBalanceResult(t, testcli.ValidatorAddr, tokz...)
|
||||||
|
|
||||||
|
// particular balance check: by symbol, ok
|
||||||
|
e.Run(t, append(cmdCheckBalance, "--token", "NFSO", "--id", hex.EncodeToString(token1ID))...)
|
||||||
|
checkBalanceResult(t, testcli.ValidatorAddr, tokz[0])
|
||||||
|
|
||||||
|
// remove token from wallet
|
||||||
|
e.In.WriteString("y\r")
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep11", "remove",
|
||||||
|
"--wallet", wall, "--token", h.StringLE())
|
||||||
|
|
||||||
|
// ownerOfD: missing contract hash
|
||||||
|
cmdOwnerOf := []string{"neo-go", "wallet", "nep11", "ownerOfD",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
}
|
||||||
|
e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdOwnerOf...)
|
||||||
|
cmdOwnerOf = append(cmdOwnerOf, "--token", h.StringLE())
|
||||||
|
|
||||||
|
// ownerOfD: missing token ID
|
||||||
|
e.RunWithError(t, cmdOwnerOf...)
|
||||||
|
cmdOwnerOf = append(cmdOwnerOf, "--id", hex.EncodeToString(token1ID))
|
||||||
|
|
||||||
|
// ownerOfD: good
|
||||||
|
e.Run(t, cmdOwnerOf...)
|
||||||
|
e.CheckNextLine(t, testcli.ValidatorAddr)
|
||||||
|
|
||||||
|
// tokensOf: missing contract hash
|
||||||
|
cmdTokensOf := []string{"neo-go", "wallet", "nep11", "tokensOf",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
}
|
||||||
|
e.RunWithErrorCheck(t, `Required flags "token, address" not set`, cmdTokensOf...)
|
||||||
|
cmdTokensOf = append(cmdTokensOf, "--token", h.StringLE())
|
||||||
|
|
||||||
|
// tokensOf: missing owner address
|
||||||
|
e.RunWithErrorCheck(t, `Required flag "address" not set`, cmdTokensOf...)
|
||||||
|
cmdTokensOf = append(cmdTokensOf, "--address", testcli.ValidatorAddr)
|
||||||
|
|
||||||
|
// tokensOf: good
|
||||||
|
e.Run(t, cmdTokensOf...)
|
||||||
|
require.Equal(t, hex.EncodeToString(token1ID), e.GetNextLine(t))
|
||||||
|
require.Equal(t, hex.EncodeToString(token2ID), e.GetNextLine(t))
|
||||||
|
e.CheckEOF(t)
|
||||||
|
|
||||||
|
// properties: no contract
|
||||||
|
cmdProperties := []string{
|
||||||
|
"neo-go", "wallet", "nep11", "properties",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
}
|
||||||
|
e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdProperties...)
|
||||||
|
cmdProperties = append(cmdProperties, "--token", h.StringLE())
|
||||||
|
|
||||||
|
// properties: no token ID
|
||||||
|
e.RunWithError(t, cmdProperties...)
|
||||||
|
cmdProperties = append(cmdProperties, "--id", hex.EncodeToString(token2ID))
|
||||||
|
|
||||||
|
// properties: additional parameter
|
||||||
|
e.RunWithError(t, append(cmdProperties, "additiona")...)
|
||||||
|
|
||||||
|
// properties: ok
|
||||||
|
e.Run(t, cmdProperties...)
|
||||||
|
jProps = e.GetNextLine(t)
|
||||||
|
props = make(map[string]string)
|
||||||
|
require.NoError(t, json.Unmarshal([]byte(jProps), &props))
|
||||||
|
require.Equal(t, base64.StdEncoding.EncodeToString(container2ID.BytesBE()), props["containerID"])
|
||||||
|
require.Equal(t, base64.StdEncoding.EncodeToString(object2ID.BytesBE()), props["objectID"])
|
||||||
|
e.CheckEOF(t)
|
||||||
|
|
||||||
|
// tokensOf: good, several tokens
|
||||||
|
e.Run(t, cmdTokensOf...)
|
||||||
|
fst, snd := token1ID, token2ID
|
||||||
|
if bytes.Compare(token1ID, token2ID) == 1 {
|
||||||
|
fst, snd = snd, fst
|
||||||
|
}
|
||||||
|
|
||||||
|
require.Equal(t, hex.EncodeToString(fst), e.GetNextLine(t))
|
||||||
|
require.Equal(t, hex.EncodeToString(snd), e.GetNextLine(t))
|
||||||
|
|
||||||
|
// tokens: missing contract hash
|
||||||
|
cmdTokens := []string{"neo-go", "wallet", "nep11", "tokens",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
}
|
||||||
|
e.RunWithErrorCheck(t, `Required flag "token" not set`, cmdTokens...)
|
||||||
|
cmdTokens = append(cmdTokens, "--token", h.StringLE())
|
||||||
|
|
||||||
|
// tokens: good, several tokens
|
||||||
|
e.Run(t, cmdTokens...)
|
||||||
|
require.Equal(t, hex.EncodeToString(fst), e.GetNextLine(t))
|
||||||
|
require.Equal(t, hex.EncodeToString(snd), e.GetNextLine(t))
|
||||||
|
|
||||||
|
// balance check: several tokens, ok
|
||||||
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||||
|
checkBalanceResult(t, testcli.ValidatorAddr, tokz...)
|
||||||
|
|
||||||
|
cmdTransfer := []string{
|
||||||
|
"neo-go", "wallet", "nep11", "transfer",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
"--wallet", wall,
|
||||||
|
"--to", nftOwnerAddr,
|
||||||
|
"--from", testcli.ValidatorAddr,
|
||||||
|
"--force",
|
||||||
|
}
|
||||||
|
|
||||||
|
// transfer: unimported token with symbol id specified
|
||||||
|
e.In.WriteString(testcli.ValidatorPass + "\r")
|
||||||
|
e.RunWithError(t, append(cmdTransfer,
|
||||||
|
"--token", "NFSO")...)
|
||||||
|
cmdTransfer = append(cmdTransfer, "--token", h.StringLE())
|
||||||
|
|
||||||
|
// transfer: no id specified
|
||||||
|
e.In.WriteString(testcli.ValidatorPass + "\r")
|
||||||
|
e.RunWithError(t, cmdTransfer...)
|
||||||
|
|
||||||
|
// transfer: good
|
||||||
|
e.In.WriteString(testcli.ValidatorPass + "\r")
|
||||||
|
e.Run(t, append(cmdTransfer, "--id", hex.EncodeToString(token1ID))...)
|
||||||
|
e.CheckTxPersisted(t)
|
||||||
|
|
||||||
|
// check balance after transfer
|
||||||
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||||
|
checkBalanceResult(t, testcli.ValidatorAddr, tokz[1]) // only token2ID expected to be on the balance
|
||||||
|
|
||||||
|
// transfer: good, 1/4 of the balance, to NEP-11-Payable contract, with data
|
||||||
|
verifyH := deployVerifyContract(t, e)
|
||||||
|
cmdTransfer = []string{
|
||||||
|
"neo-go", "wallet", "nep11", "transfer",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
"--wallet", wall,
|
||||||
|
"--to", verifyH.StringLE(),
|
||||||
|
"--from", testcli.ValidatorAddr,
|
||||||
|
"--token", h.StringLE(),
|
||||||
|
"--id", hex.EncodeToString(token2ID),
|
||||||
|
"--amount", "0.25",
|
||||||
|
"--force",
|
||||||
|
"string:some_data",
|
||||||
|
}
|
||||||
|
e.In.WriteString(testcli.ValidatorPass + "\r")
|
||||||
|
e.Run(t, cmdTransfer...)
|
||||||
|
tx, _ := e.CheckTxPersisted(t)
|
||||||
|
// check OnNEP11Payment event
|
||||||
|
aer, err := e.Chain.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 2, len(aer[0].Events))
|
||||||
|
validatorHash, err := address.StringToUint160(testcli.ValidatorAddr)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, state.NotificationEvent{
|
||||||
|
ScriptHash: verifyH,
|
||||||
|
Name: "OnNEP11Payment",
|
||||||
|
Item: stackitem.NewArray([]stackitem.Item{
|
||||||
|
stackitem.NewByteArray(validatorHash.BytesBE()),
|
||||||
|
stackitem.NewBigInteger(big.NewInt(25)),
|
||||||
|
stackitem.NewByteArray(token2ID),
|
||||||
|
stackitem.NewByteArray([]byte("some_data")),
|
||||||
|
}),
|
||||||
|
}, aer[0].Events[1])
|
||||||
|
|
||||||
|
// check balance after transfer
|
||||||
|
e.Run(t, append(cmdCheckBalance, "--token", h.StringLE())...)
|
||||||
|
tokz[1].amount = "0.75"
|
||||||
|
checkBalanceResult(t, testcli.ValidatorAddr, tokz[1])
|
||||||
|
}
|
||||||
|
|
||||||
|
func deployNFSContract(t *testing.T, e *testcli.Executor) util.Uint160 {
|
||||||
|
return testcli.DeployContract(t, e, "../../examples/nft-d/nft.go", "../../examples/nft-d/nft.yml", testcli.ValidatorWallet, testcli.ValidatorAddr, testcli.ValidatorPass)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deployNFTContract(t *testing.T, e *testcli.Executor) util.Uint160 {
|
||||||
|
return testcli.DeployContract(t, e, "../../examples/nft-nd/nft.go", "../../examples/nft-nd/nft.yml", nftOwnerWallet, nftOwnerAddr, nftOwnerPass)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deployNNSContract(t *testing.T, e *testcli.Executor) util.Uint160 {
|
||||||
|
return testcli.DeployContract(t, e, "../../examples/nft-nd-nns/", "../../examples/nft-nd-nns/nns.yml", testcli.ValidatorWallet, testcli.ValidatorAddr, testcli.ValidatorPass)
|
||||||
|
}
|
||||||
|
|
||||||
|
func deployVerifyContract(t *testing.T, e *testcli.Executor) util.Uint160 {
|
||||||
|
return testcli.DeployContract(t, e, "../smartcontract/testdata/verify.go", "../smartcontract/testdata/verify.yml", testcli.ValidatorWallet, testcli.ValidatorAddr, testcli.ValidatorPass)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,435 @@
|
||||||
|
package nep_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"io"
|
||||||
|
"math/big"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/testcli"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativehashes"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestNEP17Balance(t *testing.T) {
|
||||||
|
e := testcli.NewExecutor(t, true)
|
||||||
|
|
||||||
|
args := []string{
|
||||||
|
"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",
|
||||||
|
}
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.Run(t, args...)
|
||||||
|
e.CheckTxPersisted(t)
|
||||||
|
|
||||||
|
var checkAcc1NEO = func(t *testing.T, e *testcli.Executor, line string) {
|
||||||
|
if line == "" {
|
||||||
|
line = e.GetNextLine(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("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}...)...)
|
||||||
|
e.CheckNextLine(t, "^Account "+testcli.TestWalletMultiAccount2)
|
||||||
|
e.CheckNextLine(t, "^\\s*NEO:\\s+NeoToken \\("+e.Chain.GoverningTokenHash().StringLE()+"\\)")
|
||||||
|
e.CheckNextLine(t, "^\\s*Amount\\s*:\\s*"+fixedn.Fixed8(0).String()+"$")
|
||||||
|
e.CheckNextLine(t, "^\\s*Updated:")
|
||||||
|
e.CheckEOF(t)
|
||||||
|
})
|
||||||
|
t.Run("all accounts", func(t *testing.T) {
|
||||||
|
e.Run(t, append(cmdbase, walletparams...)...)
|
||||||
|
|
||||||
|
checkAcc1Assets(t, e)
|
||||||
|
e.CheckNextLine(t, "^\\s*$")
|
||||||
|
|
||||||
|
e.CheckNextLine(t, "^Account "+testcli.TestWalletMultiAccount2)
|
||||||
|
e.CheckNextLine(t, "^\\s*$")
|
||||||
|
|
||||||
|
e.CheckNextLine(t, "^Account "+testcli.TestWalletMultiAccount3)
|
||||||
|
e.CheckNextLine(t, "^\\s*GAS:\\s+GasToken \\("+e.Chain.UtilityTokenHash().StringLE()+"\\)")
|
||||||
|
balance := e.Chain.GetUtilityTokenBalance(testcli.TestWalletMultiAccount3Hash)
|
||||||
|
e.CheckNextLine(t, "^\\s*Amount\\s*:\\s*"+fixedn.Fixed8(balance.Int64()).String()+"$")
|
||||||
|
e.CheckNextLine(t, "^\\s*Updated:")
|
||||||
|
e.CheckEOF(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNEP17Transfer(t *testing.T) {
|
||||||
|
w, err := wallet.NewWalletFromFile("../testdata/testwallet.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
e := testcli.NewExecutor(t, true)
|
||||||
|
args := []string{
|
||||||
|
"neo-go", "wallet", "nep17", "transfer",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
"--wallet", testcli.ValidatorWallet,
|
||||||
|
"--to", w.Accounts[0].Address,
|
||||||
|
"--token", "NEO",
|
||||||
|
"--amount", "1",
|
||||||
|
"--from", testcli.ValidatorAddr,
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("missing receiver", func(t *testing.T) {
|
||||||
|
as := slices.Concat(args[:8], args[10:])
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.RunWithErrorCheck(t, `Required flag "to" not set`, as...)
|
||||||
|
e.In.Reset()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("InvalidPassword", func(t *testing.T) {
|
||||||
|
e.In.WriteString("onetwothree\r")
|
||||||
|
e.RunWithError(t, args...)
|
||||||
|
e.In.Reset()
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("no confirmation", func(t *testing.T) {
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.RunWithError(t, args...)
|
||||||
|
e.In.Reset()
|
||||||
|
})
|
||||||
|
t.Run("cancel after prompt", func(t *testing.T) {
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.RunWithError(t, args...)
|
||||||
|
e.In.Reset()
|
||||||
|
})
|
||||||
|
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.In.WriteString("Y\r")
|
||||||
|
e.Run(t, args...)
|
||||||
|
e.CheckNextLine(t, `^Network fee:\s*(\d|\.)+`)
|
||||||
|
e.CheckNextLine(t, `^System fee:\s*(\d|\.)+`)
|
||||||
|
e.CheckNextLine(t, `^Total fee:\s*(\d|\.)+`)
|
||||||
|
e.CheckTxPersisted(t)
|
||||||
|
|
||||||
|
sh := w.Accounts[0].ScriptHash()
|
||||||
|
b, _ := e.Chain.GetGoverningTokenBalance(sh)
|
||||||
|
require.Equal(t, big.NewInt(1), b)
|
||||||
|
|
||||||
|
t.Run("with force", func(t *testing.T) {
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.Run(t, append(args, "--force")...)
|
||||||
|
e.CheckTxPersisted(t)
|
||||||
|
|
||||||
|
b, _ := e.Chain.GetGoverningTokenBalance(sh)
|
||||||
|
require.Equal(t, big.NewInt(2), b)
|
||||||
|
})
|
||||||
|
|
||||||
|
hVerify := deployVerifyContract(t, e)
|
||||||
|
const validatorDefault = "Nhfg3TbpwogLvDGVvAvqyThbsHgoSUKwtn"
|
||||||
|
|
||||||
|
t.Run("default address", func(t *testing.T) {
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep17", "multitransfer",
|
||||||
|
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||||
|
"--wallet", testcli.ValidatorWallet,
|
||||||
|
"--from", testcli.ValidatorAddr,
|
||||||
|
"--force",
|
||||||
|
"NEO:"+validatorDefault+":42",
|
||||||
|
"GAS:"+validatorDefault+":7")
|
||||||
|
e.CheckTxPersisted(t)
|
||||||
|
|
||||||
|
args := args[:len(args)-2] // cut '--from' argument
|
||||||
|
args = append(args, "--force")
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.Run(t, args...)
|
||||||
|
e.CheckTxPersisted(t)
|
||||||
|
|
||||||
|
b, _ := e.Chain.GetGoverningTokenBalance(sh)
|
||||||
|
require.Equal(t, big.NewInt(3), b)
|
||||||
|
|
||||||
|
sh, err = address.StringToUint160(validatorDefault)
|
||||||
|
require.NoError(t, err)
|
||||||
|
b, _ = e.Chain.GetGoverningTokenBalance(sh)
|
||||||
|
require.Equal(t, big.NewInt(41), b)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with signers", func(t *testing.T) {
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep17", "multitransfer",
|
||||||
|
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||||
|
"--wallet", testcli.ValidatorWallet,
|
||||||
|
"--from", testcli.ValidatorAddr,
|
||||||
|
"--force",
|
||||||
|
"NEO:"+validatorDefault+":42",
|
||||||
|
"GAS:"+validatorDefault+":7",
|
||||||
|
"--", testcli.ValidatorAddr+":Global")
|
||||||
|
e.CheckTxPersisted(t)
|
||||||
|
})
|
||||||
|
|
||||||
|
validTil := e.Chain.BlockHeight() + 100
|
||||||
|
cmd := []string{
|
||||||
|
"neo-go", "wallet", "nep17", "transfer",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
"--wallet", testcli.ValidatorWallet,
|
||||||
|
"--token", "GAS",
|
||||||
|
"--amount", "1",
|
||||||
|
"--force",
|
||||||
|
"--from", testcli.ValidatorAddr}
|
||||||
|
|
||||||
|
t.Run("with await", func(t *testing.T) {
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.Run(t, append(cmd, "--to", nftOwnerAddr, "--await")...)
|
||||||
|
e.CheckAwaitableTxPersisted(t)
|
||||||
|
})
|
||||||
|
|
||||||
|
cmd = append(cmd, "--to", address.Uint160ToString(nativehashes.Notary),
|
||||||
|
"[", testcli.ValidatorAddr, strconv.Itoa(int(validTil)), "]")
|
||||||
|
|
||||||
|
t.Run("with data", func(t *testing.T) {
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.Run(t, cmd...)
|
||||||
|
e.CheckTxPersisted(t)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("with data and signers", func(t *testing.T) {
|
||||||
|
t.Run("invalid sender's scope", func(t *testing.T) {
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.RunWithError(t, append(cmd, "--", testcli.ValidatorAddr+":None")...)
|
||||||
|
})
|
||||||
|
t.Run("good", func(t *testing.T) {
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.Run(t, append(cmd, "--", testcli.ValidatorAddr+":Global")...) // CalledByEntry is enough, but it's the default value, so check something else
|
||||||
|
e.CheckTxPersisted(t)
|
||||||
|
})
|
||||||
|
t.Run("several signers", func(t *testing.T) {
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.Run(t, append(cmd, "--", testcli.ValidatorAddr, hVerify.StringLE())...)
|
||||||
|
e.CheckTxPersisted(t)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNEP17MultiTransfer(t *testing.T) {
|
||||||
|
privs, _ := testcli.GenerateKeys(t, 3)
|
||||||
|
|
||||||
|
e := testcli.NewExecutor(t, true)
|
||||||
|
neoContractHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Neo)
|
||||||
|
require.NoError(t, err)
|
||||||
|
args := []string{
|
||||||
|
"neo-go", "wallet", "nep17", "multitransfer",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
"--wallet", testcli.ValidatorWallet,
|
||||||
|
"--from", testcli.ValidatorAddr,
|
||||||
|
"--force",
|
||||||
|
"NEO:" + privs[0].Address() + ":42",
|
||||||
|
"GAS:" + privs[1].Address() + ":7",
|
||||||
|
neoContractHash.StringLE() + ":" + privs[2].Address() + ":13",
|
||||||
|
}
|
||||||
|
hVerify := deployVerifyContract(t, e)
|
||||||
|
|
||||||
|
t.Run("no cosigners", func(t *testing.T) {
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.Run(t, args...)
|
||||||
|
e.CheckTxPersisted(t)
|
||||||
|
|
||||||
|
b, _ := e.Chain.GetGoverningTokenBalance(privs[0].GetScriptHash())
|
||||||
|
require.Equal(t, big.NewInt(42), b)
|
||||||
|
b = e.Chain.GetUtilityTokenBalance(privs[1].GetScriptHash())
|
||||||
|
require.Equal(t, big.NewInt(int64(fixedn.Fixed8FromInt64(7))), b)
|
||||||
|
b, _ = e.Chain.GetGoverningTokenBalance(privs[2].GetScriptHash())
|
||||||
|
require.Equal(t, big.NewInt(13), b)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid sender scope", func(t *testing.T) {
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.RunWithError(t, append(args,
|
||||||
|
"--", testcli.ValidatorAddr+":None")...) // invalid sender scope
|
||||||
|
})
|
||||||
|
t.Run("Global sender scope", func(t *testing.T) {
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.Run(t, append(args,
|
||||||
|
"--", testcli.ValidatorAddr+":Global")...)
|
||||||
|
e.CheckTxPersisted(t)
|
||||||
|
})
|
||||||
|
t.Run("Several cosigners", func(t *testing.T) {
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.Run(t, append(args,
|
||||||
|
"--", testcli.ValidatorAddr, hVerify.StringLE())...)
|
||||||
|
e.CheckTxPersisted(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestNEP17ImportToken(t *testing.T) {
|
||||||
|
e := testcli.NewExecutor(t, true)
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
walletPath := filepath.Join(tmpDir, "walletForImport.json")
|
||||||
|
|
||||||
|
neoContractHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Neo)
|
||||||
|
require.NoError(t, err)
|
||||||
|
gasContractHash, err := e.Chain.GetNativeContractScriptHash(nativenames.Gas)
|
||||||
|
require.NoError(t, err)
|
||||||
|
nnsContractHash := deployNNSContract(t, e)
|
||||||
|
e.Run(t, "neo-go", "wallet", "init", "--wallet", walletPath)
|
||||||
|
|
||||||
|
// missing token hash
|
||||||
|
e.RunWithErrorCheck(t, `Required flag "token" not set`, "neo-go", "wallet", "nep17", "import",
|
||||||
|
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||||
|
"--wallet", walletPath)
|
||||||
|
|
||||||
|
// additional parameter
|
||||||
|
e.RunWithError(t, "neo-go", "wallet", "nep17", "import",
|
||||||
|
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||||
|
"--wallet", walletPath,
|
||||||
|
"--token", gasContractHash.StringLE(), "useless")
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep17", "import",
|
||||||
|
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||||
|
"--wallet", walletPath,
|
||||||
|
"--token", gasContractHash.StringLE())
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep17", "import",
|
||||||
|
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||||
|
"--wallet", walletPath,
|
||||||
|
"--token", address.Uint160ToString(neoContractHash)) // try address instead of sh
|
||||||
|
|
||||||
|
// not a NEP-17 token
|
||||||
|
e.RunWithError(t, "neo-go", "wallet", "nep17", "import",
|
||||||
|
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||||
|
"--wallet", walletPath,
|
||||||
|
"--token", nnsContractHash.StringLE())
|
||||||
|
|
||||||
|
t.Run("Info", func(t *testing.T) {
|
||||||
|
checkGASInfo := func(t *testing.T) {
|
||||||
|
e.CheckNextLine(t, "^Name:\\s*GasToken")
|
||||||
|
e.CheckNextLine(t, "^Symbol:\\s*GAS")
|
||||||
|
e.CheckNextLine(t, "^Hash:\\s*"+gasContractHash.StringLE())
|
||||||
|
e.CheckNextLine(t, "^Decimals:\\s*8")
|
||||||
|
e.CheckNextLine(t, "^Address:\\s*"+address.Uint160ToString(gasContractHash))
|
||||||
|
e.CheckNextLine(t, "^Standard:\\s*"+string(manifest.NEP17StandardName))
|
||||||
|
}
|
||||||
|
t.Run("excessive parameters", func(t *testing.T) {
|
||||||
|
e.RunWithError(t, "neo-go", "wallet", "nep17", "info",
|
||||||
|
"--wallet", walletPath, "--token", gasContractHash.StringLE(), "parameter")
|
||||||
|
})
|
||||||
|
t.Run("WithToken", func(t *testing.T) {
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep17", "info",
|
||||||
|
"--wallet", walletPath, "--token", gasContractHash.StringLE())
|
||||||
|
checkGASInfo(t)
|
||||||
|
})
|
||||||
|
t.Run("NoToken", func(t *testing.T) {
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep17", "info",
|
||||||
|
"--wallet", walletPath)
|
||||||
|
checkGASInfo(t)
|
||||||
|
_, err := e.Out.ReadString('\n')
|
||||||
|
require.NoError(t, err)
|
||||||
|
e.CheckNextLine(t, "^Name:\\s*NeoToken")
|
||||||
|
e.CheckNextLine(t, "^Symbol:\\s*NEO")
|
||||||
|
e.CheckNextLine(t, "^Hash:\\s*"+neoContractHash.StringLE())
|
||||||
|
e.CheckNextLine(t, "^Decimals:\\s*0")
|
||||||
|
e.CheckNextLine(t, "^Address:\\s*"+address.Uint160ToString(neoContractHash))
|
||||||
|
e.CheckNextLine(t, "^Standard:\\s*"+string(manifest.NEP17StandardName))
|
||||||
|
})
|
||||||
|
t.Run("Remove", func(t *testing.T) {
|
||||||
|
e.RunWithError(t, "neo-go", "wallet", "nep17", "remove",
|
||||||
|
"--wallet", walletPath, "--token", neoContractHash.StringLE(), "add")
|
||||||
|
e.In.WriteString("y\r")
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep17", "remove",
|
||||||
|
"--wallet", walletPath, "--token", neoContractHash.StringLE())
|
||||||
|
e.Run(t, "neo-go", "wallet", "nep17", "info",
|
||||||
|
"--wallet", walletPath)
|
||||||
|
checkGASInfo(t)
|
||||||
|
_, err := e.Out.ReadString('\n')
|
||||||
|
require.Equal(t, err, io.EOF)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,33 @@
|
||||||
|
package options_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/cli/app"
|
||||||
|
"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/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetRPCClient(t *testing.T) {
|
||||||
|
e := testcli.NewExecutor(t, true)
|
||||||
|
|
||||||
|
t.Run("no endpoint", func(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
|
||||||
|
ctx := cli.NewContext(app.New(), set, nil)
|
||||||
|
gctx, _ := options.GetTimeoutContext(ctx)
|
||||||
|
_, ec := options.GetRPCClient(gctx, ctx)
|
||||||
|
require.Equal(t, 1, ec.ExitCode())
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("success", func(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
|
||||||
|
set.String(options.RPCEndpointFlag, "http://"+e.RPC.Addresses()[0], "")
|
||||||
|
ctx := cli.NewContext(app.New(), set, nil)
|
||||||
|
gctx, _ := options.GetTimeoutContext(ctx)
|
||||||
|
_, ec := options.GetRPCClient(gctx, ctx)
|
||||||
|
require.Nil(t, ec)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,28 @@
|
||||||
|
package options
|
||||||
|
|
||||||
|
import "go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
|
// FilteringCore is custom implementation of zapcore.Core that allows to filter
|
||||||
|
// log entries using custom filtering function.
|
||||||
|
type FilteringCore struct {
|
||||||
|
zapcore.Core
|
||||||
|
filter FilterFunc
|
||||||
|
}
|
||||||
|
|
||||||
|
// FilterFunc is the filter function that is called to check whether the given
|
||||||
|
// entry together with the associated fields is to be written to a core or not.
|
||||||
|
type FilterFunc func(zapcore.Entry) bool
|
||||||
|
|
||||||
|
// NewFilteringCore returns a core middleware that uses the given filter function
|
||||||
|
// to decide whether to log this message or not.
|
||||||
|
func NewFilteringCore(next zapcore.Core, filter FilterFunc) zapcore.Core {
|
||||||
|
return &FilteringCore{next, filter}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check implements zapcore.Core interface and performs log entries filtering.
|
||||||
|
func (c *FilteringCore) Check(e zapcore.Entry, ce *zapcore.CheckedEntry) *zapcore.CheckedEntry {
|
||||||
|
if c.filter(e) {
|
||||||
|
return c.Core.Check(e, ce)
|
||||||
|
}
|
||||||
|
return ce
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,486 @@
|
||||||
|
/*
|
||||||
|
Package options contains a set of common CLI options and helper functions to use them.
|
||||||
|
*/
|
||||||
|
package options
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"net/url"
|
||||||
|
"os"
|
||||||
|
"runtime"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"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"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"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/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"
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
// DefaultTimeout is the default timeout used for RPC requests.
|
||||||
|
DefaultTimeout = 10 * time.Second
|
||||||
|
// DefaultAwaitableTimeout is the default timeout used for RPC requests that
|
||||||
|
// require transaction awaiting. It is set to the approximate time of three
|
||||||
|
// Neo N3 mainnet blocks accepting.
|
||||||
|
DefaultAwaitableTimeout = 3 * 15 * time.Second
|
||||||
|
)
|
||||||
|
|
||||||
|
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",
|
||||||
|
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",
|
||||||
|
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,
|
||||||
|
Aliases: []string{"r"},
|
||||||
|
Usage: "RPC node address",
|
||||||
|
Required: true,
|
||||||
|
Action: cmdargs.EnsureNotEmpty("rpc-endpoint"),
|
||||||
|
},
|
||||||
|
&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 {
|
||||||
|
if slices.Contains(fsRpcEndpoints, "") {
|
||||||
|
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{
|
||||||
|
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{
|
||||||
|
Name: "config-path",
|
||||||
|
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{
|
||||||
|
Name: "config-file",
|
||||||
|
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{
|
||||||
|
Name: "relative-path",
|
||||||
|
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",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Usage: "Enable debug logging (LOTS of output, overrides configuration)",
|
||||||
|
}
|
||||||
|
|
||||||
|
// ForceTimestampLogs is a flag for commands that run the node. This flag
|
||||||
|
// enables timestamp logging for every log record even if program is running
|
||||||
|
// not in terminal.
|
||||||
|
var ForceTimestampLogs = &cli.BoolFlag{
|
||||||
|
Name: "force-timestamp-logs",
|
||||||
|
Usage: "Enable timestamps for log entries",
|
||||||
|
}
|
||||||
|
|
||||||
|
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")
|
||||||
|
|
||||||
|
// GetNetwork examines Context's flags and returns the appropriate network. It
|
||||||
|
// defaults to PrivNet if no flags are given.
|
||||||
|
func GetNetwork(ctx *cli.Context) netmode.Magic {
|
||||||
|
var net = netmode.PrivNet
|
||||||
|
if ctx.Bool("testnet") {
|
||||||
|
net = netmode.TestNet
|
||||||
|
}
|
||||||
|
if ctx.Bool("mainnet") {
|
||||||
|
net = netmode.MainNet
|
||||||
|
}
|
||||||
|
if ctx.Bool("unittest") {
|
||||||
|
net = netmode.UnitTestNet
|
||||||
|
}
|
||||||
|
return net
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetTimeoutContext returns a context.Context with the default or a user-set timeout.
|
||||||
|
func GetTimeoutContext(ctx *cli.Context) (context.Context, func()) {
|
||||||
|
dur := ctx.Duration("timeout")
|
||||||
|
if dur == 0 {
|
||||||
|
dur = DefaultTimeout
|
||||||
|
}
|
||||||
|
if !ctx.IsSet("timeout") && ctx.Bool("await") {
|
||||||
|
dur = DefaultAwaitableTimeout
|
||||||
|
}
|
||||||
|
return context.WithTimeout(context.Background(), dur)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
c, err := rpcclient.New(gctx, endpoint, rpcclient.Options{})
|
||||||
|
if err != nil {
|
||||||
|
return nil, cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
err = c.Init()
|
||||||
|
if err != nil {
|
||||||
|
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, *pool.Pool, 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, nil, fmt.Errorf("failed to create NeoFS pool: %w", err)
|
||||||
|
}
|
||||||
|
if err = p.Dial(context.Background()); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("failed to dial NeoFS pool: %w", err)
|
||||||
|
}
|
||||||
|
return signer, p, 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) {
|
||||||
|
historic := ctx.String("historic")
|
||||||
|
if historic == "" {
|
||||||
|
return invoker.New(c, signers), nil
|
||||||
|
}
|
||||||
|
if index, err := strconv.ParseUint(historic, 10, 32); err == nil {
|
||||||
|
return invoker.NewHistoricAtHeight(uint32(index), c, signers), nil
|
||||||
|
}
|
||||||
|
if u256, err := util.Uint256DecodeStringLE(historic); err == nil {
|
||||||
|
// Might as well be a block hash, but it makes no practical difference.
|
||||||
|
return invoker.NewHistoricWithState(u256, c, signers), nil
|
||||||
|
}
|
||||||
|
return nil, cli.Exit(errInvalidHistoric, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRPCWithInvoker combines GetRPCClient with GetInvoker for cases where it's
|
||||||
|
// appropriate to do so.
|
||||||
|
func GetRPCWithInvoker(gctx context.Context, ctx *cli.Context, signers []transaction.Signer) (*rpcclient.Client, *invoker.Invoker, cli.ExitCoder) {
|
||||||
|
c, err := GetRPCClient(gctx, ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
inv, err := GetInvoker(c, ctx, signers)
|
||||||
|
if err != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
return c, inv, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetConfigFromContext looks at the path and the mode flags in the given config and
|
||||||
|
// returns an appropriate config.
|
||||||
|
func GetConfigFromContext(ctx *cli.Context) (config.Config, error) {
|
||||||
|
var (
|
||||||
|
configFile = ctx.String("config-file")
|
||||||
|
relativePath = ctx.String("relative-path")
|
||||||
|
)
|
||||||
|
if len(configFile) != 0 {
|
||||||
|
return config.LoadFile(configFile, relativePath)
|
||||||
|
}
|
||||||
|
var configPath = config.DefaultConfigPath
|
||||||
|
if argCp := ctx.String("config-path"); argCp != "" {
|
||||||
|
configPath = argCp
|
||||||
|
}
|
||||||
|
return config.Load(configPath, GetNetwork(ctx), relativePath)
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
// _winfileSinkRegistered denotes whether zap has registered
|
||||||
|
// user-supplied factory for all sinks with `winfile`-prefixed scheme.
|
||||||
|
_winfileSinkRegistered bool
|
||||||
|
_winfileSinkCloser func() error
|
||||||
|
)
|
||||||
|
|
||||||
|
// HandleLoggingParams reads logging parameters.
|
||||||
|
// If a user selected debug level -- function enables it.
|
||||||
|
// 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(ctx *cli.Context, cfg config.ApplicationConfiguration) (*zap.Logger, *zap.AtomicLevel, func() error, error) {
|
||||||
|
var (
|
||||||
|
level = zapcore.InfoLevel
|
||||||
|
encoding = "console"
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if len(cfg.LogLevel) > 0 {
|
||||||
|
level, err = zapcore.ParseLevel(cfg.LogLevel)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("log setting: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(cfg.LogEncoding) > 0 {
|
||||||
|
encoding = cfg.LogEncoding
|
||||||
|
}
|
||||||
|
if ctx != nil && ctx.Bool("debug") {
|
||||||
|
level = zapcore.DebugLevel
|
||||||
|
}
|
||||||
|
|
||||||
|
cc := zap.NewProductionConfig()
|
||||||
|
cc.DisableCaller = true
|
||||||
|
cc.DisableStacktrace = true
|
||||||
|
cc.EncoderConfig.EncodeDuration = zapcore.StringDurationEncoder
|
||||||
|
cc.EncoderConfig.EncodeLevel = zapcore.CapitalLevelEncoder
|
||||||
|
if cfg.LogTimestamp == nil && term.IsTerminal(int(os.Stdout.Fd())) ||
|
||||||
|
cfg.LogTimestamp != nil && *cfg.LogTimestamp ||
|
||||||
|
ctx != nil && ctx.Bool("force-timestamp-logs") {
|
||||||
|
cc.EncoderConfig.EncodeTime = zapcore.ISO8601TimeEncoder
|
||||||
|
} else {
|
||||||
|
cc.EncoderConfig.EncodeTime = func(t time.Time, encoder zapcore.PrimitiveArrayEncoder) {}
|
||||||
|
}
|
||||||
|
cc.Encoding = encoding
|
||||||
|
cc.Level = zap.NewAtomicLevelAt(level)
|
||||||
|
cc.Sampling = nil
|
||||||
|
|
||||||
|
if logPath := cfg.LogPath; logPath != "" {
|
||||||
|
if err := io.MakeDirForFile(logPath, "logger"); err != nil {
|
||||||
|
return nil, nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
if !_winfileSinkRegistered {
|
||||||
|
// See https://github.com/uber-go/zap/issues/621.
|
||||||
|
err := zap.RegisterSink("winfile", func(u *url.URL) (zap.Sink, error) {
|
||||||
|
if u.User != nil {
|
||||||
|
return nil, fmt.Errorf("user and password not allowed with file URLs: got %v", u)
|
||||||
|
}
|
||||||
|
if u.Fragment != "" {
|
||||||
|
return nil, fmt.Errorf("fragments not allowed with file URLs: got %v", u)
|
||||||
|
}
|
||||||
|
if u.RawQuery != "" {
|
||||||
|
return nil, fmt.Errorf("query parameters not allowed with file URLs: got %v", u)
|
||||||
|
}
|
||||||
|
// Error messages are better if we check hostname and port separately.
|
||||||
|
if u.Port() != "" {
|
||||||
|
return nil, fmt.Errorf("ports not allowed with file URLs: got %v", u)
|
||||||
|
}
|
||||||
|
if hn := u.Hostname(); hn != "" && hn != "localhost" {
|
||||||
|
return nil, fmt.Errorf("file URLs must leave host empty or use localhost: got %v", u)
|
||||||
|
}
|
||||||
|
switch u.Path {
|
||||||
|
case "stdout":
|
||||||
|
return os.Stdout, nil
|
||||||
|
case "stderr":
|
||||||
|
return os.Stderr, nil
|
||||||
|
}
|
||||||
|
f, err := os.OpenFile(u.Path[1:], // Remove leading slash left after url.Parse.
|
||||||
|
os.O_WRONLY|os.O_APPEND|os.O_CREATE, 0644)
|
||||||
|
_winfileSinkCloser = func() error {
|
||||||
|
_winfileSinkCloser = nil
|
||||||
|
return f.Close()
|
||||||
|
}
|
||||||
|
return f, err
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, fmt.Errorf("failed to register windows-specific sinc: %w", err)
|
||||||
|
}
|
||||||
|
_winfileSinkRegistered = true
|
||||||
|
}
|
||||||
|
logPath = "winfile:///" + logPath
|
||||||
|
}
|
||||||
|
|
||||||
|
cc.OutputPaths = []string{logPath}
|
||||||
|
}
|
||||||
|
|
||||||
|
log, err := cc.Build()
|
||||||
|
return log, &cc.Level, _winfileSinkCloser, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRPCWithActor returns an RPC client instance and Actor instance for the given context.
|
||||||
|
func GetRPCWithActor(gctx context.Context, ctx *cli.Context, signers []actor.SignerAccount) (*rpcclient.Client, *actor.Actor, cli.ExitCoder) {
|
||||||
|
c, err := GetRPCClient(gctx, ctx)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
a, actorErr := actor.New(c, signers)
|
||||||
|
if actorErr != nil {
|
||||||
|
c.Close()
|
||||||
|
return nil, nil, cli.Exit(fmt.Errorf("failed to create Actor: %w", actorErr), 1)
|
||||||
|
}
|
||||||
|
return c, a, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAccFromContext returns account and wallet from context. If address is not set, default address is used.
|
||||||
|
func GetAccFromContext(ctx *cli.Context) (*wallet.Account, *wallet.Wallet, error) {
|
||||||
|
var addr util.Uint160
|
||||||
|
|
||||||
|
wPath := ctx.String("wallet")
|
||||||
|
walletConfigPath := ctx.String("wallet-config")
|
||||||
|
if len(wPath) != 0 && len(walletConfigPath) != 0 {
|
||||||
|
return nil, nil, errConflictingWalletFlags
|
||||||
|
}
|
||||||
|
if len(wPath) == 0 && len(walletConfigPath) == 0 {
|
||||||
|
return nil, nil, ErrNoWallet
|
||||||
|
}
|
||||||
|
var pass *string
|
||||||
|
if len(walletConfigPath) != 0 {
|
||||||
|
cfg, err := ReadWalletConfig(walletConfigPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
wPath = cfg.Path
|
||||||
|
pass = &cfg.Password
|
||||||
|
}
|
||||||
|
|
||||||
|
wall, err := wallet.NewWalletFromFile(wPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
addrFlag := ctx.Generic("address").(*flags.Address)
|
||||||
|
if addrFlag.IsSet {
|
||||||
|
addr = addrFlag.Uint160()
|
||||||
|
} else {
|
||||||
|
addr = wall.GetChangeAddress()
|
||||||
|
if addr.Equals(util.Uint160{}) {
|
||||||
|
return nil, wall, errors.New("can't get default address")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
acc, err := GetUnlockedAccount(wall, addr, pass)
|
||||||
|
return acc, wall, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetUnlockedAccount returns account from wallet, address and uses pass to unlock specified account if given.
|
||||||
|
// If the password is not given, then it is requested from user.
|
||||||
|
func GetUnlockedAccount(wall *wallet.Wallet, addr util.Uint160, pass *string) (*wallet.Account, error) {
|
||||||
|
acc := wall.GetAccount(addr)
|
||||||
|
if acc == nil {
|
||||||
|
return nil, fmt.Errorf("wallet contains no account for '%s'", address.Uint160ToString(addr))
|
||||||
|
}
|
||||||
|
|
||||||
|
if acc.CanSign() || acc.EncryptedWIF == "" {
|
||||||
|
return acc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
if pass == nil {
|
||||||
|
rawPass, err := input.ReadPassword(
|
||||||
|
fmt.Sprintf("Enter account %s password > ", address.Uint160ToString(addr)))
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("error reading password: %w", err)
|
||||||
|
}
|
||||||
|
trimmed := strings.TrimRight(string(rawPass), "\n")
|
||||||
|
pass = &trimmed
|
||||||
|
}
|
||||||
|
err := acc.Decrypt(*pass, wall.Scrypt)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return acc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ReadWalletConfig reads wallet config from the given path.
|
||||||
|
func ReadWalletConfig(configPath string) (*config.Wallet, error) {
|
||||||
|
file, err := os.Open(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
defer file.Close()
|
||||||
|
|
||||||
|
configData, err := os.ReadFile(configPath)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("unable to read wallet config: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
cfg := &config.Wallet{}
|
||||||
|
|
||||||
|
err = yaml.Unmarshal(configData, &cfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to unmarshal wallet config YAML: %w", err)
|
||||||
|
}
|
||||||
|
return cfg, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,57 @@
|
||||||
|
package options
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetNetwork(t *testing.T) {
|
||||||
|
t.Run("privnet", func(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
|
||||||
|
ctx := cli.NewContext(cli.NewApp(), set, nil)
|
||||||
|
require.Equal(t, netmode.PrivNet, GetNetwork(ctx))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("testnet", func(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
|
||||||
|
set.Bool("testnet", true, "")
|
||||||
|
ctx := cli.NewContext(cli.NewApp(), set, nil)
|
||||||
|
require.Equal(t, netmode.TestNet, GetNetwork(ctx))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("mainnet", func(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
|
||||||
|
set.Bool("mainnet", true, "")
|
||||||
|
ctx := cli.NewContext(cli.NewApp(), set, nil)
|
||||||
|
require.Equal(t, netmode.MainNet, GetNetwork(ctx))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetTimeoutContext(t *testing.T) {
|
||||||
|
t.Run("default", func(t *testing.T) {
|
||||||
|
start := time.Now()
|
||||||
|
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
|
||||||
|
ctx := cli.NewContext(cli.NewApp(), set, nil)
|
||||||
|
actualCtx, _ := GetTimeoutContext(ctx)
|
||||||
|
end := time.Now().Add(DefaultTimeout)
|
||||||
|
dl, _ := actualCtx.Deadline()
|
||||||
|
require.True(t, start.Before(dl) && (dl.Before(end) || dl.Equal(end)))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("set", func(t *testing.T) {
|
||||||
|
start := time.Now()
|
||||||
|
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
|
||||||
|
set.Duration("timeout", time.Duration(20), "")
|
||||||
|
ctx := cli.NewContext(cli.NewApp(), set, nil)
|
||||||
|
actualCtx, _ := GetTimeoutContext(ctx)
|
||||||
|
end := time.Now().Add(time.Nanosecond * 20)
|
||||||
|
dl, _ := actualCtx.Deadline()
|
||||||
|
require.True(t, start.Before(dl) && (dl.Before(end) || dl.Equal(end)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,50 @@
|
||||||
|
package paramcontext
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
)
|
||||||
|
|
||||||
|
// InitAndSave creates an incompletely signed transaction which can be used
|
||||||
|
// as an input to `multisig sign`. If a wallet.Account is given and can sign,
|
||||||
|
// it's signed as well using it.
|
||||||
|
func InitAndSave(net netmode.Magic, tx *transaction.Transaction, acc *wallet.Account, filename string) error {
|
||||||
|
scCtx := context.NewParameterContext(context.TransactionType, net, tx)
|
||||||
|
if acc != nil && acc.CanSign() {
|
||||||
|
sign := acc.SignHashable(net, tx)
|
||||||
|
if err := scCtx.AddSignature(acc.ScriptHash(), acc.Contract, acc.PublicKey(), sign); err != nil {
|
||||||
|
return fmt.Errorf("can't add signature: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return Save(scCtx, filename)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Read reads the parameter context from the file.
|
||||||
|
func Read(filename string) (*context.ParameterContext, error) {
|
||||||
|
data, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't read input file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
c := new(context.ParameterContext)
|
||||||
|
if err := json.Unmarshal(data, c); err != nil {
|
||||||
|
return nil, fmt.Errorf("can't parse transaction: %w", err)
|
||||||
|
}
|
||||||
|
return c, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Save writes the parameter context to the file.
|
||||||
|
func Save(c *context.ParameterContext, filename string) error {
|
||||||
|
if data, err := json.Marshal(c); err != nil {
|
||||||
|
return fmt.Errorf("can't marshal transaction: %w", err)
|
||||||
|
} else if err := os.WriteFile(filename, data, 0644); err != nil {
|
||||||
|
return fmt.Errorf("can't write transaction to file: %w", err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,379 @@
|
||||||
|
package query
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"cmp"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"slices"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/tabwriter"
|
||||||
|
|
||||||
|
"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/pkg/core/state"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
|
||||||
|
"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/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewCommands returns 'query' command.
|
||||||
|
func NewCommands() []*cli.Command {
|
||||||
|
queryTxFlags := append([]cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "verbose",
|
||||||
|
Aliases: []string{"v"},
|
||||||
|
Usage: "Output full tx info and execution logs",
|
||||||
|
},
|
||||||
|
}, options.RPC...)
|
||||||
|
queryNotaryPoolFlags := append([]cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "verbose",
|
||||||
|
Aliases: []string{"v"},
|
||||||
|
Usage: "Output full main tx info",
|
||||||
|
},
|
||||||
|
}, options.RPC...)
|
||||||
|
return []*cli.Command{{
|
||||||
|
Name: "query",
|
||||||
|
Usage: "Query data from RPC node",
|
||||||
|
Subcommands: []*cli.Command{
|
||||||
|
{
|
||||||
|
Name: "candidates",
|
||||||
|
Usage: "Get candidates and votes",
|
||||||
|
UsageText: "neo-go query candidates -r endpoint [-s timeout]",
|
||||||
|
Action: queryCandidates,
|
||||||
|
Flags: options.RPC,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "committee",
|
||||||
|
Usage: "Get committee list",
|
||||||
|
UsageText: "neo-go query committee -r endpoint [-s timeout]",
|
||||||
|
Action: queryCommittee,
|
||||||
|
Flags: options.RPC,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "height",
|
||||||
|
Usage: "Get node height",
|
||||||
|
UsageText: "neo-go query height -r endpoint [-s timeout]",
|
||||||
|
Action: queryHeight,
|
||||||
|
Flags: options.RPC,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "notarypool",
|
||||||
|
Usage: "Query the content of the notary pool",
|
||||||
|
UsageText: "neo-go query notarypool -r endpoint [-s timeout] [-v]",
|
||||||
|
Action: queryNotaryPool,
|
||||||
|
Flags: queryNotaryPoolFlags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "tx",
|
||||||
|
Usage: "Query transaction status",
|
||||||
|
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 -r endpoint [-s timeout] <address>",
|
||||||
|
Action: queryVoter,
|
||||||
|
Flags: options.RPC,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryTx(ctx *cli.Context) error {
|
||||||
|
args := ctx.Args().Slice()
|
||||||
|
if len(args) == 0 {
|
||||||
|
return cli.Exit("transaction hash is missing", 1)
|
||||||
|
} else if len(args) > 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.Exit(fmt.Sprintf("invalid tx hash: %s", args[0]), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
c, err := options.GetRPCClient(gctx, ctx)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
txOut, err := c.GetRawTransactionVerbose(txHash)
|
||||||
|
if err != nil {
|
||||||
|
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.Exit(err, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = DumpApplicationLog(ctx, res, &txOut.Transaction, &txOut.TransactionMetadata, ctx.Bool("verbose"))
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func DumpApplicationLog(
|
||||||
|
ctx *cli.Context,
|
||||||
|
res *result.ApplicationLog,
|
||||||
|
tx *transaction.Transaction,
|
||||||
|
txMeta *result.TransactionMetadata,
|
||||||
|
verbose bool,
|
||||||
|
fallbacks ...int) error {
|
||||||
|
var buf []byte
|
||||||
|
|
||||||
|
buf = fmt.Appendf(buf, "Hash:\t%s\n", tx.Hash().StringLE())
|
||||||
|
buf = fmt.Appendf(buf, "OnChain:\t%t\n", res != nil)
|
||||||
|
if res == nil {
|
||||||
|
buf = fmt.Appendf(buf, "ValidUntil:\t%s\n", strconv.FormatUint(uint64(tx.ValidUntilBlock), 10))
|
||||||
|
if len(fallbacks) != 0 {
|
||||||
|
buf = fmt.Appendf(buf, "Fallbacks:\t%d\n", fallbacks[0])
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if txMeta != nil {
|
||||||
|
buf = fmt.Appendf(buf, "BlockHash:\t%s\n", txMeta.Blockhash.StringLE())
|
||||||
|
}
|
||||||
|
if len(res.Executions) != 1 {
|
||||||
|
buf = fmt.Appendf(buf, "Success:\tunknown (no execution data)\n")
|
||||||
|
} else {
|
||||||
|
buf = fmt.Appendf(buf, "Success:\t%t\n", res.Executions[0].VMState == vmstate.Halt)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if verbose {
|
||||||
|
for _, sig := range tx.Signers {
|
||||||
|
buf = fmt.Appendf(buf, "Signer:\t%s (%s)\n", address.Uint160ToString(sig.Account), sig.Scopes)
|
||||||
|
}
|
||||||
|
buf = fmt.Appendf(buf, "SystemFee:\t%s GAS\n", fixedn.Fixed8(tx.SystemFee).String())
|
||||||
|
buf = fmt.Appendf(buf, "NetworkFee:\t%s GAS\n", fixedn.Fixed8(tx.NetworkFee).String())
|
||||||
|
buf = fmt.Appendf(buf, "Script:\t%s\n", base64.StdEncoding.EncodeToString(tx.Script))
|
||||||
|
if len(fallbacks) == 0 {
|
||||||
|
v := vm.New()
|
||||||
|
v.Load(tx.Script)
|
||||||
|
opts := bytes.NewBuffer(nil)
|
||||||
|
v.PrintOps(opts)
|
||||||
|
buf = append(buf, opts.Bytes()...)
|
||||||
|
if res != nil {
|
||||||
|
for _, e := range res.Executions {
|
||||||
|
if e.VMState != vmstate.Halt {
|
||||||
|
buf = fmt.Appendf(buf, "Exception:\t%s\n", e.FaultException)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
tw := tabwriter.NewWriter(ctx.App.Writer, 0, 4, 4, '\t', 0)
|
||||||
|
_, err := tw.Write(buf)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tw.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryCandidates(ctx *cli.Context) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
c, err := options.GetRPCClient(gctx, ctx)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
vals, err := c.GetCandidates()
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
comm, err := c.GetCommittee()
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.SortFunc(vals, func(a, b result.Candidate) int {
|
||||||
|
if a.Active && !b.Active {
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
if !a.Active && b.Active {
|
||||||
|
return -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")
|
||||||
|
for _, val := range vals {
|
||||||
|
res = fmt.Appendf(res, "%s\t%d\t%t\t%t\n", val.PublicKey.StringCompressed(), val.Votes, comm.Contains(&val.PublicKey), val.Active)
|
||||||
|
}
|
||||||
|
tw := tabwriter.NewWriter(ctx.App.Writer, 0, 2, 2, ' ', 0)
|
||||||
|
_, err = tw.Write(res)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return tw.Flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryCommittee(ctx *cli.Context) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
c, err := options.GetRPCClient(gctx, ctx)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
comm, err := c.GetCommittee()
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, k := range comm {
|
||||||
|
fmt.Fprintln(ctx.App.Writer, k.StringCompressed())
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryHeight(ctx *cli.Context) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
c, err := options.GetRPCClient(gctx, ctx)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
blockCount, err := c.GetBlockCount()
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
blockHeight := blockCount - 1 // GetBlockCount returns block count (including 0), not the highest block index.
|
||||||
|
|
||||||
|
fmt.Fprintf(ctx.App.Writer, "Latest block: %d\n", blockHeight)
|
||||||
|
|
||||||
|
stateHeight, err := c.GetStateHeight()
|
||||||
|
if err == nil { // We can be talking to a node without getstateheight request support.
|
||||||
|
fmt.Fprintf(ctx.App.Writer, "Validated state: %d\n", stateHeight.Validated)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryVoter(ctx *cli.Context) error {
|
||||||
|
args := ctx.Args().Slice()
|
||||||
|
if len(args) == 0 {
|
||||||
|
return cli.Exit("no address specified", 1)
|
||||||
|
} else if len(args) > 1 {
|
||||||
|
return cli.Exit("this command only accepts one address", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
addr, err := flags.ParseAddress(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Sprintf("wrong address: %s", args[0]), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||||
|
defer cancel()
|
||||||
|
c, exitErr := options.GetRPCClient(gctx, ctx)
|
||||||
|
if exitErr != nil {
|
||||||
|
return exitErr
|
||||||
|
}
|
||||||
|
|
||||||
|
neoToken := neo.NewReader(invoker.New(c, nil))
|
||||||
|
|
||||||
|
st, err := neoToken.GetAccountState(addr)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
if st == nil {
|
||||||
|
st = new(state.NEOBalance)
|
||||||
|
}
|
||||||
|
dec, err := neoToken.Decimals()
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("failed to get decimals: %w", err), 1)
|
||||||
|
}
|
||||||
|
voted := "null"
|
||||||
|
if st.VoteTo != nil {
|
||||||
|
voted = fmt.Sprintf("%s (%s)", st.VoteTo.StringCompressed(), address.Uint160ToString(st.VoteTo.GetScriptHash()))
|
||||||
|
}
|
||||||
|
fmt.Fprintf(ctx.App.Writer, "\tVoted: %s\n", voted)
|
||||||
|
fmt.Fprintf(ctx.App.Writer, "\tAmount : %s\n", fixedn.ToString(&st.Balance, int(dec)))
|
||||||
|
fmt.Fprintf(ctx.App.Writer, "\tBlock: %d\n", st.BalanceHeight)
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func queryNotaryPool(ctx *cli.Context) error {
|
||||||
|
var err error
|
||||||
|
|
||||||
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
c, err := options.GetRPCClient(gctx, ctx)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
h, err := c.GetBlockCount()
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
fmt.Fprintf(ctx.App.Writer, "Current height: %d\n", h)
|
||||||
|
|
||||||
|
p, err := c.GetRawNotaryPool()
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
for m, fbs := range p.Hashes {
|
||||||
|
tx, err := c.GetRawNotaryTransaction(m)
|
||||||
|
if err != nil {
|
||||||
|
fmt.Fprintf(ctx.App.Writer, "%s (%d fallbacks): %s\n", m.StringLE(), len(fbs), err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
err = DumpApplicationLog(ctx, nil, tx, nil, ctx.Bool("verbose"), len(fbs))
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,267 @@
|
||||||
|
package query_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"context"
|
||||||
|
"encoding/base64"
|
||||||
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"regexp"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/random"
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/testcli"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/native/nativehashes"
|
||||||
|
"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/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
|
||||||
|
"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/gas"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/notary"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestQueryTx(t *testing.T) {
|
||||||
|
e := testcli.NewExecutorSuspended(t)
|
||||||
|
|
||||||
|
w, err := wallet.NewWalletFromFile("../testdata/testwallet.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
transferArgs := []string{
|
||||||
|
"neo-go", "wallet", "nep17", "transfer",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
"--wallet", testcli.ValidatorWallet,
|
||||||
|
"--to", w.Accounts[0].Address,
|
||||||
|
"--token", "NEO",
|
||||||
|
"--from", testcli.ValidatorAddr,
|
||||||
|
"--force",
|
||||||
|
}
|
||||||
|
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.Run(t, append(transferArgs, "--amount", "1")...)
|
||||||
|
line := e.GetNextLine(t)
|
||||||
|
txHash, err := util.Uint256DecodeStringLE(line)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
tx, ok := e.Chain.GetMemPool().TryGetValue(txHash)
|
||||||
|
require.True(t, ok)
|
||||||
|
|
||||||
|
args := []string{"neo-go", "query", "tx", "--rpc-endpoint", "http://" + e.RPC.Addresses()[0]}
|
||||||
|
e.Run(t, append(args, txHash.StringLE())...)
|
||||||
|
e.CheckNextLine(t, `Hash:\s+`+txHash.StringLE())
|
||||||
|
e.CheckNextLine(t, `OnChain:\s+false`)
|
||||||
|
e.CheckNextLine(t, `ValidUntil:\s+`+strconv.FormatUint(uint64(tx.ValidUntilBlock), 10))
|
||||||
|
e.CheckEOF(t)
|
||||||
|
|
||||||
|
go e.Chain.Run()
|
||||||
|
require.Eventually(t, func() bool { _, aerErr := e.Chain.GetAppExecResults(txHash, trigger.Application); return aerErr == nil }, time.Second*2, time.Millisecond*50)
|
||||||
|
|
||||||
|
e.Run(t, append(args, txHash.StringLE())...)
|
||||||
|
e.CheckNextLine(t, `Hash:\s+`+txHash.StringLE())
|
||||||
|
e.CheckNextLine(t, `OnChain:\s+true`)
|
||||||
|
|
||||||
|
_, height, err := e.Chain.GetTransaction(txHash)
|
||||||
|
require.NoError(t, err)
|
||||||
|
e.CheckNextLine(t, `BlockHash:\s+`+e.Chain.GetHeaderHash(height).StringLE())
|
||||||
|
e.CheckNextLine(t, `Success:\s+true`)
|
||||||
|
e.CheckEOF(t)
|
||||||
|
|
||||||
|
t.Run("verbose", func(t *testing.T) {
|
||||||
|
e.Run(t, append(args, "--verbose", txHash.StringLE())...)
|
||||||
|
compareQueryTxVerbose(t, e, tx)
|
||||||
|
|
||||||
|
t.Run("FAULT", func(t *testing.T) {
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.Run(t, "neo-go", "contract", "invokefunction",
|
||||||
|
"--rpc-endpoint", "http://"+e.RPC.Addresses()[0],
|
||||||
|
"--wallet", testcli.ValidatorWallet,
|
||||||
|
"--address", testcli.ValidatorAddr,
|
||||||
|
"--force",
|
||||||
|
random.Uint160().StringLE(),
|
||||||
|
"randomMethod")
|
||||||
|
|
||||||
|
e.CheckNextLine(t, `Warning:`)
|
||||||
|
e.CheckNextLine(t, "Sending transaction")
|
||||||
|
line := strings.TrimPrefix(e.GetNextLine(t), "Sent invocation transaction ")
|
||||||
|
txHash, err := util.Uint256DecodeStringLE(line)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
require.Eventually(t, func() bool { _, aerErr := e.Chain.GetAppExecResults(txHash, trigger.Application); return aerErr == nil }, time.Second*2, time.Millisecond*50)
|
||||||
|
|
||||||
|
tx, _, err := e.Chain.GetTransaction(txHash)
|
||||||
|
require.NoError(t, err)
|
||||||
|
e.Run(t, append(args, "--verbose", txHash.StringLE())...)
|
||||||
|
compareQueryTxVerbose(t, e, tx)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid", func(t *testing.T) {
|
||||||
|
t.Run("missing tx argument", func(t *testing.T) {
|
||||||
|
e.RunWithError(t, args...)
|
||||||
|
})
|
||||||
|
t.Run("excessive arguments", func(t *testing.T) {
|
||||||
|
e.RunWithError(t, append(args, txHash.StringLE(), txHash.StringLE())...)
|
||||||
|
})
|
||||||
|
t.Run("invalid hash", func(t *testing.T) {
|
||||||
|
e.RunWithError(t, append(args, "notahash")...)
|
||||||
|
})
|
||||||
|
t.Run("good hash, missing tx", func(t *testing.T) {
|
||||||
|
e.RunWithError(t, append(args, random.Uint256().StringLE())...)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func compareQueryTxVerbose(t *testing.T, e *testcli.Executor, tx *transaction.Transaction, fallbacks ...int) {
|
||||||
|
if len(fallbacks) > 0 {
|
||||||
|
e.CheckNextLine(t, `Current height:\s+(\d|\.)+`)
|
||||||
|
}
|
||||||
|
e.CheckNextLine(t, `Hash:\s+`+tx.Hash().StringLE())
|
||||||
|
e.CheckNextLine(t, `OnChain:\s+`+strconv.FormatBool(len(fallbacks) == 0))
|
||||||
|
var (
|
||||||
|
res []state.AppExecResult
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if len(fallbacks) == 0 {
|
||||||
|
_, height, err := e.Chain.GetTransaction(tx.Hash())
|
||||||
|
require.NoError(t, err)
|
||||||
|
e.CheckNextLine(t, `BlockHash:\s+`+e.Chain.GetHeaderHash(height).StringLE())
|
||||||
|
res, _ = e.Chain.GetAppExecResults(tx.Hash(), trigger.Application)
|
||||||
|
e.CheckNextLine(t, fmt.Sprintf(`Success:\s+%t`, res[0].VMState == vmstate.Halt))
|
||||||
|
} else {
|
||||||
|
e.CheckNextLine(t, `ValidUntil:\s+`+strconv.FormatUint(uint64(tx.ValidUntilBlock), 10))
|
||||||
|
e.CheckNextLine(t, `Fallbacks:\s+`+strconv.Itoa(len(fallbacks)))
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, s := range tx.Signers {
|
||||||
|
e.CheckNextLine(t, fmt.Sprintf(`Signer:\s+%s\s*\(%s\)`, address.Uint160ToString(s.Account), s.Scopes.String()))
|
||||||
|
}
|
||||||
|
e.CheckNextLine(t, `SystemFee:\s+`+fixedn.Fixed8(tx.SystemFee).String()+" GAS$")
|
||||||
|
e.CheckNextLine(t, `NetworkFee:\s+`+fixedn.Fixed8(tx.NetworkFee).String()+" GAS$")
|
||||||
|
e.CheckNextLine(t, `Script:\s+`+regexp.QuoteMeta(base64.StdEncoding.EncodeToString(tx.Script)))
|
||||||
|
if len(fallbacks) == 0 {
|
||||||
|
c := vm.NewContext(tx.Script)
|
||||||
|
n := 0
|
||||||
|
for ; c.NextIP() < c.LenInstr(); _, _, err = c.Next() {
|
||||||
|
require.NoError(t, err)
|
||||||
|
n++
|
||||||
|
}
|
||||||
|
e.CheckScriptDump(t, n)
|
||||||
|
|
||||||
|
if res[0].VMState != vmstate.Halt {
|
||||||
|
e.CheckNextLine(t, `Exception:\s+`+regexp.QuoteMeta(res[0].FaultException))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
e.CheckEOF(t)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryHeight(t *testing.T) {
|
||||||
|
e := testcli.NewExecutor(t, true)
|
||||||
|
|
||||||
|
args := []string{"neo-go", "query", "height", "--rpc-endpoint", "http://" + e.RPC.Addresses()[0]}
|
||||||
|
e.Run(t, args...)
|
||||||
|
e.CheckNextLine(t, `^Latest block: [0-9]+$`)
|
||||||
|
e.CheckNextLine(t, `^Validated state: [0-9]+$`)
|
||||||
|
e.CheckEOF(t)
|
||||||
|
t.Run("excessive arguments", func(t *testing.T) {
|
||||||
|
e.RunWithError(t, append(args, "something")...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestQueryNotaryPool(t *testing.T) {
|
||||||
|
e := testcli.NewExecutorWithConfig(t, true, true, func(cfg *config.Config) {
|
||||||
|
cfg.ProtocolConfiguration.Hardforks = map[string]uint32{
|
||||||
|
config.HFFaun.String(): 0,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
endpoint := "http://" + e.RPC.Addresses()[0]
|
||||||
|
|
||||||
|
w, err := wallet.NewWalletFromFile("../testdata/testwallet.json")
|
||||||
|
require.NoError(t, err)
|
||||||
|
acc := w.Accounts[0]
|
||||||
|
require.NoError(t, acc.Decrypt(testcli.TestWalletPass, w.Scrypt))
|
||||||
|
|
||||||
|
// Transfer some GAS to the account.
|
||||||
|
transferArgs := []string{
|
||||||
|
"neo-go", "wallet", "nep17", "transfer",
|
||||||
|
"--rpc-endpoint", "http://" + e.RPC.Addresses()[0],
|
||||||
|
"--wallet", testcli.ValidatorWallet,
|
||||||
|
"--to", acc.Address,
|
||||||
|
"--token", "GAS",
|
||||||
|
"--amount", "10000",
|
||||||
|
"--from", testcli.ValidatorAddr,
|
||||||
|
"--force",
|
||||||
|
}
|
||||||
|
e.In.WriteString("one\r")
|
||||||
|
e.Run(t, transferArgs...)
|
||||||
|
line := e.GetNextLine(t)
|
||||||
|
txHash, err := util.Uint256DecodeStringLE(line)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Eventually(t, func() bool { _, aerErr := e.Chain.GetAppExecResults(txHash, trigger.Application); return aerErr == nil }, time.Second*2, time.Millisecond*50)
|
||||||
|
|
||||||
|
// Make a notary deposit.
|
||||||
|
c, err := rpcclient.New(context.Background(), endpoint, rpcclient.Options{})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, c.Init())
|
||||||
|
signer := actor.SignerAccount{
|
||||||
|
Signer: transaction.Signer{Account: acc.ScriptHash(), Scopes: transaction.Global},
|
||||||
|
Account: acc,
|
||||||
|
}
|
||||||
|
act, err := actor.New(c, []actor.SignerAccount{signer})
|
||||||
|
require.NoError(t, err)
|
||||||
|
g := gas.New(act)
|
||||||
|
h, _, err := g.Transfer(acc.ScriptHash(), nativehashes.Notary, big.NewInt(20000000), []any{stackitem.Null{}, e.Chain.BlockHeight() + 1000})
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Eventually(t, func() bool { _, aerErr := e.Chain.GetAppExecResults(h, trigger.Application); return aerErr == nil }, time.Second*2, time.Millisecond*50)
|
||||||
|
|
||||||
|
// Submit some notary request.
|
||||||
|
pk, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
fakeAcc := notary.FakeSimpleAccount(pk.PublicKey())
|
||||||
|
ntr, err := notary.NewActor(c, []actor.SignerAccount{
|
||||||
|
signer,
|
||||||
|
{
|
||||||
|
Signer: transaction.Signer{Account: fakeAcc.ScriptHash(), Scopes: transaction.Global},
|
||||||
|
Account: fakeAcc,
|
||||||
|
},
|
||||||
|
}, acc)
|
||||||
|
require.NoError(t, err)
|
||||||
|
tx, err := ntr.MakeCall(nativehashes.GasToken, "transfer", acc.ScriptHash(), acc.ScriptHash(), big.NewInt(1), nil)
|
||||||
|
mainH, _, vub, err := ntr.Notarize(tx, err)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// Check the notary pool.
|
||||||
|
args := []string{"neo-go", "query", "notarypool", "--rpc-endpoint", endpoint}
|
||||||
|
t.Run("silent", func(t *testing.T) {
|
||||||
|
e.Run(t, args...)
|
||||||
|
e.CheckNextLine(t, `Current height:\s+(\d|\.)+`)
|
||||||
|
e.CheckNextLine(t, `Hash:\s+`+mainH.StringLE())
|
||||||
|
e.CheckNextLine(t, `OnChain:\s+false`)
|
||||||
|
e.CheckNextLine(t, `ValidUntil:\s+`+strconv.FormatUint(uint64(vub), 10))
|
||||||
|
e.CheckNextLine(t, `Fallbacks:\s+1`)
|
||||||
|
e.CheckEOF(t)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("verbose", func(t *testing.T) {
|
||||||
|
e.Run(t, append(args, "--verbose")...)
|
||||||
|
compareQueryTxVerbose(t, e, tx, 1)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("invalid", func(t *testing.T) {
|
||||||
|
t.Run("excessive arguments", func(t *testing.T) {
|
||||||
|
e.RunWithError(t, append(args, "bla")...)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,163 @@
|
||||||
|
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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// generated via `go run ./scripts/gendump/main.go --out ./cli/server/testdata/chain50x2.acc --blocks 50 --txs 2`.
|
||||||
|
const inDump = "./testdata/chain50x2.acc"
|
||||||
|
|
||||||
|
func TestDBRestoreDump(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)
|
||||||
|
|
||||||
|
stateDump := filepath.Join(tmpDir, "neogo.teststate")
|
||||||
|
baseArgs := []string{"neo-go", "db", "restore", "--unittest",
|
||||||
|
"--config-path", tmpDir, "--in", inDump, "--dump", stateDump}
|
||||||
|
|
||||||
|
t.Run("excessive restore parameters", func(t *testing.T) {
|
||||||
|
e.RunWithError(t, append(baseArgs, "something")...)
|
||||||
|
})
|
||||||
|
// First 15 blocks.
|
||||||
|
e.Run(t, append(baseArgs, "--count", "15")...)
|
||||||
|
|
||||||
|
// Big count.
|
||||||
|
e.RunWithError(t, append(baseArgs, "--count", "1000")...)
|
||||||
|
|
||||||
|
// Continue 15..25
|
||||||
|
e.Run(t, append(baseArgs, "--count", "10")...)
|
||||||
|
|
||||||
|
// Continue till end.
|
||||||
|
e.Run(t, baseArgs...)
|
||||||
|
|
||||||
|
// Dump and compare.
|
||||||
|
dumpPath := filepath.Join(tmpDir, "testdump.acc")
|
||||||
|
incDumpPath := filepath.Join(tmpDir, "testincdump.acc")
|
||||||
|
|
||||||
|
t.Run("missing config", func(t *testing.T) {
|
||||||
|
e.RunWithError(t, "neo-go", "db", "dump", "--privnet",
|
||||||
|
"--config-path", tmpDir, "--out", dumpPath)
|
||||||
|
})
|
||||||
|
t.Run("bad logger config", func(t *testing.T) {
|
||||||
|
badConfigDir := t.TempDir()
|
||||||
|
logfile := filepath.Join(badConfigDir, "logdir")
|
||||||
|
require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm))
|
||||||
|
cfg = loadConfig(t)
|
||||||
|
cfg.ApplicationConfiguration.LogPath = filepath.Join(logfile, "file.log")
|
||||||
|
out, err = yaml.Marshal(cfg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cfgPath = filepath.Join(badConfigDir, "protocol.unit_testnet.yml")
|
||||||
|
require.NoError(t, os.WriteFile(cfgPath, out, os.ModePerm))
|
||||||
|
|
||||||
|
e.RunWithError(t, "neo-go", "db", "dump", "--unittest",
|
||||||
|
"--config-path", badConfigDir, "--out", dumpPath)
|
||||||
|
})
|
||||||
|
t.Run("bad storage config", func(t *testing.T) {
|
||||||
|
badConfigDir := t.TempDir()
|
||||||
|
logfile := filepath.Join(badConfigDir, "logdir")
|
||||||
|
require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm))
|
||||||
|
cfg = loadConfig(t)
|
||||||
|
cfg.ApplicationConfiguration.DBConfiguration.Type = ""
|
||||||
|
out, err = yaml.Marshal(cfg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
cfgPath = filepath.Join(badConfigDir, "protocol.unit_testnet.yml")
|
||||||
|
require.NoError(t, os.WriteFile(cfgPath, out, os.ModePerm))
|
||||||
|
|
||||||
|
e.RunWithError(t, "neo-go", "db", "dump", "--unittest",
|
||||||
|
"--config-path", badConfigDir, "--out", dumpPath)
|
||||||
|
})
|
||||||
|
|
||||||
|
baseCmd := []string{"neo-go", "db", "dump", "--unittest",
|
||||||
|
"--config-path", tmpDir, "--out", dumpPath}
|
||||||
|
|
||||||
|
t.Run("invalid start/count", func(t *testing.T) {
|
||||||
|
e.RunWithError(t, append(baseCmd, "--start", "5", "--count", strconv.Itoa(50-5+1+1))...)
|
||||||
|
})
|
||||||
|
t.Run("excessive dump parameters", func(t *testing.T) {
|
||||||
|
e.RunWithError(t, append(baseCmd, "something")...)
|
||||||
|
})
|
||||||
|
|
||||||
|
e.Run(t, append(baseCmd, "--non-incremental", "--out", dumpPath)...)
|
||||||
|
e.Run(t, append(baseCmd, "--out", incDumpPath)...)
|
||||||
|
|
||||||
|
d1, err := os.ReadFile(inDump)
|
||||||
|
require.NoError(t, err)
|
||||||
|
d2, err := os.ReadFile(dumpPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, d1, d2, "dumps differ")
|
||||||
|
d3, err := os.ReadFile(incDumpPath)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, append([]byte{0, 0, 0, 0}, d1...), d3, "dumps differ")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDBDumpRestoreIncremental(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
chainPath := filepath.Join(tmpDir, "neogotestchain")
|
||||||
|
nonincDump := filepath.Join(tmpDir, "nonincDump.acc")
|
||||||
|
incDump := filepath.Join(tmpDir, "incDump.acc")
|
||||||
|
|
||||||
|
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
|
||||||
|
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)
|
||||||
|
|
||||||
|
// Create DB from dump.
|
||||||
|
e.Run(t, "neo-go", "db", "restore", "--unittest", "--config-path", tmpDir, "--in", inDump)
|
||||||
|
|
||||||
|
// Create two dumps: non-incremental and incremental.
|
||||||
|
dumpBaseArgs := []string{"neo-go", "db", "dump", "--unittest",
|
||||||
|
"--config-path", tmpDir}
|
||||||
|
|
||||||
|
// Dump first 15 blocks to a non-incremental dump.
|
||||||
|
e.Run(t, append(dumpBaseArgs, "--out", nonincDump, "--count", "15")...)
|
||||||
|
|
||||||
|
// Dump second 15 blocks to an incremental dump.
|
||||||
|
e.Run(t, append(dumpBaseArgs, "--out", incDump, "--start", "15", "--count", "15")...)
|
||||||
|
|
||||||
|
// Clean the DB.
|
||||||
|
require.NoError(t, os.RemoveAll(chainPath))
|
||||||
|
|
||||||
|
// Restore chain from two dumps.
|
||||||
|
restoreBaseArgs := []string{"neo-go", "db", "restore", "--unittest", "--config-path", tmpDir}
|
||||||
|
|
||||||
|
// Restore first 15 blocks from non-incremental dump.
|
||||||
|
e.Run(t, append(restoreBaseArgs, "--in", nonincDump)...)
|
||||||
|
|
||||||
|
// Restore second 15 blocks from incremental dump.
|
||||||
|
e.Run(t, append(restoreBaseArgs, "--in", incDump, "--count", "15")...)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,132 @@
|
||||||
|
package server_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"io"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"strings"
|
||||||
|
"testing"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/cli/server"
|
||||||
|
"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 TestServerStart(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
goodCfg, err := config.LoadFile(filepath.Join("..", "..", "config", "protocol.unit_testnet.yml"))
|
||||||
|
require.NoError(t, err, "could not load config")
|
||||||
|
ptr := &goodCfg
|
||||||
|
saveCfg := func(t *testing.T, f func(cfg *config.Config)) string {
|
||||||
|
cfg := *ptr
|
||||||
|
chainPath := filepath.Join(t.TempDir(), "neogotestchain")
|
||||||
|
cfg.ApplicationConfiguration.DBConfiguration.Type = dbconfig.LevelDB
|
||||||
|
cfg.ApplicationConfiguration.DBConfiguration.LevelDBOptions.DataDirectoryPath = chainPath
|
||||||
|
f(&cfg)
|
||||||
|
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))
|
||||||
|
t.Cleanup(func() {
|
||||||
|
require.NoError(t, os.Remove(cfgPath))
|
||||||
|
})
|
||||||
|
return cfgPath
|
||||||
|
}
|
||||||
|
|
||||||
|
baseCmd := []string{"neo-go", "node", "--unittest", "--config-path", tmpDir}
|
||||||
|
e := testcli.NewExecutor(t, false)
|
||||||
|
|
||||||
|
t.Run("invalid config path", func(t *testing.T) {
|
||||||
|
e.RunWithError(t, baseCmd...)
|
||||||
|
})
|
||||||
|
t.Run("bad logger config", func(t *testing.T) {
|
||||||
|
badConfigDir := t.TempDir()
|
||||||
|
logfile := filepath.Join(badConfigDir, "logdir")
|
||||||
|
require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm))
|
||||||
|
saveCfg(t, func(cfg *config.Config) {
|
||||||
|
cfg.ApplicationConfiguration.LogPath = filepath.Join(logfile, "file.log")
|
||||||
|
})
|
||||||
|
e.RunWithError(t, baseCmd...)
|
||||||
|
})
|
||||||
|
t.Run("invalid storage", func(t *testing.T) {
|
||||||
|
saveCfg(t, func(cfg *config.Config) {
|
||||||
|
cfg.ApplicationConfiguration.DBConfiguration.Type = ""
|
||||||
|
})
|
||||||
|
e.RunWithError(t, baseCmd...)
|
||||||
|
})
|
||||||
|
t.Run("stateroot service is on && StateRootInHeader=true", func(t *testing.T) {
|
||||||
|
saveCfg(t, func(cfg *config.Config) {
|
||||||
|
cfg.ApplicationConfiguration.StateRoot.Enabled = true
|
||||||
|
cfg.ProtocolConfiguration.StateRootInHeader = true
|
||||||
|
})
|
||||||
|
e.RunWithError(t, baseCmd...)
|
||||||
|
})
|
||||||
|
t.Run("invalid Oracle config", func(t *testing.T) {
|
||||||
|
saveCfg(t, func(cfg *config.Config) {
|
||||||
|
cfg.ApplicationConfiguration.Oracle.Enabled = true
|
||||||
|
cfg.ApplicationConfiguration.Oracle.UnlockWallet.Path = "bad_orc_wallet.json"
|
||||||
|
})
|
||||||
|
e.RunWithError(t, baseCmd...)
|
||||||
|
})
|
||||||
|
t.Run("invalid consensus config", func(t *testing.T) {
|
||||||
|
saveCfg(t, func(cfg *config.Config) {
|
||||||
|
cfg.ApplicationConfiguration.Consensus.Enabled = true
|
||||||
|
cfg.ApplicationConfiguration.Consensus.UnlockWallet.Path = "bad_consensus_wallet.json"
|
||||||
|
})
|
||||||
|
e.RunWithError(t, baseCmd...)
|
||||||
|
})
|
||||||
|
t.Run("invalid Notary config", func(t *testing.T) {
|
||||||
|
t.Run("malformed config", func(t *testing.T) {
|
||||||
|
saveCfg(t, func(cfg *config.Config) {
|
||||||
|
cfg.ProtocolConfiguration.P2PSigExtensions = false
|
||||||
|
cfg.ApplicationConfiguration.P2PNotary.Enabled = true
|
||||||
|
})
|
||||||
|
e.RunWithError(t, baseCmd...)
|
||||||
|
})
|
||||||
|
t.Run("invalid wallet", func(t *testing.T) {
|
||||||
|
saveCfg(t, func(cfg *config.Config) {
|
||||||
|
cfg.ProtocolConfiguration.P2PSigExtensions = true
|
||||||
|
cfg.ApplicationConfiguration.P2PNotary.Enabled = true
|
||||||
|
cfg.ApplicationConfiguration.P2PNotary.UnlockWallet.Path = "bad_notary_wallet.json"
|
||||||
|
})
|
||||||
|
e.RunWithError(t, baseCmd...)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
// We can't properly shutdown server on windows and release the resources.
|
||||||
|
// Also, windows doesn't support SIGHUP and SIGINT.
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
saveCfg(t, func(cfg *config.Config) {})
|
||||||
|
t.Run("excessive parameters", func(t *testing.T) {
|
||||||
|
e.RunWithError(t, append(baseCmd, "something")...)
|
||||||
|
})
|
||||||
|
t.Run("good", func(t *testing.T) {
|
||||||
|
go func() {
|
||||||
|
e.Run(t, baseCmd...)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var line string
|
||||||
|
require.Eventually(t, func() bool {
|
||||||
|
line, err = e.Out.ReadString('\n')
|
||||||
|
if err != nil && !errors.Is(err, io.EOF) {
|
||||||
|
t.Fatalf("unexpected error while reading CLI output: %s", err)
|
||||||
|
}
|
||||||
|
return err == nil
|
||||||
|
}, 2*time.Second, 100*time.Millisecond)
|
||||||
|
for expected := range strings.SplitSeq(server.Logo(), "\n") {
|
||||||
|
// It should be regexp, so escape all backslashes.
|
||||||
|
expected = strings.ReplaceAll(expected, `\`, `\\`)
|
||||||
|
e.CheckLine(t, line, expected)
|
||||||
|
line = e.GetNextLine(t)
|
||||||
|
}
|
||||||
|
e.CheckNextLine(t, "")
|
||||||
|
e.CheckEOF(t)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,102 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/storage/dboper"
|
||||||
|
)
|
||||||
|
|
||||||
|
type dump []blockDump
|
||||||
|
|
||||||
|
type blockDump struct {
|
||||||
|
Block uint32 `json:"block"`
|
||||||
|
Size int `json:"size"`
|
||||||
|
Storage []dboper.Operation `json:"storage"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func newDump() *dump {
|
||||||
|
return new(dump)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dump) add(index uint32, batch *storage.MemBatch) {
|
||||||
|
ops := storage.BatchToOperations(batch)
|
||||||
|
*d = append(*d, blockDump{
|
||||||
|
Block: index,
|
||||||
|
Size: len(ops),
|
||||||
|
Storage: ops,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func (d *dump) tryPersist(prefix string, index uint32) error {
|
||||||
|
if len(*d) == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
path, err := getPath(prefix, index)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
old, err := readFile(path)
|
||||||
|
if err == nil {
|
||||||
|
*old = append(*old, *d...)
|
||||||
|
} else {
|
||||||
|
old = d
|
||||||
|
}
|
||||||
|
f, err := os.Create(path)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
enc := json.NewEncoder(f)
|
||||||
|
enc.SetIndent("", " ")
|
||||||
|
if err := enc.Encode(*old); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
*d = (*d)[:0]
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readFile(path string) (*dump, error) {
|
||||||
|
data, err := os.ReadFile(path)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
d := newDump()
|
||||||
|
if err := json.Unmarshal(data, d); err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return d, err
|
||||||
|
}
|
||||||
|
|
||||||
|
// getPath returns filename for storing blocks up to index.
|
||||||
|
// Directory structure is the following:
|
||||||
|
// https://github.com/NeoResearch/neo-storage-audit#folder-organization-where-to-find-the-desired-block
|
||||||
|
// Dir `BlockStorage_$DIRNO` contains blocks up to $DIRNO (from $DIRNO-100k)
|
||||||
|
// Inside it there are files grouped by 1k blocks.
|
||||||
|
// File dump-block-$FILENO.json contains blocks from $FILENO-999, $FILENO
|
||||||
|
// Example: file `BlockStorage_100000/dump-block-6000.json` contains blocks from 5001 to 6000.
|
||||||
|
func getPath(prefix string, index uint32) (string, error) {
|
||||||
|
dirN := ((index + 99999) / 100000) * 100000
|
||||||
|
dir := fmt.Sprintf("BlockStorage_%d", dirN)
|
||||||
|
|
||||||
|
path := filepath.Join(prefix, dir)
|
||||||
|
info, err := os.Stat(path)
|
||||||
|
if os.IsNotExist(err) {
|
||||||
|
err := os.MkdirAll(path, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
} else if !info.IsDir() {
|
||||||
|
return "", fmt.Errorf("file `%s` is not a directory", path)
|
||||||
|
}
|
||||||
|
|
||||||
|
fileN := ((index + 999) / 1000) * 1000
|
||||||
|
file := fmt.Sprintf("dump-block-%d.json", fileN)
|
||||||
|
return filepath.Join(path, file), nil
|
||||||
|
}
|
||||||
|
|
@ -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, 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
|
||||||
|
}
|
||||||
|
|
@ -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...)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,23 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"path/filepath"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/stretchr/testify/require"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGetPath(t *testing.T) {
|
||||||
|
testPath := t.TempDir()
|
||||||
|
actual, err := getPath(testPath, 123)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, filepath.Join(testPath, "BlockStorage_100000", "dump-block-1000.json"), actual)
|
||||||
|
|
||||||
|
actual, err = getPath(testPath, 1230)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, filepath.Join(testPath, "BlockStorage_100000", "dump-block-2000.json"), actual)
|
||||||
|
|
||||||
|
actual, err = getPath(testPath, 123000)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, filepath.Join(testPath, "BlockStorage_200000", "dump-block-123000.json"), actual)
|
||||||
|
}
|
||||||
|
|
@ -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,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,709 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
stdio "io"
|
||||||
|
"os"
|
||||||
|
"os/signal"
|
||||||
|
"slices"
|
||||||
|
"syscall"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||||
|
"github.com/nspcc-dev/neo-go/cli/options"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/config/netmode"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/consensus"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/chaindump"
|
||||||
|
corestate "github.com/nspcc-dev/neo-go/pkg/core/stateroot"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/storage"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/network"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/services/metrics"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/services/notary"
|
||||||
|
"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/v2"
|
||||||
|
"go.uber.org/zap"
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewCommands returns 'node' command.
|
||||||
|
func NewCommands() []*cli.Command {
|
||||||
|
cfgFlags := []cli.Flag{options.Config, options.ConfigFile, options.RelativePath}
|
||||||
|
cfgFlags = append(cfgFlags, options.Network...)
|
||||||
|
|
||||||
|
var cfgWithCountFlags = slices.Clone(cfgFlags)
|
||||||
|
cfgFlags = append(cfgFlags, options.Debug, options.ForceTimestampLogs)
|
||||||
|
cfgWithCountFlags = append(cfgWithCountFlags,
|
||||||
|
&cli.UintFlag{
|
||||||
|
Name: "count",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Usage: "Number of blocks to be processed (default or 0: all chain)",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
var cfgCountOutFlags = slices.Clone(cfgWithCountFlags)
|
||||||
|
cfgCountOutFlags = append(cfgCountOutFlags,
|
||||||
|
&cli.UintFlag{
|
||||||
|
Name: "start",
|
||||||
|
Aliases: []string{"s"},
|
||||||
|
Usage: "Block number to start from",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "out",
|
||||||
|
Aliases: []string{"o"},
|
||||||
|
Usage: "Output file (stdout if not given)",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
var cfgCountInFlags = slices.Clone(cfgWithCountFlags)
|
||||||
|
cfgCountInFlags = append(cfgCountInFlags,
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "in",
|
||||||
|
Aliases: []string{"i"},
|
||||||
|
Usage: "Input file (stdin if not given)",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "dump",
|
||||||
|
Usage: "Directory for storing JSON dumps",
|
||||||
|
},
|
||||||
|
)
|
||||||
|
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{
|
||||||
|
{
|
||||||
|
Name: "node",
|
||||||
|
Usage: "Start a NeoGo node",
|
||||||
|
UsageText: "neo-go node [--config-path path] [-d] [-p/-m/-t] [--config-file file] [--force-timestamp-logs]",
|
||||||
|
Action: startServer,
|
||||||
|
Flags: cfgFlags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "db",
|
||||||
|
Usage: "Database manipulations",
|
||||||
|
Subcommands: []*cli.Command{
|
||||||
|
{
|
||||||
|
Name: "dump",
|
||||||
|
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] [--force-timestamp-logs]",
|
||||||
|
Action: dumpDB,
|
||||||
|
Flags: append(cfgCountOutFlags,
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "non-incremental",
|
||||||
|
Usage: "Force legacy (non-incremental) dump output",
|
||||||
|
},
|
||||||
|
),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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] [--force-timestamp-logs]",
|
||||||
|
Action: dumpBin,
|
||||||
|
Flags: cfgCountOutFlags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "restore",
|
||||||
|
Usage: "Restore blocks from the file",
|
||||||
|
UsageText: "neo-go db restore [-i file] [--dump] [-c count] [--config-path path] [-p/-m/-t] [--config-file file] [--force-timestamp-logs]",
|
||||||
|
Action: restoreDB,
|
||||||
|
Flags: cfgCountInFlags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "reset",
|
||||||
|
Usage: "Reset database to the previous state",
|
||||||
|
UsageText: "neo-go db reset --height height [--config-path path] [-p/-m/-t] [--config-file file] [--force-timestamp-logs]",
|
||||||
|
Action: resetDB,
|
||||||
|
Flags: cfgHeightFlags,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func newGraceContext() context.Context {
|
||||||
|
ctx, cancel := context.WithCancel(context.Background())
|
||||||
|
stop := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(stop, os.Interrupt)
|
||||||
|
signal.Notify(stop, syscall.SIGTERM)
|
||||||
|
go func() {
|
||||||
|
<-stop
|
||||||
|
cancel()
|
||||||
|
}()
|
||||||
|
return ctx
|
||||||
|
}
|
||||||
|
|
||||||
|
// InitBCWithMetrics initializes the blockchain with metrics with the given configuration.
|
||||||
|
func InitBCWithMetrics(cfg config.Config, log *zap.Logger) (*core.Blockchain, *metrics.Service, *metrics.Service, error) {
|
||||||
|
chain, _, err := initBlockChain(cfg, log)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, nil, cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
prometheus := metrics.NewPrometheusService(cfg.ApplicationConfiguration.Prometheus, log)
|
||||||
|
pprof := metrics.NewPprofService(cfg.ApplicationConfiguration.Pprof, log)
|
||||||
|
|
||||||
|
go chain.Run()
|
||||||
|
err = prometheus.Start()
|
||||||
|
if err != nil {
|
||||||
|
return 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.Exit(fmt.Errorf("failed to start Pprof service: %w", err), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return chain, prometheus, pprof, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func dumpDB(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, 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"))
|
||||||
|
|
||||||
|
var outStream = os.Stdout
|
||||||
|
if out := ctx.String("out"); out != "" {
|
||||||
|
outStream, err = os.Create(out)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer outStream.Close()
|
||||||
|
writer := io.NewBinWriterFromIO(outStream)
|
||||||
|
|
||||||
|
chain, prometheus, pprof, err := InitBCWithMetrics(cfg, log)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
pprof.ShutDown()
|
||||||
|
prometheus.ShutDown()
|
||||||
|
chain.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
chainCount := chain.BlockHeight() + 1
|
||||||
|
if start+count > chainCount {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
if start != 0 || !ctx.Bool("non-incremental") {
|
||||||
|
writer.WriteU32LE(start)
|
||||||
|
}
|
||||||
|
writer.WriteU32LE(count)
|
||||||
|
err = chaindump.Dump(chain, writer, start, count)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err.Error(), 1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func restoreDB(ctx *cli.Context) error {
|
||||||
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
cfg, err := options.GetConfigFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
log, _, logCloser, err := options.HandleLoggingParams(ctx, cfg.ApplicationConfiguration)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
if logCloser != nil {
|
||||||
|
defer func() { _ = logCloser() }()
|
||||||
|
}
|
||||||
|
count := uint32(ctx.Uint("count"))
|
||||||
|
|
||||||
|
var inStream = os.Stdin
|
||||||
|
if in := ctx.String("in"); in != "" {
|
||||||
|
inStream, err = os.Open(in)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
defer inStream.Close()
|
||||||
|
reader := io.NewBinReaderFromIO(inStream)
|
||||||
|
|
||||||
|
dumpDir := ctx.String("dump")
|
||||||
|
if dumpDir != "" {
|
||||||
|
cfg.ApplicationConfiguration.SaveStorageBatch = true
|
||||||
|
}
|
||||||
|
|
||||||
|
chain, prometheus, pprof, err := InitBCWithMetrics(cfg, log)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
pprof.ShutDown()
|
||||||
|
prometheus.ShutDown()
|
||||||
|
chain.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
var start = reader.ReadU32LE()
|
||||||
|
if reader.Err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
var allBlocks = reader.ReadU32LE()
|
||||||
|
if reader.Err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
var versionOrLen = reader.ReadU32LE()
|
||||||
|
if reader.Err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var buf []byte
|
||||||
|
if versionOrLen == block.VersionInitial {
|
||||||
|
// Block length > 0 => we read block version, so we have ordinary chain dump.
|
||||||
|
buf = binary.LittleEndian.AppendUint32(buf, allBlocks)
|
||||||
|
allBlocks = start
|
||||||
|
start = 0
|
||||||
|
}
|
||||||
|
buf = binary.LittleEndian.AppendUint32(buf, versionOrLen)
|
||||||
|
reader = io.NewBinReaderFromIO(stdio.MultiReader(
|
||||||
|
bytes.NewReader(buf),
|
||||||
|
inStream,
|
||||||
|
))
|
||||||
|
|
||||||
|
if chain.BlockHeight()+1 < start {
|
||||||
|
return cli.Exit(fmt.Errorf("expected height: %d, dump starts at %d",
|
||||||
|
chain.BlockHeight()+1, start), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
var skip uint32
|
||||||
|
if chain.BlockHeight() != 0 {
|
||||||
|
skip = chain.BlockHeight() + 1 - start
|
||||||
|
}
|
||||||
|
|
||||||
|
if skip+count > allBlocks {
|
||||||
|
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
|
||||||
|
}
|
||||||
|
log.Info("initialize restore",
|
||||||
|
zap.Uint32("start", start),
|
||||||
|
zap.Uint32("height", chain.BlockHeight()),
|
||||||
|
zap.Uint32("skip", skip),
|
||||||
|
zap.Uint32("count", count))
|
||||||
|
|
||||||
|
gctx := newGraceContext()
|
||||||
|
var lastIndex uint32
|
||||||
|
dump := newDump()
|
||||||
|
defer func() {
|
||||||
|
_ = dump.tryPersist(dumpDir, lastIndex)
|
||||||
|
}()
|
||||||
|
|
||||||
|
var f = func(b *block.Block) error {
|
||||||
|
select {
|
||||||
|
case <-gctx.Done():
|
||||||
|
return gctx.Err()
|
||||||
|
default:
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if dumpDir != "" {
|
||||||
|
f = func(b *block.Block) error {
|
||||||
|
select {
|
||||||
|
case <-gctx.Done():
|
||||||
|
return gctx.Err()
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
batch := chain.LastBatch()
|
||||||
|
// The genesis block may already be persisted, so LastBatch() will return nil.
|
||||||
|
if batch == nil && b.Index == 0 {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
dump.add(b.Index, batch)
|
||||||
|
lastIndex = b.Index
|
||||||
|
if b.Index%1000 == 0 {
|
||||||
|
if err := dump.tryPersist(dumpDir, b.Index); err != nil {
|
||||||
|
return fmt.Errorf("can't dump storage to file: %w", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
err = chaindump.Restore(chain, reader, skip, count, f)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("wrong dump file or settings mismatch: %w", err), 1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func resetDB(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)
|
||||||
|
}
|
||||||
|
h := uint32(ctx.Uint("height"))
|
||||||
|
|
||||||
|
log, _, logCloser, err := options.HandleLoggingParams(ctx, cfg.ApplicationConfiguration)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
if logCloser != nil {
|
||||||
|
defer func() { _ = logCloser() }()
|
||||||
|
}
|
||||||
|
chain, store, err := initBlockChain(cfg, log)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("failed to create Blockchain instance: %w", err), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = chain.Reset(h)
|
||||||
|
if err != nil {
|
||||||
|
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.Exit(fmt.Errorf("failed to close the DB: %w", err), 1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// oracleService is an interface representing Oracle service with network.Service
|
||||||
|
// capabilities and ability to submit oracle responses.
|
||||||
|
type oracleService interface {
|
||||||
|
rpcsrv.OracleHandler
|
||||||
|
network.Service
|
||||||
|
}
|
||||||
|
|
||||||
|
func mkOracle(config config.OracleConfiguration, magic netmode.Magic, chain *core.Blockchain, serv *network.Server, log *zap.Logger) (oracleService, error) {
|
||||||
|
if !config.Enabled {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
orcCfg := oracle.Config{
|
||||||
|
Log: log,
|
||||||
|
Network: magic,
|
||||||
|
MainCfg: config,
|
||||||
|
Chain: chain,
|
||||||
|
OnTransaction: serv.RelayTxn,
|
||||||
|
}
|
||||||
|
orc, err := oracle.NewOracle(orcCfg)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't initialize Oracle module: %w", err)
|
||||||
|
}
|
||||||
|
chain.SetOracle(orc)
|
||||||
|
serv.AddService(orc)
|
||||||
|
return orc, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mkConsensus(config config.Consensus, chain *core.Blockchain, serv *network.Server, log *zap.Logger) (consensus.Service, error) {
|
||||||
|
if !config.Enabled {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
srv, err := consensus.NewService(consensus.Config{
|
||||||
|
Logger: log,
|
||||||
|
Broadcast: serv.BroadcastExtensible,
|
||||||
|
Chain: chain,
|
||||||
|
BlockQueue: serv.GetBlockQueue(),
|
||||||
|
ProtocolConfiguration: chain.GetConfig().ProtocolConfiguration,
|
||||||
|
RequestTx: serv.RequestTx,
|
||||||
|
StopTxFlow: serv.StopTxFlow,
|
||||||
|
Wallet: config.UnlockWallet,
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("can't initialize Consensus module: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
serv.AddConsensusService(srv, srv.OnPayload, srv.OnTransaction)
|
||||||
|
return srv, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func mkP2PNotary(config config.P2PNotary, chain *core.Blockchain, serv *network.Server, log *zap.Logger) (*notary.Notary, error) {
|
||||||
|
if !config.Enabled {
|
||||||
|
return nil, nil
|
||||||
|
}
|
||||||
|
if !chain.P2PSigExtensionsEnabled() {
|
||||||
|
return nil, errors.New("P2PSigExtensions are disabled, but Notary service is enabled")
|
||||||
|
}
|
||||||
|
cfg := notary.Config{
|
||||||
|
MainCfg: config,
|
||||||
|
Chain: chain,
|
||||||
|
Log: log,
|
||||||
|
}
|
||||||
|
n, err := notary.NewNotary(cfg, serv.Net, serv.GetNotaryPool(), func(tx *transaction.Transaction) error {
|
||||||
|
err := serv.RelayTxn(tx)
|
||||||
|
if err != nil && !errors.Is(err, core.ErrAlreadyExists) && !errors.Is(err, core.ErrAlreadyInPool) {
|
||||||
|
return fmt.Errorf("can't relay completed notary transaction: hash %s, error: %w", tx.Hash().StringLE(), err)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
})
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to create Notary module: %w", err)
|
||||||
|
}
|
||||||
|
serv.AddService(n)
|
||||||
|
chain.SetNotary(n)
|
||||||
|
return n, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func startServer(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)
|
||||||
|
}
|
||||||
|
var logDebug = ctx.Bool("debug")
|
||||||
|
log, logLevel, logCloser, err := options.HandleLoggingParams(ctx, cfg.ApplicationConfiguration)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
if logCloser != nil {
|
||||||
|
defer func() { _ = logCloser() }()
|
||||||
|
}
|
||||||
|
|
||||||
|
grace, cancel := context.WithCancel(newGraceContext())
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
serverConfig, err := network.NewServerConfig(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
chain, prometheus, pprof, err := InitBCWithMetrics(cfg, log)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
defer func() {
|
||||||
|
pprof.ShutDown()
|
||||||
|
prometheus.ShutDown()
|
||||||
|
chain.Close()
|
||||||
|
}()
|
||||||
|
|
||||||
|
serv, err := network.NewServer(serverConfig, chain, chain.GetStateSyncModule(), log)
|
||||||
|
if err != nil {
|
||||||
|
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.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.Exit(err, 1)
|
||||||
|
}
|
||||||
|
dbftSrv, err := mkConsensus(cfg.ApplicationConfiguration.Consensus, chain, serv, log)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
p2pNotary, err := mkP2PNotary(cfg.ApplicationConfiguration.P2PNotary, chain, serv, log)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
errChan := make(chan error)
|
||||||
|
rpcServer := rpcsrv.New(chain, cfg.ApplicationConfiguration.RPC, serv, oracleSrv, log, errChan)
|
||||||
|
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
|
||||||
|
// deadlock: Start() can write errors to errChan which is not yet read in the
|
||||||
|
// current execution context (see for-loop below).
|
||||||
|
go rpcServer.Start()
|
||||||
|
}
|
||||||
|
|
||||||
|
sigCh := make(chan os.Signal, 1)
|
||||||
|
signal.Notify(sigCh, sighup)
|
||||||
|
signal.Notify(sigCh, sigusr1)
|
||||||
|
signal.Notify(sigCh, sigusr2)
|
||||||
|
|
||||||
|
fmt.Fprintln(ctx.App.Writer, Logo())
|
||||||
|
fmt.Fprintln(ctx.App.Writer, serv.UserAgent)
|
||||||
|
fmt.Fprintln(ctx.App.Writer)
|
||||||
|
|
||||||
|
var shutdownErr error
|
||||||
|
Main:
|
||||||
|
for {
|
||||||
|
select {
|
||||||
|
case err := <-errChan:
|
||||||
|
shutdownErr = fmt.Errorf("server error: %w", err)
|
||||||
|
cancel()
|
||||||
|
case sig := <-sigCh:
|
||||||
|
var newLogLevel = zapcore.InvalidLevel
|
||||||
|
|
||||||
|
log.Info("signal received", zap.Stringer("name", sig))
|
||||||
|
cfgnew, err := options.GetConfigFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("can't reread the config file, signal ignored", zap.Error(err))
|
||||||
|
break // Continue working.
|
||||||
|
}
|
||||||
|
if !cfg.ProtocolConfiguration.Equals(&cfgnew.ProtocolConfiguration) {
|
||||||
|
log.Warn("ProtocolConfiguration changed, signal ignored")
|
||||||
|
break // Continue working.
|
||||||
|
}
|
||||||
|
if !cfg.ApplicationConfiguration.EqualsButServices(&cfgnew.ApplicationConfiguration) {
|
||||||
|
log.Warn("ApplicationConfiguration changed in incompatible way, signal ignored")
|
||||||
|
break // Continue working.
|
||||||
|
}
|
||||||
|
if !logDebug && cfgnew.ApplicationConfiguration.LogLevel != cfg.ApplicationConfiguration.LogLevel {
|
||||||
|
newLogLevel, err = zapcore.ParseLevel(cfgnew.ApplicationConfiguration.LogLevel)
|
||||||
|
if err != nil {
|
||||||
|
log.Warn("wrong LogLevel in ApplicationConfiguration, signal ignored", zap.Error(err))
|
||||||
|
break // Continue working.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
switch sig {
|
||||||
|
case sighup:
|
||||||
|
if newLogLevel != zapcore.InvalidLevel {
|
||||||
|
logLevel.SetLevel(newLogLevel)
|
||||||
|
log.Warn("using new logging level", zap.Stringer("level", newLogLevel))
|
||||||
|
}
|
||||||
|
serv.DelService(rpcServer)
|
||||||
|
rpcServer.Shutdown()
|
||||||
|
rpcServer = rpcsrv.New(chain, cfgnew.ApplicationConfiguration.RPC, serv, oracleSrv, log, errChan)
|
||||||
|
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()
|
||||||
|
}
|
||||||
|
pprof.ShutDown()
|
||||||
|
pprof = metrics.NewPprofService(cfgnew.ApplicationConfiguration.Pprof, log)
|
||||||
|
err = pprof.Start()
|
||||||
|
if err != nil {
|
||||||
|
shutdownErr = fmt.Errorf("failed to start Pprof service: %w", err)
|
||||||
|
cancel() // Fatal error, like for RPC server.
|
||||||
|
}
|
||||||
|
prometheus.ShutDown()
|
||||||
|
prometheus = metrics.NewPrometheusService(cfgnew.ApplicationConfiguration.Prometheus, log)
|
||||||
|
err = prometheus.Start()
|
||||||
|
if err != nil {
|
||||||
|
shutdownErr = fmt.Errorf("failed to start Prometheus service: %w", err)
|
||||||
|
cancel() // Fatal error, like for RPC server.
|
||||||
|
}
|
||||||
|
case sigusr1:
|
||||||
|
if oracleSrv != nil {
|
||||||
|
serv.DelService(oracleSrv)
|
||||||
|
chain.SetOracle(nil)
|
||||||
|
rpcServer.SetOracleHandler(nil)
|
||||||
|
oracleSrv.Shutdown()
|
||||||
|
}
|
||||||
|
oracleSrv, err = mkOracle(cfgnew.ApplicationConfiguration.Oracle, cfgnew.ProtocolConfiguration.Magic, chain, serv, log)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("failed to create oracle service", zap.Error(err))
|
||||||
|
break // Keep going.
|
||||||
|
}
|
||||||
|
if oracleSrv != nil {
|
||||||
|
rpcServer.SetOracleHandler(oracleSrv)
|
||||||
|
if serv.IsInSync() {
|
||||||
|
oracleSrv.Start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if p2pNotary != nil {
|
||||||
|
serv.DelService(p2pNotary)
|
||||||
|
chain.SetNotary(nil)
|
||||||
|
p2pNotary.Shutdown()
|
||||||
|
}
|
||||||
|
p2pNotary, err = mkP2PNotary(cfgnew.ApplicationConfiguration.P2PNotary, chain, serv, log)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("failed to create notary service", zap.Error(err))
|
||||||
|
break // Keep going.
|
||||||
|
}
|
||||||
|
if p2pNotary != nil && serv.IsInSync() {
|
||||||
|
p2pNotary.Start()
|
||||||
|
}
|
||||||
|
serv.DelExtensibleService(sr, stateroot.Category)
|
||||||
|
srMod.SetUpdateValidatorsCallback(nil)
|
||||||
|
sr.Shutdown()
|
||||||
|
sr, err = stateroot.New(cfgnew.ApplicationConfiguration.StateRoot, srMod, log, chain, serv.BroadcastExtensible)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("failed to create state validation service", zap.Error(err))
|
||||||
|
break // The show must go on.
|
||||||
|
}
|
||||||
|
serv.AddExtensibleService(sr, stateroot.Category, sr.OnPayload)
|
||||||
|
if serv.IsInSync() {
|
||||||
|
sr.Start()
|
||||||
|
}
|
||||||
|
case sigusr2:
|
||||||
|
if dbftSrv != nil {
|
||||||
|
serv.DelConsensusService(dbftSrv)
|
||||||
|
dbftSrv.Shutdown()
|
||||||
|
}
|
||||||
|
dbftSrv, err = mkConsensus(cfgnew.ApplicationConfiguration.Consensus, chain, serv, log)
|
||||||
|
if err != nil {
|
||||||
|
log.Error("failed to create consensus service", zap.Error(err))
|
||||||
|
break // Whatever happens, I'll leave it all to chance.
|
||||||
|
}
|
||||||
|
if dbftSrv != nil && serv.IsInSync() {
|
||||||
|
dbftSrv.Start()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cfg = cfgnew
|
||||||
|
case <-grace.Done():
|
||||||
|
signal.Stop(sigCh)
|
||||||
|
serv.Shutdown()
|
||||||
|
break Main
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if shutdownErr != nil {
|
||||||
|
return cli.Exit(shutdownErr, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// initBlockChain initializes BlockChain with preselected DB.
|
||||||
|
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.Exit(fmt.Errorf("could not initialize storage: %w", err), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
chain, err := core.NewBlockchain(store, cfg.Blockchain(), log)
|
||||||
|
if err != nil {
|
||||||
|
errText := "could not initialize blockchain: %w"
|
||||||
|
errArgs := []any{err}
|
||||||
|
closeErr := store.Close()
|
||||||
|
if closeErr != nil {
|
||||||
|
errText += "; failed to close the DB: %w"
|
||||||
|
errArgs = append(errArgs, closeErr)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil, nil, cli.Exit(fmt.Errorf(errText, errArgs...), 1)
|
||||||
|
}
|
||||||
|
return chain, store, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logo returns NeoGo logo.
|
||||||
|
func Logo() string {
|
||||||
|
return `
|
||||||
|
_ ____________ __________
|
||||||
|
/ | / / ____/ __ \ / ____/ __ \
|
||||||
|
/ |/ / __/ / / / /_____/ / __/ / / /
|
||||||
|
/ /| / /___/ /_/ /_____/ /_/ / /_/ /
|
||||||
|
/_/ |_/_____/\____/ \____/\____/
|
||||||
|
`
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,408 @@
|
||||||
|
package server
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/binary"
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"go.uber.org/zap/zapcore"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/cli/options"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/config"
|
||||||
|
"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/v2"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// serverTestWD is the default working directory for server tests.
|
||||||
|
var serverTestWD string
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
var err error
|
||||||
|
serverTestWD, err = os.Getwd()
|
||||||
|
if err != nil {
|
||||||
|
panic("can't get current working directory")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGetConfigFromContext(t *testing.T) {
|
||||||
|
t.Run("config-path", func(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
|
||||||
|
set.String("config-path", "../../config", "")
|
||||||
|
set.Bool("testnet", true, "")
|
||||||
|
ctx := cli.NewContext(cli.NewApp(), set, nil)
|
||||||
|
cfg, err := options.GetConfigFromContext(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, netmode.TestNet, cfg.ProtocolConfiguration.Magic)
|
||||||
|
})
|
||||||
|
t.Run("config-file", func(t *testing.T) {
|
||||||
|
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
|
||||||
|
set.String("config-path", "../../config", "")
|
||||||
|
set.Bool("testnet", true, "")
|
||||||
|
set.String("config-file", "../../config/protocol.testnet.yml", "")
|
||||||
|
ctx := cli.NewContext(cli.NewApp(), set, nil)
|
||||||
|
cfg, err := options.GetConfigFromContext(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, netmode.TestNet, cfg.ProtocolConfiguration.Magic)
|
||||||
|
})
|
||||||
|
t.Run("relative-path windows", func(t *testing.T) {
|
||||||
|
if runtime.GOOS != "windows" {
|
||||||
|
t.Skip("skipping Windows specific test")
|
||||||
|
}
|
||||||
|
|
||||||
|
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
|
||||||
|
set.String("relative-path", "..\\..\\config", "")
|
||||||
|
set.Bool("testnet", true, "")
|
||||||
|
set.String("config-file", ".\\testdata\\protocol.testnet.windows.yml", "")
|
||||||
|
ctx := cli.NewContext(cli.NewApp(), set, nil)
|
||||||
|
cfg, err := options.GetConfigFromContext(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, filepath.Join("..", "..", "config", "chains", "testnet"), cfg.ApplicationConfiguration.DBConfiguration.LevelDBOptions.DataDirectoryPath)
|
||||||
|
require.Equal(t, "C:\\someFolder\\cn_wallet.json", cfg.ApplicationConfiguration.Consensus.UnlockWallet.Path)
|
||||||
|
require.Equal(t, "C:\\someFolder\\notary_wallet.json", cfg.ApplicationConfiguration.P2PNotary.UnlockWallet.Path)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("relative-path non-windows", func(t *testing.T) {
|
||||||
|
if runtime.GOOS == "windows" {
|
||||||
|
t.Skip("skipping non-Windows specific test")
|
||||||
|
}
|
||||||
|
|
||||||
|
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
|
||||||
|
set.String("relative-path", "../../config", "")
|
||||||
|
set.Bool("testnet", true, "")
|
||||||
|
set.String("config-file", "../../config/protocol.testnet.yml", "")
|
||||||
|
ctx := cli.NewContext(cli.NewApp(), set, nil)
|
||||||
|
cfg, err := options.GetConfigFromContext(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, filepath.Join("..", "..", "config", "chains", "testnet"), cfg.ApplicationConfiguration.DBConfiguration.LevelDBOptions.DataDirectoryPath)
|
||||||
|
require.Equal(t, "/cn_wallet.json", cfg.ApplicationConfiguration.Consensus.UnlockWallet.Path)
|
||||||
|
require.Equal(t, "/notary_wallet.json", cfg.ApplicationConfiguration.P2PNotary.UnlockWallet.Path)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestHandleLoggingParams(t *testing.T) {
|
||||||
|
d := t.TempDir()
|
||||||
|
testLog := filepath.Join(d, "file.log")
|
||||||
|
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
|
||||||
|
debug := set.Bool("debug", false, "")
|
||||||
|
ctx := cli.NewContext(cli.NewApp(), set, nil)
|
||||||
|
|
||||||
|
t.Run("logdir is a file", func(t *testing.T) {
|
||||||
|
logfile := filepath.Join(d, "logdir")
|
||||||
|
require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm))
|
||||||
|
cfg := config.ApplicationConfiguration{
|
||||||
|
Logger: config.Logger{
|
||||||
|
LogPath: filepath.Join(logfile, "file.log"),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, lvl, closer, err := options.HandleLoggingParams(ctx, cfg)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Nil(t, lvl)
|
||||||
|
require.Nil(t, closer)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("broken level", func(t *testing.T) {
|
||||||
|
cfg := config.ApplicationConfiguration{
|
||||||
|
Logger: config.Logger{
|
||||||
|
LogPath: testLog,
|
||||||
|
LogLevel: "qwerty",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
_, lvl, closer, err := options.HandleLoggingParams(ctx, cfg)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Nil(t, lvl)
|
||||||
|
require.Nil(t, closer)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("default", func(t *testing.T) {
|
||||||
|
cfg := config.ApplicationConfiguration{
|
||||||
|
Logger: config.Logger{
|
||||||
|
LogPath: testLog,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
logger, lvl, closer, err := options.HandleLoggingParams(ctx, cfg)
|
||||||
|
require.NotNil(t, lvl)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if closer != nil {
|
||||||
|
require.NoError(t, closer())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
require.Equal(t, zapcore.InfoLevel, lvl.Level())
|
||||||
|
require.True(t, logger.Core().Enabled(zapcore.InfoLevel))
|
||||||
|
require.False(t, logger.Core().Enabled(zapcore.DebugLevel))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("warn", func(t *testing.T) {
|
||||||
|
cfg := config.ApplicationConfiguration{
|
||||||
|
Logger: config.Logger{
|
||||||
|
LogPath: testLog,
|
||||||
|
LogLevel: "warn",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
logger, lvl, closer, err := options.HandleLoggingParams(ctx, cfg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if closer != nil {
|
||||||
|
require.NoError(t, closer())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
require.Equal(t, zapcore.WarnLevel, lvl.Level())
|
||||||
|
require.True(t, logger.Core().Enabled(zapcore.WarnLevel))
|
||||||
|
require.False(t, logger.Core().Enabled(zapcore.InfoLevel))
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("debug", func(t *testing.T) {
|
||||||
|
cfg := config.ApplicationConfiguration{
|
||||||
|
Logger: config.Logger{
|
||||||
|
LogPath: testLog,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
*debug = true
|
||||||
|
t.Cleanup(func() { *debug = false })
|
||||||
|
logger, lvl, closer, err := options.HandleLoggingParams(ctx, cfg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if closer != nil {
|
||||||
|
require.NoError(t, closer())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
require.Equal(t, zapcore.DebugLevel, lvl.Level())
|
||||||
|
require.True(t, logger.Core().Enabled(zapcore.InfoLevel))
|
||||||
|
require.True(t, logger.Core().Enabled(zapcore.DebugLevel))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitBCWithMetrics(t *testing.T) {
|
||||||
|
d := t.TempDir()
|
||||||
|
err := os.Chdir(d)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { require.NoError(t, os.Chdir(serverTestWD)) })
|
||||||
|
|
||||||
|
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
|
||||||
|
set.String("config-path", filepath.Join(serverTestWD, "..", "..", "config"), "")
|
||||||
|
set.Bool("testnet", true, "")
|
||||||
|
set.Bool("debug", true, "")
|
||||||
|
ctx := cli.NewContext(cli.NewApp(), set, nil)
|
||||||
|
cfg, err := options.GetConfigFromContext(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
logger, _, closer, err := options.HandleLoggingParams(ctx, cfg.ApplicationConfiguration)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
if closer != nil {
|
||||||
|
require.NoError(t, closer())
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("bad store", func(t *testing.T) {
|
||||||
|
_, _, _, err = InitBCWithMetrics(config.Config{}, logger)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
chain, prometheus, pprof, err := InitBCWithMetrics(cfg, logger)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() {
|
||||||
|
chain.Close()
|
||||||
|
prometheus.ShutDown()
|
||||||
|
pprof.ShutDown()
|
||||||
|
})
|
||||||
|
require.Equal(t, netmode.TestNet, chain.GetConfig().Magic)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDumpDB(t *testing.T) {
|
||||||
|
testDump := "file.acc"
|
||||||
|
|
||||||
|
t.Run("too low chain", func(t *testing.T) {
|
||||||
|
d := t.TempDir()
|
||||||
|
err := os.Chdir(d)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { require.NoError(t, os.Chdir(serverTestWD)) })
|
||||||
|
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
|
||||||
|
set.String("config-path", filepath.Join(serverTestWD, "..", "..", "config"), "")
|
||||||
|
set.Bool("privnet", true, "")
|
||||||
|
set.Bool("debug", true, "")
|
||||||
|
set.Int("start", 0, "")
|
||||||
|
set.Int("count", 5, "")
|
||||||
|
set.String("out", testDump, "")
|
||||||
|
ctx := cli.NewContext(cli.NewApp(), set, nil)
|
||||||
|
err = dumpDB(ctx)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("positive", func(t *testing.T) {
|
||||||
|
d := t.TempDir()
|
||||||
|
err := os.Chdir(d)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { require.NoError(t, os.Chdir(serverTestWD)) })
|
||||||
|
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
|
||||||
|
set.String("config-path", filepath.Join(serverTestWD, "..", "..", "config"), "")
|
||||||
|
set.Bool("privnet", true, "")
|
||||||
|
set.Bool("debug", true, "")
|
||||||
|
set.Int("start", 0, "")
|
||||||
|
set.Int("count", 1, "")
|
||||||
|
set.String("out", testDump, "")
|
||||||
|
ctx := cli.NewContext(cli.NewApp(), set, nil)
|
||||||
|
err = dumpDB(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRestoreDB(t *testing.T) {
|
||||||
|
d := t.TempDir()
|
||||||
|
testDump := "file1.acc"
|
||||||
|
saveDump := "file2.acc"
|
||||||
|
err := os.Chdir(d)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { require.NoError(t, os.Chdir(serverTestWD)) })
|
||||||
|
|
||||||
|
// dump first
|
||||||
|
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
|
||||||
|
goodCfg := filepath.Join(serverTestWD, "..", "..", "config")
|
||||||
|
cfgPath := set.String("config-path", goodCfg, "")
|
||||||
|
set.Bool("privnet", true, "")
|
||||||
|
set.Bool("debug", true, "")
|
||||||
|
set.Bool("non-incremental", true, "")
|
||||||
|
set.Int("start", 0, "")
|
||||||
|
set.Int("count", 1, "")
|
||||||
|
set.String("out", testDump, "")
|
||||||
|
ctx := cli.NewContext(cli.NewApp(), set, nil)
|
||||||
|
err = dumpDB(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
// and then restore
|
||||||
|
t.Run("invalid config", func(t *testing.T) {
|
||||||
|
*cfgPath = filepath.Join(serverTestWD, "..", "..", "config_invalid")
|
||||||
|
require.Error(t, restoreDB(ctx))
|
||||||
|
})
|
||||||
|
t.Run("invalid logger path", func(t *testing.T) {
|
||||||
|
badCfgDir := t.TempDir()
|
||||||
|
logfile := filepath.Join(badCfgDir, "logdir")
|
||||||
|
require.NoError(t, os.WriteFile(logfile, []byte{1, 2, 3}, os.ModePerm))
|
||||||
|
cfg, err := config.LoadFile(filepath.Join(goodCfg, "protocol.privnet.yml"))
|
||||||
|
require.NoError(t, err, "could not load config")
|
||||||
|
cfg.ApplicationConfiguration.LogPath = filepath.Join(logfile, "file.log")
|
||||||
|
out, err := yaml.Marshal(cfg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
badCfgPath := filepath.Join(badCfgDir, "protocol.privnet.yml")
|
||||||
|
require.NoError(t, os.WriteFile(badCfgPath, out, os.ModePerm))
|
||||||
|
|
||||||
|
*cfgPath = badCfgDir
|
||||||
|
require.Error(t, restoreDB(ctx))
|
||||||
|
|
||||||
|
*cfgPath = goodCfg
|
||||||
|
})
|
||||||
|
t.Run("invalid bc config", func(t *testing.T) {
|
||||||
|
badCfgDir := t.TempDir()
|
||||||
|
cfg, err := config.LoadFile(filepath.Join(goodCfg, "protocol.privnet.yml"))
|
||||||
|
require.NoError(t, err, "could not load config")
|
||||||
|
cfg.ApplicationConfiguration.DBConfiguration.Type = ""
|
||||||
|
out, err := yaml.Marshal(cfg)
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
badCfgPath := filepath.Join(badCfgDir, "protocol.privnet.yml")
|
||||||
|
require.NoError(t, os.WriteFile(badCfgPath, out, os.ModePerm))
|
||||||
|
|
||||||
|
*cfgPath = badCfgDir
|
||||||
|
require.Error(t, restoreDB(ctx))
|
||||||
|
|
||||||
|
*cfgPath = goodCfg
|
||||||
|
})
|
||||||
|
|
||||||
|
in := set.String("in", testDump, "")
|
||||||
|
incremental := set.Bool("incremental", false, "")
|
||||||
|
t.Run("invalid in", func(t *testing.T) {
|
||||||
|
*in = "unknown-file"
|
||||||
|
require.Error(t, restoreDB(ctx))
|
||||||
|
|
||||||
|
*in = testDump
|
||||||
|
})
|
||||||
|
t.Run("corrupted in: invalid block count", func(t *testing.T) {
|
||||||
|
inPath := filepath.Join(t.TempDir(), "file3.acc")
|
||||||
|
require.NoError(t, os.WriteFile(inPath, []byte{1, 2, 3}, // file is expected to start from uint32
|
||||||
|
os.ModePerm))
|
||||||
|
*in = inPath
|
||||||
|
require.Error(t, restoreDB(ctx))
|
||||||
|
|
||||||
|
*in = testDump
|
||||||
|
})
|
||||||
|
t.Run("corrupted in: corrupted block", func(t *testing.T) {
|
||||||
|
inPath := filepath.Join(t.TempDir(), "file3.acc")
|
||||||
|
b, err := os.ReadFile(testDump)
|
||||||
|
require.NoError(t, err)
|
||||||
|
b[5] = 0xff // file is expected to start from uint32 (4 bytes) followed by the first block, so corrupt the first block bytes
|
||||||
|
require.NoError(t, os.WriteFile(inPath, b, os.ModePerm))
|
||||||
|
*in = inPath
|
||||||
|
require.Error(t, restoreDB(ctx))
|
||||||
|
|
||||||
|
*in = testDump
|
||||||
|
})
|
||||||
|
t.Run("incremental dump", func(t *testing.T) {
|
||||||
|
inPath := filepath.Join(t.TempDir(), "file1_incremental.acc")
|
||||||
|
b, err := os.ReadFile(testDump)
|
||||||
|
require.NoError(t, err)
|
||||||
|
start := make([]byte, 4)
|
||||||
|
t.Run("good", func(t *testing.T) {
|
||||||
|
binary.LittleEndian.PutUint32(start, 1) // start from the first block
|
||||||
|
require.NoError(t, os.WriteFile(inPath, append(start, b...),
|
||||||
|
os.ModePerm))
|
||||||
|
*in = inPath
|
||||||
|
*incremental = true
|
||||||
|
|
||||||
|
require.NoError(t, restoreDB(ctx))
|
||||||
|
})
|
||||||
|
t.Run("dump is too high", func(t *testing.T) {
|
||||||
|
binary.LittleEndian.PutUint32(start, 2) // start from the second block
|
||||||
|
require.NoError(t, os.WriteFile(inPath, append(start, b...),
|
||||||
|
os.ModePerm))
|
||||||
|
*in = inPath
|
||||||
|
*incremental = true
|
||||||
|
|
||||||
|
require.Error(t, restoreDB(ctx))
|
||||||
|
})
|
||||||
|
|
||||||
|
*in = testDump
|
||||||
|
*incremental = false
|
||||||
|
})
|
||||||
|
|
||||||
|
set.String("dump", saveDump, "")
|
||||||
|
require.NoError(t, restoreDB(ctx))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestInitBlockChain(t *testing.T) {
|
||||||
|
t.Run("bad storage", func(t *testing.T) {
|
||||||
|
_, _, err := initBlockChain(config.Config{}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
|
||||||
|
t.Run("empty logger", func(t *testing.T) {
|
||||||
|
_, _, err := initBlockChain(config.Config{
|
||||||
|
ApplicationConfiguration: config.ApplicationConfiguration{
|
||||||
|
DBConfiguration: dbconfig.DBConfiguration{
|
||||||
|
Type: dbconfig.InMemoryDB,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}, nil)
|
||||||
|
require.Error(t, err)
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestResetDB(t *testing.T) {
|
||||||
|
d := t.TempDir()
|
||||||
|
err := os.Chdir(d)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { require.NoError(t, os.Chdir(serverTestWD)) })
|
||||||
|
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
|
||||||
|
set.String("config-path", filepath.Join(serverTestWD, "..", "..", "config"), "")
|
||||||
|
set.Bool("privnet", true, "")
|
||||||
|
set.Bool("debug", true, "")
|
||||||
|
set.Int("height", 0, "")
|
||||||
|
ctx := cli.NewContext(cli.NewApp(), set, nil)
|
||||||
|
err = resetDB(ctx)
|
||||||
|
require.NoError(t, err)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,11 @@
|
||||||
|
//go:build !windows
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const (
|
||||||
|
sighup = syscall.SIGHUP
|
||||||
|
sigusr1 = syscall.SIGUSR1
|
||||||
|
sigusr2 = syscall.SIGUSR2
|
||||||
|
)
|
||||||
|
|
@ -0,0 +1,12 @@
|
||||||
|
//go:build windows
|
||||||
|
|
||||||
|
package server
|
||||||
|
|
||||||
|
import "syscall"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// Doesn't really matter, Windows can't do it.
|
||||||
|
sighup = syscall.SIGHUP
|
||||||
|
sigusr1 = syscall.Signal(0xa)
|
||||||
|
sigusr2 = syscall.Signal(0xc)
|
||||||
|
)
|
||||||
File diff suppressed because it is too large
Load Diff
|
|
@ -0,0 +1,124 @@
|
||||||
|
package smartcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/cli/cmdargs"
|
||||||
|
"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/v2"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
var generatorFlags = []cli.Flag{
|
||||||
|
&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",
|
||||||
|
Aliases: []string{"m"},
|
||||||
|
Required: true,
|
||||||
|
Usage: "Read contract manifest (*.manifest.json) file",
|
||||||
|
Action: cmdargs.EnsureNotEmpty("manifest"),
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "out",
|
||||||
|
Aliases: []string{"o"},
|
||||||
|
Required: true,
|
||||||
|
Usage: "Output of the compiled wrapper",
|
||||||
|
Action: cmdargs.EnsureNotEmpty("out"),
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "hash",
|
||||||
|
Usage: "Smart-contract hash. If not passed, the wrapper will be designed for dynamic hash usage",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
var generateWrapperCmd = &cli.Command{
|
||||||
|
Name: "generate-wrapper",
|
||||||
|
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
|
||||||
|
invocation as an optimization of the wrapper contract code. If omitted, the
|
||||||
|
generated wrapper will be designed for dynamic hash usage, allowing
|
||||||
|
the hash to be specified at runtime.
|
||||||
|
`,
|
||||||
|
Action: contractGenerateWrapper,
|
||||||
|
Flags: generatorFlags,
|
||||||
|
}
|
||||||
|
|
||||||
|
var generateRPCWrapperCmd = &cli.Command{
|
||||||
|
Name: "generate-rpcwrapper",
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
|
||||||
|
func contractGenerateWrapper(ctx *cli.Context) error {
|
||||||
|
return contractGenerateSomething(ctx, binding.Generate)
|
||||||
|
}
|
||||||
|
|
||||||
|
func contractGenerateRPCWrapper(ctx *cli.Context) error {
|
||||||
|
return contractGenerateSomething(ctx, rpcbinding.Generate)
|
||||||
|
}
|
||||||
|
|
||||||
|
// contractGenerateSomething reads generator parameters and calls the given callback.
|
||||||
|
func contractGenerateSomething(ctx *cli.Context, cb func(binding.Config) error) error {
|
||||||
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
var (
|
||||||
|
h util.Uint160
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if hStr := ctx.String("hash"); len(hStr) != 0 {
|
||||||
|
h, err = util.Uint160DecodeStringLE(strings.TrimPrefix(hStr, "0x"))
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("invalid contract hash: %w", err), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
m, _, err := readManifest(ctx.String("manifest"), h)
|
||||||
|
if err != nil {
|
||||||
|
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.Exit(fmt.Errorf("can't read config file: %w", err), 1)
|
||||||
|
}
|
||||||
|
decoder := yaml.NewDecoder(bytes.NewReader(bs))
|
||||||
|
decoder.KnownFields(true)
|
||||||
|
|
||||||
|
err = decoder.Decode(&cfg)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("can't parse config file: %w", err), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
cfg.Manifest = m
|
||||||
|
cfg.Hash = h
|
||||||
|
|
||||||
|
f, err := os.Create(ctx.String("out"))
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("can't create output file: %w", err), 1)
|
||||||
|
}
|
||||||
|
defer f.Close()
|
||||||
|
|
||||||
|
cfg.Output = f
|
||||||
|
|
||||||
|
err = cb(cfg)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("error during generation: %w", err), 1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,701 @@
|
||||||
|
package smartcontract_test
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestGenerate(t *testing.T) {
|
||||||
|
m := manifest.NewManifest("MyContract")
|
||||||
|
m.ABI.Methods = append(m.ABI.Methods,
|
||||||
|
manifest.Method{
|
||||||
|
Name: manifest.MethodDeploy,
|
||||||
|
ReturnType: smartcontract.VoidType,
|
||||||
|
},
|
||||||
|
manifest.Method{
|
||||||
|
Name: "sum",
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
manifest.NewParameter("first", smartcontract.IntegerType),
|
||||||
|
manifest.NewParameter("second", smartcontract.IntegerType),
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.IntegerType,
|
||||||
|
},
|
||||||
|
manifest.Method{
|
||||||
|
Name: "sum", // overloaded method
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
manifest.NewParameter("first", smartcontract.IntegerType),
|
||||||
|
manifest.NewParameter("second", smartcontract.IntegerType),
|
||||||
|
manifest.NewParameter("third", smartcontract.IntegerType),
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.IntegerType,
|
||||||
|
},
|
||||||
|
manifest.Method{
|
||||||
|
Name: "sum3",
|
||||||
|
Parameters: []manifest.Parameter{},
|
||||||
|
ReturnType: smartcontract.IntegerType,
|
||||||
|
Safe: true,
|
||||||
|
},
|
||||||
|
manifest.Method{
|
||||||
|
Name: "zum",
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
manifest.NewParameter("type", smartcontract.IntegerType),
|
||||||
|
manifest.NewParameter("typev", smartcontract.IntegerType),
|
||||||
|
manifest.NewParameter("func", smartcontract.IntegerType),
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.IntegerType,
|
||||||
|
},
|
||||||
|
manifest.Method{
|
||||||
|
Name: "justExecute",
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
manifest.NewParameter("arr", smartcontract.ArrayType),
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.VoidType,
|
||||||
|
},
|
||||||
|
manifest.Method{
|
||||||
|
Name: "getPublicKey",
|
||||||
|
Parameters: nil,
|
||||||
|
ReturnType: smartcontract.PublicKeyType,
|
||||||
|
},
|
||||||
|
manifest.Method{
|
||||||
|
Name: "otherTypes",
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
manifest.NewParameter("ctr", smartcontract.Hash160Type),
|
||||||
|
manifest.NewParameter("tx", smartcontract.Hash256Type),
|
||||||
|
manifest.NewParameter("sig", smartcontract.SignatureType),
|
||||||
|
manifest.NewParameter("data", smartcontract.AnyType),
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.BoolType,
|
||||||
|
},
|
||||||
|
manifest.Method{
|
||||||
|
Name: "searchStorage",
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
manifest.NewParameter("ctx", smartcontract.InteropInterfaceType),
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.InteropInterfaceType,
|
||||||
|
},
|
||||||
|
manifest.Method{
|
||||||
|
Name: "getFromMap",
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
manifest.NewParameter("intMap", smartcontract.MapType),
|
||||||
|
manifest.NewParameter("indices", smartcontract.ArrayType),
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.ArrayType,
|
||||||
|
},
|
||||||
|
manifest.Method{
|
||||||
|
Name: "doSomething",
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
manifest.NewParameter("bytes", smartcontract.ByteArrayType),
|
||||||
|
manifest.NewParameter("str", smartcontract.StringType),
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.InteropInterfaceType,
|
||||||
|
},
|
||||||
|
manifest.Method{
|
||||||
|
Name: "getBlockWrapper",
|
||||||
|
Parameters: []manifest.Parameter{},
|
||||||
|
ReturnType: smartcontract.InteropInterfaceType,
|
||||||
|
},
|
||||||
|
manifest.Method{
|
||||||
|
Name: "myFunc",
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
manifest.NewParameter("in", smartcontract.MapType),
|
||||||
|
},
|
||||||
|
ReturnType: smartcontract.ArrayType,
|
||||||
|
})
|
||||||
|
|
||||||
|
manifestFile := filepath.Join(t.TempDir(), "manifest.json")
|
||||||
|
outFile := filepath.Join(t.TempDir(), "out.go")
|
||||||
|
|
||||||
|
rawManifest, err := json.Marshal(m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm))
|
||||||
|
|
||||||
|
h := util.Uint160{
|
||||||
|
0x04, 0x08, 0x15, 0x16, 0x23, 0x42, 0x43, 0x44, 0x00, 0x01,
|
||||||
|
0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF, 0x03, 0x04,
|
||||||
|
}
|
||||||
|
e := testcli.NewExecutor(t, false)
|
||||||
|
|
||||||
|
rawCfg := `package: wrapper
|
||||||
|
hash: ` + h.StringLE() + `
|
||||||
|
overrides:
|
||||||
|
searchStorage.ctx: storage.Context
|
||||||
|
searchStorage: iterator.Iterator
|
||||||
|
getFromMap.intMap: "map[string]int"
|
||||||
|
getFromMap.indices: "[]string"
|
||||||
|
getFromMap: "[]int"
|
||||||
|
getBlockWrapper: ledger.Block
|
||||||
|
myFunc.in: "map[int]github.com/heyitsme/mycontract.Input"
|
||||||
|
myFunc: "[]github.com/heyitsme/mycontract.Output"
|
||||||
|
callflags:
|
||||||
|
doSomething: ReadStates
|
||||||
|
`
|
||||||
|
cfgPath := filepath.Join(t.TempDir(), "binding.yml")
|
||||||
|
require.NoError(t, os.WriteFile(cfgPath, []byte(rawCfg), os.ModePerm))
|
||||||
|
|
||||||
|
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.
|
||||||
|
|
||||||
|
// Package wrapper contains wrappers for MyContract contract.
|
||||||
|
package wrapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/heyitsme/mycontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/neogointernal"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hash contains contract hash in big-endian form.
|
||||||
|
const Hash = "\x04\x08\x15\x16\x23\x42\x43\x44\x00\x01\xca\xfe\xba\xbe\xde\xad\xbe\xef\x03\x04"
|
||||||
|
|
||||||
|
// Sum invokes ` + "`sum`" + ` method of contract.
|
||||||
|
func Sum(first int, second int) int {
|
||||||
|
return neogointernal.CallWithToken(Hash, "sum", int(contract.All), first, second).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum2 invokes ` + "`sum`" + ` method of contract.
|
||||||
|
func Sum2(first int, second int, third int) int {
|
||||||
|
return neogointernal.CallWithToken(Hash, "sum", int(contract.All), first, second, third).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum3 invokes ` + "`sum3`" + ` method of contract.
|
||||||
|
func Sum3() int {
|
||||||
|
return neogointernal.CallWithToken(Hash, "sum3", int(contract.ReadOnly)).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zum invokes ` + "`zum`" + ` method of contract.
|
||||||
|
func Zum(typev int, typev_ int, funcv int) int {
|
||||||
|
return neogointernal.CallWithToken(Hash, "zum", int(contract.All), typev, typev_, funcv).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JustExecute invokes ` + "`justExecute`" + ` method of contract.
|
||||||
|
func JustExecute(arr []any) {
|
||||||
|
neogointernal.CallWithTokenNoRet(Hash, "justExecute", int(contract.All), arr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPublicKey invokes ` + "`getPublicKey`" + ` method of contract.
|
||||||
|
func GetPublicKey() interop.PublicKey {
|
||||||
|
return neogointernal.CallWithToken(Hash, "getPublicKey", int(contract.All)).(interop.PublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OtherTypes invokes ` + "`otherTypes`" + ` method of contract.
|
||||||
|
func OtherTypes(ctr interop.Hash160, tx interop.Hash256, sig interop.Signature, data any) bool {
|
||||||
|
return neogointernal.CallWithToken(Hash, "otherTypes", int(contract.All), ctr, tx, sig, data).(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchStorage invokes ` + "`searchStorage`" + ` method of contract.
|
||||||
|
func SearchStorage(ctx storage.Context) iterator.Iterator {
|
||||||
|
return neogointernal.CallWithToken(Hash, "searchStorage", int(contract.All), ctx).(iterator.Iterator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFromMap invokes ` + "`getFromMap`" + ` method of contract.
|
||||||
|
func GetFromMap(intMap map[string]int, indices []string) []int {
|
||||||
|
return neogointernal.CallWithToken(Hash, "getFromMap", int(contract.All), intMap, indices).([]int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoSomething invokes ` + "`doSomething`" + ` method of contract.
|
||||||
|
func DoSomething(bytes []byte, str string) any {
|
||||||
|
return neogointernal.CallWithToken(Hash, "doSomething", int(contract.ReadStates), bytes, str).(any)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockWrapper invokes ` + "`getBlockWrapper`" + ` method of contract.
|
||||||
|
func GetBlockWrapper() ledger.Block {
|
||||||
|
return neogointernal.CallWithToken(Hash, "getBlockWrapper", int(contract.All)).(ledger.Block)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MyFunc invokes ` + "`myFunc`" + ` method of contract.
|
||||||
|
func MyFunc(in map[int]mycontract.Input) []mycontract.Output {
|
||||||
|
return neogointernal.CallWithToken(Hash, "myFunc", int(contract.All), in).([]mycontract.Output)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
|
||||||
|
data, err := os.ReadFile(outFile)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, string(data))
|
||||||
|
|
||||||
|
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.
|
||||||
|
package wrapper
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/heyitsme/mycontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Contract represents the MyContract smart contract.
|
||||||
|
type Contract struct {
|
||||||
|
Hash interop.Hash160
|
||||||
|
}
|
||||||
|
|
||||||
|
// NewContract returns a new Contract instance with the specified hash.
|
||||||
|
func NewContract(hash interop.Hash160) Contract {
|
||||||
|
return Contract{Hash: hash}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum invokes ` + "`sum`" + ` method of contract.
|
||||||
|
func (c Contract) Sum(first int, second int) int {
|
||||||
|
return contract.Call(c.Hash, "sum", contract.All, first, second).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum2 invokes ` + "`sum`" + ` method of contract.
|
||||||
|
func (c Contract) Sum2(first int, second int, third int) int {
|
||||||
|
return contract.Call(c.Hash, "sum", contract.All, first, second, third).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sum3 invokes ` + "`sum3`" + ` method of contract.
|
||||||
|
func (c Contract) Sum3() int {
|
||||||
|
return contract.Call(c.Hash, "sum3", contract.ReadOnly).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Zum invokes ` + "`zum`" + ` method of contract.
|
||||||
|
func (c Contract) Zum(typev int, typev_ int, funcv int) int {
|
||||||
|
return contract.Call(c.Hash, "zum", contract.All, typev, typev_, funcv).(int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// JustExecute invokes ` + "`justExecute`" + ` method of contract.
|
||||||
|
func (c Contract) JustExecute(arr []any) {
|
||||||
|
contract.Call(c.Hash, "justExecute", contract.All, arr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPublicKey invokes ` + "`getPublicKey`" + ` method of contract.
|
||||||
|
func (c Contract) GetPublicKey() interop.PublicKey {
|
||||||
|
return contract.Call(c.Hash, "getPublicKey", contract.All).(interop.PublicKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OtherTypes invokes ` + "`otherTypes`" + ` method of contract.
|
||||||
|
func (c Contract) OtherTypes(ctr interop.Hash160, tx interop.Hash256, sig interop.Signature, data any) bool {
|
||||||
|
return contract.Call(c.Hash, "otherTypes", contract.All, ctr, tx, sig, data).(bool)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SearchStorage invokes ` + "`searchStorage`" + ` method of contract.
|
||||||
|
func (c Contract) SearchStorage(ctx storage.Context) iterator.Iterator {
|
||||||
|
return contract.Call(c.Hash, "searchStorage", contract.All, ctx).(iterator.Iterator)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetFromMap invokes ` + "`getFromMap`" + ` method of contract.
|
||||||
|
func (c Contract) GetFromMap(intMap map[string]int, indices []string) []int {
|
||||||
|
return contract.Call(c.Hash, "getFromMap", contract.All, intMap, indices).([]int)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DoSomething invokes ` + "`doSomething`" + ` method of contract.
|
||||||
|
func (c Contract) DoSomething(bytes []byte, str string) any {
|
||||||
|
return contract.Call(c.Hash, "doSomething", contract.ReadStates, bytes, str).(any)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetBlockWrapper invokes ` + "`getBlockWrapper`" + ` method of contract.
|
||||||
|
func (c Contract) GetBlockWrapper() ledger.Block {
|
||||||
|
return contract.Call(c.Hash, "getBlockWrapper", contract.All).(ledger.Block)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MyFunc invokes ` + "`myFunc`" + ` method of contract.
|
||||||
|
func (c Contract) MyFunc(in map[int]mycontract.Input) []mycontract.Output {
|
||||||
|
return contract.Call(c.Hash, "myFunc", contract.All, in).([]mycontract.Output)
|
||||||
|
}
|
||||||
|
`
|
||||||
|
data, err = os.ReadFile(outFile)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expectedWithDynamicHash, string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateValidPackageName(t *testing.T) {
|
||||||
|
m := manifest.NewManifest("My space\tcontract")
|
||||||
|
m.ABI.Methods = append(m.ABI.Methods,
|
||||||
|
manifest.Method{
|
||||||
|
Name: "get",
|
||||||
|
Parameters: []manifest.Parameter{},
|
||||||
|
ReturnType: smartcontract.IntegerType,
|
||||||
|
Safe: true,
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
manifestFile := filepath.Join(t.TempDir(), "manifest.json")
|
||||||
|
outFile := filepath.Join(t.TempDir(), "out.go")
|
||||||
|
|
||||||
|
rawManifest, err := json.Marshal(m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm))
|
||||||
|
|
||||||
|
h := util.Uint160{
|
||||||
|
0x04, 0x08, 0x15, 0x16, 0x23, 0x42, 0x43, 0x44, 0x00, 0x01,
|
||||||
|
0xCA, 0xFE, 0xBA, 0xBE, 0xDE, 0xAD, 0xBE, 0xEF, 0x03, 0x04,
|
||||||
|
}
|
||||||
|
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)
|
||||||
|
require.Equal(t, `// Code generated by neo-go contract generate-wrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||||
|
|
||||||
|
// Package myspacecontract contains wrappers for My space contract contract.
|
||||||
|
package myspacecontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/neogointernal"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hash contains contract hash in big-endian form.
|
||||||
|
const Hash = "\x04\x08\x15\x16\x23\x42\x43\x44\x00\x01\xca\xfe\xba\xbe\xde\xad\xbe\xef\x03\x04"
|
||||||
|
|
||||||
|
// Get invokes `+"`get`"+` method of contract.
|
||||||
|
func Get() int {
|
||||||
|
return neogointernal.CallWithToken(Hash, "get", int(contract.ReadOnly)).(int)
|
||||||
|
}
|
||||||
|
`, string(data))
|
||||||
|
e.Run(t, []string{"", "contract", "generate-rpcwrapper",
|
||||||
|
"--manifest", manifestFile,
|
||||||
|
"--out", outFile,
|
||||||
|
"--hash", "0x" + h.StringLE(),
|
||||||
|
}...)
|
||||||
|
|
||||||
|
data, err = os.ReadFile(outFile)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, `// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||||
|
|
||||||
|
// Package myspacecontract contains RPC wrappers for My space contract contract.
|
||||||
|
package myspacecontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"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/util"
|
||||||
|
"math/big"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hash contains contract hash.
|
||||||
|
var Hash = util.Uint160{0x4, 0x8, 0x15, 0x16, 0x23, 0x42, 0x43, 0x44, 0x0, 0x1, 0xca, 0xfe, 0xba, 0xbe, 0xde, 0xad, 0xbe, 0xef, 0x3, 0x4}
|
||||||
|
|
||||||
|
// Invoker is used by ContractReader to call various safe methods.
|
||||||
|
type Invoker interface {
|
||||||
|
Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContractReader implements safe contract methods.
|
||||||
|
type ContractReader struct {
|
||||||
|
invoker Invoker
|
||||||
|
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{invoker, hash}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get invokes `+"`get`"+` method of contract.
|
||||||
|
func (c *ContractReader) Get() (*big.Int, error) {
|
||||||
|
return unwrap.BigInt(c.invoker.Call(c.hash, "get"))
|
||||||
|
}
|
||||||
|
`, string(data))
|
||||||
|
}
|
||||||
|
|
||||||
|
// rewriteExpectedOutputs denotes whether expected output files should be rewritten
|
||||||
|
// for TestGenerateRPCBindings and TestAssistedRPCBindings.
|
||||||
|
const rewriteExpectedOutputs = false
|
||||||
|
|
||||||
|
func TestGenerateRPCBindings(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
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")
|
||||||
|
e.Run(t, []string{"", "contract", "generate-rpcwrapper",
|
||||||
|
"--manifest", manifest,
|
||||||
|
"--out", outFile,
|
||||||
|
"--hash", hash,
|
||||||
|
}...)
|
||||||
|
|
||||||
|
data, err := os.ReadFile(outFile)
|
||||||
|
require.NoError(t, err)
|
||||||
|
data = bytes.ReplaceAll(data, []byte("\r"), []byte{}) // Windows.
|
||||||
|
if rewriteExpectedOutputs {
|
||||||
|
require.NoError(t, os.WriteFile(good, data, os.ModePerm))
|
||||||
|
} else {
|
||||||
|
expected, err := os.ReadFile(good)
|
||||||
|
require.NoError(t, err)
|
||||||
|
expected = bytes.ReplaceAll(expected, []byte("\r"), []byte{}) // Windows.
|
||||||
|
require.Equal(t, string(expected), string(data))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
checkBinding(filepath.Join("testdata", "nex", "nex.manifest.json"),
|
||||||
|
"0xa2a67f09e8cf22c6bfd5cea24adc0f4bf0a11aa8",
|
||||||
|
filepath.Join("testdata", "nex", "nex.go"))
|
||||||
|
checkBinding(filepath.Join("testdata", "nameservice", "nns.manifest.json"),
|
||||||
|
"0x50ac1c37690cc2cfc594472833cf57505d5f46de",
|
||||||
|
filepath.Join("testdata", "nameservice", "nns.go"))
|
||||||
|
checkBinding(filepath.Join("testdata", "gas", "gas.manifest.json"),
|
||||||
|
"0xd2a4cff31913016155e38e474a2c06d08be276cf",
|
||||||
|
filepath.Join("testdata", "gas", "gas.go"))
|
||||||
|
checkBinding(filepath.Join("testdata", "verifyrpc", "verify.manifest.json"),
|
||||||
|
"0x00112233445566778899aabbccddeeff00112233",
|
||||||
|
filepath.Join("rpcbindings", "verify", "rpcbindings_test.go"))
|
||||||
|
checkBinding(filepath.Join("testdata", "nonepiter", "iter.manifest.json"),
|
||||||
|
"0x00112233445566778899aabbccddeeff00112233",
|
||||||
|
filepath.Join("testdata", "nonepiter", "iter.go"))
|
||||||
|
|
||||||
|
require.False(t, rewriteExpectedOutputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAssistedRPCBindings(t *testing.T) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
e := testcli.NewExecutor(t, false)
|
||||||
|
|
||||||
|
var checkBinding = func(source, configFile, expectedFile string, hasDefinedHash, guessEventTypes bool, suffix ...string) {
|
||||||
|
testName := source
|
||||||
|
if len(suffix) != 0 {
|
||||||
|
testName += suffix[0]
|
||||||
|
}
|
||||||
|
testName += fmt.Sprintf(", predefined hash: %t", hasDefinedHash)
|
||||||
|
t.Run(testName, func(t *testing.T) {
|
||||||
|
outFile := filepath.Join(tmpDir, "out.go")
|
||||||
|
if configFile == "" {
|
||||||
|
if len(suffix) != 0 {
|
||||||
|
configFile = filepath.Join(source, "config_"+suffix[0]+".yml")
|
||||||
|
} else {
|
||||||
|
configFile = filepath.Join(source, "config.yml")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if expectedFile == "" {
|
||||||
|
// Expected bindings are moved out of testdata to be able to perform static code analysis on them.
|
||||||
|
expectedRoot := strings.Replace(source, "testdata", ".", 1)
|
||||||
|
expectedFile = filepath.Join(expectedRoot, "rpcbindings", "rpcbindings_test.go")
|
||||||
|
if len(suffix) != 0 {
|
||||||
|
expectedFile = filepath.Join(expectedRoot, "rpcbindings", suffix[0], "rpcbindings_test.go")
|
||||||
|
} else if !hasDefinedHash {
|
||||||
|
expectedFile = filepath.Join(expectedRoot, "rpcbindings", "dynamic_hash", "rpcbindings_test.go")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
manifestF := filepath.Join(tmpDir, "manifest.json")
|
||||||
|
bindingF := filepath.Join(tmpDir, "binding.yml")
|
||||||
|
nefF := filepath.Join(tmpDir, "out.nef")
|
||||||
|
cmd := []string{"", "contract", "compile",
|
||||||
|
"--in", source,
|
||||||
|
"--config", configFile,
|
||||||
|
"--manifest", manifestF,
|
||||||
|
"--bindings", bindingF,
|
||||||
|
"--out", nefF,
|
||||||
|
}
|
||||||
|
if guessEventTypes {
|
||||||
|
cmd = append(cmd, "--guess-eventtypes")
|
||||||
|
}
|
||||||
|
e.Run(t, cmd...)
|
||||||
|
|
||||||
|
cmds := []string{"", "contract", "generate-rpcwrapper",
|
||||||
|
"--config", bindingF,
|
||||||
|
"--manifest", manifestF,
|
||||||
|
"--out", outFile,
|
||||||
|
}
|
||||||
|
if hasDefinedHash {
|
||||||
|
cmds = append(cmds, "--hash", "0x00112233445566778899aabbccddeeff00112233")
|
||||||
|
}
|
||||||
|
e.Run(t, cmds...)
|
||||||
|
|
||||||
|
data, err := os.ReadFile(outFile)
|
||||||
|
require.NoError(t, err)
|
||||||
|
data = bytes.ReplaceAll(data, []byte("\r"), []byte{}) // Windows.
|
||||||
|
if rewriteExpectedOutputs {
|
||||||
|
require.NoError(t, os.WriteFile(expectedFile, data, os.ModePerm))
|
||||||
|
} else {
|
||||||
|
expected, err := os.ReadFile(expectedFile)
|
||||||
|
require.NoError(t, err)
|
||||||
|
expected = bytes.ReplaceAll(expected, []byte("\r"), []byte{}) // Windows.
|
||||||
|
require.Equal(t, string(expected), string(data))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
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", "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("..", "..", "examples", "nft-d"), filepath.Join("..", "..", "examples", "nft-d", "nft.yml"), filepath.Join("rpcbindings", "nft-d", "dynamic_hash", "rpcbindings_test.go"), false, false)
|
||||||
|
checkBinding(filepath.Join("..", "..", "examples", "nft-d"), filepath.Join("..", "..", "examples", "nft-d", "nft.yml"), filepath.Join("rpcbindings", "nft-d", "rpcbindings_test.go"), true, true)
|
||||||
|
checkBinding(filepath.Join("..", "..", "examples", "nft-nd"), filepath.Join("..", "..", "examples", "nft-nd", "nft.yml"), filepath.Join("rpcbindings", "nft-nd", "dynamic_hash", "rpcbindings_test.go"), false, false)
|
||||||
|
checkBinding(filepath.Join("..", "..", "examples", "nft-nd"), filepath.Join("..", "..", "examples", "nft-nd", "nft.yml"), filepath.Join("rpcbindings", "nft-nd", "rpcbindings_test.go"), true, true)
|
||||||
|
|
||||||
|
require.False(t, rewriteExpectedOutputs)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerate_Errors(t *testing.T) {
|
||||||
|
e := testcli.NewExecutor(t, false)
|
||||||
|
args := []string{"neo-go", "contract", "generate-wrapper"}
|
||||||
|
|
||||||
|
t.Run("invalid hash", func(t *testing.T) {
|
||||||
|
e.RunWithErrorCheckExit(t, "invalid contract hash", append(args, "--hash", "xxx", "--manifest", "yyy", "--out", "zzz")...)
|
||||||
|
})
|
||||||
|
t.Run("missing manifest argument", func(t *testing.T) {
|
||||||
|
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) {
|
||||||
|
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))
|
||||||
|
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")
|
||||||
|
m := manifest.NewManifest("MyContract") // no methods
|
||||||
|
rawManifest, err := json.Marshal(m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm))
|
||||||
|
e.RunWithErrorCheckExit(t, "ABI: no methods", append(args, "--manifest", manifestFile, "--hash", util.Uint160{}.StringLE(), "--out", "zzz")...)
|
||||||
|
})
|
||||||
|
|
||||||
|
manifestFile := filepath.Join(t.TempDir(), "manifest.json")
|
||||||
|
m := manifest.NewManifest("MyContract")
|
||||||
|
m.ABI.Methods = append(m.ABI.Methods, manifest.Method{
|
||||||
|
Name: "method0",
|
||||||
|
Offset: 0,
|
||||||
|
ReturnType: smartcontract.AnyType,
|
||||||
|
Safe: true,
|
||||||
|
})
|
||||||
|
rawManifest, err := json.Marshal(m)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.NoError(t, os.WriteFile(manifestFile, rawManifest, os.ModePerm))
|
||||||
|
|
||||||
|
t.Run("missing config", func(t *testing.T) {
|
||||||
|
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
|
||||||
|
callflags:
|
||||||
|
someFunc: ReadSometimes
|
||||||
|
`
|
||||||
|
cfgPath := filepath.Join(t.TempDir(), "binding.yml")
|
||||||
|
require.NoError(t, os.WriteFile(cfgPath, []byte(rawCfg), os.ModePerm))
|
||||||
|
|
||||||
|
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) {
|
||||||
|
e := testcli.NewExecutor(t, false)
|
||||||
|
check := func(t *testing.T, source string, expectedErrText string) {
|
||||||
|
tmpDir := t.TempDir()
|
||||||
|
configFile := filepath.Join(source, "invalid.yml")
|
||||||
|
manifestF := filepath.Join(tmpDir, "invalid.manifest.json")
|
||||||
|
bindingF := filepath.Join(tmpDir, "invalid.binding.yml")
|
||||||
|
nefF := filepath.Join(tmpDir, "invalid.out.nef")
|
||||||
|
cmd := []string{"", "contract", "compile",
|
||||||
|
"--in", source,
|
||||||
|
"--config", configFile,
|
||||||
|
"--manifest", manifestF,
|
||||||
|
"--bindings", bindingF,
|
||||||
|
"--out", nefF,
|
||||||
|
"--guess-eventtypes",
|
||||||
|
}
|
||||||
|
e.RunWithErrorCheckExit(t, expectedErrText, cmd...)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("not declared in manifest", func(t *testing.T) {
|
||||||
|
check(t, filepath.Join("testdata", "rpcbindings", "invalid1"), "inconsistent usages of event `Non declared event`: not declared in the contract config")
|
||||||
|
})
|
||||||
|
t.Run("invalid number of params", func(t *testing.T) {
|
||||||
|
check(t, filepath.Join("testdata", "rpcbindings", "invalid2"), "inconsistent usages of event `SomeEvent` against config: number of params mismatch: 2 vs 1")
|
||||||
|
})
|
||||||
|
/*
|
||||||
|
// TODO: this on is a controversial one. If event information is provided in the config file, then conversion code
|
||||||
|
// will be emitted by the compiler according to the parameter type provided via config. Thus, we can be sure that
|
||||||
|
// either event parameter has the type specified in the config file or the execution of the contract will fail.
|
||||||
|
// Thus, this testcase is always failing (no compilation error occurs).
|
||||||
|
// Question: do we want to compare `RealType` of the emitted parameter with the one expected in the manifest?
|
||||||
|
t.Run("SC parameter type mismatch", func(t *testing.T) {
|
||||||
|
check(t, filepath.Join("testdata", "rpcbindings", "invalid3"), "inconsistent usages of event `SomeEvent` against config: number of params mismatch: 2 vs 1")
|
||||||
|
})
|
||||||
|
*/
|
||||||
|
t.Run("extended types mismatch", func(t *testing.T) {
|
||||||
|
check(t, filepath.Join("testdata", "rpcbindings", "invalid4"), "inconsistent usages of event `SomeEvent`: extended type of param #0 mismatch")
|
||||||
|
})
|
||||||
|
t.Run("named types redeclare", func(t *testing.T) {
|
||||||
|
check(t, filepath.Join("testdata", "rpcbindings", "invalid5"), "configured declared named type intersects with the contract's one: `invalid5.NamedStruct`")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestGenerateRPCBindings_Errors(t *testing.T) {
|
||||||
|
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()
|
||||||
|
source := filepath.Join("testdata", "rpcbindings", packageName)
|
||||||
|
configFile := filepath.Join(source, "invalid.yml")
|
||||||
|
out := filepath.Join(tmpDir, "rpcbindings.out")
|
||||||
|
manifestF := filepath.Join(tmpDir, "manifest.json")
|
||||||
|
bindingF := filepath.Join(tmpDir, "binding.yml")
|
||||||
|
nefF := filepath.Join(tmpDir, "out.nef")
|
||||||
|
cmd := []string{"", "contract", "compile",
|
||||||
|
"--in", source,
|
||||||
|
"--config", configFile,
|
||||||
|
"--manifest", manifestF,
|
||||||
|
"--bindings", bindingF,
|
||||||
|
"--out", nefF,
|
||||||
|
}
|
||||||
|
if autogen {
|
||||||
|
cmd = append(cmd, "--guess-eventtypes")
|
||||||
|
}
|
||||||
|
e.Run(t, cmd...)
|
||||||
|
|
||||||
|
cmds := []string{"", "contract", "generate-rpcwrapper",
|
||||||
|
"--config", bindingF,
|
||||||
|
"--manifest", manifestF,
|
||||||
|
"--out", out,
|
||||||
|
}
|
||||||
|
e.RunWithErrorCheckExit(t, expectedError, cmds...)
|
||||||
|
}
|
||||||
|
|
||||||
|
t.Run("event", func(t *testing.T) {
|
||||||
|
check(t, "invalid6", false, "error during generation: named type `SomeStruct` has two fields with identical resulting binding name `Field`")
|
||||||
|
})
|
||||||
|
t.Run("autogen event", func(t *testing.T) {
|
||||||
|
check(t, "invalid7", true, "error during generation: named type `invalid7.SomeStruct` has two fields with identical resulting binding name `Field`")
|
||||||
|
})
|
||||||
|
t.Run("struct", func(t *testing.T) {
|
||||||
|
check(t, "invalid8", false, "error during generation: named type `invalid8.SomeStruct` has two fields with identical resulting binding name `Field`")
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,104 @@
|
||||||
|
package smartcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/json"
|
||||||
|
"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/pkg/core/state"
|
||||||
|
"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/v2"
|
||||||
|
)
|
||||||
|
|
||||||
|
func manifestAddGroup(ctx *cli.Context) error {
|
||||||
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sender := ctx.Generic("sender").(*flags.Address)
|
||||||
|
nf, _, err := readNEFFile(ctx.String("nef"))
|
||||||
|
if err != nil {
|
||||||
|
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.Exit(fmt.Errorf("can't read contract manifest: %w", err), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
h := state.CreateContractHash(sender.Uint160(), nf.Checksum, m.Name)
|
||||||
|
|
||||||
|
gAcc, w, err := options.GetAccFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("can't get account to sign group with: %w", err), 1)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
var found bool
|
||||||
|
|
||||||
|
sig := gAcc.PrivateKey().Sign(h.BytesBE())
|
||||||
|
pub := gAcc.PublicKey()
|
||||||
|
for i := range m.Groups {
|
||||||
|
if m.Groups[i].PublicKey.Equal(pub) {
|
||||||
|
m.Groups[i].Signature = sig
|
||||||
|
found = true
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !found {
|
||||||
|
m.Groups = append(m.Groups, manifest.Group{
|
||||||
|
PublicKey: pub,
|
||||||
|
Signature: sig,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
rawM, err := json.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("can't marshal manifest: %w", err), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = os.WriteFile(mPath, rawM, os.ModePerm)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("can't write manifest file: %w", err), 1)
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func readNEFFile(filename string) (*nef.File, []byte, error) {
|
||||||
|
f, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
nefFile, err := nef.FileFromBytes(f)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("can't parse NEF file: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return &nefFile, f, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// readManifest unmarshalls manifest got from the provided filename and checks
|
||||||
|
// 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) {
|
||||||
|
manifestBytes, err := os.ReadFile(filename)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
m := new(manifest.Manifest)
|
||||||
|
err = json.Unmarshal(manifestBytes, m)
|
||||||
|
if err != nil {
|
||||||
|
return nil, nil, err
|
||||||
|
}
|
||||||
|
if err := m.IsValid(hash, true); err != nil {
|
||||||
|
return nil, nil, fmt.Errorf("manifest is invalid: %w", err)
|
||||||
|
}
|
||||||
|
return m, manifestBytes, nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,130 @@
|
||||||
|
package smartcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
type permission manifest.Permission
|
||||||
|
|
||||||
|
const (
|
||||||
|
permHashKey = "hash"
|
||||||
|
permGroupKey = "group"
|
||||||
|
permMethodKey = "methods"
|
||||||
|
)
|
||||||
|
|
||||||
|
func (p permission) MarshalYAML() (any, error) {
|
||||||
|
m := yaml.Node{Kind: yaml.MappingNode}
|
||||||
|
switch p.Contract.Type {
|
||||||
|
case manifest.PermissionWildcard:
|
||||||
|
case manifest.PermissionHash:
|
||||||
|
m.Content = append(m.Content,
|
||||||
|
&yaml.Node{Kind: yaml.ScalarNode, Value: permHashKey},
|
||||||
|
&yaml.Node{Kind: yaml.ScalarNode, Value: p.Contract.Value.(util.Uint160).StringLE()})
|
||||||
|
case manifest.PermissionGroup:
|
||||||
|
m.Content = append(m.Content,
|
||||||
|
&yaml.Node{Kind: yaml.ScalarNode, Value: permGroupKey},
|
||||||
|
&yaml.Node{Kind: yaml.ScalarNode, Value: p.Contract.Value.(*keys.PublicKey).StringCompressed()})
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("invalid permission type: %d", p.Contract.Type)
|
||||||
|
}
|
||||||
|
|
||||||
|
var val any = "*"
|
||||||
|
if !p.Methods.IsWildcard() {
|
||||||
|
val = p.Methods.Value
|
||||||
|
}
|
||||||
|
|
||||||
|
n := &yaml.Node{Kind: yaml.ScalarNode}
|
||||||
|
err := n.Encode(val)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
m.Content = append(m.Content,
|
||||||
|
&yaml.Node{Kind: yaml.ScalarNode, Value: permMethodKey},
|
||||||
|
n)
|
||||||
|
|
||||||
|
return m, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *permission) UnmarshalYAML(node *yaml.Node) error {
|
||||||
|
var m map[string]any
|
||||||
|
if err := node.Decode(&m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if err := p.fillType(m); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
return p.fillMethods(m)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *permission) fillType(m map[string]any) error {
|
||||||
|
vh, ok1 := m[permHashKey]
|
||||||
|
vg, ok2 := m[permGroupKey]
|
||||||
|
switch {
|
||||||
|
case ok1 && ok2:
|
||||||
|
return errors.New("permission must have either 'hash' or 'group' field")
|
||||||
|
case ok1:
|
||||||
|
s, ok := vh.(string)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid 'hash' type")
|
||||||
|
}
|
||||||
|
|
||||||
|
u, err := util.Uint160DecodeStringLE(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Contract.Type = manifest.PermissionHash
|
||||||
|
p.Contract.Value = u
|
||||||
|
case ok2:
|
||||||
|
s, ok := vg.(string)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid 'hash' type")
|
||||||
|
}
|
||||||
|
|
||||||
|
pub, err := keys.NewPublicKeyFromString(s)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
p.Contract.Type = manifest.PermissionGroup
|
||||||
|
p.Contract.Value = pub
|
||||||
|
default:
|
||||||
|
p.Contract.Type = manifest.PermissionWildcard
|
||||||
|
}
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *permission) fillMethods(m map[string]any) error {
|
||||||
|
methods, ok := m[permMethodKey]
|
||||||
|
if !ok {
|
||||||
|
return errors.New("'methods' field is missing from permission")
|
||||||
|
}
|
||||||
|
|
||||||
|
switch mt := methods.(type) {
|
||||||
|
case string:
|
||||||
|
if mt == "*" {
|
||||||
|
p.Methods.Value = nil
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
case []any:
|
||||||
|
ms := make([]string, len(mt))
|
||||||
|
for i := range mt {
|
||||||
|
ms[i], ok = mt[i].(string)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("invalid permission method name")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
p.Methods.Value = ms
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
}
|
||||||
|
return errors.New("'methods' field is invalid")
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,60 @@
|
||||||
|
// 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/rpcclient/nep11"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep22"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep24"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep31"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NEP22Contract is an alias for nep22.Contract.
|
||||||
|
type NEP22Contract = nep22.Contract
|
||||||
|
|
||||||
|
// NEP31Contract is an alias for nep31.Contract.
|
||||||
|
type NEP31Contract = nep31.Contract
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
NEP22Contract
|
||||||
|
NEP31Contract
|
||||||
|
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, NEP22Contract(*nep22.NewContract(actor, hash)), NEP31Contract(*nep31.NewContract(actor, hash)), actor, hash}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
// 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/rpcclient/nep11"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep22"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep24"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep31"
|
||||||
|
"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}
|
||||||
|
|
||||||
|
// NEP22Contract is an alias for nep22.Contract.
|
||||||
|
type NEP22Contract = nep22.Contract
|
||||||
|
|
||||||
|
// NEP31Contract is an alias for nep31.Contract.
|
||||||
|
type NEP31Contract = nep31.Contract
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
NEP22Contract
|
||||||
|
NEP31Contract
|
||||||
|
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, NEP22Contract(*nep22.NewContract(actor, hash)), NEP31Contract(*nep31.NewContract(actor, hash)), actor, hash}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,234 @@
|
||||||
|
// 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/nep22"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep24"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep31"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// NEP22Contract is an alias for nep22.Contract.
|
||||||
|
type NEP22Contract = nep22.Contract
|
||||||
|
|
||||||
|
// NEP31Contract is an alias for nep31.Contract.
|
||||||
|
type NEP31Contract = nep31.Contract
|
||||||
|
|
||||||
|
// 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
|
||||||
|
NEP22Contract
|
||||||
|
NEP31Contract
|
||||||
|
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, NEP22Contract(*nep22.NewContract(actor, hash)), NEP31Contract(*nep31.NewContract(actor, hash)), actor, hash}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,239 @@
|
||||||
|
// 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/nep22"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep24"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep31"
|
||||||
|
"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}
|
||||||
|
|
||||||
|
// NEP22Contract is an alias for nep22.Contract.
|
||||||
|
type NEP22Contract = nep22.Contract
|
||||||
|
|
||||||
|
// NEP31Contract is an alias for nep31.Contract.
|
||||||
|
type NEP31Contract = nep31.Contract
|
||||||
|
|
||||||
|
// 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
|
||||||
|
NEP22Contract
|
||||||
|
NEP31Contract
|
||||||
|
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, NEP22Contract(*nep22.NewContract(actor, hash)), NEP31Contract(*nep31.NewContract(actor, hash)), actor, hash}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,746 @@
|
||||||
|
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||||
|
|
||||||
|
// Package structs contains RPC wrappers for Notifications contract.
|
||||||
|
package structs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"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"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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}
|
||||||
|
|
||||||
|
// CrazyStruct is a contract-specific crazyStruct type used by its methods.
|
||||||
|
type CrazyStruct struct {
|
||||||
|
I *big.Int
|
||||||
|
B bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// SimpleStruct is a contract-specific simpleStruct type used by its methods.
|
||||||
|
type SimpleStruct struct {
|
||||||
|
I *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComplicatedNameEvent represents "! complicated name %$#" event emitted by the contract.
|
||||||
|
type ComplicatedNameEvent struct {
|
||||||
|
ComplicatedParam string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeMapEvent represents "SomeMap" event emitted by the contract.
|
||||||
|
type SomeMapEvent struct {
|
||||||
|
M map[*big.Int]map[string][]util.Uint160
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeStructEvent represents "SomeStruct" event emitted by the contract.
|
||||||
|
type SomeStructEvent struct {
|
||||||
|
S *CrazyStruct
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeArrayEvent represents "SomeArray" event emitted by the contract.
|
||||||
|
type SomeArrayEvent struct {
|
||||||
|
A [][]*big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeUnexportedFieldEvent represents "SomeUnexportedField" event emitted by the contract.
|
||||||
|
type SomeUnexportedFieldEvent struct {
|
||||||
|
S *SimpleStruct
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array creates a transaction invoking `array` 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) Array() (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "array")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayTransaction creates a transaction invoking `array` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) ArrayTransaction() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "array")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayUnsigned creates a transaction invoking `array` 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) ArrayUnsigned() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "array", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CrazyMap creates a transaction invoking `crazyMap` 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) CrazyMap() (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "crazyMap")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CrazyMapTransaction creates a transaction invoking `crazyMap` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) CrazyMapTransaction() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "crazyMap")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CrazyMapUnsigned creates a transaction invoking `crazyMap` 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) CrazyMapUnsigned() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "crazyMap", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main creates a transaction invoking `main` 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) Main() (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "main")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MainTransaction creates a transaction invoking `main` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) MainTransaction() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "main")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MainUnsigned creates a transaction invoking `main` 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) MainUnsigned() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "main", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Struct creates a transaction invoking `struct` 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) Struct() (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructTransaction creates a transaction invoking `struct` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) StructTransaction() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructUnsigned creates a transaction invoking `struct` 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) StructUnsigned() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "struct", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnexportedField creates a transaction invoking `unexportedField` 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) UnexportedField() (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "unexportedField")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnexportedFieldTransaction creates a transaction invoking `unexportedField` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) UnexportedFieldTransaction() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "unexportedField")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnexportedFieldUnsigned creates a transaction invoking `unexportedField` 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) UnexportedFieldUnsigned() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "unexportedField", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
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.I, err = arr[index].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field I: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
index++
|
||||||
|
res.B, err = arr[index].TryBool()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field B: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
res.I, err = arr[index].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field I: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*ComplicatedNameEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "! complicated name %$#" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(ComplicatedNameEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize ComplicatedNameEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to ComplicatedNameEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *ComplicatedNameEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.ComplicatedParam, err = func(item stackitem.Item) (string, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
return "", errors.New("not a UTF-8 string")
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}(arr[index])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field ComplicatedParam: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeMapEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "SomeMap" name from the provided [result.ApplicationLog].
|
||||||
|
func SomeMapEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeMapEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*SomeMapEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "SomeMap" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(SomeMapEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize SomeMapEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to SomeMapEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *SomeMapEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.M, err = func(item stackitem.Item) (map[*big.Int]map[string][]util.Uint160, error) {
|
||||||
|
m, ok := item.Value().([]stackitem.MapElement)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s is not a map", item.Type().String())
|
||||||
|
}
|
||||||
|
res := make(map[*big.Int]map[string][]util.Uint160)
|
||||||
|
for i := range m {
|
||||||
|
k, err := m[i].Key.TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("key %d: %w", i, err)
|
||||||
|
}
|
||||||
|
v, err := func(item stackitem.Item) (map[string][]util.Uint160, error) {
|
||||||
|
m, ok := item.Value().([]stackitem.MapElement)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s is not a map", item.Type().String())
|
||||||
|
}
|
||||||
|
res := make(map[string][]util.Uint160)
|
||||||
|
for i := range m {
|
||||||
|
k, err := func(item stackitem.Item) (string, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
return "", errors.New("not a UTF-8 string")
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}(m[i].Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("key %d: %w", i, err)
|
||||||
|
}
|
||||||
|
v, err := func(item stackitem.Item) ([]util.Uint160, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([]util.Uint160, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], 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[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(m[i].Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("value %d: %w", i, err)
|
||||||
|
}
|
||||||
|
res[k] = v
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(m[i].Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("value %d: %w", i, err)
|
||||||
|
}
|
||||||
|
res[k] = v
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(arr[index])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field M: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeStructEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "SomeStruct" name from the provided [result.ApplicationLog].
|
||||||
|
func SomeStructEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeStructEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*SomeStructEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "SomeStruct" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(SomeStructEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize SomeStructEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to SomeStructEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *SomeStructEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.S, err = itemToCrazyStruct(arr[index], nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field S: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeArrayEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "SomeArray" name from the provided [result.ApplicationLog].
|
||||||
|
func SomeArrayEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeArrayEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*SomeArrayEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "SomeArray" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(SomeArrayEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize SomeArrayEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to SomeArrayEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *SomeArrayEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.A, err = func(item stackitem.Item) ([][]*big.Int, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([][]*big.Int, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], err = func(item stackitem.Item) ([]*big.Int, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([]*big.Int, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], err = arr[i].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(arr[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(arr[index])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field A: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeUnexportedFieldEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "SomeUnexportedField" name from the provided [result.ApplicationLog].
|
||||||
|
func SomeUnexportedFieldEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeUnexportedFieldEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*SomeUnexportedFieldEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "SomeUnexportedField" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(SomeUnexportedFieldEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize SomeUnexportedFieldEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to SomeUnexportedFieldEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *SomeUnexportedFieldEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.S, err = itemToSimpleStruct(arr[index], nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field S: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,759 @@
|
||||||
|
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||||
|
|
||||||
|
// Package structs contains RPC wrappers for Notifications contract.
|
||||||
|
package structs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"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"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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}
|
||||||
|
|
||||||
|
// Unnamed is a contract-specific unnamed type used by its methods.
|
||||||
|
type Unnamed struct {
|
||||||
|
I *big.Int
|
||||||
|
B bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnnamedX is a contract-specific unnamedX type used by its methods.
|
||||||
|
type UnnamedX struct {
|
||||||
|
I *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// ComplicatedNameEvent represents "! complicated name %$#" event emitted by the contract.
|
||||||
|
type ComplicatedNameEvent struct {
|
||||||
|
ComplicatedParam string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeMapEvent represents "SomeMap" event emitted by the contract.
|
||||||
|
type SomeMapEvent struct {
|
||||||
|
M map[*big.Int][]map[string][]util.Uint160
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeStructEvent represents "SomeStruct" event emitted by the contract.
|
||||||
|
type SomeStructEvent struct {
|
||||||
|
S *Unnamed
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeArrayEvent represents "SomeArray" event emitted by the contract.
|
||||||
|
type SomeArrayEvent struct {
|
||||||
|
A [][]*big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeUnexportedFieldEvent represents "SomeUnexportedField" event emitted by the contract.
|
||||||
|
type SomeUnexportedFieldEvent struct {
|
||||||
|
S *UnnamedX
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array creates a transaction invoking `array` 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) Array() (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "array")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayTransaction creates a transaction invoking `array` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) ArrayTransaction() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "array")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayUnsigned creates a transaction invoking `array` 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) ArrayUnsigned() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "array", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CrazyMap creates a transaction invoking `crazyMap` 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) CrazyMap() (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "crazyMap")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CrazyMapTransaction creates a transaction invoking `crazyMap` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) CrazyMapTransaction() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "crazyMap")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CrazyMapUnsigned creates a transaction invoking `crazyMap` 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) CrazyMapUnsigned() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "crazyMap", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main creates a transaction invoking `main` 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) Main() (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "main")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MainTransaction creates a transaction invoking `main` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) MainTransaction() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "main")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MainUnsigned creates a transaction invoking `main` 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) MainUnsigned() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "main", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Struct creates a transaction invoking `struct` 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) Struct() (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructTransaction creates a transaction invoking `struct` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) StructTransaction() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructUnsigned creates a transaction invoking `struct` 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) StructUnsigned() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "struct", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnexportedField creates a transaction invoking `unexportedField` 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) UnexportedField() (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "unexportedField")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnexportedFieldTransaction creates a transaction invoking `unexportedField` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) UnexportedFieldTransaction() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "unexportedField")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnexportedFieldUnsigned creates a transaction invoking `unexportedField` 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) UnexportedFieldUnsigned() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "unexportedField", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
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.I, err = arr[index].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field I: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
index++
|
||||||
|
res.B, err = arr[index].TryBool()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field B: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
res.I, err = arr[index].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field I: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*ComplicatedNameEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "! complicated name %$#" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(ComplicatedNameEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize ComplicatedNameEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to ComplicatedNameEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *ComplicatedNameEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.ComplicatedParam, err = func(item stackitem.Item) (string, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
return "", errors.New("not a UTF-8 string")
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}(arr[index])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field ComplicatedParam: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeMapEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "SomeMap" name from the provided [result.ApplicationLog].
|
||||||
|
func SomeMapEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeMapEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*SomeMapEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "SomeMap" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(SomeMapEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize SomeMapEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to SomeMapEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *SomeMapEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.M, err = func(item stackitem.Item) (map[*big.Int][]map[string][]util.Uint160, error) {
|
||||||
|
m, ok := item.Value().([]stackitem.MapElement)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s is not a map", item.Type().String())
|
||||||
|
}
|
||||||
|
res := make(map[*big.Int][]map[string][]util.Uint160)
|
||||||
|
for i := range m {
|
||||||
|
k, err := m[i].Key.TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("key %d: %w", i, err)
|
||||||
|
}
|
||||||
|
v, err := func(item stackitem.Item) ([]map[string][]util.Uint160, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([]map[string][]util.Uint160, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], err = func(item stackitem.Item) (map[string][]util.Uint160, error) {
|
||||||
|
m, ok := item.Value().([]stackitem.MapElement)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s is not a map", item.Type().String())
|
||||||
|
}
|
||||||
|
res := make(map[string][]util.Uint160)
|
||||||
|
for i := range m {
|
||||||
|
k, err := func(item stackitem.Item) (string, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
return "", errors.New("not a UTF-8 string")
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}(m[i].Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("key %d: %w", i, err)
|
||||||
|
}
|
||||||
|
v, err := func(item stackitem.Item) ([]util.Uint160, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([]util.Uint160, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], 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[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(m[i].Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("value %d: %w", i, err)
|
||||||
|
}
|
||||||
|
res[k] = v
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(arr[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(m[i].Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("value %d: %w", i, err)
|
||||||
|
}
|
||||||
|
res[k] = v
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(arr[index])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field M: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeStructEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "SomeStruct" name from the provided [result.ApplicationLog].
|
||||||
|
func SomeStructEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeStructEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*SomeStructEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "SomeStruct" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(SomeStructEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize SomeStructEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to SomeStructEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *SomeStructEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.S, err = itemToUnnamed(arr[index], nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field S: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeArrayEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "SomeArray" name from the provided [result.ApplicationLog].
|
||||||
|
func SomeArrayEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeArrayEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*SomeArrayEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "SomeArray" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(SomeArrayEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize SomeArrayEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to SomeArrayEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *SomeArrayEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.A, err = func(item stackitem.Item) ([][]*big.Int, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([][]*big.Int, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], err = func(item stackitem.Item) ([]*big.Int, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([]*big.Int, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], err = arr[i].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(arr[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(arr[index])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field A: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeUnexportedFieldEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "SomeUnexportedField" name from the provided [result.ApplicationLog].
|
||||||
|
func SomeUnexportedFieldEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeUnexportedFieldEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*SomeUnexportedFieldEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "SomeUnexportedField" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(SomeUnexportedFieldEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize SomeUnexportedFieldEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to SomeUnexportedFieldEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *SomeUnexportedFieldEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.S, err = itemToUnnamedX(arr[index], nil)
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field S: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,500 @@
|
||||||
|
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||||
|
|
||||||
|
// Package structs contains RPC wrappers for Notifications contract.
|
||||||
|
package structs
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"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/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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}
|
||||||
|
|
||||||
|
// ComplicatedNameEvent represents "! complicated name %$#" event emitted by the contract.
|
||||||
|
type ComplicatedNameEvent struct {
|
||||||
|
ComplicatedParam string
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeMapEvent represents "SomeMap" event emitted by the contract.
|
||||||
|
type SomeMapEvent struct {
|
||||||
|
M map[any]any
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeStructEvent represents "SomeStruct" event emitted by the contract.
|
||||||
|
type SomeStructEvent struct {
|
||||||
|
S []any
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeArrayEvent represents "SomeArray" event emitted by the contract.
|
||||||
|
type SomeArrayEvent struct {
|
||||||
|
A []any
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeUnexportedFieldEvent represents "SomeUnexportedField" event emitted by the contract.
|
||||||
|
type SomeUnexportedFieldEvent struct {
|
||||||
|
S []any
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Array creates a transaction invoking `array` 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) Array() (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "array")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayTransaction creates a transaction invoking `array` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) ArrayTransaction() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "array")
|
||||||
|
}
|
||||||
|
|
||||||
|
// ArrayUnsigned creates a transaction invoking `array` 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) ArrayUnsigned() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "array", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CrazyMap creates a transaction invoking `crazyMap` 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) CrazyMap() (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "crazyMap")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CrazyMapTransaction creates a transaction invoking `crazyMap` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) CrazyMapTransaction() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "crazyMap")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CrazyMapUnsigned creates a transaction invoking `crazyMap` 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) CrazyMapUnsigned() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "crazyMap", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Main creates a transaction invoking `main` 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) Main() (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "main")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MainTransaction creates a transaction invoking `main` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) MainTransaction() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "main")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MainUnsigned creates a transaction invoking `main` 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) MainUnsigned() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "main", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Struct creates a transaction invoking `struct` 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) Struct() (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructTransaction creates a transaction invoking `struct` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) StructTransaction() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "struct")
|
||||||
|
}
|
||||||
|
|
||||||
|
// StructUnsigned creates a transaction invoking `struct` 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) StructUnsigned() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "struct", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnexportedField creates a transaction invoking `unexportedField` 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) UnexportedField() (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "unexportedField")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnexportedFieldTransaction creates a transaction invoking `unexportedField` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) UnexportedFieldTransaction() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "unexportedField")
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnexportedFieldUnsigned creates a transaction invoking `unexportedField` 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) UnexportedFieldUnsigned() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "unexportedField", 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) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*ComplicatedNameEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "! complicated name %$#" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(ComplicatedNameEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize ComplicatedNameEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to ComplicatedNameEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *ComplicatedNameEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.ComplicatedParam, err = func(item stackitem.Item) (string, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
return "", errors.New("not a UTF-8 string")
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}(arr[index])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field ComplicatedParam: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeMapEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "SomeMap" name from the provided [result.ApplicationLog].
|
||||||
|
func SomeMapEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeMapEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*SomeMapEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "SomeMap" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(SomeMapEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize SomeMapEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to SomeMapEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *SomeMapEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.M, err = func(item stackitem.Item) (map[any]any, error) {
|
||||||
|
m, ok := item.Value().([]stackitem.MapElement)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s is not a map", item.Type().String())
|
||||||
|
}
|
||||||
|
res := make(map[any]any)
|
||||||
|
for i := range m {
|
||||||
|
k, err := m[i].Key.Value(), error(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("key %d: %w", i, err)
|
||||||
|
}
|
||||||
|
v, err := m[i].Value.Value(), error(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("value %d: %w", i, err)
|
||||||
|
}
|
||||||
|
res[k] = v
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(arr[index])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field M: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeStructEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "SomeStruct" name from the provided [result.ApplicationLog].
|
||||||
|
func SomeStructEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeStructEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*SomeStructEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "SomeStruct" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(SomeStructEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize SomeStructEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to SomeStructEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *SomeStructEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.S, err = func(item stackitem.Item) ([]any, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([]any, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], err = arr[i].Value(), error(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(arr[index])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field S: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeArrayEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "SomeArray" name from the provided [result.ApplicationLog].
|
||||||
|
func SomeArrayEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeArrayEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*SomeArrayEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "SomeArray" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(SomeArrayEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize SomeArrayEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to SomeArrayEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *SomeArrayEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.A, err = func(item stackitem.Item) ([]any, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([]any, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], err = arr[i].Value(), error(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(arr[index])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field A: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// SomeUnexportedFieldEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "SomeUnexportedField" name from the provided [result.ApplicationLog].
|
||||||
|
func SomeUnexportedFieldEventsFromApplicationLog(log *result.ApplicationLog) ([]*SomeUnexportedFieldEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*SomeUnexportedFieldEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "SomeUnexportedField" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(SomeUnexportedFieldEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize SomeUnexportedFieldEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to SomeUnexportedFieldEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *SomeUnexportedFieldEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.S, err = func(item stackitem.Item) ([]any, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([]any, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], err = arr[i].Value(), error(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(arr[index])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field S: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
|
|
@ -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)
|
||||||
|
}
|
||||||
2860
cli/smartcontract/rpcbindings/structs/rpcbindings/dynamic_hash/rpcbindings_test.go
Executable file
2860
cli/smartcontract/rpcbindings/structs/rpcbindings/dynamic_hash/rpcbindings_test.go
Executable file
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
563
cli/smartcontract/rpcbindings/types/rpcbindings/dynamic_hash/rpcbindings_test.go
Executable file
563
cli/smartcontract/rpcbindings/types/rpcbindings/dynamic_hash/rpcbindings_test.go
Executable file
|
|
@ -0,0 +1,563 @@
|
||||||
|
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||||
|
|
||||||
|
// Package types contains RPC wrappers for Types contract.
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"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"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Unnamed is a contract-specific unnamed type used by its methods.
|
||||||
|
type Unnamed struct {
|
||||||
|
I *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnnamedX is a contract-specific unnamedX type used by its methods.
|
||||||
|
type UnnamedX struct {
|
||||||
|
I *big.Int
|
||||||
|
B bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoker is used by ContractReader to call various safe methods.
|
||||||
|
type Invoker interface {
|
||||||
|
Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContractReader implements safe contract methods.
|
||||||
|
type ContractReader struct {
|
||||||
|
invoker Invoker
|
||||||
|
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{invoker, hash}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AAAStrings invokes `aAAStrings` method of contract.
|
||||||
|
func (c *ContractReader) AAAStrings(s [][][]string) ([][][]string, error) {
|
||||||
|
return func(item stackitem.Item, err error) ([][][]string, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return func(item stackitem.Item) ([][][]string, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([][][]string, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], err = func(item stackitem.Item) ([][]string, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([][]string, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], err = func(item stackitem.Item) ([]string, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([]string, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], err = func(item stackitem.Item) (string, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
return "", errors.New("not a UTF-8 string")
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}(arr[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(arr[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(arr[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(item)
|
||||||
|
}(unwrap.Item(c.invoker.Call(c.hash, "aAAStrings", s)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any invokes `any` method of contract.
|
||||||
|
func (c *ContractReader) Any(a any) (any, error) {
|
||||||
|
return func(item stackitem.Item, err error) (any, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return item.Value(), error(nil)
|
||||||
|
}(unwrap.Item(c.invoker.Call(c.hash, "any", a)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnyMaps invokes `anyMaps` method of contract.
|
||||||
|
func (c *ContractReader) AnyMaps(m map[*big.Int]any) (map[*big.Int]any, error) {
|
||||||
|
return func(item stackitem.Item, err error) (map[*big.Int]any, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return func(item stackitem.Item) (map[*big.Int]any, error) {
|
||||||
|
m, ok := item.Value().([]stackitem.MapElement)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s is not a map", item.Type().String())
|
||||||
|
}
|
||||||
|
res := make(map[*big.Int]any)
|
||||||
|
for i := range m {
|
||||||
|
k, err := m[i].Key.TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("key %d: %w", i, err)
|
||||||
|
}
|
||||||
|
v, err := m[i].Value.Value(), error(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("value %d: %w", i, err)
|
||||||
|
}
|
||||||
|
res[k] = v
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(item)
|
||||||
|
}(unwrap.Item(c.invoker.Call(c.hash, "anyMaps", m)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool invokes `bool` method of contract.
|
||||||
|
func (c *ContractReader) Bool(b bool) (bool, error) {
|
||||||
|
return unwrap.Bool(c.invoker.Call(c.hash, "bool", b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bools invokes `bools` method of contract.
|
||||||
|
func (c *ContractReader) Bools(b []bool) ([]bool, error) {
|
||||||
|
return unwrap.ArrayOfBools(c.invoker.Call(c.hash, "bools", b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes invokes `bytes` method of contract.
|
||||||
|
func (c *ContractReader) Bytes(b []byte) ([]byte, error) {
|
||||||
|
return unwrap.Bytes(c.invoker.Call(c.hash, "bytes", b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytess invokes `bytess` method of contract.
|
||||||
|
func (c *ContractReader) Bytess(b [][]byte) ([][]byte, error) {
|
||||||
|
return unwrap.ArrayOfBytes(c.invoker.Call(c.hash, "bytess", b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CrazyMaps invokes `crazyMaps` method of contract.
|
||||||
|
func (c *ContractReader) CrazyMaps(m map[*big.Int][]map[string][]util.Uint160) (map[*big.Int][]map[string][]util.Uint160, error) {
|
||||||
|
return func(item stackitem.Item, err error) (map[*big.Int][]map[string][]util.Uint160, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return func(item stackitem.Item) (map[*big.Int][]map[string][]util.Uint160, error) {
|
||||||
|
m, ok := item.Value().([]stackitem.MapElement)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s is not a map", item.Type().String())
|
||||||
|
}
|
||||||
|
res := make(map[*big.Int][]map[string][]util.Uint160)
|
||||||
|
for i := range m {
|
||||||
|
k, err := m[i].Key.TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("key %d: %w", i, err)
|
||||||
|
}
|
||||||
|
v, err := func(item stackitem.Item) ([]map[string][]util.Uint160, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([]map[string][]util.Uint160, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], err = func(item stackitem.Item) (map[string][]util.Uint160, error) {
|
||||||
|
m, ok := item.Value().([]stackitem.MapElement)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s is not a map", item.Type().String())
|
||||||
|
}
|
||||||
|
res := make(map[string][]util.Uint160)
|
||||||
|
for i := range m {
|
||||||
|
k, err := func(item stackitem.Item) (string, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
return "", errors.New("not a UTF-8 string")
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}(m[i].Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("key %d: %w", i, err)
|
||||||
|
}
|
||||||
|
v, err := func(item stackitem.Item) ([]util.Uint160, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([]util.Uint160, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], 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[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(m[i].Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("value %d: %w", i, err)
|
||||||
|
}
|
||||||
|
res[k] = v
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(arr[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(m[i].Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("value %d: %w", i, err)
|
||||||
|
}
|
||||||
|
res[k] = v
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(item)
|
||||||
|
}(unwrap.Item(c.invoker.Call(c.hash, "crazyMaps", m)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash160 invokes `hash160` method of contract.
|
||||||
|
func (c *ContractReader) Hash160(h util.Uint160) (util.Uint160, error) {
|
||||||
|
return unwrap.Uint160(c.invoker.Call(c.hash, "hash160", h))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash160s invokes `hash160s` method of contract.
|
||||||
|
func (c *ContractReader) Hash160s(h []util.Uint160) ([]util.Uint160, error) {
|
||||||
|
return unwrap.ArrayOfUint160(c.invoker.Call(c.hash, "hash160s", h))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash256 invokes `hash256` method of contract.
|
||||||
|
func (c *ContractReader) Hash256(h util.Uint256) (util.Uint256, error) {
|
||||||
|
return unwrap.Uint256(c.invoker.Call(c.hash, "hash256", h))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash256s invokes `hash256s` method of contract.
|
||||||
|
func (c *ContractReader) Hash256s(h []util.Uint256) ([]util.Uint256, error) {
|
||||||
|
return unwrap.ArrayOfUint256(c.invoker.Call(c.hash, "hash256s", h))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int invokes `int` method of contract.
|
||||||
|
func (c *ContractReader) Int(i *big.Int) (*big.Int, error) {
|
||||||
|
return unwrap.BigInt(c.invoker.Call(c.hash, "int", i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ints invokes `ints` method of contract.
|
||||||
|
func (c *ContractReader) Ints(i []*big.Int) ([]*big.Int, error) {
|
||||||
|
return unwrap.ArrayOfBigInts(c.invoker.Call(c.hash, "ints", i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maps invokes `maps` method of contract.
|
||||||
|
func (c *ContractReader) Maps(m map[string]string) (map[string]string, error) {
|
||||||
|
return func(item stackitem.Item, err error) (map[string]string, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return func(item stackitem.Item) (map[string]string, error) {
|
||||||
|
m, ok := item.Value().([]stackitem.MapElement)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s is not a map", item.Type().String())
|
||||||
|
}
|
||||||
|
res := make(map[string]string)
|
||||||
|
for i := range m {
|
||||||
|
k, err := func(item stackitem.Item) (string, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
return "", errors.New("not a UTF-8 string")
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}(m[i].Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("key %d: %w", i, err)
|
||||||
|
}
|
||||||
|
v, err := func(item stackitem.Item) (string, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
return "", errors.New("not a UTF-8 string")
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}(m[i].Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("value %d: %w", i, err)
|
||||||
|
}
|
||||||
|
res[k] = v
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(item)
|
||||||
|
}(unwrap.Item(c.invoker.Call(c.hash, "maps", m)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicKey invokes `publicKey` method of contract.
|
||||||
|
func (c *ContractReader) PublicKey(k *keys.PublicKey) (*keys.PublicKey, error) {
|
||||||
|
return unwrap.PublicKey(c.invoker.Call(c.hash, "publicKey", k))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicKeys invokes `publicKeys` method of contract.
|
||||||
|
func (c *ContractReader) PublicKeys(k keys.PublicKeys) (keys.PublicKeys, error) {
|
||||||
|
return unwrap.ArrayOfPublicKeys(c.invoker.Call(c.hash, "publicKeys", k))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signature invokes `signature` method of contract.
|
||||||
|
func (c *ContractReader) Signature(s []byte) ([]byte, error) {
|
||||||
|
return unwrap.Bytes(c.invoker.Call(c.hash, "signature", s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signatures invokes `signatures` method of contract.
|
||||||
|
func (c *ContractReader) Signatures(s [][]byte) ([][]byte, error) {
|
||||||
|
return unwrap.ArrayOfBytes(c.invoker.Call(c.hash, "signatures", s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// String invokes `string` method of contract.
|
||||||
|
func (c *ContractReader) String(s string) (string, error) {
|
||||||
|
return unwrap.UTF8String(c.invoker.Call(c.hash, "string", s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings invokes `strings` method of contract.
|
||||||
|
func (c *ContractReader) Strings(s []string) ([]string, error) {
|
||||||
|
return unwrap.ArrayOfUTF8Strings(c.invoker.Call(c.hash, "strings", s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnnamedStructs invokes `unnamedStructs` method of contract.
|
||||||
|
func (c *ContractReader) UnnamedStructs() (*Unnamed, error) {
|
||||||
|
return itemToUnnamed(unwrap.Item(c.invoker.Call(c.hash, "unnamedStructs")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnnamedStructsX invokes `unnamedStructsX` method of contract.
|
||||||
|
func (c *ContractReader) UnnamedStructsX() (*UnnamedX, error) {
|
||||||
|
return itemToUnnamedX(unwrap.Item(c.invoker.Call(c.hash, "unnamedStructsX")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
res.I, err = arr[index].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field I: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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.I, err = arr[index].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field I: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
index++
|
||||||
|
res.B, err = arr[index].TryBool()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field B: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,567 @@
|
||||||
|
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||||
|
|
||||||
|
// Package types contains RPC wrappers for Types contract.
|
||||||
|
package types
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"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"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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}
|
||||||
|
|
||||||
|
// Unnamed is a contract-specific unnamed type used by its methods.
|
||||||
|
type Unnamed struct {
|
||||||
|
I *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnnamedX is a contract-specific unnamedX type used by its methods.
|
||||||
|
type UnnamedX struct {
|
||||||
|
I *big.Int
|
||||||
|
B bool
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoker is used by ContractReader to call various safe methods.
|
||||||
|
type Invoker interface {
|
||||||
|
Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContractReader implements safe contract methods.
|
||||||
|
type ContractReader struct {
|
||||||
|
invoker Invoker
|
||||||
|
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{invoker, hash}
|
||||||
|
}
|
||||||
|
|
||||||
|
// AAAStrings invokes `aAAStrings` method of contract.
|
||||||
|
func (c *ContractReader) AAAStrings(s [][][]string) ([][][]string, error) {
|
||||||
|
return func(item stackitem.Item, err error) ([][][]string, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return func(item stackitem.Item) ([][][]string, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([][][]string, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], err = func(item stackitem.Item) ([][]string, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([][]string, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], err = func(item stackitem.Item) ([]string, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([]string, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], err = func(item stackitem.Item) (string, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
return "", errors.New("not a UTF-8 string")
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}(arr[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(arr[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(arr[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(item)
|
||||||
|
}(unwrap.Item(c.invoker.Call(c.hash, "aAAStrings", s)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Any invokes `any` method of contract.
|
||||||
|
func (c *ContractReader) Any(a any) (any, error) {
|
||||||
|
return func(item stackitem.Item, err error) (any, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return item.Value(), error(nil)
|
||||||
|
}(unwrap.Item(c.invoker.Call(c.hash, "any", a)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// AnyMaps invokes `anyMaps` method of contract.
|
||||||
|
func (c *ContractReader) AnyMaps(m map[*big.Int]any) (map[*big.Int]any, error) {
|
||||||
|
return func(item stackitem.Item, err error) (map[*big.Int]any, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return func(item stackitem.Item) (map[*big.Int]any, error) {
|
||||||
|
m, ok := item.Value().([]stackitem.MapElement)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s is not a map", item.Type().String())
|
||||||
|
}
|
||||||
|
res := make(map[*big.Int]any)
|
||||||
|
for i := range m {
|
||||||
|
k, err := m[i].Key.TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("key %d: %w", i, err)
|
||||||
|
}
|
||||||
|
v, err := m[i].Value.Value(), error(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("value %d: %w", i, err)
|
||||||
|
}
|
||||||
|
res[k] = v
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(item)
|
||||||
|
}(unwrap.Item(c.invoker.Call(c.hash, "anyMaps", m)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bool invokes `bool` method of contract.
|
||||||
|
func (c *ContractReader) Bool(b bool) (bool, error) {
|
||||||
|
return unwrap.Bool(c.invoker.Call(c.hash, "bool", b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bools invokes `bools` method of contract.
|
||||||
|
func (c *ContractReader) Bools(b []bool) ([]bool, error) {
|
||||||
|
return unwrap.ArrayOfBools(c.invoker.Call(c.hash, "bools", b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytes invokes `bytes` method of contract.
|
||||||
|
func (c *ContractReader) Bytes(b []byte) ([]byte, error) {
|
||||||
|
return unwrap.Bytes(c.invoker.Call(c.hash, "bytes", b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bytess invokes `bytess` method of contract.
|
||||||
|
func (c *ContractReader) Bytess(b [][]byte) ([][]byte, error) {
|
||||||
|
return unwrap.ArrayOfBytes(c.invoker.Call(c.hash, "bytess", b))
|
||||||
|
}
|
||||||
|
|
||||||
|
// CrazyMaps invokes `crazyMaps` method of contract.
|
||||||
|
func (c *ContractReader) CrazyMaps(m map[*big.Int][]map[string][]util.Uint160) (map[*big.Int][]map[string][]util.Uint160, error) {
|
||||||
|
return func(item stackitem.Item, err error) (map[*big.Int][]map[string][]util.Uint160, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return func(item stackitem.Item) (map[*big.Int][]map[string][]util.Uint160, error) {
|
||||||
|
m, ok := item.Value().([]stackitem.MapElement)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s is not a map", item.Type().String())
|
||||||
|
}
|
||||||
|
res := make(map[*big.Int][]map[string][]util.Uint160)
|
||||||
|
for i := range m {
|
||||||
|
k, err := m[i].Key.TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("key %d: %w", i, err)
|
||||||
|
}
|
||||||
|
v, err := func(item stackitem.Item) ([]map[string][]util.Uint160, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([]map[string][]util.Uint160, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], err = func(item stackitem.Item) (map[string][]util.Uint160, error) {
|
||||||
|
m, ok := item.Value().([]stackitem.MapElement)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s is not a map", item.Type().String())
|
||||||
|
}
|
||||||
|
res := make(map[string][]util.Uint160)
|
||||||
|
for i := range m {
|
||||||
|
k, err := func(item stackitem.Item) (string, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
return "", errors.New("not a UTF-8 string")
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}(m[i].Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("key %d: %w", i, err)
|
||||||
|
}
|
||||||
|
v, err := func(item stackitem.Item) ([]util.Uint160, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([]util.Uint160, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], 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[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(m[i].Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("value %d: %w", i, err)
|
||||||
|
}
|
||||||
|
res[k] = v
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(arr[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(m[i].Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("value %d: %w", i, err)
|
||||||
|
}
|
||||||
|
res[k] = v
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(item)
|
||||||
|
}(unwrap.Item(c.invoker.Call(c.hash, "crazyMaps", m)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash160 invokes `hash160` method of contract.
|
||||||
|
func (c *ContractReader) Hash160(h util.Uint160) (util.Uint160, error) {
|
||||||
|
return unwrap.Uint160(c.invoker.Call(c.hash, "hash160", h))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash160s invokes `hash160s` method of contract.
|
||||||
|
func (c *ContractReader) Hash160s(h []util.Uint160) ([]util.Uint160, error) {
|
||||||
|
return unwrap.ArrayOfUint160(c.invoker.Call(c.hash, "hash160s", h))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash256 invokes `hash256` method of contract.
|
||||||
|
func (c *ContractReader) Hash256(h util.Uint256) (util.Uint256, error) {
|
||||||
|
return unwrap.Uint256(c.invoker.Call(c.hash, "hash256", h))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hash256s invokes `hash256s` method of contract.
|
||||||
|
func (c *ContractReader) Hash256s(h []util.Uint256) ([]util.Uint256, error) {
|
||||||
|
return unwrap.ArrayOfUint256(c.invoker.Call(c.hash, "hash256s", h))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Int invokes `int` method of contract.
|
||||||
|
func (c *ContractReader) Int(i *big.Int) (*big.Int, error) {
|
||||||
|
return unwrap.BigInt(c.invoker.Call(c.hash, "int", i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ints invokes `ints` method of contract.
|
||||||
|
func (c *ContractReader) Ints(i []*big.Int) ([]*big.Int, error) {
|
||||||
|
return unwrap.ArrayOfBigInts(c.invoker.Call(c.hash, "ints", i))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Maps invokes `maps` method of contract.
|
||||||
|
func (c *ContractReader) Maps(m map[string]string) (map[string]string, error) {
|
||||||
|
return func(item stackitem.Item, err error) (map[string]string, error) {
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return func(item stackitem.Item) (map[string]string, error) {
|
||||||
|
m, ok := item.Value().([]stackitem.MapElement)
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("%s is not a map", item.Type().String())
|
||||||
|
}
|
||||||
|
res := make(map[string]string)
|
||||||
|
for i := range m {
|
||||||
|
k, err := func(item stackitem.Item) (string, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
return "", errors.New("not a UTF-8 string")
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}(m[i].Key)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("key %d: %w", i, err)
|
||||||
|
}
|
||||||
|
v, err := func(item stackitem.Item) (string, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
return "", errors.New("not a UTF-8 string")
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}(m[i].Value)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("value %d: %w", i, err)
|
||||||
|
}
|
||||||
|
res[k] = v
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(item)
|
||||||
|
}(unwrap.Item(c.invoker.Call(c.hash, "maps", m)))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicKey invokes `publicKey` method of contract.
|
||||||
|
func (c *ContractReader) PublicKey(k *keys.PublicKey) (*keys.PublicKey, error) {
|
||||||
|
return unwrap.PublicKey(c.invoker.Call(c.hash, "publicKey", k))
|
||||||
|
}
|
||||||
|
|
||||||
|
// PublicKeys invokes `publicKeys` method of contract.
|
||||||
|
func (c *ContractReader) PublicKeys(k keys.PublicKeys) (keys.PublicKeys, error) {
|
||||||
|
return unwrap.ArrayOfPublicKeys(c.invoker.Call(c.hash, "publicKeys", k))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signature invokes `signature` method of contract.
|
||||||
|
func (c *ContractReader) Signature(s []byte) ([]byte, error) {
|
||||||
|
return unwrap.Bytes(c.invoker.Call(c.hash, "signature", s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Signatures invokes `signatures` method of contract.
|
||||||
|
func (c *ContractReader) Signatures(s [][]byte) ([][]byte, error) {
|
||||||
|
return unwrap.ArrayOfBytes(c.invoker.Call(c.hash, "signatures", s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// String invokes `string` method of contract.
|
||||||
|
func (c *ContractReader) String(s string) (string, error) {
|
||||||
|
return unwrap.UTF8String(c.invoker.Call(c.hash, "string", s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Strings invokes `strings` method of contract.
|
||||||
|
func (c *ContractReader) Strings(s []string) ([]string, error) {
|
||||||
|
return unwrap.ArrayOfUTF8Strings(c.invoker.Call(c.hash, "strings", s))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnnamedStructs invokes `unnamedStructs` method of contract.
|
||||||
|
func (c *ContractReader) UnnamedStructs() (*Unnamed, error) {
|
||||||
|
return itemToUnnamed(unwrap.Item(c.invoker.Call(c.hash, "unnamedStructs")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// UnnamedStructsX invokes `unnamedStructsX` method of contract.
|
||||||
|
func (c *ContractReader) UnnamedStructsX() (*UnnamedX, error) {
|
||||||
|
return itemToUnnamedX(unwrap.Item(c.invoker.Call(c.hash, "unnamedStructsX")))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
res.I, err = arr[index].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field I: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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.I, err = arr[index].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field I: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
index++
|
||||||
|
res.B, err = arr[index].TryBool()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field B: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,147 @@
|
||||||
|
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||||
|
|
||||||
|
// Package verify contains RPC wrappers for verify contract.
|
||||||
|
package verify
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"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"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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}
|
||||||
|
|
||||||
|
// HelloWorldEvent represents "Hello world!" event emitted by the contract.
|
||||||
|
type HelloWorldEvent struct {
|
||||||
|
Args []any
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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}
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// HelloWorldEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "Hello world!" name from the provided [result.ApplicationLog].
|
||||||
|
func HelloWorldEventsFromApplicationLog(log *result.ApplicationLog) ([]*HelloWorldEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*HelloWorldEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "Hello world!" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(HelloWorldEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize HelloWorldEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to HelloWorldEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *HelloWorldEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 1 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.Args, err = func(item stackitem.Item) ([]any, error) {
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("not an array")
|
||||||
|
}
|
||||||
|
res := make([]any, len(arr))
|
||||||
|
for i := range res {
|
||||||
|
res[i], err = arr[i].Value(), error(nil)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item %d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}(arr[index])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field Args: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,993 @@
|
||||||
|
package smartcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"encoding/hex"
|
||||||
|
"encoding/json"
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"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"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
||||||
|
"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/neorpc/result"
|
||||||
|
"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/rpcclient/management"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract/binding"
|
||||||
|
"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/nspcc-dev/neo-go/pkg/vm"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||||
|
"github.com/urfave/cli/v2"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 (
|
||||||
|
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("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)",
|
||||||
|
}
|
||||||
|
inFlag = &cli.StringFlag{
|
||||||
|
Name: "in",
|
||||||
|
Aliases: []string{"i"},
|
||||||
|
Usage: "Input file for the smart contract (*.nef)",
|
||||||
|
Action: cmdargs.EnsureNotEmpty("in"),
|
||||||
|
}
|
||||||
|
manifestFlag = &cli.StringFlag{
|
||||||
|
Name: "manifest",
|
||||||
|
Aliases: []string{"m"},
|
||||||
|
Usage: "Manifest input file (*.manifest.json)",
|
||||||
|
Action: cmdargs.EnsureNotEmpty("manifest"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
// ModVersion contains `pkg/interop` module version
|
||||||
|
// suitable to be used in go.mod.
|
||||||
|
var ModVersion string
|
||||||
|
|
||||||
|
const (
|
||||||
|
// smartContractTmpl is written to a file when used with `init` command.
|
||||||
|
// %s is parsed to be the smartContractName.
|
||||||
|
smartContractTmpl = `package %s
|
||||||
|
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
|
||||||
|
var notificationName string
|
||||||
|
|
||||||
|
// init initializes notificationName before calling any other smart-contract method
|
||||||
|
func init() {
|
||||||
|
notificationName = "Hello world!"
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuntimeNotify sends runtime notification with "Hello world!" name
|
||||||
|
func RuntimeNotify(args []any) {
|
||||||
|
runtime.Notify(notificationName, args)
|
||||||
|
}`
|
||||||
|
)
|
||||||
|
|
||||||
|
// NewCommands returns 'contract' command.
|
||||||
|
func NewCommands() []*cli.Command {
|
||||||
|
testInvokeScriptFlags := []cli.Flag{
|
||||||
|
&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,
|
||||||
|
}
|
||||||
|
testInvokeScriptFlags = append(testInvokeScriptFlags, options.RPC...)
|
||||||
|
testInvokeFunctionFlags := []cli.Flag{options.Historic}
|
||||||
|
testInvokeFunctionFlags = append(testInvokeFunctionFlags, options.RPC...)
|
||||||
|
invokeFunctionFlags := []cli.Flag{
|
||||||
|
addressFlag,
|
||||||
|
txctx.GasFlag,
|
||||||
|
txctx.SysGasFlag,
|
||||||
|
txctx.OutFlag,
|
||||||
|
txctx.ForceFlag,
|
||||||
|
txctx.AwaitFlag,
|
||||||
|
}
|
||||||
|
invokeFunctionFlags = append(invokeFunctionFlags, options.Wallet...)
|
||||||
|
invokeFunctionFlags = append(invokeFunctionFlags, options.RPC...)
|
||||||
|
deployFlags := append(invokeFunctionFlags,
|
||||||
|
cloneFlag(inFlag, true),
|
||||||
|
cloneFlag(manifestFlag, true),
|
||||||
|
)
|
||||||
|
updateFlags := append([]cli.Flag{
|
||||||
|
cloneFlag(inFlag, false),
|
||||||
|
cloneFlag(manifestFlag, false),
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "strict",
|
||||||
|
Usage: "Fail immediately on NEP-22 nil-data call failure (skip 2-param fallback attempt)",
|
||||||
|
},
|
||||||
|
}, invokeFunctionFlags...)
|
||||||
|
manifestAddGroupFlags := append([]cli.Flag{
|
||||||
|
&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.
|
||||||
|
Aliases: []string{addressFlagAlias},
|
||||||
|
Required: true,
|
||||||
|
Usage: "Account to sign group with",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "nef",
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Required: true,
|
||||||
|
Usage: "Path to the NEF file",
|
||||||
|
Action: cmdargs.EnsureNotEmpty("nef"),
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "manifest",
|
||||||
|
Aliases: []string{"m"},
|
||||||
|
Required: true,
|
||||||
|
Usage: "Path to the manifest",
|
||||||
|
Action: cmdargs.EnsureNotEmpty("manifest"),
|
||||||
|
},
|
||||||
|
}, options.Wallet...)
|
||||||
|
return []*cli.Command{{
|
||||||
|
Name: "contract",
|
||||||
|
Usage: "Compile - debug - deploy smart contracts",
|
||||||
|
Subcommands: []*cli.Command{
|
||||||
|
{
|
||||||
|
Name: "compile",
|
||||||
|
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
|
||||||
|
asked to. If none of --out, --manifest, --config, --bindings flags are specified,
|
||||||
|
then the output filenames for these flags will be guessed using the contract
|
||||||
|
name or path provided via --in option by trimming/adding corresponding suffixes
|
||||||
|
to the common part of the path. In the latter case the configuration filepath
|
||||||
|
will be guessed from the --in option using the same rule.
|
||||||
|
`,
|
||||||
|
Action: contractCompile,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&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",
|
||||||
|
Aliases: []string{"o"},
|
||||||
|
Usage: "Output of the compiled contract",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "verbose",
|
||||||
|
Aliases: []string{"v"},
|
||||||
|
Usage: "Print out additional information after a compiling",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "debug",
|
||||||
|
Aliases: []string{"d"},
|
||||||
|
Usage: "Emit debug info in a separate file",
|
||||||
|
},
|
||||||
|
&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",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Usage: "Configuration input file (*.yml)",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "no-standards",
|
||||||
|
Usage: "Do not check compliance with supported standards",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "no-events",
|
||||||
|
Usage: "Do not check emitted events with the manifest",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "no-permissions",
|
||||||
|
Usage: "Do not check if invoked contracts are allowed in manifest",
|
||||||
|
},
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "guess-eventtypes",
|
||||||
|
Usage: "Guess event types for smart-contract bindings configuration from the code usages",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "bindings",
|
||||||
|
Usage: "Output file for smart-contract bindings configuration",
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "deploy",
|
||||||
|
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
|
||||||
|
parameter is an optional parameter to be passed to '_deploy' method. When
|
||||||
|
--await flag is specified, it waits for the transaction to be included
|
||||||
|
in a block.
|
||||||
|
`,
|
||||||
|
Action: contractDeploy,
|
||||||
|
Flags: deployFlags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "update",
|
||||||
|
Usage: "Update deployed smart contract on the blockchain",
|
||||||
|
UsageText: "neo-go contract update -r endpoint -w wallet [-a address] [-g gas] [-e sysgas] --in contract.nef --manifest contract.manifest.json [--out file] [--force] [--await] [--strict] scripthash [data...] [--] [signers...]",
|
||||||
|
Description: `Updates deployed contract on the chain. The gas parameter is for additional
|
||||||
|
gas to be added as a network fee to prioritize the transaction. The data
|
||||||
|
parameter is an optional parameter to be passed to '_deploy' method. When
|
||||||
|
--await flag is specified, it waits for the transaction to be included
|
||||||
|
in a block.
|
||||||
|
`,
|
||||||
|
Action: contractUpdate,
|
||||||
|
Flags: updateFlags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "destroy",
|
||||||
|
Usage: "Destroy deployed smart contract",
|
||||||
|
UsageText: "neo-go contract destroy -r endpoint -w wallet [-a address] [-g gas] [-e sysgas] [--out file] [--force] [--await] scripthash [--] [signers...]",
|
||||||
|
Description: `Destroys deployed contract. The gas parameter is for additional
|
||||||
|
gas to be added as a network fee to prioritize the transaction. When --await flag is
|
||||||
|
specified, it waits for the transaction to be included in a block.
|
||||||
|
`,
|
||||||
|
Action: contractDestroy,
|
||||||
|
Flags: invokeFunctionFlags,
|
||||||
|
},
|
||||||
|
generateWrapperCmd,
|
||||||
|
generateRPCWrapperCmd,
|
||||||
|
{
|
||||||
|
Name: "invokefunction",
|
||||||
|
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
|
||||||
|
with None witness scope. If you'd like to change default sender's scope,
|
||||||
|
specify it via signers parameter. See testinvokefunction documentation for
|
||||||
|
the details about parameters. It differs from testinvokefunction in that this
|
||||||
|
command sends an invocation transaction to the network. When --await flag is
|
||||||
|
specified, it waits for the transaction to be included in a block.
|
||||||
|
`,
|
||||||
|
Action: invokeFunction,
|
||||||
|
Flags: invokeFunctionFlags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "testinvokefunction",
|
||||||
|
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
|
||||||
|
"" is passed to the script, if no arguments are given, an empty array is
|
||||||
|
passed, if no signers are given no array is passed. If signers are specified,
|
||||||
|
the first one of them is treated as a sender. All of the given arguments are
|
||||||
|
encapsulated into array before invoking the script. The script thus should
|
||||||
|
follow the regular convention of smart contract arguments (method string and
|
||||||
|
an array of other arguments).
|
||||||
|
|
||||||
|
` + cmdargs.ParamsParsingDoc + `
|
||||||
|
|
||||||
|
` + cmdargs.SignersParsingDoc + `
|
||||||
|
`,
|
||||||
|
Action: testInvokeFunction,
|
||||||
|
Flags: testInvokeFunctionFlags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "testinvokescript",
|
||||||
|
Usage: "Invoke compiled AVM code in NEF format on the blockchain (test mode, not creating a transaction for it)",
|
||||||
|
UsageText: "neo-go contract testinvokescript -r endpoint -i input.nef [--historic index/hash] [signers...]",
|
||||||
|
Description: `Executes given compiled AVM instructions in NEF format with the given set of
|
||||||
|
signers not included sender by default. See testinvokefunction documentation
|
||||||
|
for the details about parameters.
|
||||||
|
`,
|
||||||
|
Action: testInvokeScript,
|
||||||
|
Flags: testInvokeScriptFlags,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "init",
|
||||||
|
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",
|
||||||
|
Aliases: []string{"n"},
|
||||||
|
Required: true,
|
||||||
|
Usage: "Name of the smart-contract to be initialized",
|
||||||
|
Action: cmdargs.EnsureNotEmpty("name"),
|
||||||
|
},
|
||||||
|
&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",
|
||||||
|
UsageText: "neo-go contract inspect -i file [-c]",
|
||||||
|
Action: inspect,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&cli.BoolFlag{
|
||||||
|
Name: "compile",
|
||||||
|
Aliases: []string{"c"},
|
||||||
|
Usage: "Compile input file (it should be go code then)",
|
||||||
|
},
|
||||||
|
&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",
|
||||||
|
UsageText: "neo-go contract calc-hash -i nef -m manifest -s address",
|
||||||
|
Action: calcHash,
|
||||||
|
Flags: []cli.Flag{
|
||||||
|
&flags.AddressFlag{
|
||||||
|
Name: "sender",
|
||||||
|
Aliases: []string{"s"},
|
||||||
|
Required: true,
|
||||||
|
Usage: "Sender script hash or address",
|
||||||
|
},
|
||||||
|
&cli.StringFlag{
|
||||||
|
Name: "in",
|
||||||
|
Required: true,
|
||||||
|
Usage: "Path to NEF file",
|
||||||
|
Action: cmdargs.EnsureNotEmpty("in"),
|
||||||
|
},
|
||||||
|
&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{
|
||||||
|
{
|
||||||
|
Name: "add-group",
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
// initSmartContract initializes a given directory with some boiler plate code.
|
||||||
|
func initSmartContract(ctx *cli.Context) error {
|
||||||
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
contractName := ctx.String("name")
|
||||||
|
|
||||||
|
// Check if the file already exists, if yes, exit
|
||||||
|
if _, err := os.Stat(contractName); err == nil {
|
||||||
|
return cli.Exit(errFileExist, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
basePath := contractName
|
||||||
|
contractName = filepath.Base(contractName)
|
||||||
|
fileName := "main.go"
|
||||||
|
|
||||||
|
// create base directory
|
||||||
|
if err := os.Mkdir(basePath, os.ModePerm); err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
m := ProjectConfig{
|
||||||
|
Name: contractName,
|
||||||
|
SourceURL: "http://example.com/",
|
||||||
|
SupportedStandards: []string{},
|
||||||
|
SafeMethods: []string{},
|
||||||
|
Events: []compiler.HybridEvent{
|
||||||
|
{
|
||||||
|
Name: "Hello world!",
|
||||||
|
Parameters: []compiler.HybridParameter{
|
||||||
|
{
|
||||||
|
Parameter: manifest.Parameter{
|
||||||
|
Name: "args",
|
||||||
|
Type: smartcontract.ArrayType,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
Permissions: []permission{permission(*manifest.NewPermission(manifest.PermissionWildcard))},
|
||||||
|
}
|
||||||
|
b, err := yaml.Marshal(m)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
if err := os.WriteFile(filepath.Join(basePath, "neo-go.yml"), b, 0644); err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
ver := ModVersion
|
||||||
|
if ver == "" {
|
||||||
|
ver = "latest"
|
||||||
|
}
|
||||||
|
|
||||||
|
gm := []byte("module " + contractName + `
|
||||||
|
|
||||||
|
go 1.24
|
||||||
|
|
||||||
|
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.Exit(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
data := []byte(fmt.Sprintf(smartContractTmpl, contractName))
|
||||||
|
if err := os.WriteFile(filepath.Join(basePath, fileName), data, 0644); err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintf(ctx.App.Writer, "Successfully initialized smart contract [%s]\n", contractName)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func contractCompile(ctx *cli.Context) error {
|
||||||
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
src := ctx.String("in")
|
||||||
|
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.Exit(errNoConfFile, 1)
|
||||||
|
}
|
||||||
|
autocomplete := len(manifestFile) == 0 &&
|
||||||
|
len(confFile) == 0 &&
|
||||||
|
len(out) == 0 &&
|
||||||
|
len(bindings) == 0
|
||||||
|
if autocomplete {
|
||||||
|
var root string
|
||||||
|
fileInfo, err := os.Stat(src)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("failed to stat source file or directory: %w", err), 1)
|
||||||
|
}
|
||||||
|
if fileInfo.IsDir() {
|
||||||
|
base := filepath.Base(fileInfo.Name())
|
||||||
|
if base == string(filepath.Separator) {
|
||||||
|
base = "contract"
|
||||||
|
}
|
||||||
|
root = filepath.Join(src, base)
|
||||||
|
} else {
|
||||||
|
root = strings.TrimSuffix(src, ".go")
|
||||||
|
}
|
||||||
|
manifestFile = root + ".manifest.json"
|
||||||
|
confFile = root + ".yml"
|
||||||
|
out = root + ".nef"
|
||||||
|
bindings = root + ".bindings.yml"
|
||||||
|
}
|
||||||
|
|
||||||
|
o := &compiler.Options{
|
||||||
|
Outfile: out,
|
||||||
|
|
||||||
|
DebugInfo: debugFile,
|
||||||
|
ManifestFile: manifestFile,
|
||||||
|
BindingsFile: bindings,
|
||||||
|
|
||||||
|
NoStandardCheck: ctx.Bool("no-standards"),
|
||||||
|
NoEventsCheck: ctx.Bool("no-events"),
|
||||||
|
NoPermissionsCheck: ctx.Bool("no-permissions"),
|
||||||
|
|
||||||
|
GuessEventTypes: ctx.Bool("guess-eventtypes"),
|
||||||
|
}
|
||||||
|
|
||||||
|
if len(confFile) != 0 {
|
||||||
|
conf, err := ParseContractConfig(confFile)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
o.Name = conf.Name
|
||||||
|
o.SourceURL = conf.SourceURL
|
||||||
|
o.ContractEvents = conf.Events
|
||||||
|
o.DeclaredNamedTypes = conf.NamedTypes
|
||||||
|
o.ContractSupportedStandards = conf.SupportedStandards
|
||||||
|
o.Permissions = make([]manifest.Permission, len(conf.Permissions))
|
||||||
|
for i := range conf.Permissions {
|
||||||
|
o.Permissions[i] = manifest.Permission(conf.Permissions[i])
|
||||||
|
}
|
||||||
|
o.SafeMethods = conf.SafeMethods
|
||||||
|
o.Overloads = conf.Overloads
|
||||||
|
}
|
||||||
|
|
||||||
|
result, err := compiler.CompileAndSave(src, o)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
if ctx.Bool("verbose") {
|
||||||
|
fmt.Fprintln(ctx.App.Writer, hex.EncodeToString(result))
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func calcHash(ctx *cli.Context) error {
|
||||||
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
sender := ctx.Generic("sender").(*flags.Address)
|
||||||
|
p := ctx.String("in")
|
||||||
|
mpath := ctx.String("manifest")
|
||||||
|
|
||||||
|
f, err := os.ReadFile(p)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("can't read .nef file: %w", err), 1)
|
||||||
|
}
|
||||||
|
nefFile, err := nef.FileFromBytes(f)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("can't unmarshal .nef file: %w", err), 1)
|
||||||
|
}
|
||||||
|
manifestBytes, err := os.ReadFile(mpath)
|
||||||
|
if err != nil {
|
||||||
|
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.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
|
||||||
|
}
|
||||||
|
|
||||||
|
func testInvokeFunction(ctx *cli.Context) error {
|
||||||
|
return invokeInternal(ctx, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeFunction(ctx *cli.Context) error {
|
||||||
|
return invokeInternal(ctx, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeInternal(ctx *cli.Context, signAndPush bool) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
exitErr cli.ExitCoder
|
||||||
|
operation string
|
||||||
|
params []any
|
||||||
|
paramsStart = 1
|
||||||
|
scParams []smartcontract.Parameter
|
||||||
|
cosigners []transaction.Signer
|
||||||
|
cosignersOffset = 0
|
||||||
|
)
|
||||||
|
|
||||||
|
args := ctx.Args()
|
||||||
|
if !args.Present() {
|
||||||
|
return cli.Exit(errNoScriptHash, 1)
|
||||||
|
}
|
||||||
|
argsSlice := args.Slice()
|
||||||
|
script, err := flags.ParseAddress(argsSlice[0])
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("incorrect script hash: %w", err), 1)
|
||||||
|
}
|
||||||
|
if len(argsSlice) <= 1 {
|
||||||
|
return cli.Exit(errNoMethod, 1)
|
||||||
|
}
|
||||||
|
operation = argsSlice[1]
|
||||||
|
paramsStart++
|
||||||
|
|
||||||
|
if len(argsSlice) > paramsStart {
|
||||||
|
cosignersOffset, scParams, err = cmdargs.ParseParams(argsSlice[paramsStart:], true)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
params = make([]any, len(scParams))
|
||||||
|
for i := range scParams {
|
||||||
|
params[i] = scParams[i]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
cosignersStart := paramsStart + cosignersOffset
|
||||||
|
cosigners, exitErr = cmdargs.GetSignersFromContext(ctx, cosignersStart)
|
||||||
|
if exitErr != nil {
|
||||||
|
return exitErr
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
acc *wallet.Account
|
||||||
|
w *wallet.Wallet
|
||||||
|
)
|
||||||
|
if signAndPush {
|
||||||
|
acc, w, err = options.GetAccFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
}
|
||||||
|
|
||||||
|
return invokeWithArgs(ctx, acc, w, script, operation, params, cosigners)
|
||||||
|
}
|
||||||
|
|
||||||
|
func invokeWithArgs(ctx *cli.Context, acc *wallet.Account, wall *wallet.Wallet, script util.Uint160, operation string, params []any, cosigners []transaction.Signer) error {
|
||||||
|
var (
|
||||||
|
err error
|
||||||
|
signersAccounts []actor.SignerAccount
|
||||||
|
resp *result.Invoke
|
||||||
|
signAndPush = acc != nil
|
||||||
|
inv *invoker.Invoker
|
||||||
|
act *actor.Actor
|
||||||
|
)
|
||||||
|
if signAndPush {
|
||||||
|
signersAccounts, err = cmdargs.GetSignersAccounts(acc, wall, cosigners, transaction.None)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("invalid signers: %w", err), 1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||||
|
defer cancel()
|
||||||
|
if signAndPush {
|
||||||
|
_, act, err = options.GetRPCWithActor(gctx, ctx, signersAccounts)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
inv = &act.Invoker
|
||||||
|
} else {
|
||||||
|
_, inv, err = options.GetRPCWithInvoker(gctx, ctx, cosigners)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
|
out := ctx.String("out")
|
||||||
|
resp, err = inv.Call(script, operation, params...)
|
||||||
|
if err != nil {
|
||||||
|
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.Exit(errText, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
action := "send"
|
||||||
|
process := "Sending"
|
||||||
|
if out != "" {
|
||||||
|
action = "save"
|
||||||
|
process = "Saving"
|
||||||
|
}
|
||||||
|
if !ctx.Bool("force") {
|
||||||
|
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.Exit(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(ctx.App.Writer, string(b))
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
if len(resp.Script) == 0 {
|
||||||
|
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.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")
|
||||||
|
b, err := os.ReadFile(src)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
nefFile, err := nef.FileFromBytes(b)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("failed to restore .nef file: %w", err), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
signers, exitErr := cmdargs.GetSignersFromContext(ctx, 0)
|
||||||
|
if exitErr != nil {
|
||||||
|
return exitErr
|
||||||
|
}
|
||||||
|
|
||||||
|
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||||
|
defer cancel()
|
||||||
|
|
||||||
|
_, inv, err := options.GetRPCWithInvoker(gctx, ctx, signers)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := inv.Run(nefFile.Script)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
b, err = json.MarshalIndent(resp, "", " ")
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
fmt.Fprintln(ctx.App.Writer, string(b))
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// ProjectConfig contains project metadata.
|
||||||
|
type ProjectConfig struct {
|
||||||
|
Name string
|
||||||
|
SourceURL string
|
||||||
|
SafeMethods []string
|
||||||
|
SupportedStandards []string
|
||||||
|
Events []compiler.HybridEvent
|
||||||
|
Permissions []permission
|
||||||
|
Overloads map[string]string `yaml:"overloads,omitempty"`
|
||||||
|
NamedTypes map[string]binding.ExtendedType `yaml:"namedtypes,omitempty"`
|
||||||
|
}
|
||||||
|
|
||||||
|
func inspect(ctx *cli.Context) error {
|
||||||
|
if err := cmdargs.EnsureNone(ctx); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
in := ctx.String("in")
|
||||||
|
compile := ctx.Bool("compile")
|
||||||
|
var (
|
||||||
|
b []byte
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
if compile {
|
||||||
|
b, err = compiler.Compile(in, nil)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("failed to compile: %w", err), 1)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
f, err := os.ReadFile(in)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("failed to read .nef file: %w", err), 1)
|
||||||
|
}
|
||||||
|
nefFile, err := nef.FileFromBytes(f)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("failed to restore .nef file: %w", err), 1)
|
||||||
|
}
|
||||||
|
b = nefFile.Script
|
||||||
|
}
|
||||||
|
v := vm.New()
|
||||||
|
v.LoadScript(b)
|
||||||
|
v.PrintOps(ctx.App.Writer)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// contractDeploy deploys contract.
|
||||||
|
func contractDeploy(ctx *cli.Context) error {
|
||||||
|
nefFile, f, err := readNEFFile(ctx.String("in"))
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
m, manifestBytes, err := readManifest(ctx.String("manifest"), util.Uint160{})
|
||||||
|
if err != nil {
|
||||||
|
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().Slice(), true)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("unable to parse 'data' parameter: %w", err), 1)
|
||||||
|
}
|
||||||
|
if len(data) > 1 {
|
||||||
|
return cli.Exit("'data' should be represented as a single parameter", 1)
|
||||||
|
}
|
||||||
|
if len(data) != 0 {
|
||||||
|
appCallParams = append(appCallParams, data[0])
|
||||||
|
}
|
||||||
|
|
||||||
|
acc, w, err := options.GetAccFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("can't get sender address: %w", err), 1)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
sender := acc.ScriptHash()
|
||||||
|
|
||||||
|
cosigners, sgnErr := cmdargs.GetSignersFromContext(ctx, signOffset)
|
||||||
|
if sgnErr != nil {
|
||||||
|
return err
|
||||||
|
} else if len(cosigners) == 0 {
|
||||||
|
cosigners = []transaction.Signer{{
|
||||||
|
Account: acc.Contract.ScriptHash(),
|
||||||
|
Scopes: transaction.CalledByEntry,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
extErr := invokeWithArgs(ctx, acc, w, management.Hash, "deploy", appCallParams, cosigners)
|
||||||
|
if extErr != nil {
|
||||||
|
return extErr
|
||||||
|
}
|
||||||
|
|
||||||
|
hash := state.CreateContractHash(sender, nefFile.Checksum, m.Name)
|
||||||
|
fmt.Fprintf(ctx.App.Writer, "Contract: %s\n", hash.StringLE())
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// contractUpdate updates an existing smart contract.
|
||||||
|
func contractUpdate(ctx *cli.Context) error {
|
||||||
|
var (
|
||||||
|
paramsStart = 0
|
||||||
|
args []string
|
||||||
|
nefBytes []byte
|
||||||
|
manifestBytes []byte
|
||||||
|
nefParam any
|
||||||
|
manifestParam any
|
||||||
|
)
|
||||||
|
args = ctx.Args().Slice()
|
||||||
|
if len(args) < 1 {
|
||||||
|
return cli.Exit(errNoScriptHash, 1)
|
||||||
|
}
|
||||||
|
contractHash, err := flags.ParseAddress(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("invalid contract hash '%s': %w", args[0], err), 1)
|
||||||
|
}
|
||||||
|
paramsStart++
|
||||||
|
|
||||||
|
if ctx.String("in") != "" {
|
||||||
|
_, nefBytes, err = readNEFFile(ctx.String("in"))
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("failed to read .nef file: %w", err), 1)
|
||||||
|
}
|
||||||
|
nefParam = nefBytes
|
||||||
|
}
|
||||||
|
if ctx.String("manifest") != "" {
|
||||||
|
_, manifestBytes, err = readManifest(ctx.String("manifest"), contractHash)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("failed to read manifest file: %w", err), 1)
|
||||||
|
}
|
||||||
|
manifestParam = manifestBytes
|
||||||
|
}
|
||||||
|
if nefBytes == nil && manifestBytes == nil {
|
||||||
|
return cli.Exit(fmt.Errorf("either manifest or .nef required"), 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
params := []any{nefParam, manifestParam}
|
||||||
|
|
||||||
|
signOffset, data, err := cmdargs.ParseParams(args[paramsStart:], true)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("unable to parse 'data' parameter: %w", err), 1)
|
||||||
|
}
|
||||||
|
if len(data) > 1 {
|
||||||
|
return cli.Exit("'data' should be represented as a single parameter", 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
acc, w, err := options.GetAccFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("can't get sender address: %w", err), 1)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
cosigners, exitErr := cmdargs.GetSignersFromContext(ctx, paramsStart+signOffset)
|
||||||
|
if exitErr != nil {
|
||||||
|
return exitErr
|
||||||
|
}
|
||||||
|
if len(cosigners) == 0 {
|
||||||
|
cosigners = []transaction.Signer{{
|
||||||
|
Account: acc.Contract.ScriptHash(),
|
||||||
|
Scopes: transaction.CalledByEntry,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
if len(data) != 0 {
|
||||||
|
params = append(params, data[0])
|
||||||
|
return invokeWithArgs(ctx, acc, w, contractHash, "update", params, cosigners)
|
||||||
|
}
|
||||||
|
|
||||||
|
// First, try the NEP-22-style update (script, manifest, data)
|
||||||
|
// (or pass nil as “data” for NEP-22 compatibility).
|
||||||
|
params = append(params, nil)
|
||||||
|
err = invokeWithArgs(ctx, acc, w, contractHash, "update", params, cosigners)
|
||||||
|
// If that fails (and --strict isn’t set), fallback to the original format
|
||||||
|
// which expects only (script, manifest) with no extra data.
|
||||||
|
if err == nil || ctx.Bool("strict") ||
|
||||||
|
!strings.Contains(err.Error(), "System.Contract.Call failed: method not found: update/3.") &&
|
||||||
|
!strings.Contains(err.Error(), `Method "update" with 3 parameter(s) doesn't exist in the contract`) {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return invokeWithArgs(ctx, acc, w, contractHash, "update", params[:2], cosigners)
|
||||||
|
}
|
||||||
|
|
||||||
|
// contractDestroy destroys an existing smart contract.
|
||||||
|
func contractDestroy(ctx *cli.Context) error {
|
||||||
|
var (
|
||||||
|
signOffset = 1
|
||||||
|
paramsStart = 0
|
||||||
|
args []string
|
||||||
|
)
|
||||||
|
args = ctx.Args().Slice()
|
||||||
|
if len(args) < 1 {
|
||||||
|
return cli.Exit(errNoScriptHash, 1)
|
||||||
|
}
|
||||||
|
contractHash, err := flags.ParseAddress(args[0])
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("invalid contract hash '%s': %w", args[0], err), 1)
|
||||||
|
}
|
||||||
|
paramsStart++
|
||||||
|
|
||||||
|
acc, w, err := options.GetAccFromContext(ctx)
|
||||||
|
if err != nil {
|
||||||
|
return cli.Exit(fmt.Errorf("can't get sender address: %w", err), 1)
|
||||||
|
}
|
||||||
|
defer w.Close()
|
||||||
|
|
||||||
|
cosigners, exitErr := cmdargs.GetSignersFromContext(ctx, paramsStart+signOffset)
|
||||||
|
if exitErr != nil {
|
||||||
|
return exitErr
|
||||||
|
}
|
||||||
|
if len(cosigners) == 0 {
|
||||||
|
cosigners = []transaction.Signer{{
|
||||||
|
Account: acc.Contract.ScriptHash(),
|
||||||
|
Scopes: transaction.CalledByEntry,
|
||||||
|
}}
|
||||||
|
}
|
||||||
|
|
||||||
|
return invokeWithArgs(ctx, acc, w, contractHash, "destroy", nil, cosigners)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ParseContractConfig reads contract configuration file (.yaml) and returns unmarshalled ProjectConfig.
|
||||||
|
func ParseContractConfig(confFile string) (ProjectConfig, error) {
|
||||||
|
conf := ProjectConfig{}
|
||||||
|
confBytes, err := os.ReadFile(confFile)
|
||||||
|
if err != nil {
|
||||||
|
return conf, cli.Exit(err, 1)
|
||||||
|
}
|
||||||
|
|
||||||
|
err = yaml.Unmarshal(confBytes, &conf)
|
||||||
|
if err != nil {
|
||||||
|
return conf, cli.Exit(fmt.Errorf("bad config: %w", err), 1)
|
||||||
|
}
|
||||||
|
return conf, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func cloneFlag(p *cli.StringFlag, required bool) *cli.StringFlag {
|
||||||
|
f := *p
|
||||||
|
f.Required = required
|
||||||
|
return &f
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,138 @@
|
||||||
|
package smartcontract
|
||||||
|
|
||||||
|
import (
|
||||||
|
"flag"
|
||||||
|
"os"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"github.com/nspcc-dev/neo-go/internal/random"
|
||||||
|
"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/v2"
|
||||||
|
"gopkg.in/yaml.v3"
|
||||||
|
)
|
||||||
|
|
||||||
|
func TestInitSmartContract(t *testing.T) {
|
||||||
|
d := t.TempDir()
|
||||||
|
testWD, err := os.Getwd()
|
||||||
|
require.NoError(t, err)
|
||||||
|
err = os.Chdir(d)
|
||||||
|
require.NoError(t, err)
|
||||||
|
t.Cleanup(func() { require.NoError(t, os.Chdir(testWD)) })
|
||||||
|
contractName := "testContract"
|
||||||
|
|
||||||
|
set := flag.NewFlagSet("flagSet", flag.ExitOnError)
|
||||||
|
set.String("name", contractName, "")
|
||||||
|
ctx := cli.NewContext(cli.NewApp(), set, nil)
|
||||||
|
require.NoError(t, initSmartContract(ctx))
|
||||||
|
dirInfo, err := os.Stat(contractName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.True(t, dirInfo.IsDir())
|
||||||
|
files, err := os.ReadDir(contractName)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 3, len(files))
|
||||||
|
require.Equal(t, "go.mod", files[0].Name())
|
||||||
|
require.Equal(t, "main.go", files[1].Name())
|
||||||
|
require.Equal(t, "neo-go.yml", files[2].Name())
|
||||||
|
main, err := os.ReadFile(contractName + "/" + files[1].Name())
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t,
|
||||||
|
`package `+contractName+`
|
||||||
|
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
|
||||||
|
var notificationName string
|
||||||
|
|
||||||
|
// init initializes notificationName before calling any other smart-contract method
|
||||||
|
func init() {
|
||||||
|
notificationName = "Hello world!"
|
||||||
|
}
|
||||||
|
|
||||||
|
// RuntimeNotify sends runtime notification with "Hello world!" name
|
||||||
|
func RuntimeNotify(args []any) {
|
||||||
|
runtime.Notify(notificationName, args)
|
||||||
|
}`, string(main))
|
||||||
|
|
||||||
|
manifest, err := os.ReadFile(contractName + "/" + files[2].Name())
|
||||||
|
require.NoError(t, err)
|
||||||
|
expected := `name: testContract
|
||||||
|
sourceurl: http://example.com/
|
||||||
|
safemethods: []
|
||||||
|
supportedstandards: []
|
||||||
|
events:
|
||||||
|
- name: Hello world!
|
||||||
|
parameters:
|
||||||
|
- name: args
|
||||||
|
type: Array
|
||||||
|
permissions:
|
||||||
|
- methods: '*'
|
||||||
|
`
|
||||||
|
require.Equal(t, expected, string(manifest))
|
||||||
|
}
|
||||||
|
|
||||||
|
func testPermissionMarshal(t *testing.T, p *manifest.Permission, expected string) {
|
||||||
|
out, err := yaml.Marshal((*permission)(p))
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, expected, string(out))
|
||||||
|
|
||||||
|
t.Run("Unmarshal", func(t *testing.T) {
|
||||||
|
actual := new(permission)
|
||||||
|
require.NoError(t, yaml.Unmarshal(out, actual))
|
||||||
|
require.Equal(t, p, (*manifest.Permission)(actual))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPermissionMarshal(t *testing.T) {
|
||||||
|
t.Run("Wildcard", func(t *testing.T) {
|
||||||
|
p := manifest.NewPermission(manifest.PermissionWildcard)
|
||||||
|
testPermissionMarshal(t, p, "methods: '*'\n")
|
||||||
|
})
|
||||||
|
t.Run("no allowed methods", func(t *testing.T) {
|
||||||
|
p := manifest.NewPermission(manifest.PermissionWildcard)
|
||||||
|
p.Methods.Restrict()
|
||||||
|
testPermissionMarshal(t, p, "methods: []\n")
|
||||||
|
})
|
||||||
|
t.Run("hash", func(t *testing.T) {
|
||||||
|
h := random.Uint160()
|
||||||
|
p := manifest.NewPermission(manifest.PermissionHash, h)
|
||||||
|
testPermissionMarshal(t, p,
|
||||||
|
"hash: "+h.StringLE()+"\n"+
|
||||||
|
"methods: '*'\n")
|
||||||
|
})
|
||||||
|
t.Run("group with some methods", func(t *testing.T) {
|
||||||
|
priv, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
p := manifest.NewPermission(manifest.PermissionGroup, priv.PublicKey())
|
||||||
|
p.Methods.Add("abc")
|
||||||
|
p.Methods.Add("lamao")
|
||||||
|
testPermissionMarshal(t, p,
|
||||||
|
"group: "+priv.PublicKey().StringCompressed()+"\n"+
|
||||||
|
"methods:\n - abc\n - lamao\n")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPermissionUnmarshalInvalid(t *testing.T) {
|
||||||
|
priv, err := keys.NewPrivateKey()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
pub := priv.PublicKey().StringCompressed()
|
||||||
|
u160 := random.Uint160().StringLE()
|
||||||
|
testCases := []string{
|
||||||
|
"hash: []\nmethods: '*'\n", // invalid hash type
|
||||||
|
"hash: notahex\nmethods: '*'\n", // invalid hash
|
||||||
|
"group: []\nmethods: '*'\n", // invalid group type
|
||||||
|
"group: notahex\nmethods: '*'\n", // invalid group
|
||||||
|
"hash: " + u160 + "\n", // missing methods
|
||||||
|
"group: " + pub + "\nhash: " + u160 + "\nmethods: '*'", // hash/group conflict
|
||||||
|
"hash: " + u160 + "\nmethods:\n a: b\n", // invalid methods type
|
||||||
|
"hash: " + u160 + "\nmethods:\n- []\n", // methods array, invalid single
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc, func(t *testing.T) {
|
||||||
|
require.Error(t, yaml.Unmarshal([]byte(tc), new(permission)))
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,91 @@
|
||||||
|
package deploy
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/cli/smartcontract/testdata/deploy/sub"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/contract"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/iterator"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
)
|
||||||
|
|
||||||
|
var key = "key"
|
||||||
|
|
||||||
|
const mgmtKey = "mgmt"
|
||||||
|
|
||||||
|
func _deploy(data any, isUpdate bool) {
|
||||||
|
var value string
|
||||||
|
|
||||||
|
ctx := storage.GetContext()
|
||||||
|
if isUpdate {
|
||||||
|
value = "on update"
|
||||||
|
} else {
|
||||||
|
value = "on create"
|
||||||
|
sh := runtime.GetCallingScriptHash()
|
||||||
|
storage.Put(ctx, mgmtKey, sh)
|
||||||
|
|
||||||
|
if data != nil {
|
||||||
|
arr := data.([]any)
|
||||||
|
for i := 0; i < len(arr)-1; i += 2 {
|
||||||
|
storage.Put(ctx, arr[i], arr[i+1])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
storage.Put(ctx, key, value)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Fail just fails.
|
||||||
|
func Fail() {
|
||||||
|
panic("as expected")
|
||||||
|
}
|
||||||
|
|
||||||
|
// CheckSenderWitness checks sender's witness.
|
||||||
|
func CheckSenderWitness() {
|
||||||
|
tx := runtime.GetScriptContainer()
|
||||||
|
if !runtime.CheckWitness(tx.Sender) {
|
||||||
|
panic("not witnessed")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update updates the contract with a new one.
|
||||||
|
func Update(script, manifest []byte, data any) {
|
||||||
|
ctx := storage.GetReadOnlyContext()
|
||||||
|
mgmt := storage.Get(ctx, mgmtKey).(interop.Hash160)
|
||||||
|
contract.Call(mgmt, "update", contract.All, script, manifest, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValue returns the stored value.
|
||||||
|
func GetValue() string {
|
||||||
|
ctx := storage.GetReadOnlyContext()
|
||||||
|
val1 := storage.Get(ctx, key)
|
||||||
|
val2 := storage.Get(ctx, sub.Key)
|
||||||
|
return val1.(string) + "|" + val2.(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetValueWithKey returns the stored value with the specified key.
|
||||||
|
func GetValueWithKey(key string) string {
|
||||||
|
ctx := storage.GetReadOnlyContext()
|
||||||
|
return storage.Get(ctx, key).(string)
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestFind finds items with the specified prefix.
|
||||||
|
func TestFind(f storage.FindFlags) []any {
|
||||||
|
ctx := storage.GetContext()
|
||||||
|
storage.Put(ctx, "findkey1", "value1")
|
||||||
|
storage.Put(ctx, "findkey2", "value2")
|
||||||
|
|
||||||
|
var result []any
|
||||||
|
iter := storage.Find(ctx, "findkey", f)
|
||||||
|
for iterator.Next(iter) {
|
||||||
|
result = append(result, iterator.Value(iter))
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destroy destroys the contract.
|
||||||
|
func Destroy() {
|
||||||
|
ctx := storage.GetReadOnlyContext()
|
||||||
|
mgmt := storage.Get(ctx, mgmtKey).(interop.Hash160)
|
||||||
|
contract.Call(mgmt, "destroy", contract.All)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,4 @@
|
||||||
|
name: Test deploy
|
||||||
|
permissions:
|
||||||
|
- hash: fffdc93764dbaddd97c48f252a53ea4643faa3fd
|
||||||
|
methods: ["update", "destroy"]
|
||||||
|
|
@ -0,0 +1,14 @@
|
||||||
|
package sub
|
||||||
|
|
||||||
|
import "github.com/nspcc-dev/neo-go/pkg/interop/storage"
|
||||||
|
|
||||||
|
var Key = "sub"
|
||||||
|
|
||||||
|
func _deploy(data any, isUpdate bool) {
|
||||||
|
ctx := storage.GetContext()
|
||||||
|
value := "sub create"
|
||||||
|
if isUpdate {
|
||||||
|
value = "sub update"
|
||||||
|
}
|
||||||
|
storage.Put(ctx, Key, value)
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"name":"Test deploy","abi":{"methods":[{"name":"_initialize","offset":0,"parameters":[],"returntype":"Void","safe":false},{"name":"_deploy","offset":15,"parameters":[{"name":"data","type":"Any"},{"name":"isUpdate","type":"Boolean"}],"returntype":"Void","safe":false},{"name":"checkSenderWitness","offset":201,"parameters":[],"returntype":"Void","safe":false},{"name":"fail","offset":186,"parameters":[],"returntype":"Void","safe":false},{"name":"getValueUpdated","offset":294,"parameters":[],"returntype":"String","safe":false},{"name":"getValueWithKey","offset":343,"parameters":[{"name":"key","type":"String"}],"returntype":"String","safe":false},{"name":"testFind","offset":368,"parameters":[{"name":"f","type":"InteropInterface"}],"returntype":"Array","safe":false},{"name":"update","offset":238,"parameters":[{"name":"script","type":"ByteArray"},{"name":"manifest","type":"ByteArray"},{"name":"data","type":"Any"}],"returntype":"Void","safe":false}],"events":[]},"features":{},"groups":[],"permissions":[{"contract":"0xfffdc93764dbaddd97c48f252a53ea4643faa3fd","methods":["update"]}],"supportedstandards":[],"trusts":[],"extra":null}
|
||||||
|
|
@ -0,0 +1,6 @@
|
||||||
|
package deploy
|
||||||
|
|
||||||
|
// NewMethod in updated contract.
|
||||||
|
func NewMethod() int {
|
||||||
|
return 42
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,52 @@
|
||||||
|
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||||
|
|
||||||
|
// Package gastoken contains RPC wrappers for GasToken contract.
|
||||||
|
package gastoken
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hash contains contract hash.
|
||||||
|
var Hash = util.Uint160{0xcf, 0x76, 0xe2, 0x8b, 0xd0, 0x6, 0x2c, 0x4a, 0x47, 0x8e, 0xe3, 0x55, 0x61, 0x1, 0x13, 0x19, 0xf3, 0xcf, 0xa4, 0xd2}
|
||||||
|
|
||||||
|
// Invoker is used by ContractReader to call various safe methods.
|
||||||
|
type Invoker interface {
|
||||||
|
nep17.Invoker
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actor is used by Contract to call state-changing methods.
|
||||||
|
type Actor interface {
|
||||||
|
Invoker
|
||||||
|
|
||||||
|
nep17.Actor
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContractReader implements safe contract methods.
|
||||||
|
type ContractReader struct {
|
||||||
|
nep17.TokenReader
|
||||||
|
invoker Invoker
|
||||||
|
hash util.Uint160
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contract implements all contract methods.
|
||||||
|
type Contract struct {
|
||||||
|
ContractReader
|
||||||
|
nep17.TokenWriter
|
||||||
|
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{*nep17.NewReader(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 nep17t = nep17.New(actor, hash)
|
||||||
|
return &Contract{ContractReader{nep17t.TokenReader, actor, hash}, nep17t.TokenWriter, actor, hash}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1 @@
|
||||||
|
{"name":"GasToken","groups":[],"features":{},"supportedstandards":["NEP-17"],"abi":{"methods":[{"name":"balanceOf","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","offset":0,"safe":true},{"name":"decimals","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"symbol","parameters":[],"returntype":"String","offset":14,"safe":true},{"name":"totalSupply","parameters":[],"returntype":"Integer","offset":21,"safe":true},{"name":"transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","offset":28,"safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null}
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
// invalid is an example of a contract which doesn't pass event check.
|
||||||
|
package invalid1
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Notify1 emits a correctly typed event.
|
||||||
|
func Notify1() bool {
|
||||||
|
runtime.Notify("Event", interop.Hash160{1, 2, 3})
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify2 emits an invalid event (ByteString instead of Hash160).
|
||||||
|
func Notify2() bool {
|
||||||
|
runtime.Notify("Event", []byte{1, 2, 3})
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
name: "Invalid example"
|
||||||
|
supportedstandards: []
|
||||||
|
events:
|
||||||
|
- name: Event
|
||||||
|
parameters:
|
||||||
|
- name: address
|
||||||
|
type: Hash160
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
// invalid is an example of a contract which doesn't pass event check.
|
||||||
|
package invalid2
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Notify1 emits a correctly typed event.
|
||||||
|
func Notify1() bool {
|
||||||
|
runtime.Notify("Event", interop.Hash160{1, 2, 3})
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify2 emits an invalid event (extra parameter).
|
||||||
|
func Notify2() bool {
|
||||||
|
runtime.Notify("Event", interop.Hash160{1, 2, 3}, "extra parameter")
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
name: "Invalid example"
|
||||||
|
supportedstandards: []
|
||||||
|
events:
|
||||||
|
- name: Event
|
||||||
|
parameters:
|
||||||
|
- name: address
|
||||||
|
type: Hash160
|
||||||
|
|
@ -0,0 +1,21 @@
|
||||||
|
// invalid is an example of a contract which doesn't pass event check.
|
||||||
|
package invalid3
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Notify1 emits a correctly typed event.
|
||||||
|
func Notify1() bool {
|
||||||
|
runtime.Notify("Event", interop.Hash160{1, 2, 3})
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
// Notify2 emits an invalid event (missing from manifest).
|
||||||
|
func Notify2() bool {
|
||||||
|
runtime.Notify("AnotherEvent", interop.Hash160{1, 2, 3})
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,7 @@
|
||||||
|
name: "Invalid example"
|
||||||
|
supportedstandards: []
|
||||||
|
events:
|
||||||
|
- name: Event
|
||||||
|
parameters:
|
||||||
|
- name: address
|
||||||
|
type: Hash160
|
||||||
|
|
@ -0,0 +1,5 @@
|
||||||
|
package invalid4
|
||||||
|
|
||||||
|
func Verify() bool {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,2 @@
|
||||||
|
name: Test bad source url
|
||||||
|
sourceurl: http://example.com/with/some/huge/path/that/cant/be/ever/normally/reached/but/we/need/to/test/for/it/anyway/because/there/is/some/path/in/the/code/that/does/this/check/and/it/should/be/triggered/to/ensure/proper/coverage/in/this/particular/component/of/our/perfect/compiler
|
||||||
|
|
@ -0,0 +1,511 @@
|
||||||
|
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||||
|
|
||||||
|
// Package nameservice contains RPC wrappers for NameService contract.
|
||||||
|
package nameservice
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"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/rpcclient/nep11"
|
||||||
|
"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"
|
||||||
|
"unicode/utf8"
|
||||||
|
)
|
||||||
|
|
||||||
|
// Hash contains contract hash.
|
||||||
|
var Hash = util.Uint160{0xde, 0x46, 0x5f, 0x5d, 0x50, 0x57, 0xcf, 0x33, 0x28, 0x47, 0x94, 0xc5, 0xcf, 0xc2, 0xc, 0x69, 0x37, 0x1c, 0xac, 0x50}
|
||||||
|
|
||||||
|
// SetAdminEvent represents "SetAdmin" event emitted by the contract.
|
||||||
|
type SetAdminEvent struct {
|
||||||
|
Name string
|
||||||
|
OldAdmin util.Uint160
|
||||||
|
NewAdmin util.Uint160
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewEvent represents "Renew" event emitted by the contract.
|
||||||
|
type RenewEvent struct {
|
||||||
|
Name string
|
||||||
|
OldExpiration *big.Int
|
||||||
|
NewExpiration *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
|
||||||
|
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), 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)
|
||||||
|
return &Contract{ContractReader{nep11ndt.NonDivisibleReader, actor, hash}, nep11ndt.BaseWriter, actor, hash}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Roots invokes `roots` method of contract.
|
||||||
|
func (c *ContractReader) Roots() (uuid.UUID, result.Iterator, error) {
|
||||||
|
return unwrap.SessionIterator(c.invoker.Call(c.hash, "roots"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// RootsExpanded is similar to Roots (uses the same contract
|
||||||
|
// method), but can be useful if the server used doesn't support sessions and
|
||||||
|
// doesn't expand iterators. It creates a script that will get the specified
|
||||||
|
// number of result items from the iterator right in the VM and return them to
|
||||||
|
// you. It's only limited by VM stack and GAS available for RPC invocations.
|
||||||
|
func (c *ContractReader) RootsExpanded(_numOfIteratorItems int) ([]stackitem.Item, error) {
|
||||||
|
return unwrap.Array(c.invoker.CallAndExpandIterator(c.hash, "roots", _numOfIteratorItems))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPrice invokes `getPrice` method of contract.
|
||||||
|
func (c *ContractReader) GetPrice(length *big.Int) (*big.Int, error) {
|
||||||
|
return unwrap.BigInt(c.invoker.Call(c.hash, "getPrice", length))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAvailable invokes `isAvailable` method of contract.
|
||||||
|
func (c *ContractReader) IsAvailable(name string) (bool, error) {
|
||||||
|
return unwrap.Bool(c.invoker.Call(c.hash, "isAvailable", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetRecord invokes `getRecord` method of contract.
|
||||||
|
func (c *ContractReader) GetRecord(name string, typev *big.Int) (string, error) {
|
||||||
|
return unwrap.UTF8String(c.invoker.Call(c.hash, "getRecord", name, typev))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllRecords invokes `getAllRecords` method of contract.
|
||||||
|
func (c *ContractReader) GetAllRecords(name string) (uuid.UUID, result.Iterator, error) {
|
||||||
|
return unwrap.SessionIterator(c.invoker.Call(c.hash, "getAllRecords", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllRecordsExpanded is similar to GetAllRecords (uses the same contract
|
||||||
|
// method), but can be useful if the server used doesn't support sessions and
|
||||||
|
// doesn't expand iterators. It creates a script that will get the specified
|
||||||
|
// number of result items from the iterator right in the VM and return them to
|
||||||
|
// you. It's only limited by VM stack and GAS available for RPC invocations.
|
||||||
|
func (c *ContractReader) GetAllRecordsExpanded(name string, _numOfIteratorItems int) ([]stackitem.Item, error) {
|
||||||
|
return unwrap.Array(c.invoker.CallAndExpandIterator(c.hash, "getAllRecords", _numOfIteratorItems, name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Resolve invokes `resolve` method of contract.
|
||||||
|
func (c *ContractReader) Resolve(name string, typev *big.Int) (string, error) {
|
||||||
|
return unwrap.UTF8String(c.invoker.Call(c.hash, "resolve", name, typev))
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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 string) (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 string) (*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 string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "update", nil, nef, manifest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRoot creates a transaction invoking `addRoot` 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) AddRoot(root string) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "addRoot", root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRootTransaction creates a transaction invoking `addRoot` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) AddRootTransaction(root string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "addRoot", root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRootUnsigned creates a transaction invoking `addRoot` 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) AddRootUnsigned(root string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "addRoot", nil, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPrice creates a transaction invoking `setPrice` 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) SetPrice(priceList []any) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "setPrice", priceList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPriceTransaction creates a transaction invoking `setPrice` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) SetPriceTransaction(priceList []any) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "setPrice", priceList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPriceUnsigned creates a transaction invoking `setPrice` 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) SetPriceUnsigned(priceList []any) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "setPrice", nil, priceList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Contract) scriptForRegister(name string, owner util.Uint160) ([]byte, error) {
|
||||||
|
return smartcontract.CreateCallWithAssertScript(c.hash, "register", name, owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register creates a transaction invoking `register` 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) Register(name string, owner util.Uint160) (util.Uint256, uint32, error) {
|
||||||
|
script, err := c.scriptForRegister(name, owner)
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint256{}, 0, err
|
||||||
|
}
|
||||||
|
return c.actor.SendRun(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterTransaction creates a transaction invoking `register` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) RegisterTransaction(name string, owner util.Uint160) (*transaction.Transaction, error) {
|
||||||
|
script, err := c.scriptForRegister(name, owner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.actor.MakeRun(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterUnsigned creates a transaction invoking `register` 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) RegisterUnsigned(name string, owner util.Uint160) (*transaction.Transaction, error) {
|
||||||
|
script, err := c.scriptForRegister(name, owner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.actor.MakeUnsignedRun(script, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renew creates a transaction invoking `renew` 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) Renew(name string) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "renew", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewTransaction creates a transaction invoking `renew` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) RenewTransaction(name string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "renew", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewUnsigned creates a transaction invoking `renew` 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) RenewUnsigned(name string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "renew", nil, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renew2 creates a transaction invoking `renew` 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) Renew2(name string, years *big.Int) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "renew", name, years)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renew2Transaction creates a transaction invoking `renew` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) Renew2Transaction(name string, years *big.Int) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "renew", name, years)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renew2Unsigned creates a transaction invoking `renew` 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) Renew2Unsigned(name string, years *big.Int) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "renew", nil, name, years)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAdmin creates a transaction invoking `setAdmin` 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) SetAdmin(name string, admin util.Uint160) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "setAdmin", name, admin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAdminTransaction creates a transaction invoking `setAdmin` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) SetAdminTransaction(name string, admin util.Uint160) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "setAdmin", name, admin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAdminUnsigned creates a transaction invoking `setAdmin` 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) SetAdminUnsigned(name string, admin util.Uint160) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "setAdmin", nil, name, admin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRecord creates a transaction invoking `setRecord` 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) SetRecord(name string, typev *big.Int, data string) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "setRecord", name, typev, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRecordTransaction creates a transaction invoking `setRecord` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) SetRecordTransaction(name string, typev *big.Int, data string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "setRecord", name, typev, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRecordUnsigned creates a transaction invoking `setRecord` 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) SetRecordUnsigned(name string, typev *big.Int, data string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "setRecord", nil, name, typev, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRecord creates a transaction invoking `deleteRecord` 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) DeleteRecord(name string, typev *big.Int) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "deleteRecord", name, typev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRecordTransaction creates a transaction invoking `deleteRecord` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) DeleteRecordTransaction(name string, typev *big.Int) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "deleteRecord", name, typev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRecordUnsigned creates a transaction invoking `deleteRecord` 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) DeleteRecordUnsigned(name string, typev *big.Int) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "deleteRecord", nil, name, typev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAdminEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "SetAdmin" name from the provided [result.ApplicationLog].
|
||||||
|
func SetAdminEventsFromApplicationLog(log *result.ApplicationLog) ([]*SetAdminEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*SetAdminEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "SetAdmin" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(SetAdminEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize SetAdminEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to SetAdminEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *SetAdminEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 3 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.Name, err = func(item stackitem.Item) (string, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
return "", errors.New("not a UTF-8 string")
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}(arr[index])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field Name: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
index++
|
||||||
|
e.OldAdmin, 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 OldAdmin: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
index++
|
||||||
|
e.NewAdmin, 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 NewAdmin: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "Renew" name from the provided [result.ApplicationLog].
|
||||||
|
func RenewEventsFromApplicationLog(log *result.ApplicationLog) ([]*RenewEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*RenewEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "Renew" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(RenewEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize RenewEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to RenewEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *RenewEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 3 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.Name, err = func(item stackitem.Item) (string, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
return "", errors.New("not a UTF-8 string")
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}(arr[index])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field Name: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
index++
|
||||||
|
e.OldExpiration, err = arr[index].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field OldExpiration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
index++
|
||||||
|
e.NewExpiration, err = arr[index].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field NewExpiration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,441 @@
|
||||||
|
{
|
||||||
|
"abi" : {
|
||||||
|
"events" : [
|
||||||
|
{
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"name" : "from",
|
||||||
|
"type" : "Hash160"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "to",
|
||||||
|
"type" : "Hash160"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "amount",
|
||||||
|
"type" : "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type" : "ByteArray",
|
||||||
|
"name" : "tokenId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name" : "Transfer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"type" : "String",
|
||||||
|
"name" : "name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type" : "Hash160",
|
||||||
|
"name" : "oldAdmin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type" : "Hash160",
|
||||||
|
"name" : "newAdmin"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name" : "SetAdmin"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "Renew",
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"name" : "name",
|
||||||
|
"type" : "String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type" : "Integer",
|
||||||
|
"name" : "oldExpiration"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "newExpiration",
|
||||||
|
"type" : "Integer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"methods" : [
|
||||||
|
{
|
||||||
|
"safe" : true,
|
||||||
|
"parameters" : [],
|
||||||
|
"name" : "symbol",
|
||||||
|
"returntype" : "String",
|
||||||
|
"offset" : 0
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters" : [],
|
||||||
|
"name" : "decimals",
|
||||||
|
"returntype" : "Integer",
|
||||||
|
"safe" : true,
|
||||||
|
"offset" : 6
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 8,
|
||||||
|
"returntype" : "Integer",
|
||||||
|
"name" : "totalSupply",
|
||||||
|
"parameters" : [],
|
||||||
|
"safe" : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 53,
|
||||||
|
"safe" : true,
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"type" : "ByteArray",
|
||||||
|
"name" : "tokenId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name" : "ownerOf",
|
||||||
|
"returntype" : "Hash160"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"safe" : true,
|
||||||
|
"name" : "properties",
|
||||||
|
"returntype" : "Map",
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"type" : "ByteArray",
|
||||||
|
"name" : "tokenId"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"offset" : 184
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"safe" : true,
|
||||||
|
"returntype" : "Integer",
|
||||||
|
"name" : "balanceOf",
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"name" : "owner",
|
||||||
|
"type" : "Hash160"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"offset" : 341
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"safe" : true,
|
||||||
|
"returntype" : "InteropInterface",
|
||||||
|
"name" : "tokens",
|
||||||
|
"parameters" : [],
|
||||||
|
"offset" : 453
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"safe" : true,
|
||||||
|
"name" : "tokensOf",
|
||||||
|
"returntype" : "InteropInterface",
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"name" : "owner",
|
||||||
|
"type" : "Hash160"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"offset" : 494
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 600,
|
||||||
|
"safe" : false,
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"type" : "Hash160",
|
||||||
|
"name" : "to"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "tokenId",
|
||||||
|
"type" : "ByteArray"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "data",
|
||||||
|
"type" : "Any"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name" : "transfer",
|
||||||
|
"returntype" : "Boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "update",
|
||||||
|
"returntype" : "Void",
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"type" : "ByteArray",
|
||||||
|
"name" : "nef"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "manifest",
|
||||||
|
"type" : "String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"safe" : false,
|
||||||
|
"offset" : 1121
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 1291,
|
||||||
|
"returntype" : "Void",
|
||||||
|
"name" : "addRoot",
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"name" : "root",
|
||||||
|
"type" : "String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"safe" : false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 1725,
|
||||||
|
"safe" : true,
|
||||||
|
"parameters" : [],
|
||||||
|
"returntype" : "InteropInterface",
|
||||||
|
"name" : "roots"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 1757,
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"type" : "Array",
|
||||||
|
"name" : "priceList"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name" : "setPrice",
|
||||||
|
"returntype" : "Void",
|
||||||
|
"safe" : false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 1952,
|
||||||
|
"safe" : true,
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"name" : "length",
|
||||||
|
"type" : "Integer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name" : "getPrice",
|
||||||
|
"returntype" : "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 2017,
|
||||||
|
"safe" : true,
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"type" : "String",
|
||||||
|
"name" : "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name" : "isAvailable",
|
||||||
|
"returntype" : "Boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 2405,
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"type" : "String",
|
||||||
|
"name" : "name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type" : "Hash160",
|
||||||
|
"name" : "owner"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name" : "register",
|
||||||
|
"returntype" : "Boolean",
|
||||||
|
"safe" : false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "renew",
|
||||||
|
"returntype" : "Integer",
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"type" : "String",
|
||||||
|
"name" : "name"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"safe" : false,
|
||||||
|
"offset" : 3113
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 3123,
|
||||||
|
"safe" : false,
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"name" : "name",
|
||||||
|
"type" : "String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "years",
|
||||||
|
"type" : "Integer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name" : "renew",
|
||||||
|
"returntype" : "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 3697,
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"type" : "String",
|
||||||
|
"name" : "name"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "admin",
|
||||||
|
"type" : "Hash160"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name" : "setAdmin",
|
||||||
|
"returntype" : "Void",
|
||||||
|
"safe" : false
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"safe" : false,
|
||||||
|
"returntype" : "Void",
|
||||||
|
"name" : "setRecord",
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"name" : "name",
|
||||||
|
"type" : "String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type" : "Integer",
|
||||||
|
"name" : "type"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "data",
|
||||||
|
"type" : "String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"offset" : 3921
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "getRecord",
|
||||||
|
"returntype" : "String",
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"name" : "name",
|
||||||
|
"type" : "String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type" : "Integer",
|
||||||
|
"name" : "type"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"safe" : true,
|
||||||
|
"offset" : 5877
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"returntype" : "InteropInterface",
|
||||||
|
"name" : "getAllRecords",
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"name" : "name",
|
||||||
|
"type" : "String"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"safe" : true,
|
||||||
|
"offset" : 6201
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"safe" : false,
|
||||||
|
"returntype" : "Void",
|
||||||
|
"name" : "deleteRecord",
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"name" : "name",
|
||||||
|
"type" : "String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type" : "Integer",
|
||||||
|
"name" : "type"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"offset" : 6281
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 6565,
|
||||||
|
"name" : "resolve",
|
||||||
|
"returntype" : "String",
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"name" : "name",
|
||||||
|
"type" : "String"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type" : "Integer",
|
||||||
|
"name" : "type"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"safe" : true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"safe" : false,
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"name" : "data",
|
||||||
|
"type" : "Any"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "update",
|
||||||
|
"type" : "Boolean"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name" : "_deploy",
|
||||||
|
"returntype" : "Void",
|
||||||
|
"offset" : 7152
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 7514,
|
||||||
|
"parameters" : [],
|
||||||
|
"returntype" : "Void",
|
||||||
|
"name" : "_initialize",
|
||||||
|
"safe" : false
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"supportedstandards" : [
|
||||||
|
"NEP-11"
|
||||||
|
],
|
||||||
|
"permissions" : [
|
||||||
|
{
|
||||||
|
"contract" : "0x726cb6e0cd8628a1350a611384688911ab75f51b",
|
||||||
|
"methods" : [
|
||||||
|
"ripemd160"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"contract" : "0xacce6fd80d44e1796aa0c2c625e9e4e0ce39efc0",
|
||||||
|
"methods" : [
|
||||||
|
"atoi",
|
||||||
|
"deserialize",
|
||||||
|
"serialize",
|
||||||
|
"stringSplit"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"contract" : "0xef4073a0f2b305a38ec4050e4d3d28bc40ea63f5",
|
||||||
|
"methods" : [
|
||||||
|
"getCommittee"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"contract" : "0xfffdc93764dbaddd97c48f252a53ea4643faa3fd",
|
||||||
|
"methods" : [
|
||||||
|
"getContract",
|
||||||
|
"update"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"methods" : [
|
||||||
|
"onNEP11Payment"
|
||||||
|
],
|
||||||
|
"contract" : "*"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"features" : {},
|
||||||
|
"name" : "NameService",
|
||||||
|
"trusts" : [],
|
||||||
|
"extra" : {
|
||||||
|
"Author" : "The Neo Project",
|
||||||
|
"Description" : "Neo Name Service",
|
||||||
|
"Email" : "dev@neo.org"
|
||||||
|
},
|
||||||
|
"groups" : []
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,339 @@
|
||||||
|
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||||
|
|
||||||
|
// Package nextoken contains RPC wrappers for NEX Token contract.
|
||||||
|
package nextoken
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||||
|
"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/nep17"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||||
|
"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{0xa8, 0x1a, 0xa1, 0xf0, 0x4b, 0xf, 0xdc, 0x4a, 0xa2, 0xce, 0xd5, 0xbf, 0xc6, 0x22, 0xcf, 0xe8, 0x9, 0x7f, 0xa6, 0xa2}
|
||||||
|
|
||||||
|
// OnMintEvent represents "OnMint" event emitted by the contract.
|
||||||
|
type OnMintEvent struct {
|
||||||
|
From util.Uint160
|
||||||
|
To util.Uint160
|
||||||
|
Amount *big.Int
|
||||||
|
SwapId *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoker is used by ContractReader to call various safe methods.
|
||||||
|
type Invoker interface {
|
||||||
|
nep17.Invoker
|
||||||
|
}
|
||||||
|
|
||||||
|
// Actor is used by Contract to call state-changing methods.
|
||||||
|
type Actor interface {
|
||||||
|
Invoker
|
||||||
|
|
||||||
|
nep17.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 {
|
||||||
|
nep17.TokenReader
|
||||||
|
invoker Invoker
|
||||||
|
hash util.Uint160
|
||||||
|
}
|
||||||
|
|
||||||
|
// Contract implements all contract methods.
|
||||||
|
type Contract struct {
|
||||||
|
ContractReader
|
||||||
|
nep17.TokenWriter
|
||||||
|
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{*nep17.NewReader(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 nep17t = nep17.New(actor, hash)
|
||||||
|
return &Contract{ContractReader{nep17t.TokenReader, actor, hash}, nep17t.TokenWriter, actor, hash}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cap invokes `cap` method of contract.
|
||||||
|
func (c *ContractReader) Cap() (*big.Int, error) {
|
||||||
|
return unwrap.BigInt(c.invoker.Call(c.hash, "cap"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetMinter invokes `getMinter` method of contract.
|
||||||
|
func (c *ContractReader) GetMinter() (*keys.PublicKey, error) {
|
||||||
|
return unwrap.PublicKey(c.invoker.Call(c.hash, "getMinter"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetOwner invokes `getOwner` method of contract.
|
||||||
|
func (c *ContractReader) GetOwner() (util.Uint160, error) {
|
||||||
|
return unwrap.Uint160(c.invoker.Call(c.hash, "getOwner"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TotalMinted invokes `totalMinted` method of contract.
|
||||||
|
func (c *ContractReader) TotalMinted() (*big.Int, error) {
|
||||||
|
return unwrap.BigInt(c.invoker.Call(c.hash, "totalMinted"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeMinter creates a transaction invoking `changeMinter` 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) ChangeMinter(newMinter *keys.PublicKey) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "changeMinter", newMinter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeMinterTransaction creates a transaction invoking `changeMinter` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) ChangeMinterTransaction(newMinter *keys.PublicKey) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "changeMinter", newMinter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeMinterUnsigned creates a transaction invoking `changeMinter` 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) ChangeMinterUnsigned(newMinter *keys.PublicKey) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "changeMinter", nil, newMinter)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeOwner creates a transaction invoking `changeOwner` 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) ChangeOwner(newOwner util.Uint160) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "changeOwner", newOwner)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeOwnerTransaction creates a transaction invoking `changeOwner` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) ChangeOwnerTransaction(newOwner util.Uint160) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "changeOwner", newOwner)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ChangeOwnerUnsigned creates a transaction invoking `changeOwner` 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) ChangeOwnerUnsigned(newOwner util.Uint160) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "changeOwner", nil, newOwner)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxSupply creates a transaction invoking `maxSupply` 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) MaxSupply() (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "maxSupply")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxSupplyTransaction creates a transaction invoking `maxSupply` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) MaxSupplyTransaction() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "maxSupply")
|
||||||
|
}
|
||||||
|
|
||||||
|
// MaxSupplyUnsigned creates a transaction invoking `maxSupply` 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) MaxSupplyUnsigned() (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "maxSupply", nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Mint creates a transaction invoking `mint` 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) Mint(from util.Uint160, to util.Uint160, amount *big.Int, swapId *big.Int, signature []byte, data any) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "mint", from, to, amount, swapId, signature, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MintTransaction creates a transaction invoking `mint` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) MintTransaction(from util.Uint160, to util.Uint160, amount *big.Int, swapId *big.Int, signature []byte, data any) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "mint", from, to, amount, swapId, signature, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// MintUnsigned creates a transaction invoking `mint` 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) MintUnsigned(from util.Uint160, to util.Uint160, amount *big.Int, swapId *big.Int, signature []byte, data any) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "mint", nil, from, to, amount, swapId, signature, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCap creates a transaction invoking `updateCap` 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) UpdateCap(newCap *big.Int) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "updateCap", newCap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCapTransaction creates a transaction invoking `updateCap` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) UpdateCapTransaction(newCap *big.Int) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "updateCap", newCap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateCapUnsigned creates a transaction invoking `updateCap` 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) UpdateCapUnsigned(newCap *big.Int) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "updateCap", nil, newCap)
|
||||||
|
}
|
||||||
|
|
||||||
|
// OnMintEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "OnMint" name from the provided [result.ApplicationLog].
|
||||||
|
func OnMintEventsFromApplicationLog(log *result.ApplicationLog) ([]*OnMintEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*OnMintEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "OnMint" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(OnMintEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize OnMintEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to OnMintEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *OnMintEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 4 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.From, 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 From: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
index++
|
||||||
|
e.To, 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 To: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
index++
|
||||||
|
e.Amount, err = arr[index].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field Amount: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
index++
|
||||||
|
e.SwapId, err = arr[index].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field SwapId: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,275 @@
|
||||||
|
{
|
||||||
|
"name" : "NEX Token",
|
||||||
|
"abi" : {
|
||||||
|
"events" : [
|
||||||
|
{
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"type" : "Hash160",
|
||||||
|
"name" : "from"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "to",
|
||||||
|
"type" : "Hash160"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "amount",
|
||||||
|
"type" : "Integer"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name" : "Transfer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "OnMint",
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"name" : "from",
|
||||||
|
"type" : "Hash160"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type" : "Hash160",
|
||||||
|
"name" : "to"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type" : "Integer",
|
||||||
|
"name" : "amount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "swapId",
|
||||||
|
"type" : "Integer"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"methods" : [
|
||||||
|
{
|
||||||
|
"parameters" : [],
|
||||||
|
"offset" : 0,
|
||||||
|
"name" : "_initialize",
|
||||||
|
"safe" : false,
|
||||||
|
"returntype" : "Void"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"type" : "Any",
|
||||||
|
"name" : "data"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "isUpdate",
|
||||||
|
"type" : "Boolean"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"offset" : 3,
|
||||||
|
"name" : "_deploy",
|
||||||
|
"safe" : false,
|
||||||
|
"returntype" : "Void"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"type" : "Hash160",
|
||||||
|
"name" : "holder"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"offset" : 484,
|
||||||
|
"returntype" : "Integer",
|
||||||
|
"safe" : true,
|
||||||
|
"name" : "balanceOf"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"safe" : true,
|
||||||
|
"returntype" : "Integer",
|
||||||
|
"name" : "cap",
|
||||||
|
"offset" : 1881,
|
||||||
|
"parameters" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "changeMinter",
|
||||||
|
"safe" : false,
|
||||||
|
"returntype" : "Void",
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"name" : "newMinter",
|
||||||
|
"type" : "PublicKey"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"offset" : 1678
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"type" : "Hash160",
|
||||||
|
"name" : "newOwner"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"offset" : 1659,
|
||||||
|
"name" : "changeOwner",
|
||||||
|
"safe" : false,
|
||||||
|
"returntype" : "Void"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 466,
|
||||||
|
"parameters" : [],
|
||||||
|
"safe" : true,
|
||||||
|
"name" : "decimals",
|
||||||
|
"returntype" : "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"returntype" : "Void",
|
||||||
|
"safe" : false,
|
||||||
|
"name" : "destroy",
|
||||||
|
"parameters" : [],
|
||||||
|
"offset" : 1194
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"safe" : true,
|
||||||
|
"returntype" : "PublicKey",
|
||||||
|
"name" : "getMinter",
|
||||||
|
"offset" : 1671,
|
||||||
|
"parameters" : []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"parameters" : [],
|
||||||
|
"offset" : 1652,
|
||||||
|
"name" : "getOwner",
|
||||||
|
"safe" : true,
|
||||||
|
"returntype" : "Hash160"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "maxSupply",
|
||||||
|
"safe" : false,
|
||||||
|
"returntype" : "Integer",
|
||||||
|
"parameters" : [],
|
||||||
|
"offset" : 468
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 1222,
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"name" : "from",
|
||||||
|
"type" : "Hash160"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "to",
|
||||||
|
"type" : "Hash160"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type" : "Integer",
|
||||||
|
"name" : "amount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "swapId",
|
||||||
|
"type" : "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "signature",
|
||||||
|
"type" : "Signature"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "data",
|
||||||
|
"type" : "Any"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"safe" : false,
|
||||||
|
"name" : "mint",
|
||||||
|
"returntype" : "Void"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"safe" : true,
|
||||||
|
"name" : "symbol",
|
||||||
|
"returntype" : "String",
|
||||||
|
"parameters" : [],
|
||||||
|
"offset" : 460
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 1854,
|
||||||
|
"parameters" : [],
|
||||||
|
"name" : "totalMinted",
|
||||||
|
"safe" : true,
|
||||||
|
"returntype" : "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 478,
|
||||||
|
"parameters" : [],
|
||||||
|
"name" : "totalSupply",
|
||||||
|
"safe" : true,
|
||||||
|
"returntype" : "Integer"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 543,
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"name" : "from",
|
||||||
|
"type" : "Hash160"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "to",
|
||||||
|
"type" : "Hash160"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type" : "Integer",
|
||||||
|
"name" : "amount"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "data",
|
||||||
|
"type" : "Any"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"name" : "transfer",
|
||||||
|
"safe" : false,
|
||||||
|
"returntype" : "Boolean"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 1205,
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"type" : "ByteArray",
|
||||||
|
"name" : "nef"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name" : "manifest",
|
||||||
|
"type" : "ByteArray"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"safe" : false,
|
||||||
|
"returntype" : "Void",
|
||||||
|
"name" : "update"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"offset" : 1717,
|
||||||
|
"parameters" : [
|
||||||
|
{
|
||||||
|
"type" : "Integer",
|
||||||
|
"name" : "newCap"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"returntype" : "Void",
|
||||||
|
"safe" : false,
|
||||||
|
"name" : "updateCap"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"supportedstandards" : [
|
||||||
|
"NEP-17"
|
||||||
|
],
|
||||||
|
"extra" : null,
|
||||||
|
"trusts" : [],
|
||||||
|
"features" : {},
|
||||||
|
"groups" : [],
|
||||||
|
"permissions" : [
|
||||||
|
{
|
||||||
|
"methods" : [
|
||||||
|
"onNEP17Payment"
|
||||||
|
],
|
||||||
|
"contract" : "*"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"methods" : [
|
||||||
|
"update",
|
||||||
|
"destroy"
|
||||||
|
],
|
||||||
|
"contract" : "0xfffdc93764dbaddd97c48f252a53ea4643faa3fd"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,63 @@
|
||||||
|
// Code generated by neo-go contract generate-rpcwrapper --manifest <file.json> --out <file.go> [--hash <hash>] [--config <config>]; DO NOT EDIT.
|
||||||
|
|
||||||
|
// Package nonnepxxcontractwithiterators contains RPC wrappers for Non-NEPXX contract with iterators contract.
|
||||||
|
package nonnepxxcontractwithiterators
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"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/util"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
)
|
||||||
|
|
||||||
|
// 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 {
|
||||||
|
Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error)
|
||||||
|
CallAndExpandIterator(contract util.Uint160, method string, maxItems int, params ...any) (*result.Invoke, error)
|
||||||
|
TerminateSession(sessionID uuid.UUID) error
|
||||||
|
TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContractReader implements safe contract methods.
|
||||||
|
type ContractReader struct {
|
||||||
|
invoker Invoker
|
||||||
|
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{invoker, hash}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Tokens invokes `tokens` method of contract.
|
||||||
|
func (c *ContractReader) Tokens() (uuid.UUID, result.Iterator, error) {
|
||||||
|
return unwrap.SessionIterator(c.invoker.Call(c.hash, "tokens"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// TokensExpanded is similar to Tokens (uses the same contract
|
||||||
|
// method), but can be useful if the server used doesn't support sessions and
|
||||||
|
// doesn't expand iterators. It creates a script that will get the specified
|
||||||
|
// number of result items from the iterator right in the VM and return them to
|
||||||
|
// you. It's only limited by VM stack and GAS available for RPC invocations.
|
||||||
|
func (c *ContractReader) TokensExpanded(_numOfIteratorItems int) ([]stackitem.Item, error) {
|
||||||
|
return unwrap.Array(c.invoker.CallAndExpandIterator(c.hash, "tokens", _numOfIteratorItems))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllRecords invokes `getAllRecords` method of contract.
|
||||||
|
func (c *ContractReader) GetAllRecords(name string) (uuid.UUID, result.Iterator, error) {
|
||||||
|
return unwrap.SessionIterator(c.invoker.Call(c.hash, "getAllRecords", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetAllRecordsExpanded is similar to GetAllRecords (uses the same contract
|
||||||
|
// method), but can be useful if the server used doesn't support sessions and
|
||||||
|
// doesn't expand iterators. It creates a script that will get the specified
|
||||||
|
// number of result items from the iterator right in the VM and return them to
|
||||||
|
// you. It's only limited by VM stack and GAS available for RPC invocations.
|
||||||
|
func (c *ContractReader) GetAllRecordsExpanded(name string, _numOfIteratorItems int) ([]stackitem.Item, error) {
|
||||||
|
return unwrap.Array(c.invoker.CallAndExpandIterator(c.hash, "getAllRecords", _numOfIteratorItems, name))
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue