diff --git a/cli/wallet/nep5.go b/cli/wallet/nep5.go index 129fe5aa8..58c2cfb22 100644 --- a/cli/wallet/nep5.go +++ b/cli/wallet/nep5.go @@ -17,6 +17,9 @@ import ( "github.com/urfave/cli" ) +// validUntilBlockIncrement is the number of extra blocks to add to an exported transaction +const validUntilBlockIncrement = 50 + var ( neoToken = wallet.NewToken(client.NeoContractHash, "NEO", "neo", 0) gasToken = wallet.NewToken(client.GasContractHash, "GAS", "gas", 8) @@ -366,6 +369,8 @@ func transferNEP5(ctx *cli.Context) error { } if outFile := ctx.String("out"); outFile != "" { + // avoid fast transaction expiration + tx.ValidUntilBlock += validUntilBlockIncrement priv := acc.PrivateKey() pub := priv.PublicKey() sign := priv.Sign(tx.GetSignedPart()) diff --git a/examples/runtime/runtime.go b/examples/runtime/runtime.go index bb9e4718c..c2f700d3d 100644 --- a/examples/runtime/runtime.go +++ b/examples/runtime/runtime.go @@ -14,10 +14,7 @@ func Main(operation string, args []interface{}) bool { // Log owner upon Verification trigger if trigger == runtime.Verification() { - if runtime.CheckWitness(owner) { - runtime.Log("Verified Owner") - } - return true + return CheckWitness() } // Discerns between log and notify for this test @@ -30,15 +27,33 @@ func Main(operation string, args []interface{}) bool { func handleOperation(operation string, args []interface{}) bool { if operation == "log" { - message := args[0].(string) - runtime.Log(message) - return true + return Log(args) } if operation == "notify" { - runtime.Notify(args[0]) - return true + return Notify(args) } return false } + +// CheckWitness checks owner's witness +func CheckWitness() bool { + if runtime.CheckWitness(owner) { + runtime.Log("Verified Owner") + } + return true +} + +// Log logs given message +func Log(args []interface{}) bool { + message := args[0].(string) + runtime.Log(message) + return true +} + +// Notify notifies about given message +func Notify(args []interface{}) bool { + runtime.Notify(args[0]) + return true +} diff --git a/examples/storage/storage.go b/examples/storage/storage.go index a117f9f3b..bf2c6f522 100644 --- a/examples/storage/storage.go +++ b/examples/storage/storage.go @@ -7,51 +7,72 @@ import ( // Main is a very useful function. func Main(operation string, args []interface{}) interface{} { - ctx := storage.GetContext() - - // Puts value at key if operation == "put" { - if checkArgs(args, 2) { - key := args[0].([]byte) - value := args[1].([]byte) - storage.Put(ctx, key, value) - return key - } + return Put(args) } - // Returns the value at passed key if operation == "get" { - if checkArgs(args, 1) { - key := args[0].([]byte) - return storage.Get(ctx, key) - } + return Get(args) } - // Deletes the value at passed key if operation == "delete" { - key := args[0].([]byte) - storage.Delete(ctx, key) - return true + return Delete(args) } - // Returns an array of key-value pairs with key that matched the passed value if operation == "find" { - if checkArgs(args, 1) { - value := args[0].([]byte) - iter := storage.Find(ctx, value) - result := []string{} - for iterator.Next(iter) { - val := iterator.Value(iter) - key := iterator.Key(iter) - result = append(result, key.(string)+":"+val.(string)) - } - return result - } + return Find(args) } return false } +// Put puts value at key. +func Put(args []interface{}) interface{} { + ctx := storage.GetContext() + if checkArgs(args, 2) { + key := args[0].([]byte) + value := args[1].([]byte) + storage.Put(ctx, key, value) + return key + } + return false +} + +// Get returns the value at passed key. +func Get(args []interface{}) interface{} { + ctx := storage.GetContext() + if checkArgs(args, 1) { + key := args[0].([]byte) + return storage.Get(ctx, key) + } + return false +} + +// Delete deletes the value at passed key. +func Delete(args []interface{}) interface{} { + ctx := storage.GetContext() + key := args[0].([]byte) + storage.Delete(ctx, key) + return true +} + +// Find returns an array of key-value pairs with key that matched the passed value. +func Find(args []interface{}) interface{} { + ctx := storage.GetContext() + if checkArgs(args, 1) { + value := args[0].([]byte) + iter := storage.Find(ctx, value) + result := []string{} + for iterator.Next(iter) { + val := iterator.Value(iter) + key := iterator.Key(iter) + result = append(result, key.(string)+":"+val.(string)) + } + return result + } + return false +} + func checkArgs(args []interface{}, length int) bool { if len(args) == length { return true diff --git a/examples/token/nep5/nep5.go b/examples/token/nep5/nep5.go index 89c92994c..e463ab581 100644 --- a/examples/token/nep5/nep5.go +++ b/examples/token/nep5/nep5.go @@ -38,13 +38,14 @@ func (t Token) GetSupply(ctx storage.Context) interface{} { return getIntFromDB(ctx, []byte(t.CirculationKey)) } -// BalanceOf gets the token balance of a specific address -func (t Token) BalanceOf(ctx storage.Context, hodler []byte) interface{} { - return getIntFromDB(ctx, hodler) +// TBalanceOf gets the token balance of a specific address +// TODO: https://github.com/nspcc-dev/neo-go/issues/1150 +func (t Token) TBalanceOf(ctx storage.Context, holder []byte) interface{} { + return getIntFromDB(ctx, holder) } -// Transfer token from one user to another -func (t Token) Transfer(ctx storage.Context, from []byte, to []byte, amount int) bool { +// TTransfer token from one user to another +func (t Token) TTransfer(ctx storage.Context, from []byte, to []byte, amount int) bool { amountFrom := t.CanTransfer(ctx, from, to, amount) if amountFrom == -1 { return false @@ -104,8 +105,8 @@ func IsUsableAddress(addr []byte) bool { return false } -// Mint initial supply of tokens. -func (t Token) Mint(ctx storage.Context, to []byte) bool { +// TMint initial supply of tokens. +func (t Token) TMint(ctx storage.Context, to []byte) bool { if !IsUsableAddress(t.Owner) { return false } diff --git a/examples/token/token.go b/examples/token/token.go index 15b285294..c7b090383 100644 --- a/examples/token/token.go +++ b/examples/token/token.go @@ -2,7 +2,6 @@ package tokencontract import ( "github.com/nspcc-dev/neo-go/examples/token/nep5" - "github.com/nspcc-dev/neo-go/pkg/interop/storage" "github.com/nspcc-dev/neo-go/pkg/interop/util" ) @@ -12,10 +11,10 @@ const ( multiplier = 100000000 ) -var owner = util.FromAddress("NPAsqZkx9WhNd4P72uhZxBhLinSuNkxfB8") +var owner = util.FromAddress("NMipL5VsNoLUBUJKPKLhxaEbPQVCZnyJyB") -// CreateToken initializes the Token Interface for the Smart Contract to operate with -func CreateToken() nep5.Token { +// createToken initializes the Token Interface for the Smart Contract to operate with +func createToken() nep5.Token { return nep5.Token{ Name: "Awesome NEO Token", Symbol: "ANT", @@ -28,47 +27,91 @@ func CreateToken() nep5.Token { // Main function = contract entry func Main(operation string, args []interface{}) interface{} { - token := CreateToken() - if operation == "name" { - return token.Name + return Name() } if operation == "symbol" { - return token.Symbol + return Symbol() } if operation == "decimals" { - return token.Decimals + return Decimals() } - // The following operations need ctx - ctx := storage.GetContext() - if operation == "totalSupply" { - return token.GetSupply(ctx) + return TotalSupply() } + if operation == "balanceOf" { hodler := args[0].([]byte) - return token.BalanceOf(ctx, hodler) + return BalanceOf(hodler) } - if operation == "transfer" && CheckArgs(args, 3) { + + if operation == "transfer" && checkArgs(args, 3) { from := args[0].([]byte) to := args[1].([]byte) amount := args[2].(int) - return token.Transfer(ctx, from, to, amount) + return Transfer(from, to, amount) } - if operation == "mint" && CheckArgs(args, 1) { + + if operation == "mint" && checkArgs(args, 1) { addr := args[0].([]byte) - return token.Mint(ctx, addr) + return Mint(addr) } return true } -// CheckArgs checks args array against a length indicator -func CheckArgs(args []interface{}, length int) bool { +// checkArgs checks args array against a length indicator +func checkArgs(args []interface{}, length int) bool { if len(args) == length { return true } return false } + +// Name returns the token name +func Name() string { + t := createToken() + return t.Name +} + +// Symbol returns the token symbol +func Symbol() string { + t := createToken() + return t.Symbol +} + +// Decimals returns the token decimals +func Decimals() int { + t := createToken() + return t.Decimals +} + +// TotalSupply returns the token total supply value +func TotalSupply() interface{} { + t := createToken() + ctx := storage.GetContext() + return t.GetSupply(ctx) +} + +// BalanceOf returns the amount of token on the specified address +func BalanceOf(holder []byte) interface{} { + t := createToken() + ctx := storage.GetContext() + return t.TBalanceOf(ctx, holder) +} + +// Transfer token from one user to another +func Transfer(from []byte, to []byte, amount int) bool { + t := createToken() + ctx := storage.GetContext() + return t.TTransfer(ctx, from, to, amount) +} + +// Mint initial supply of tokens +func Mint(to []byte) bool { + t := createToken() + ctx := storage.GetContext() + return t.TMint(ctx, to) +} diff --git a/pkg/compiler/codegen.go b/pkg/compiler/codegen.go index 84a9a0f4e..212919adf 100644 --- a/pkg/compiler/codegen.go +++ b/pkg/compiler/codegen.go @@ -272,7 +272,7 @@ func (c *codegen) convertGlobals(f ast.Node) { }) } -func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) { +func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl, pkg *types.Package) { var ( f *funcScope ok, isLambda bool @@ -290,6 +290,7 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) { c.setLabel(f.label) } else { f = c.newFunc(decl) + f.pkg = pkg } f.rng.Start = uint16(c.prog.Len()) @@ -348,7 +349,7 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) { if !isLambda { for _, f := range c.lambda { - c.convertFuncDecl(file, f.decl) + c.convertFuncDecl(file, f.decl, pkg) } c.lambda = make(map[string]*funcScope) } @@ -1433,7 +1434,7 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error { c.traverseGlobals(mainFile) // convert the entry point first. - c.convertFuncDecl(mainFile, main) + c.convertFuncDecl(mainFile, main, pkg.Pkg) // sort map keys to generate code deterministically. keys := make([]*types.Package, 0, len(info.program.AllPackages)) @@ -1454,7 +1455,7 @@ func (c *codegen) compile(info *buildInfo, pkg *loader.PackageInfo) error { // Don't convert the function if it's not used. This will save a lot // of bytecode space. if n.Name.Name != mainIdent && funUsage.funcUsed(n.Name.Name) { - c.convertFuncDecl(f, n) + c.convertFuncDecl(f, n, k) } } } diff --git a/pkg/compiler/debug.go b/pkg/compiler/debug.go index 7747cae4f..5c9129710 100644 --- a/pkg/compiler/debug.go +++ b/pkg/compiler/debug.go @@ -28,6 +28,8 @@ type MethodDebugInfo struct { ID string `json:"id"` // Name is the name of the method together with the namespace it belongs to. Name DebugMethodName `json:"name"` + // IsExported defines whether method is exported. + IsExported bool `json:"-"` // Range is the range of smart-contract's opcodes corresponding to the method. Range DebugRange `json:"range"` // Parameters is a list of method's parameters. @@ -134,8 +136,12 @@ func (c *codegen) methodInfoFromScope(name string, scope *funcScope) *MethodDebu } } return &MethodDebugInfo{ - ID: name, - Name: DebugMethodName{Name: name}, + ID: name, + Name: DebugMethodName{ + Name: name, + Namespace: scope.pkg.Name(), + }, + IsExported: scope.decl.Name.IsExported(), Range: scope.rng, Parameters: params, ReturnType: c.scReturnTypeFromScope(scope), @@ -263,7 +269,7 @@ func (m *MethodDebugInfo) ToManifestMethod() (manifest.Method, error) { if err != nil { return result, err } - result.Name = m.Name.Name + result.Name = strings.ToLower(string(m.Name.Name[0])) + m.Name.Name[1:] result.Parameters = parameters result.ReturnType = returnType return result, nil @@ -335,8 +341,9 @@ func parsePairJSON(data []byte, sep string) (string, string, error) { // Note: manifest is taken from the external source, however it can be generated ad-hoc. See #1038. func (di *DebugInfo) convertToManifest(fs smartcontract.PropertyState) (*manifest.Manifest, error) { var ( - entryPoint manifest.Method - err error + entryPoint manifest.Method + mainNamespace string + err error ) for _, method := range di.Methods { if method.Name.Name == mainIdent { @@ -344,15 +351,16 @@ func (di *DebugInfo) convertToManifest(fs smartcontract.PropertyState) (*manifes if err != nil { return nil, err } + mainNamespace = method.Name.Namespace break } } if entryPoint.Name == "" { return nil, errors.New("no Main method was found") } - methods := make([]manifest.Method, 0, len(di.Methods)-1) + methods := make([]manifest.Method, 0) for _, method := range di.Methods { - if method.Name.Name != mainIdent { + if method.Name.Name != mainIdent && method.IsExported && method.Name.Namespace == mainNamespace { mMethod, err := method.ToManifestMethod() if err != nil { return nil, err diff --git a/pkg/compiler/debug_test.go b/pkg/compiler/debug_test.go index c614a670a..107456318 100644 --- a/pkg/compiler/debug_test.go +++ b/pkg/compiler/debug_test.go @@ -18,27 +18,30 @@ func TestCodeGen_DebugInfo(t *testing.T) { func Main(op string) bool { var s string _ = s - res := methodInt(op) - _ = methodString() - _ = methodByteArray() - _ = methodArray() - _ = methodStruct() + res := MethodInt(op) + _ = MethodString() + _ = MethodByteArray() + _ = MethodArray() + _ = MethodStruct() + _ = MethodConcat("a", "b", "c") + _ = unexportedMethod() return res == 42 } -func methodInt(a string) int { +func MethodInt(a string) int { if a == "get42" { return 42 } return 3 } -func methodConcat(a, b string, c string) string{ +func MethodConcat(a, b string, c string) string{ return a + b + c } -func methodString() string { return "" } -func methodByteArray() []byte { return nil } -func methodArray() []bool { return nil } -func methodStruct() struct{} { return struct{}{} } +func MethodString() string { return "" } +func MethodByteArray() []byte { return nil } +func MethodArray() []bool { return nil } +func MethodStruct() struct{} { return struct{}{} } +func unexportedMethod() int { return 1 } ` info, err := getBuildInfo(src) @@ -58,11 +61,12 @@ func methodStruct() struct{} { return struct{}{} } t.Run("return types", func(t *testing.T) { returnTypes := map[string]string{ - "methodInt": "Integer", - "methodConcat": "String", - "methodString": "String", "methodByteArray": "ByteArray", - "methodArray": "Array", "methodStruct": "Struct", - "Main": "Boolean", + "MethodInt": "Integer", + "MethodConcat": "String", + "MethodString": "String", "MethodByteArray": "ByteArray", + "MethodArray": "Array", "MethodStruct": "Struct", + "Main": "Boolean", + "unexportedMethod": "Integer", } for i := range d.Methods { name := d.Methods[i].Name.Name @@ -84,11 +88,11 @@ func methodStruct() struct{} { return struct{}{} } t.Run("param types", func(t *testing.T) { paramTypes := map[string][]DebugParam{ - "methodInt": {{ + "MethodInt": {{ Name: "a", Type: "String", }}, - "methodConcat": { + "MethodConcat": { { Name: "a", Type: "String", @@ -129,7 +133,7 @@ func methodStruct() struct{} { return struct{}{} } ABI: manifest.ABI{ Hash: hash.Hash160(buf), EntryPoint: manifest.Method{ - Name: "Main", + Name: "main", Parameters: []manifest.Parameter{ { Name: "op", @@ -169,6 +173,24 @@ func methodStruct() struct{} { return struct{}{} } Parameters: []manifest.Parameter{}, ReturnType: smartcontract.ArrayType, }, + { + Name: "methodConcat", + Parameters: []manifest.Parameter{ + { + Name: "a", + Type: smartcontract.StringType, + }, + { + Name: "b", + Type: smartcontract.StringType, + }, + { + Name: "c", + Type: smartcontract.StringType, + }, + }, + ReturnType: smartcontract.StringType, + }, }, Events: []manifest.Event{}, },