neo-go/docs/compiler.md
Roman Khimov 57bf0810e2 README/docs: add 3.0 notice, update links to relative
Start real 3.0 branch, yay!
2020-04-09 19:46:30 +03:00

4.9 KiB

NEO-GO smart contract compiler

The neo-go compiler compiles Go programs to bytecode that the NEO virtual machine can understand.

Currently supported

Go internals

  • type checking
  • multiple assignments
  • global variables
  • types int, string, byte and booleans
  • struct types + method receives
  • functions
  • composite literals []int, []string, []byte
  • basic if statements
  • binary expressions
  • return statements
  • for loops
  • imports

Go builtins

  • len
  • append

VM API (interop layer)

Compiler translates interop function calls into NEO VM syscalls or (for custom functions) into NEO VM instructions. Refer to GoDoc for full API documentation.

Standard NEO Smart Contract API

  • account
  • asset
  • attribute
  • block
  • blockchain
  • contract
  • engine
  • header
  • input
  • iterator
  • output
  • runtime
  • storage
  • transaction

Custom VM utility helper functions

  • crypto:
    • SHA1
    • SHA256
    • Hash256
    • Hash160
  • enumerator
  • util:
    • Equals (to emit EQUALS opcode, not needed usually)
    • FromAddress(address string) []byte

Not supported

Due to the limitations of the NEO virtual machine, features listed below will not be supported.

  • channels
  • goroutines
  • returning multiple values from functions

Quick start

Compile a smart contract

./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 your smart contract

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

Test invoke a compiled contract

You can simulate a test invocation of your compiled contract by the VM, to know the total gas cost for example, with the following command:

./bin/neo-go contract testinvoke -i mycompiledcontract.avm

Will output something like:

{
  "state": "HALT, BREAK",
  "gas_consumed": "0.006",
  "Stack": [
    {
      "type": "Integer",
      "value": "9"
    }
  ]
}

At the moment this is implemented via RPC call to the remote server.

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