Compiler arrays (#49)
* implemented operation and param flags in the cli invoke cmd. * reverted prev changes and added debug flag for compiling. * change transactionType variable to Type, for package convention * index support for arrays. * implemented builtin (len) for the compiler. * bumped version -> 0.35.0 * updated compiler README and changed invoke to testinvoke.
This commit is contained in:
parent
62ceb0b42c
commit
34bd9d31ac
11 changed files with 270 additions and 81 deletions
2
VERSION
2
VERSION
|
@ -1 +1 @@
|
|||
0.34.1
|
||||
0.35.0
|
||||
|
|
|
@ -35,10 +35,14 @@ func NewCommand() cli.Command {
|
|||
Name: "out, o",
|
||||
Usage: "Output of the compiled contract",
|
||||
},
|
||||
cli.BoolFlag{
|
||||
Name: "debug, d",
|
||||
Usage: "Debug mode will print out additional information after a compiling",
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "invoke",
|
||||
Name: "testinvoke",
|
||||
Usage: "Test an invocation of a smart contract on the blockchain",
|
||||
Action: testInvoke,
|
||||
Flags: []cli.Flag{
|
||||
|
@ -71,12 +75,13 @@ func contractCompile(ctx *cli.Context) error {
|
|||
|
||||
o := &compiler.Options{
|
||||
Outfile: ctx.String("out"),
|
||||
Debug: true,
|
||||
Debug: ctx.Bool("debug"),
|
||||
}
|
||||
|
||||
if err := compiler.CompileAndSave(src, o); err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -85,6 +90,7 @@ func testInvoke(ctx *cli.Context) error {
|
|||
if len(src) == 0 {
|
||||
return cli.NewExitError(errNoInput, 1)
|
||||
}
|
||||
|
||||
b, err := ioutil.ReadFile(src)
|
||||
if err != nil {
|
||||
return cli.NewExitError(err, 1)
|
||||
|
|
|
@ -3,8 +3,6 @@ package transaction
|
|||
// TXType is the type of a transaction.
|
||||
type TXType uint8
|
||||
|
||||
// All processes in NEO system are recorded in transactions.
|
||||
// Valid transaction types.
|
||||
const (
|
||||
MinerType TXType = 0x00
|
||||
IssueType TXType = 0x01
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package rpc
|
||||
|
||||
import "github.com/CityOfZion/neo-go/pkg/smartcontract"
|
||||
|
||||
// GetBlock returns a block by its hash or index/height. If verbose is true
|
||||
// the response will contain a pretty Block object instead of the raw hex string.
|
||||
func (c *Client) GetBlock(indexOrHash interface{}, verbose bool) (*response, error) {
|
||||
|
@ -41,6 +43,20 @@ func (c *Client) InvokeScript(script string) (*InvokeScriptResponse, error) {
|
|||
return resp, nil
|
||||
}
|
||||
|
||||
// InvokeFunction return the results after calling a the smart contract scripthash
|
||||
// with the given operation and parameters.
|
||||
// NOTE: this is test invoke and will not affect the blockchain.
|
||||
func (c *Client) InvokeFunction(script, operation string, params []smartcontract.Parameter) (*InvokeScriptResponse, error) {
|
||||
var (
|
||||
p = newParams(script, operation, params)
|
||||
resp = &InvokeScriptResponse{}
|
||||
)
|
||||
if err := c.performRequest("invokefunction", p, resp); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// SendRawTransaction broadcasts a transaction over the NEO network.
|
||||
// The given hex string needs to be signed with a keypair.
|
||||
// When the result of the response object is true, the TX has successfully
|
||||
|
|
|
@ -15,8 +15,8 @@ type InvokeResult struct {
|
|||
|
||||
// StackParam respresent a stack parameter.
|
||||
type StackParam struct {
|
||||
Type string `json:"type"`
|
||||
Value string `json:"value"`
|
||||
Type string `json:"type"`
|
||||
Value interface{} `json:"value"`
|
||||
}
|
||||
|
||||
// AccountStateResponse holds the getaccountstate response.
|
||||
|
|
|
@ -1,34 +1,33 @@
|
|||
# NEO-GO Compiler
|
||||
# NEO-GO smart contract compiler
|
||||
|
||||
The neo-go compiler compiles Go programs to bytecode that the NEO virtual machine can understand.
|
||||
|
||||
***NOTE:*** The neo-go compiler is under very active development and will be updated on a weekly basis. The API is likely going to change, ***hence do not use this in production environments (mainnet)*** yet.
|
||||
|
||||
For help, questions and discussion feel free to join the [City Of Zion discord](https://discordapp.com/invite/R8v48YA) and hop in the #golang channel. Or reach out to me on twitter [@anthdm](https://twitter.com/anthdm)
|
||||
|
||||
## Currently supported
|
||||
|
||||
### Go internals
|
||||
- type checker
|
||||
- multiple assigns
|
||||
- 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.
|
||||
- binary expressions
|
||||
- return statements
|
||||
- imports
|
||||
|
||||
### Go builtins
|
||||
- len
|
||||
|
||||
### VM API (interop layer)
|
||||
- storage
|
||||
- runtime
|
||||
|
||||
## Not yet implemented
|
||||
- for loops
|
||||
- ranges
|
||||
- builtins (append, len, ..)
|
||||
- range
|
||||
- builtin (append)
|
||||
- some parts of the interop layer (VM API)
|
||||
|
||||
## Not supported
|
||||
|
@ -37,6 +36,66 @@ Due to the limitations of the NEO virtual machine, features listed below will no
|
|||
- goroutines
|
||||
- multiple returns
|
||||
|
||||
## 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 opdump -i mycontract.go
|
||||
```
|
||||
|
||||
This will result in something like this:
|
||||
|
||||
```
|
||||
INDEX OPCODE DESC
|
||||
0 0x52 OpPush2
|
||||
1 0xc5 OpNewArray
|
||||
2 0x6b OpToAltStack
|
||||
3 0x 0 OpPush0
|
||||
4 0x6c OpFromAltStack
|
||||
5 0x76 OpDup
|
||||
6 0x6b OpToAltStack
|
||||
7 0x 0 OpPush0
|
||||
8 0x52 OpPush2
|
||||
9 0x7a OpRoll
|
||||
10 0xc4 OpSetItem
|
||||
```
|
||||
|
||||
### 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
## Smart contract examples
|
||||
|
||||
### Check if the invoker of the contract is the owning address
|
||||
|
@ -124,40 +183,3 @@ func Main(operation string, args []interface{}) bool {
|
|||
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
|
||||
|
||||
## 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 you called the command from. 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 opdump -i mycontract.go
|
||||
```
|
||||
|
||||
This will result in something like this:
|
||||
|
||||
```
|
||||
INDEX OPCODE DESC
|
||||
0 0x52 OpPush2
|
||||
1 0xc5 OpNewArray
|
||||
2 0x6b OpToAltStack
|
||||
3 0x 0 OpPush0
|
||||
4 0x6c OpFromAltStack
|
||||
5 0x76 OpDup
|
||||
6 0x6b OpToAltStack
|
||||
7 0x 0 OpPush0
|
||||
8 0x52 OpPush2
|
||||
9 0x7a OpRoll
|
||||
10 0xc4 OpSetItem
|
||||
```
|
||||
|
|
|
@ -6,10 +6,23 @@ import (
|
|||
"go/types"
|
||||
"log"
|
||||
|
||||
"github.com/CityOfZion/neo-go/pkg/vm"
|
||||
"golang.org/x/tools/go/loader"
|
||||
)
|
||||
|
||||
// typeAndValueForField returns a zero initializd typeAndValue or the given type.Var.
|
||||
var (
|
||||
// Go language builtin functions.
|
||||
builtinFuncs = []string{"len", "append"}
|
||||
|
||||
// VM system calls that have no return value.
|
||||
noRetSyscalls = []string{
|
||||
"Notify", "Log", "Put", "Register", "Delete",
|
||||
"SetVotes", "ContractDestroy", "MerkleRoot", "Hash",
|
||||
"PrevHash", "GetHeader",
|
||||
}
|
||||
)
|
||||
|
||||
// typeAndValueForField returns a zero initialized typeAndValue for the given type.Var.
|
||||
func typeAndValueForField(fld *types.Var) types.TypeAndValue {
|
||||
switch t := fld.Type().(type) {
|
||||
case *types.Basic:
|
||||
|
@ -137,6 +150,18 @@ func analyzeFuncUsage(pkgs map[*types.Package]*loader.PackageInfo) funcUsage {
|
|||
return usage
|
||||
}
|
||||
|
||||
func isBuiltin(expr ast.Expr) bool {
|
||||
if ident, ok := expr.(*ast.Ident); ok {
|
||||
for _, n := range builtinFuncs {
|
||||
if ident.Name == n {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isByteArray(lit *ast.CompositeLit, tInfo *types.Info) bool {
|
||||
if len(lit.Elts) == 0 {
|
||||
return false
|
||||
|
@ -152,3 +177,18 @@ func isByteArray(lit *ast.CompositeLit, tInfo *types.Info) bool {
|
|||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func isSyscall(name string) bool {
|
||||
_, ok := vm.Syscalls[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
// isNoRetSyscall checks if the syscall has a return value.
|
||||
func isNoRetSyscall(name string) bool {
|
||||
for _, s := range noRetSyscalls {
|
||||
if s == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -80,7 +80,10 @@ func (c *codegen) emitLoadLocal(name string) {
|
|||
if pos < 0 {
|
||||
log.Fatalf("cannot load local variable with position: %d", pos)
|
||||
}
|
||||
c.emitLoadLocalPos(pos)
|
||||
}
|
||||
|
||||
func (c *codegen) emitLoadLocalPos(pos int) {
|
||||
emitOpcode(c.prog, vm.Ofromaltstack)
|
||||
emitOpcode(c.prog, vm.Odup)
|
||||
emitOpcode(c.prog, vm.Otoaltstack)
|
||||
|
@ -104,7 +107,7 @@ func (c *codegen) emitStoreLocal(pos int) {
|
|||
emitOpcode(c.prog, vm.Osetitem)
|
||||
}
|
||||
|
||||
func (c *codegen) emitLoadStructField(i int) {
|
||||
func (c *codegen) emitLoadField(i int) {
|
||||
emitInt(c.prog, int64(i))
|
||||
emitOpcode(c.prog, vm.Opickitem)
|
||||
}
|
||||
|
@ -383,7 +386,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
switch fun := n.Fun.(type) {
|
||||
case *ast.Ident:
|
||||
f, ok = c.funcs[fun.Name]
|
||||
if !ok {
|
||||
if !ok && !isBuiltin(n.Fun) {
|
||||
log.Fatalf("could not resolve function %s", fun.Name)
|
||||
}
|
||||
case *ast.SelectorExpr:
|
||||
|
@ -418,7 +421,12 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
// will put them in. ^^
|
||||
emitOpcode(c.prog, vm.Onop)
|
||||
|
||||
if isSyscall(f.name) {
|
||||
// Check builtin first to avoid nil pointer on funcScope!
|
||||
if isBuiltin(n.Fun) {
|
||||
// Use the ident to check, builtins are not in func scopes.
|
||||
// We can be sure builtins are of type *ast.Ident.
|
||||
c.convertBuiltin(n.Fun.(*ast.Ident).Name, n)
|
||||
} else if isSyscall(f.name) {
|
||||
c.convertSyscall(f.name)
|
||||
} else {
|
||||
emitCall(c.prog, vm.Ocall, int16(f.label))
|
||||
|
@ -438,7 +446,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
if strct, ok := typ.(*types.Struct); ok {
|
||||
c.emitLoadLocal(t.Name) // load the struct
|
||||
i := indexOfStruct(strct, n.Sel.Name)
|
||||
c.emitLoadStructField(i) // load the field
|
||||
c.emitLoadField(i) // load the field
|
||||
}
|
||||
default:
|
||||
log.Fatal("nested selectors not supported yet")
|
||||
|
@ -446,7 +454,23 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
|
|||
return nil
|
||||
|
||||
case *ast.UnaryExpr:
|
||||
// fmt.Println(n)
|
||||
// TODO(@anthdm)
|
||||
|
||||
case *ast.IndexExpr:
|
||||
// Walk the expression, this could be either an Ident or SelectorExpr.
|
||||
// This will load local whatever X is.
|
||||
ast.Walk(c, n.X)
|
||||
|
||||
switch n.Index.(type) {
|
||||
case *ast.BasicLit:
|
||||
t := c.typeInfo.Types[n.Index]
|
||||
val, _ := constant.Int64Val(t.Value)
|
||||
c.emitLoadField(int(val))
|
||||
default:
|
||||
ast.Walk(c, n.Index)
|
||||
emitOpcode(c.prog, vm.Opickitem) // just pickitem here
|
||||
}
|
||||
return nil
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
@ -460,6 +484,15 @@ func (c *codegen) convertSyscall(name string) {
|
|||
emitOpcode(c.prog, vm.Onop)
|
||||
}
|
||||
|
||||
func (c *codegen) convertBuiltin(name string, expr *ast.CallExpr) {
|
||||
switch name {
|
||||
case "len":
|
||||
emitOpcode(c.prog, vm.Oarraysize)
|
||||
case "append":
|
||||
log.Fatal("builtin (append) not yet implemented.")
|
||||
}
|
||||
}
|
||||
|
||||
func (c *codegen) convertByteArray(lit *ast.CompositeLit) {
|
||||
buf := make([]byte, len(lit.Elts))
|
||||
for i := 0; i < len(lit.Elts); i++ {
|
||||
|
@ -633,24 +666,3 @@ func (c *codegen) writeJumps() {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
func isSyscall(name string) bool {
|
||||
_, ok := vm.Syscalls[name]
|
||||
return ok
|
||||
}
|
||||
|
||||
var noRetSyscalls = []string{
|
||||
"Notify", "Log", "Put", "Register", "Delete",
|
||||
"SetVotes", "ContractDestroy", "MerkleRoot", "Hash",
|
||||
"PrevHash", "GetHeader",
|
||||
}
|
||||
|
||||
// isNoRetSyscall checks if the syscall has a return value.
|
||||
func isNoRetSyscall(name string) bool {
|
||||
for _, s := range noRetSyscalls {
|
||||
if s == name {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
|
|
@ -23,4 +23,81 @@ var arrayTestCases = []testCase{
|
|||
`,
|
||||
"52c56b06666f6f6261720362617203666f6f53c16c766b00527ac46203006c766b00c3616c7566",
|
||||
},
|
||||
{
|
||||
"array item assign",
|
||||
`
|
||||
package foo
|
||||
func Main() int {
|
||||
x := []int{0, 1, 2}
|
||||
y := x[0]
|
||||
return y
|
||||
}
|
||||
`,
|
||||
"53c56b52510053c16c766b00527ac46c766b00c300c36c766b51527ac46203006c766b51c3616c7566",
|
||||
},
|
||||
{
|
||||
"array item return",
|
||||
`
|
||||
package foo
|
||||
func Main() int {
|
||||
x := []int{0, 1, 2}
|
||||
return x[1]
|
||||
}
|
||||
`,
|
||||
"52c56b52510053c16c766b00527ac46203006c766b00c351c3616c7566",
|
||||
},
|
||||
{
|
||||
"array item in bin expr",
|
||||
`
|
||||
package foo
|
||||
func Main() int {
|
||||
x := []int{0, 1, 2}
|
||||
return x[1] + 10
|
||||
}
|
||||
`,
|
||||
"52c56b52510053c16c766b00527ac46203006c766b00c351c35a93616c7566",
|
||||
},
|
||||
{
|
||||
"array item ident",
|
||||
`
|
||||
package foo
|
||||
func Main() int {
|
||||
x := 1
|
||||
y := []int{0, 1, 2}
|
||||
return y[x]
|
||||
}
|
||||
`,
|
||||
"53c56b516c766b00527ac452510053c16c766b51527ac46203006c766b51c36c766b00c3c3616c7566",
|
||||
},
|
||||
{
|
||||
"array item index with binExpr",
|
||||
`
|
||||
package foo
|
||||
func Main() int {
|
||||
x := 1
|
||||
y := []int{0, 1, 2}
|
||||
return y[x + 1]
|
||||
}
|
||||
`,
|
||||
"53c56b516c766b00527ac452510053c16c766b51527ac46203006c766b51c36c766b00c35193c3616c7566",
|
||||
},
|
||||
{
|
||||
"array item struct",
|
||||
`
|
||||
package foo
|
||||
|
||||
type Bar struct {
|
||||
arr []int
|
||||
}
|
||||
|
||||
func Main() int {
|
||||
b := Bar{
|
||||
arr: []int{0, 1, 2},
|
||||
}
|
||||
x := b.arr[2]
|
||||
return x + 2
|
||||
}
|
||||
`,
|
||||
"53c56b6151c66b52510053c16c766b00527ac46c6c766b00527ac46c766b00c300c352c36c766b51527ac46203006c766b51c35293616c7566",
|
||||
},
|
||||
}
|
||||
|
|
17
pkg/vm/compiler/tests/builtin_test.go
Normal file
17
pkg/vm/compiler/tests/builtin_test.go
Normal file
|
@ -0,0 +1,17 @@
|
|||
package compiler
|
||||
|
||||
var builtinTestCases = []testCase{
|
||||
{
|
||||
"array len",
|
||||
`
|
||||
package foo
|
||||
|
||||
func Main() int {
|
||||
x := []int{0, 1, 2}
|
||||
y := len(x)
|
||||
return y
|
||||
}
|
||||
`,
|
||||
"53c56b52510053c16c766b00527ac46c766b00c361c06c766b51527ac46203006c766b51c3616c7566",
|
||||
},
|
||||
}
|
|
@ -23,6 +23,7 @@ func TestAllCases(t *testing.T) {
|
|||
testCases := []testCase{}
|
||||
|
||||
// The Go language
|
||||
testCases = append(testCases, builtinTestCases...)
|
||||
testCases = append(testCases, assignTestCases...)
|
||||
testCases = append(testCases, arrayTestCases...)
|
||||
testCases = append(testCases, binaryExprTestCases...)
|
||||
|
|
Loading…
Reference in a new issue