neo-go/docs/compiler.md
Roman Khimov 94d6b5466f docs: update compiler.md, bring it up to date
And add one more reference to it into the main README.
2020-05-19 13:13:15 +03:00

7.8 KiB

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:

  • make() ane new() are not supported, most of the time you can substitute them with composite literals
  • 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
  • even though panic() is supported, recover() is not, panic shuts the VM down
  • lambdas are not supported
  • global variables can't be changed in functions (#638)
  • it's not possible to rename imported interop packages, they won't work this way (#397, #913)
  • nested selectors are not yet supported (#957)
  • using value variable in range-based loops is not yet supported (#958)

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 for full API documentation. In general it provides the same level of functionality as Neo .net Framework library.

Quick start

Compiling

./bin/neo-go contract compile -i mycontract.go

By default the filename will be the name of your .go file with the .avm 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 mycontract.go --out /Users/foo/bar/contract.avm

Debugging

You can dump the opcodes generated by the compiler with the following command:

./bin/neo-go contract inspect -i mycontract.go

This will result in something like this:

INDEX    OPCODE    DESC
0        0x54      PUSH4
1        0xc5      NEWARRAY
2        0x6b      TOALTSTACK
3        0x1       PUSHBYTES1
3        0x2a      *
5        0x6a      DUPFROMALTSTACK
6        0x0       PUSH0
7        0x52      PUSH2
8        0x7a      ROLL
9        0xc4      SETITEM
10       0x6a      DUPFROMALTSTACK
11       0x0       PUSH0
12       0xc3      PICKITEM
13       0x5a      PUSH10
14       0xa2      GTE
15       0x64      JMPIFNOT
16       0x7       7
16       0x0       0
18       0x51      PUSH1
19       0x6c      FROMALTSTACK
20       0x75      DROP
21       0x66      RET
22       0x0       PUSH0
23       0x6c      FROMALTSTACK
24       0x75      DROP
25       0x66      RET

Neo Smart Contract Debugger support

It's possible to debug contracts written in Go using standard Neo Smart Contract Debugger which is a part of 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 -o contract.avm --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 a configuration file with contract's metadata in YAML format, like the following:

project:
  author: Jack Smith
  email: jack@example.com
  version: 1.0
  name: 'Smart contract'
  description: 'Even smarter than Jack himself'
  hasstorage: true
  hasdynamicinvocation: false
  ispayable: false
  returntype: ByteArray
  parameters: ['String', 'Array']

It's passed to the deploy command via -c option:

$ ./bin/neo-go contract deploy -i contract.avm -c contract.yml -e http://localhost:20331 -w wallet.json -g 0.001

Deployment works via an RPC server, an address of which is passed via -e option and should be signed using a wallet from -w option. More details can be found in deploy command help.

Neo Express support

It's possible to deploy contracts written in Go using Neo Express which is a part of 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.avm --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 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 -e http://localhost:20331 -w my_wallet.json -g 0.00001 f84d6a337fbc3d3a201d41da99e86b479e7a2554 balanceOf AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y

Smart contract examples

Some examples are provided in the examples directory.

Check if the invoker of the contract is the owning address

package mycontract

import (
    "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
    "github.com/nspcc-dev/neo-go/pkg/interop/util"
)

var owner = util.FromAddress("AJX1jGfj3qPBbpAKjY527nPbnrnvSx9nCg") 

func Main() bool {
    isOwner := runtime.CheckWitness(owner)

    if isOwner {
        runtime.Log("invoker is the owner")
        return true
    }

    return false
}

Simple token

package mytoken

import (
	"github.com/nspcc-dev/neo-go/pkg/interop/runtime"
	"github.com/nspcc-dev/neo-go/pkg/interop/storage"
)

var owner = util.FromAddress("AJX1jGfj3qPBbpAKjY527nPbnrnvSx9nCg") 

type Token struct {
	Name        string
	Symbol      string
	TotalSupply int
	Owner       []byte
}

func (t Token) AddToCirculation(amount int) bool {
	ctx := storage.Context()
	inCirc := storage.Get(ctx, "in_circ").(int)
	inCirc += amount
	storage.Put(ctx, "in_circ", inCirc)
	return true
}

func newToken() Token {
	return Token{
		Name:        "your awesome NEO token",
		Symbol:      "YANT",
		TotalSupply: 1000,
		Owner:       owner,
	}
}

func Main(operation string, args []interface{}) bool {
	token := newToken()
	trigger := runtime.GetTrigger()

	if trigger == runtime.Verification() {
		isOwner := runtime.CheckWitness(token.Owner)
		if isOwner {
			return true
		}
		return false
	}

	if trigger == runtime.Application() {
		if operation == "mintTokens" {
			token.AddToCirculation(100)
		}
	}

	return true
}

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