diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index f89af6d9e..d95778773 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -292,7 +292,8 @@ func isSyscall(fun *funcScope) bool { if fun.selector == nil || fun.pkg == nil || !isInteropPath(fun.pkg.Path()) { return false } - return fun.pkg.Name() == "neogointernal" && strings.HasPrefix(fun.name, "Syscall") + return fun.pkg.Name() == "neogointernal" && (strings.HasPrefix(fun.name, "Syscall") || + strings.HasPrefix(fun.name, "Opcode")) } const interopPrefix = "github.com/nspcc-dev/neo-go/pkg/interop" @@ -330,6 +331,5 @@ func canInline(s string) bool { return false } return !strings.HasPrefix(s[len(interopPrefix):], "/neogointernal") && - !strings.HasPrefix(s[len(interopPrefix):], "/util") && - !strings.HasPrefix(s[len(interopPrefix):], "/convert") + !strings.HasPrefix(s[len(interopPrefix):], "/util") } diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 62cbe2f34..466b0d9a3 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -939,7 +939,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { c.emittedEvents[name] = append(c.emittedEvents[name], params) } } - c.convertSyscall(n) + c.convertSyscall(f, n) default: emit.Call(c.prog.BinWriter, opcode.CALLL, f.label) } @@ -1226,6 +1226,10 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { // not the assertion type. case *ast.TypeAssertExpr: ast.Walk(c, n.X) + if c.isCallExprSyscall(n.X) { + return nil + } + goTyp := c.typeOf(n.Type) if canConvert(goTyp.String()) { typ := toNeoType(goTyp) @@ -1246,6 +1250,20 @@ func (c *codegen) packVarArgs(n *ast.CallExpr, typ *types.Signature) int { return varSize } +func (c *codegen) isCallExprSyscall(e ast.Expr) bool { + ce, ok := e.(*ast.CallExpr) + if !ok { + return false + } + sel, ok := ce.Fun.(*ast.SelectorExpr) + if !ok { + return false + } + name, _ := c.getFuncNameFromSelector(sel) + f, ok := c.funcs[name] + return ok && isSyscall(f) +} + // processDefers emits code for `defer` statements. // TRY-related opcodes handle exception as follows: // 1. CATCH block is executed only if exception has occurred. @@ -1530,18 +1548,27 @@ func (c *codegen) getByteArray(expr ast.Expr) []byte { } } -func (c *codegen) convertSyscall(expr *ast.CallExpr) { +func (c *codegen) convertSyscall(f *funcScope, expr *ast.CallExpr) { for _, arg := range expr.Args[1:] { ast.Walk(c, arg) } - c.emitReverse(len(expr.Args) - 1) tv := c.typeAndValueOf(expr.Args[0]) - syscall := constant.StringVal(tv.Value) - emit.Syscall(c.prog.BinWriter, syscall) + name := constant.StringVal(tv.Value) + if strings.HasPrefix(f.name, "Syscall") { + c.emitReverse(len(expr.Args) - 1) + emit.Syscall(c.prog.BinWriter, name) - // This NOP instruction is basically not needed, but if we do, we have a - // one to one matching avm file with neo-python which is very nice for debugging. - emit.Opcodes(c.prog.BinWriter, opcode.NOP) + // This NOP instruction is basically not needed, but if we do, we have a + // one to one matching avm file with neo-python which is very nice for debugging. + emit.Opcodes(c.prog.BinWriter, opcode.NOP) + } else { + op, err := opcode.FromString(name) + if err != nil { + c.prog.Err = fmt.Errorf("invalid opcode: %s", op) + return + } + emit.Opcodes(c.prog.BinWriter, op) + } } // emitSliceHelper emits 3 items on stack: slice, its first index, and its size. @@ -1681,15 +1708,6 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) { c.emitStoreByIndex(varGlobal, c.exceptionIndex) case "delete": emit.Opcodes(c.prog.BinWriter, opcode.REMOVE) - case "ToInteger", "ToByteArray", "ToBool": - typ := stackitem.IntegerT - switch name { - case "ToByteArray": - typ = stackitem.ByteArrayT - case "ToBool": - typ = stackitem.BooleanT - } - c.emitConvert(typ) case "Remove": if !isCompoundSlice(c.typeOf(expr.Args[0])) { c.prog.Err = errors.New("`Remove` supports only non-byte slices") diff --git a/pkg/compiler/constant_test.go b/pkg/compiler/constant_test.go index 42456659a..9b5c7976c 100644 --- a/pkg/compiler/constant_test.go +++ b/pkg/compiler/constant_test.go @@ -40,7 +40,7 @@ func TestByteConstant(t *testing.T) { src := `package foo import "github.com/nspcc-dev/neo-go/pkg/interop/convert" const a byte = 0xFF - func Main() int64 { + func Main() int { x := convert.ToInteger(a) return x+1 }` diff --git a/pkg/compiler/convert_test.go b/pkg/compiler/convert_test.go index 44e4a696b..c50e11d75 100644 --- a/pkg/compiler/convert_test.go +++ b/pkg/compiler/convert_test.go @@ -18,7 +18,7 @@ func getFunctionName(typ string) string { return "Bool" case "[]byte": return "ByteArray" - case "int64": + case "int": return "Integer" } panic("invalid type") @@ -40,12 +40,12 @@ func TestConvert(t *testing.T) { {"bool", "[]byte{0, 1, 0}", true}, {"bool", "[]byte{0}", true}, {"bool", `""`, false}, - {"int64", "true", big.NewInt(1)}, - {"int64", "false", big.NewInt(0)}, - {"int64", "12", big.NewInt(12)}, - {"int64", "0", big.NewInt(0)}, - {"int64", "[]byte{0, 1, 0}", big.NewInt(256)}, - {"int64", "[]byte{0}", big.NewInt(0)}, + {"int", "true", big.NewInt(1)}, + {"int", "false", big.NewInt(0)}, + {"int", "12", big.NewInt(12)}, + {"int", "0", big.NewInt(0)}, + {"int", "[]byte{0, 1, 0}", big.NewInt(256)}, + {"int", "[]byte{0}", big.NewInt(0)}, {"[]byte", "true", []byte{1}}, {"[]byte", "false", []byte{0}}, {"[]byte", "12", []byte{0x0C}}, diff --git a/pkg/compiler/syscall_test.go b/pkg/compiler/syscall_test.go index f5dbd8e35..9630c1f8c 100644 --- a/pkg/compiler/syscall_test.go +++ b/pkg/compiler/syscall_test.go @@ -92,3 +92,105 @@ func TestSyscallInGlobalInit(t *testing.T) { require.NoError(t, v.Run()) require.Equal(t, []byte{1, 2}, v.Estack().Pop().Value()) } + +func TestOpcode(t *testing.T) { + t.Run("1 argument", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal" + func abs(a int) int { + return neogointernal.Opcode1("ABS", a).(int) + } + func Main() int { + return abs(-42) + }` + eval(t, src, big.NewInt(42)) + }) + t.Run("2 arguments", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal" + func add3(a, b, c int) int { + return neogointernal.Opcode2("SUB", a, + neogointernal.Opcode2("SUB", b, c).(int)).(int) + } + func Main() int { + return add3(53, 12, 1) + }` + eval(t, src, big.NewInt(42)) + }) + t.Run("POW", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/math" + func Main() int { + return math.Pow(2, math.Pow(3, 2)) + }` + eval(t, src, big.NewInt(512)) + }) + t.Run("SRQT", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/math" + func Main() int { + return math.Sqrt(math.Sqrt(101)) // == sqrt(10) == 3 + }` + eval(t, src, big.NewInt(3)) + }) + t.Run("SIGN", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/math" + func Main() []int { + signs := make([]int, 3) + signs[0] = math.Sign(-123) + signs[1] = math.Sign(0) + signs[2] = math.Sign(42) + return signs + }` + eval(t, src, []stackitem.Item{ + stackitem.Make(-1), + stackitem.Make(0), + stackitem.Make(1), + }) + }) + t.Run("ABS", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/math" + func Main() int { + return math.Abs(-3) + }` + eval(t, src, big.NewInt(3)) + }) + t.Run("MAX", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/math" + func Main() int { + return math.Max(1, 2) + math.Max(8, 3) + }` + eval(t, src, big.NewInt(10)) + }) + t.Run("MIN", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/math" + func Main() int { + return math.Min(1, 2) + math.Min(8, 3) + }` + eval(t, src, big.NewInt(4)) + }) + t.Run("WITHIN", func(t *testing.T) { + src := `package foo + import "github.com/nspcc-dev/neo-go/pkg/interop/math" + func Main() []bool { + r := make([]bool, 5) + r[0] = math.Within(2, 3, 5) + r[1] = math.Within(3, 3, 5) + r[2] = math.Within(4, 3, 5) + r[3] = math.Within(5, 3, 5) + r[4] = math.Within(6, 3, 5) + return r + }` + eval(t, src, []stackitem.Item{ + stackitem.Make(false), + stackitem.Make(true), + stackitem.Make(true), + stackitem.Make(false), + stackitem.Make(false), + }) + }) +} diff --git a/pkg/interop/contract/contract.go b/pkg/interop/contract/contract.go index 5c5e92887..e37064ce1 100644 --- a/pkg/interop/contract/contract.go +++ b/pkg/interop/contract/contract.go @@ -45,8 +45,8 @@ func CreateStandardAccount(pub interop.PublicKey) []byte { // GetCallFlags returns calling flags which execution context was created with. // This function uses `System.Contract.GetCallFlags` syscall. -func GetCallFlags() int64 { - return neogointernal.Syscall0("System.Contract.GetFlags").(int64) +func GetCallFlags() CallFlag { + return neogointernal.Syscall0("System.Contract.GetFlags").(CallFlag) } // Call executes previously deployed blockchain contract with specified hash diff --git a/pkg/interop/convert/convert.go b/pkg/interop/convert/convert.go index 9cba7ae0f..e4c606a81 100644 --- a/pkg/interop/convert/convert.go +++ b/pkg/interop/convert/convert.go @@ -2,16 +2,16 @@ package convert // ToInteger converts it's argument to an Integer. -func ToInteger(v interface{}) int64 { - return 0 +func ToInteger(v interface{}) int { + return v.(int) } // ToByteArray converts it's argument to a ByteArray. func ToByteArray(v interface{}) []byte { - return nil + return v.([]byte) } // ToBool converts it's argument to a Boolean. func ToBool(v interface{}) bool { - return false + return v.(bool) } diff --git a/pkg/interop/math/math.go b/pkg/interop/math/math.go new file mode 100644 index 000000000..6097bae1c --- /dev/null +++ b/pkg/interop/math/math.go @@ -0,0 +1,43 @@ +package math + +import "github.com/nspcc-dev/neo-go/pkg/interop/neogointernal" + +// Pow returns a^b using POW VM opcode. +// b must be >= 0 and <= 2^31-1. +func Pow(a, b int) int { + return neogointernal.Opcode2("POW", a, b).(int) +} + +// Sqrt returns positive square root of x rounded down. +func Sqrt(x int) int { + return neogointernal.Opcode1("SQRT", x).(int) +} + +// Sign returns: +// +// -1 if x < 0 +// 0 if x == 0 +// +1 if x > 0 +func Sign(a int) int { + return neogointernal.Opcode1("SIGN", a).(int) +} + +// Abs returns absolute value of a. +func Abs(a int) int { + return neogointernal.Opcode1("ABS", a).(int) +} + +// Max returns maximum of a, b. +func Max(a, b int) int { + return neogointernal.Opcode2("MAX", a, b).(int) +} + +// Min returns minimum of a, b. +func Min(a, b int) int { + return neogointernal.Opcode2("MIN", a, b).(int) +} + +// Within returns true if a <= x < b. +func Within(x, a, b int) bool { + return neogointernal.Opcode3("WITHIN", x, a, b).(bool) +} diff --git a/pkg/interop/neogointernal/opcode.go b/pkg/interop/neogointernal/opcode.go new file mode 100644 index 000000000..2e18b936b --- /dev/null +++ b/pkg/interop/neogointernal/opcode.go @@ -0,0 +1,16 @@ +package neogointernal + +// Opcode1 emits opcode with 1 argument. +func Opcode1(op string, arg interface{}) interface{} { + return nil +} + +// Opcode2 emits opcode with 2 arguments. +func Opcode2(op string, arg1, arg2 interface{}) interface{} { + return nil +} + +// Opcode3 emits opcode with 3 arguments. +func Opcode3(op string, arg1, arg2, arg3 interface{}) interface{} { + return nil +} diff --git a/pkg/interop/runtime/runtime.go b/pkg/interop/runtime/runtime.go index a8595317c..8a8b8cb16 100644 --- a/pkg/interop/runtime/runtime.go +++ b/pkg/interop/runtime/runtime.go @@ -62,8 +62,8 @@ func GetTrigger() byte { // GasLeft returns the amount of gas available for the current execution. // This function uses `System.Runtime.GasLeft` syscall. -func GasLeft() int64 { - return neogointernal.Syscall0("System.Runtime.GasLeft").(int64) +func GasLeft() int { + return neogointernal.Syscall0("System.Runtime.GasLeft").(int) } // GetNotifications returns notifications emitted by contract h. diff --git a/pkg/interop/storage/storage.go b/pkg/interop/storage/storage.go index 5526aa96f..c9775ebfa 100644 --- a/pkg/interop/storage/storage.go +++ b/pkg/interop/storage/storage.go @@ -38,6 +38,16 @@ const ( PickField1 FindFlags = 1 << 5 ) +// PutFlags represents flag of `PutEx` syscall. +type PutFlags byte + +const ( + // PutDefault is a storage flag for non-constant items. + PutDefault PutFlags = 0 + // PutConstant is a storage flag for constant items. + PutConstant PutFlags = 0x01 +) + // ConvertContextToReadOnly returns new context from the given one, but with // writing capability turned off, so that you could only invoke Get and Find // using this new Context. If Context is already read-only this function is a @@ -74,7 +84,7 @@ func Put(ctx Context, key interface{}, value interface{}) { // can either be odd for constant storage items or even for variable storage items. // Refer to Put function description for details on how to pass the remaining // arguments. This function uses `System.Storage.PutEx` syscall. -func PutEx(ctx Context, key interface{}, value interface{}, flag int64) { +func PutEx(ctx Context, key interface{}, value interface{}, flag PutFlags) { neogointernal.Syscall4NoReturn("System.Storage.PutEx", ctx, key, value, flag) }