619bbb40c4
Signed-off-by: Evgeniy Stratonikov <evgeniy@nspcc.ru>
325 lines
13 KiB
Markdown
325 lines
13 KiB
Markdown
# NEO-GO smart contract compiler
|
|
|
|
The neo-go compiler compiles Go programs to bytecode that the NEO virtual machine can understand.
|
|
|
|
## Language compatibility
|
|
|
|
The compiler is mostly compatible with regular Go language specification, but
|
|
there are some important deviations that you need to be aware of that make it
|
|
a dialect of Go rather than a complete port of the language:
|
|
* `new()` is not supported, most of the time you can substitute structs with composite literals
|
|
* `make()` is supported for maps and slices with elements of basic types
|
|
* `copy()` is supported only for byte slices, because of underlying `MEMCPY` opcode
|
|
* pointers are supported only for struct literals, one can't take an address
|
|
of an arbitrary variable
|
|
* there is no real distinction between different integer types, all of them
|
|
work as big.Int in Go with a limit of 256 bit in width, so you can use
|
|
`int` for just about anything. This is the way integers work in Neo VM and
|
|
adding proper Go types emulation is considered to be too costly.
|
|
* goroutines, channels and garbage collection are not supported and will
|
|
never be because emulating that aspects of Go runtime on top of Neo VM is
|
|
close to impossible
|
|
* `defer` and `recover` are supported except for cases where panic occurs in
|
|
`return` statement, because this complicates implementation and imposes runtime
|
|
overhead for all contracts. This can easily be mitigated by first storing values
|
|
in variables and returning the result.
|
|
* lambdas are supported, but closures are not.
|
|
* maps are supported, but valid map keys are booleans, integers and strings with length <= 64
|
|
|
|
## VM API (interop layer)
|
|
Compiler translates interop function calls into NEO VM syscalls or (for custom
|
|
functions) into NEO VM instructions. [Refer to
|
|
pkg.go.dev](https://pkg.go.dev/github.com/nspcc-dev/neo-go/pkg/interop)
|
|
for full API documentation. In general it provides the same level of
|
|
functionality as Neo .net Framework library.
|
|
|
|
Compiler provides some helpful builtins in `util` and `convert` packages.
|
|
Refer to them for detailed documentation.
|
|
|
|
`_deploy()` function has a special meaning and is executed when contract is deployed.
|
|
It should return no value and accept single bool argument which will be true on contract update.
|
|
`_deploy()` functions are called for every imported package in the same order as `init()`.
|
|
|
|
## Quick start
|
|
|
|
### Go setup
|
|
|
|
The compiler uses Go parser internally and depends on regular Go compiler
|
|
presence, so make sure you have it installed and set up. On some distributions
|
|
this requires you to set proper `GOROOT` environment variable, like
|
|
```
|
|
export GOROOT=/usr/lib64/go/1.14
|
|
```
|
|
|
|
### Compiling
|
|
|
|
```
|
|
./bin/neo-go contract compile -i contract.go
|
|
```
|
|
|
|
By default the filename will be the name of your .go file with the .nef extension, the file will be located in the same directory where your Go contract is. If you want another location for your compiled contract:
|
|
|
|
```
|
|
./bin/neo-go contract compile -i contract.go --out /Users/foo/bar/contract.nef
|
|
```
|
|
|
|
If you contract is split across multiple files, you must provide a path
|
|
to the directory where package files are contained instead of a single Go file
|
|
(`out.nef` will be used as the default output file in this case):
|
|
```
|
|
./bin/neo-go contract compile -i ./path/to/contract
|
|
```
|
|
|
|
### Debugging
|
|
You can dump the opcodes generated by the compiler with the following command:
|
|
|
|
```
|
|
./bin/neo-go contract inspect -i contract.go -c
|
|
```
|
|
|
|
This will result in something like this:
|
|
|
|
```
|
|
INDEX OPCODE PARAMETER
|
|
0 INITSLOT 0500 ("\x05\x00") <<
|
|
3 PUSH0
|
|
4 REVERSEN
|
|
5 SYSCALL "\x9a\x1f\x19J"
|
|
10 NOP
|
|
11 STLOC0
|
|
12 LDLOC0
|
|
13 PUSH1
|
|
14 REVERSEN
|
|
15 PUSH1
|
|
16 PACK
|
|
17 SYSCALL "\x05\a\x92\x16"
|
|
22 NOP
|
|
23 PUSH0
|
|
24 REVERSEN
|
|
25 SYSCALL "E\x99Z\\"
|
|
30 NOP
|
|
31 STLOC1
|
|
32 LDLOC1
|
|
33 PUSH1
|
|
34 REVERSEN
|
|
35 PUSH1
|
|
36 PACK
|
|
37 SYSCALL "\x05\a\x92\x16"
|
|
42 NOP
|
|
43 PUSH0
|
|
44 REVERSEN
|
|
45 SYSCALL "\x87\xc3\xd2d"
|
|
50 NOP
|
|
51 STLOC2
|
|
52 LDLOC2
|
|
53 PUSH1
|
|
54 REVERSEN
|
|
55 PUSH1
|
|
56 PACK
|
|
57 SYSCALL "\x05\a\x92\x16"
|
|
62 NOP
|
|
63 PUSH0
|
|
64 REVERSEN
|
|
65 SYSCALL "\x1dY\xe1\x19"
|
|
70 NOP
|
|
71 STLOC3
|
|
72 LDLOC3
|
|
73 PUSH1
|
|
74 REVERSEN
|
|
75 PUSH1
|
|
76 PACK
|
|
77 SYSCALL "\x05\a\x92\x16"
|
|
82 NOP
|
|
83 PUSH1
|
|
84 RET
|
|
```
|
|
|
|
#### Neo Smart Contract Debugger support
|
|
|
|
It's possible to debug contracts written in Go using standard [Neo Smart
|
|
Contract Debugger](https://github.com/neo-project/neo-debugger/) which is a
|
|
part of [Neo Blockchain
|
|
Toolkit](https://github.com/neo-project/neo-blockchain-toolkit/). To do that
|
|
you need to generate debug information using `--debug` option, like this:
|
|
|
|
```
|
|
$ ./bin/neo-go contract compile -i contract.go -c contract.yml -m contract.manifest.json -o contract.nef --debug contract.debug.json
|
|
```
|
|
|
|
This file can then be used by debugger and set up to work just like for any
|
|
other supported language.
|
|
|
|
### Deploying
|
|
|
|
Deploying a contract to blockchain with neo-go requires both NEF and JSON
|
|
manifest generated by the compiler from configuration file provided in YAML
|
|
format. To create contract manifest pass YAML file with `-c` parameter and
|
|
specify manifest output file with `-m`:
|
|
```
|
|
./bin/neo-go contract compile -i contract.go -c config.yml -m contract.manifest.json
|
|
```
|
|
|
|
Example YAML file contents:
|
|
```
|
|
name: Contract
|
|
safemethods: []
|
|
supportedstandards: []
|
|
events:
|
|
- name: info
|
|
parameters:
|
|
- name: message
|
|
type: ByteString
|
|
```
|
|
|
|
Then the manifest can be passed to the `deploy` command via `-m` option:
|
|
|
|
```
|
|
$ ./bin/neo-go contract deploy -i contract.nef -m contract.manifest.json -r http://localhost:20331 -w wallet.json
|
|
```
|
|
|
|
Deployment works via an RPC server, an address of which is passed via `-r`
|
|
option and should be signed using a wallet from `-w` option. More details can
|
|
be found in `deploy` command help.
|
|
|
|
#### Config file
|
|
Configuration file contains following options:
|
|
|
|
| Parameter | Description | Example |
|
|
| --- | --- | --- |
|
|
| `name` | Contract name in the manifest. | `"My awesome contract"`
|
|
| `safemethods` | List of methods which don't change contract state, don't emit notifications and are available for anyone to call. | `["balanceOf", "decimals"]`
|
|
| `supportedstandards` | List of standards this contract implements. For example, `NEP-11` or `NEP-17` token standard. This will enable additional checks in compiler. The check can be disabled with `--no-standards` flag. | `["NEP-17"]`
|
|
| `events` | Notifications emitted by this contract. | See [Events](#Events). |
|
|
| `permissions` | Foreign calls allowed for this contract. | See [Permissions](#Permissions). |
|
|
|
|
##### Events
|
|
Each event must have a name and 0 or more parameters. Parameters are specified using their name and type.
|
|
Both event and parameter names must be strings.
|
|
Parameter type can be one of the following:
|
|
|
|
Type in code | Type in config file
|
|
--- | ---
|
|
`bool` | `Boolean`
|
|
`int`, `int64` etc.| `Integer`
|
|
`[]byte` | `ByteArray`
|
|
`string` | `String`
|
|
Any non-byte slice `[]T`| `Array`
|
|
`map[K]V` | `Map`
|
|
`interop.Hash160` | `Hash160`
|
|
`interop.Hash256` | `Hash256`
|
|
`interop.Interface` | `InteropInterface`
|
|
`interop.PublicKey` | `PublicKey`
|
|
`interop.Signature` | `Signature`
|
|
anything else | `Any`
|
|
|
|
`interop.*` types are defined as aliases in `github.com/nspcc-dev/neo-go/pkg/interop` module
|
|
with the sole purpose of correct manifest generation.
|
|
|
|
As an example consider `Transfer` event from `NEP-17` standard:
|
|
```
|
|
- name: Transfer
|
|
parameters:
|
|
- name: from
|
|
type: Hash160
|
|
- name: to
|
|
type: Hash160
|
|
- name: amount
|
|
type: Integer
|
|
```
|
|
|
|
By default compiler performs some sanity checks. Most of the time
|
|
it will report missing events and/or parameter type mismatch.
|
|
Using variable as an event name in code isn't prohibited but will prevent
|
|
compiler from analyzing an event. It is better to use either constant or string literal.
|
|
The check can be disabled with `--no-events` flag.
|
|
|
|
##### Permissions
|
|
Each permission specifies contracts and methods allowed for this permission.
|
|
If contract is not specified in a rule, specified set of methods can be called on any contract.
|
|
By default, no calls are allowed. Simplest permission is to allow everything:
|
|
```
|
|
- methods: '*'
|
|
```
|
|
|
|
Another common case is to allow calling `onNEP17Payment`, which is necessary
|
|
for most of the NEP-17 token implementations:
|
|
```
|
|
- methods: ["onNEP17Payment"]
|
|
```
|
|
|
|
In addition to `methods` permission can have one of these fields:
|
|
1. `hash` contains hash and restricts set of contracts to a single contract.
|
|
2. `group` contains public key and restricts set of contracts to those who
|
|
have corresponding group in their manifest.
|
|
|
|
Consider an example:
|
|
```
|
|
- methods: ["onNEP17Payment"]
|
|
- hash: fffdc93764dbaddd97c48f252a53ea4643faa3fd
|
|
methods: ["start", "stop"]
|
|
- group: 03184b018d6b2bc093e535519732b3fd3f7551c8cffaf4621dd5a0b89482ca66c9
|
|
methods: ["update"]
|
|
```
|
|
|
|
This set of permissions allows calling:
|
|
- `onNEP17Payment` method of any contract
|
|
- `start` and `stop` methods of contract with hash `fffdc93764dbaddd97c48f252a53ea4643faa3fd`
|
|
- `update` method of contract in group with public key `03184b018d6b2bc093e535519732b3fd3f7551c8cffaf4621dd5a0b89482ca66c9`
|
|
|
|
Also note, that native contract must be included here too. For example, if your contract
|
|
transfers NEO/GAS or gets some info from the `Ledger` contract, all of these
|
|
calls must be allowed in permissions.
|
|
|
|
Compiler does its best to ensure correct permissions are specified in config.
|
|
Incorrect permissions will result in runtime invocation failures.
|
|
Using either constant or literal for contract hash and method will allow compiler
|
|
to perform more extensive analysis.
|
|
This check can be disabled with `--no-permissions` flag.
|
|
|
|
#### Neo Express support
|
|
|
|
It's possible to deploy contracts written in Go using [Neo
|
|
Express](https://github.com/neo-project/neo-express) which is a part of [Neo
|
|
Blockchain
|
|
Toolkit](https://github.com/neo-project/neo-blockchain-toolkit/). To do that
|
|
you need to generate a different metadata file using YAML written for
|
|
deployment with neo-go. It's done in the same step with compilation via
|
|
`--config` input parameter and `--abi` output parameter, combined with debug
|
|
support the command line will look like this:
|
|
|
|
```
|
|
$ ./bin/neo-go contract compile -i contract.go --config contract.yml -o contract.nef --debug contract.debug.json --abi contract.abi.json
|
|
```
|
|
|
|
This file can then be used by toolkit to deploy contract the same way
|
|
contracts in other languagues are deployed.
|
|
|
|
|
|
### Invoking
|
|
You can import your contract into the standalone VM and run it there (see [VM
|
|
documentation](vm.md) for more info), but that only works for simple contracts
|
|
that don't use blockchain a lot. For more real contracts you need to deploy
|
|
them first and then do test invocations and regular invocations with `contract
|
|
testinvokefunction` and `contract invokefunction` commands (or their variants,
|
|
see `contract` command help for more details. They all work via RPC, so it's a
|
|
mandatory parameter.
|
|
|
|
Example call (contract `f84d6a337fbc3d3a201d41da99e86b479e7a2554` with method
|
|
`balanceOf` and method's parameter `AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y` using
|
|
given RPC server and wallet and paying 0.00001 GAS for this transaction):
|
|
|
|
```
|
|
$ ./bin/neo-go contract invokefunction -r http://localhost:20331 -w my_wallet.json -g 0.00001 f84d6a337fbc3d3a201d41da99e86b479e7a2554 balanceOf AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y
|
|
```
|
|
|
|
## Smart contract examples
|
|
|
|
Some examples are provided in the [examples directory](../examples). For more
|
|
sophisticated real-world contracts written in Go check out [NeoFS
|
|
contracts](https://github.com/nspcc-dev/neofs-contract/).
|
|
|
|
## How to report compiler bugs
|
|
1. Make a proper testcase (example testcases can be found in the tests folder)
|
|
2. Create an issue on Github
|
|
3. Make a PR with a reference to the created issue, containing the testcase that proves the bug
|
|
4. Either you fix the bug yourself or wait for patch that solves the problem
|