diff --git a/.gitignore b/.gitignore index adf7fe80f..b6c1b81c1 100644 --- a/.gitignore +++ b/.gitignore @@ -13,6 +13,7 @@ # Added by CoZ developers vendor/ bin/ +!examples/**/vendor # text editors # vscode @@ -32,3 +33,7 @@ chains/ chain/ blockchain/ blockchains/ + +# patch +*.orig +*.rej diff --git a/README.md b/README.md index d6b06371d..38f245873 100644 --- a/README.md +++ b/README.md @@ -126,7 +126,57 @@ ApplicationConfiguration: ``` ## Writing smart contracts in Go -Golang's development is been moved to a separate repository which you can find here [neo-storm](https://github.com/CityOfZion/neo-storm) +In depth documentation about the **neo-go** compiler and smart contract examples can be found inside the [compiler package](https://github.com/CityOfZion/neo-go/tree/master/pkg/vm/compiler). + +### 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 inspect -i mycontract.go +``` + +This will result in something like this: + +``` +INDEX OPCODE DESC +0 0x54 PUSH4 +1 0xc5 NEWARRAY +2 0x6b TOALTSTACK +3 0x5a PUSH10 +4 0x6a DUPFROMALTSTACK +5 0x0 PUSH0 +6 0x52 PUSH2 +7 0x7a ROLL +8 0xc4 SETITEM +9 0x6a DUPFROMALTSTACK +10 0x0 PUSH0 +11 0xc3 PICKITEM +12 0x5a PUSH10 +13 0xa2 GTE +14 0x64 JMPIFNOT +15 0x7 7 +15 0x0 0 +17 0x51 PUSH1 +18 0x6c FROMALTSTACK +19 0x75 DROP +20 0x66 RET +21 0x0 PUSH0 +22 0x6c FROMALTSTACK +23 0x75 DROP +24 0x66 RET +``` # Contributing diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 57a4a4a2b..333aa01c1 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -1,19 +1,38 @@ package smartcontract import ( + "bufio" + "bytes" "context" "encoding/hex" "encoding/json" + "errors" "fmt" "io/ioutil" + "os" + "path/filepath" "github.com/CityOfZion/neo-go/pkg/rpc" "github.com/CityOfZion/neo-go/pkg/vm/compiler" "github.com/urfave/cli" ) -const ( - errNoInput = "Input file is mandatory and should be passed using -i flag." +var ( + errNoInput = errors.New("No input file was found, specify an input file with the '--in or -i' flag") + errNoSmartContractName = errors.New("No name was provided, specify the '--name or -n' flag") + errFileExist = errors.New("A file with given smart-contract name already exists") +) + +var ( + // smartContractTmpl is written to a file when used with `init` command. + // %s is parsed to be the smartContractName + smartContractTmpl = `package %s + +import "github.com/CityOfZion/neo-go/pkg/interop/runtime" + +func Main(op string, args []interface{}) { + runtime.Notify("Hello world!") +}` ) // NewCommand returns a new contract command. @@ -53,13 +72,28 @@ func NewCommand() cli.Command { }, }, { - Name: "opdump", - Usage: "dump the opcode of a .go file", - Action: contractDumpOpcode, + Name: "init", + Usage: "initialize a new smart-contract in a directory with boiler plate code", + Action: initSmartContract, + Flags: []cli.Flag{ + cli.StringFlag{ + Name: "name, n", + Usage: "name of the smart-contract to be initialized", + }, + cli.BoolFlag{ + Name: "skip-details, skip", + Usage: "skip filling in the projects and contract details", + }, + }, + }, + { + Name: "inspect", + Usage: "creates a user readable dump of the program instructions", + Action: inspect, Flags: []cli.Flag{ cli.StringFlag{ Name: "in, i", - Usage: "Input file for the smart contract", + Usage: "input file of the program", }, }, }, @@ -67,6 +101,45 @@ func NewCommand() cli.Command { } } +// initSmartContract initializes a given directory with some boiler plate code. +func initSmartContract(ctx *cli.Context) error { + contractName := ctx.String("name") + if contractName == "" { + return cli.NewExitError(errNoSmartContractName, 1) + } + + // Check if the file already exists, if yes, exit + if _, err := os.Stat(contractName); err == nil { + return cli.NewExitError(errFileExist, 1) + } + + basePath := contractName + fileName := "main.go" + + // create base directory + if err := os.Mkdir(basePath, os.ModePerm); err != nil { + return cli.NewExitError(err, 1) + } + + // Ask contract information and write a neo-go.yml file unless the -skip-details flag is set. + // TODO: Fix the missing neo-go.yml file with the `init` command when the package manager is in place. + if !ctx.Bool("skip-details") { + details := parseContractDetails() + if err := ioutil.WriteFile(filepath.Join(basePath, "neo-go.yml"), details.toStormFile(), 0644); err != nil { + return cli.NewExitError(err, 1) + } + } + + data := []byte(fmt.Sprintf(smartContractTmpl, contractName)) + if err := ioutil.WriteFile(filepath.Join(basePath, fileName), data, 0644); err != nil { + return cli.NewExitError(err, 1) + } + + fmt.Printf("Successfully initialized smart contract [%s]\n", contractName) + + return nil +} + func contractCompile(ctx *cli.Context) error { src := ctx.String("in") if len(src) == 0 { @@ -99,9 +172,8 @@ func testInvoke(ctx *cli.Context) error { // For now we will hardcode the endpoint. // On the long term the internal VM will run the script. // TODO: remove RPC dependency, hardcoded node. - endpoint := "http://seed5.bridgeprotocol.io:10332" - opts := rpc.ClientOptions{} - client, err := rpc.NewClient(context.TODO(), endpoint, opts) + endpoint := "http://node1.ams2.bridgeprotocol.io:10332" + client, err := rpc.NewClient(context.TODO(), endpoint, rpc.ClientOptions{}) if err != nil { return cli.NewExitError(err, 1) } @@ -122,12 +194,67 @@ func testInvoke(ctx *cli.Context) error { return nil } -func contractDumpOpcode(ctx *cli.Context) error { +type ContractDetails struct { + Author string + Email string + Version string + ProjectName string + Description string +} + +func (d ContractDetails) toStormFile() []byte { + buf := new(bytes.Buffer) + + buf.WriteString("# NEO-GO specific configuration. Do not modify this unless you know what you are doing!\n") + buf.WriteString("neo-go:\n") + buf.WriteString(" version: 1.0\n") + + buf.WriteString("\n") + + buf.WriteString("# Project section contains information about your smart contract\n") + buf.WriteString("project:\n") + buf.WriteString(" author: " + d.Author) + buf.WriteString(" email: " + d.Email) + buf.WriteString(" version: " + d.Version) + buf.WriteString(" name: " + d.ProjectName) + buf.WriteString(" description: " + d.Description) + + buf.WriteString("\n") + + buf.WriteString("# Module section contains a list of imported modules\n") + buf.WriteString("# This will be automatically managed by the neo-go package manager\n") + buf.WriteString("modules: \n") + return buf.Bytes() +} + +func parseContractDetails() ContractDetails { + details := ContractDetails{} + reader := bufio.NewReader(os.Stdin) + + fmt.Print("Author: ") + details.Author, _ = reader.ReadString('\n') + + fmt.Print("Email: ") + details.Email, _ = reader.ReadString('\n') + + fmt.Print("Version: ") + details.Version, _ = reader.ReadString('\n') + + fmt.Print("Project name: ") + details.ProjectName, _ = reader.ReadString('\n') + + fmt.Print("Description: ") + details.Description, _ = reader.ReadString('\n') + + return details +} + +func inspect(ctx *cli.Context) error { src := ctx.String("in") if len(src) == 0 { return cli.NewExitError(errNoInput, 1) } - if err := compiler.DumpOpcode(src); err != nil { + if err := compiler.CompileAndInspect(src); err != nil { return cli.NewExitError(err, 1) } return nil diff --git a/docs/runtime.md b/docs/runtime.md new file mode 100644 index 000000000..e98b3af20 --- /dev/null +++ b/docs/runtime.md @@ -0,0 +1,523 @@ +# Runtime +A brief overview of NEO smart contract API's that can be used in the neo-go framework. + +# Overview +1. [Account]() +2. [Asset]() +3. [Attribute]() +4. [Block]() +5. [Blockchain]() +6. [Contract]() +7. [Crypto]() +8. [Engine]() +9. [Enumerator]() +10. [Iterator]() +11. [Header]() +12. [Input]() +13. [Output]() +14. [Runtime]() +15. [Storage]() +16. [Transaction]() +17. [Util]() + +## Account +#### GetScriptHash +``` +GetScriptHash(a Account) []byte +``` +Returns the script hash of the given account. + +#### GetVotes +``` +GetVotes(a Account) [][]byte +``` +Returns the the votes (a slice of public keys) of the given account. + +#### GetBalance +``` +GetBalance(a Account, assetID []byte) int +``` +Returns the balance of the given asset id for the given account. + +## Asset +#### GetAssetID +``` +GetAssetID(a Asset) []byte +``` +Returns the id of the given asset. + +#### GetAmount +``` +GetAmount(a Asset) int +``` +Returns the amount of the given asset id. + +#### GetAvailable +``` +GetAvailable(a Asset) int +``` +Returns the available amount of the given asset. + +#### GetPrecision +``` +GetPrecision(a Asset) byte +``` +Returns the precision of the given Asset. + +#### GetOwner +``` +GetOwner(a Asset) []byte +``` +Returns the owner of the given asset. + +#### GetAdmin +``` +GetAdmin(a Asset) []byte +``` +Returns the admin of the given asset. + +#### GetIssuer +``` +GetIssuer(a Asset) []byte +``` +Returns the issuer of the given asset. + +#### Create +``` +Create(type byte, name string, amount int, precision byte, owner, admin, issuer []byte) +``` +Creates a new asset on the blockchain. + +#### Renew +``` +Renew(asset Asset, years int) +``` +Renews the given asset as long as the given years. + +## Attribute +#### GetUsage +``` +GetUsage(attr Attribute) []byte +``` +Returns the usage of the given attribute. + +#### GetData +``` +GetData(attr Attribute) []byte +``` +Returns the data of the given attribute. + +## Block +#### GetTransactionCount +``` +GetTransactionCount(b Block) int +``` +Returns the number of transactions that are recorded in the given block. + +#### GetTransactions +``` +GetTransactions(b Block) []transaction.Transaction +``` +Returns a slice of the transactions that are recorded in the given block. + +#### GetTransaction +``` +GetTransaction(b Block, hash []byte) transaction.Transaction +``` +Returns the transaction by the given hash that is recorded in the given block. + +## Blockchain +#### GetHeight +``` +GetHeight() int +``` +Returns the current height of the blockchain. + +#### GetHeader +``` +GetHeader(heightOrHash []interface{}) header.Header +``` +Return the header by the given hash or index. + +#### GetBlock +``` +GetBlock(heightOrHash interface{}) block.Block +``` +Returns the block by the given hash or index. + +#### GetTransaction +``` +GetTransaction(hash []byte) transaction.Transaction +``` +Returns a transaction by the given hash. + +#### GetContract +``` +GetContract(scriptHash []byte) contract.Contract +``` +Returns the contract found by the given script hash. + +#### GetAccount +``` +GetAccount(scriptHash []byte) account.Account +``` +Returns the account found by the given script hash. + +#### GetValiditors +``` +GetValidators() [][]byte +``` +Returns a list of validators public keys. + +#### GetAsset +``` +GetAsset(assetID []byte) asset.Asset +``` +Returns the asset found by the given asset id. + +## Contract +#### GetScript +``` +GetScript(c Contract) []byte +``` +Return the script of the given contract. + +#### IsPayable +``` +IsPayable(c Contract) bool +``` +Returns whether the given contract is payable. + +#### GetStorageContext +``` +GetStorageContext(c Contract) +``` +Returns the storage context of the given contract. + +#### Create +``` +Create( + script []byte, + params []interface{}, + returnType byte, + properties interface{}, + name, + version, + author, + email, + description string) +``` +Creates a new contract on the blockchain. + +#### Migrate +``` +Migrate( + script []byte, + params []interface{}, + returnType byte, + properties interface{}, + name, + version, + author, + email, + description string) +``` +Migrates a contract on the blockchain. + +#### Destroy +``` +Destroy(c Contract) +``` +Deletes the given contract from the blockchain. + +## Crypto +#### SHA1 +``` +SHA1(data []byte) []byte +``` +Computes the sha1 hash of the given bytes + +#### SHA256 +``` +SHA256(data []byte) []byte +``` +Computes the sha256 hash of the given bytes + +#### Hash256 +``` +Hash256(data []byte) []byte +``` +Computes the sha256^2 of the given data. + +#### Hash160 +``` +Hash160(data []byte) []byte) []byte +``` +Computes the ripemd160 over the sha256 hash of the given data. + +## Engine +#### GetScriptContainer +``` +GetScriptContainer() transaction.Transaction +``` +Returns the transaction that is in the context of the VM execution. + +#### GetExecutingScriptHash +``` +GetExecutingScriptHash() []byte +``` +Returns the script hash of the contract that is currently being executed. + +#### GetCallingScriptHash +``` +GetCallingScriptHash() []byte +``` +Returns the script hash of the contract that has started the execution of the current script. + +#### GetEntryScriptHash +``` +GetEntryScriptHash() []byte +``` +Returns the script hash of the contract that started the execution from the start. + +## Enumerator +#### Create +``` +Create(items []inteface{}) Enumerator +``` +Create a enumerator from the given items. + +#### Next +``` +Next(e Enumerator) interface{} +``` +Returns the next item from the given enumerator. + +#### Value +``` +Value(e Enumerator) interface{} +``` +Returns the enumerator value. + +## Iterator +#### Create +``` +Create(items []inteface{}) Iterator +``` +Creates an iterator from the given items. + +#### Key +``` +Key(it Iterator) interface{} +``` +Return the key from the given iterator. + +#### Keys +``` +Keys(it Iterator) []interface{} +``` +Returns the iterator's keys + +#### Values +``` +Values(it Iterator) []interface{} +``` +Returns the iterator's values + +## Header +#### GetIndex +``` +GetIndex(h Header) int +``` +Returns the height of the given header. + +#### GetHash +``` +GetHash(h Header) []byte +``` +Returns the hash of the given header. + +#### GetPrevHash +``` +GetPrevhash(h Header) []byte +``` +Returns the previous hash of the given header. + +#### GetTimestamp +``` +GetTimestamp(h Header) int +``` +Returns the timestamp of the given header. + +#### GetVersion +``` +GetVersion(h Header) int +``` +Returns the version of the given header. + +#### GetMerkleroot +``` +GetMerkleRoot(h Header) []byte +``` +Returns the merkle root of the given header. + +#### GetConsensusData +``` +GetConsensusData(h Header) int +``` +Returns the consensus data of the given header. + +#### GetNextConsensus +``` +GetNextConsensus(h Header) []byte +``` +Returns the next consensus of the given header. + +## Input +#### GetHash +``` +GetHash(in Input) []byte +``` +Returns the hash field of the given input. + +#### GetIndex +``` +GetIndex(in Input) int +``` +Returns the index field of the given input. + +## Output +#### GetAssetID +``` +GetAssetId(out Output) []byte +``` +Returns the asset id field of the given output. + +#### GetValue +``` +GetValue(out Output) int +``` +Returns the value field of the given output. + +#### GetScriptHash +``` +GetScriptHash(out Output) []byte +``` +Returns the script hash field of the given output. + +## Runtime +#### CheckWitness +``` +CheckWitness(hash []byte) bool +``` +Verifies if the given hash is the hash of the contract owner. + +#### Log +``` +Log(message string) +``` +Logs the given message. + +#### Notify +``` +Notify(args ...interface{}) int +``` +Notify any number of arguments to the VM. + +#### GetTime +``` +GetTime() int +``` +Returns the current time based on the highest block in the chain. + +#### GetTrigger +``` +GetTrigger() byte +``` +Returns the trigger type of the execution. + +#### Serialize +``` +Serialize(item interface{}) []byte +``` +Serialize the given stack item to a slice of bytes. + +#### Deserialize +``` +Deserialize(data []byte) interface{} +``` +Deserializes the given data to a stack item. + +## Storage +#### GetContext +``` +GetContext() Context +``` +Returns the current storage context. + +#### Put +``` +Put(ctx Context, key, value []interface{}) +``` +Stores the given value at the given key. + +#### Get +``` +Get(ctx Context, key interface{}) interface{} +``` +Returns the value found at the given key. + +#### Delete +``` +Delete(ctx Context, key interface{}) +``` +Delete's the given key from storage. + +#### Find +``` +Find(ctx Context, key interface{}) iterator.Iterator +``` +Find returns an iterator key-values that match the given key. + +## Transaction +#### GetHash +``` +GetHash(t Transacfion) []byte +``` +Returns the hash for the given transaction. + +#### GetType +``` +GetType(t Transacfion) byte +``` +Returns the type of the given transaction. + +#### GetAttributes +``` +GetAttributes(t Transacfion) []attribute.Attribute +``` +Returns the attributes of the given transaction. + +#### GetReferences +``` +GetReferences(t Transacfion) interface{} +``` +Returns the references of the given transaction. + +#### GetUnspentCoins +``` +GetUnspentCoins(t Transacfion) interface{} +``` +Returns the unspent coins of the given transaction. + +#### GetOutputs +``` +GetOutputs(t Transacfion) []output.Output +``` +Returns the outputs of the given transaction + +#### GetInputs +``` +GetInputs(t Transacfion) []input.Input +``` +Returns the inputs of the given transaction diff --git a/examples/engine/engine.go b/examples/engine/engine.go new file mode 100644 index 000000000..aa210abdf --- /dev/null +++ b/examples/engine/engine.go @@ -0,0 +1,22 @@ +package engine_contract + +import ( + "github.com/CityOfZion/neo-go/pkg/interop/engine" + "github.com/CityOfZion/neo-go/pkg/interop/runtime" +) + +func Main() bool { + tx := engine.GetScriptContainer() + runtime.Notify(tx) + + callingScriptHash := engine.GetCallingScriptHash() + runtime.Notify(callingScriptHash) + + execScriptHash := engine.GetExecutingScriptHash() + runtime.Notify(execScriptHash) + + entryScriptHash := engine.GetEntryScriptHash() + runtime.Notify(entryScriptHash) + + return true +} diff --git a/examples/iterator/iterator.go b/examples/iterator/iterator.go new file mode 100644 index 000000000..0809b173f --- /dev/null +++ b/examples/iterator/iterator.go @@ -0,0 +1,18 @@ +package iterator_contract + +import ( + "github.com/CityOfZion/neo-go/pkg/interop/iterator" + "github.com/CityOfZion/neo-go/pkg/interop/runtime" + "github.com/CityOfZion/neo-go/pkg/interop/storage" +) + +func Main() bool { + iter := storage.Find(storage.GetContext(), []byte("foo")) + values := iterator.Values(iter) + keys := iterator.Keys(iter) + + runtime.Notify("found storage values", values) + runtime.Notify("found storage keys", keys) + + return true +} diff --git a/examples/runtime/runtime.go b/examples/runtime/runtime.go new file mode 100644 index 000000000..4d914e445 --- /dev/null +++ b/examples/runtime/runtime.go @@ -0,0 +1,43 @@ +package runtime_contract + +import ( + "github.com/CityOfZion/neo-go/pkg/interop/runtime" + "github.com/CityOfZion/neo-go/pkg/interop/util" +) + +// Check if the invoker of the contract is the specified owner +var owner = util.FromAddress("Aej1fe4mUgou48Zzup5j8sPrE3973cJ5oz") + +func Main(operation string, args []interface{}) bool { + trigger := runtime.GetTrigger() + + // Log owner upon Verification trigger + if trigger == runtime.Verification() { + if runtime.CheckWitness(owner) { + runtime.Log("Verified Owner") + } + return true + } + + // Discerns between log and notify for this test + if trigger == runtime.Application() { + return handleOperation(operation, args) + } + + return false +} + +func handleOperation(operation string, args []interface{}) bool { + if operation == "log" { + message := args[0].(string) + runtime.Log(message) + return true + } + + if operation == "notify" { + runtime.Notify(args[0]) + return true + } + + return false +} diff --git a/examples/storage/storage.go b/examples/storage/storage.go new file mode 100644 index 000000000..319e9e71f --- /dev/null +++ b/examples/storage/storage.go @@ -0,0 +1,46 @@ +package storage_contract + +import ( + "github.com/CityOfZion/neo-go/pkg/interop/storage" +) + +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 + } + } + + // Returns the value at passed key + if operation == "get" { + if checkArgs(args, 1) { + key := args[0].([]byte) + return storage.Get(ctx, key) + } + } + + // Deletes the value at passed key + if operation == "delete" { + key := args[0].([]byte) + storage.Delete(ctx, key) + return true + } + + // TODO: storage.Find() + + return false +} + +func checkArgs(args []interface{}, length int) bool { + if len(args) == length { + return true + } + + return false +} diff --git a/examples/token-sale/token_sale.avm b/examples/token-sale/token_sale.avm deleted file mode 100755 index 790e1bdc8..000000000 Binary files a/examples/token-sale/token_sale.avm and /dev/null differ diff --git a/examples/token-sale/token_sale.go b/examples/token-sale/token_sale.go index 4402d8ccc..2dd8edb0f 100644 --- a/examples/token-sale/token_sale.go +++ b/examples/token-sale/token_sale.go @@ -1,9 +1,9 @@ package tokensale import ( - "github.com/CityOfZion/neo-go/pkg/vm/api/runtime" - "github.com/CityOfZion/neo-go/pkg/vm/api/storage" - "github.com/CityOfZion/neo-go/pkg/vm/api/util" + "github.com/CityOfZion/neo-go/pkg/interop/runtime" + "github.com/CityOfZion/neo-go/pkg/interop/storage" + "github.com/CityOfZion/neo-go/pkg/interop/util" ) const ( diff --git a/examples/token/nep5/nep5.go b/examples/token/nep5/nep5.go new file mode 100644 index 000000000..e4bc2267e --- /dev/null +++ b/examples/token/nep5/nep5.go @@ -0,0 +1,95 @@ +package nep5 + +import ( + "github.com/CityOfZion/neo-go/pkg/interop/engine" + "github.com/CityOfZion/neo-go/pkg/interop/runtime" + "github.com/CityOfZion/neo-go/pkg/interop/storage" + "github.com/CityOfZion/neo-go/pkg/interop/util" +) + +// Token holds all token info +type Token struct { + // Token name + Name string + // Ticker symbol + Symbol string + // Amount of decimals + Decimals int + // Token owner address + Owner []byte + // Total tokens * multiplier + TotalSupply int + // Storage key for circulation value + CirculationKey string +} + +// GetSupply gets the token totalSupply value from VM storage +func (t Token) GetSupply(ctx storage.Context) interface{} { + return storage.Get(ctx, t.CirculationKey) +} + +// BalanceOf gets the token balance of a specific address +func (t Token) BalanceOf(ctx storage.Context, hodler []byte) interface{} { + return storage.Get(ctx, hodler) +} + +// Transfer token from one user to another +func (t Token) Transfer(ctx storage.Context, from []byte, to []byte, amount int) bool { + amountFrom := t.CanTransfer(ctx, from, to, amount) + if amountFrom == -1 { + return false + } + + if amountFrom == 0 { + storage.Delete(ctx, from) + } + + if amountFrom > 0 { + diff := amountFrom - amount + storage.Put(ctx, from, diff) + } + + amountTo := storage.Get(ctx, to).(int) + totalAmountTo := amountTo + amount + storage.Put(ctx, to, totalAmountTo) + runtime.Notify("transfer", from, to, amount) + return true +} + +// CanTransfer returns the amount it can transfer +func (t Token) CanTransfer(ctx storage.Context, from []byte, to []byte, amount int) int { + if len(to) != 20 && !IsUsableAddress(from) { + return -1 + } + + amountFrom := storage.Get(ctx, from).(int) + if amountFrom < amount { + return -1 + } + + // Tell Transfer the result is equal - special case since it uses Delete + if amountFrom == amount { + return 0 + } + + // return amountFrom value back to Transfer, reduces extra Get + return amountFrom +} + +// IsUsableAddress checks if the sender is either the correct NEO address or SC address +func IsUsableAddress(addr []byte) bool { + if len(addr) == 20 { + + if runtime.CheckWitness(addr) { + return true + } + + // Check if a smart contract is calling scripthash + callingScriptHash := engine.GetCallingScriptHash() + if util.Equals(callingScriptHash, addr) { + return true + } + } + + return false +} diff --git a/examples/token/token.go b/examples/token/token.go new file mode 100644 index 000000000..1f1c62fa8 --- /dev/null +++ b/examples/token/token.go @@ -0,0 +1,70 @@ +package token_contract + +import ( + "github.com/CityOfZion/neo-go/examples/token/nep5" + + "github.com/CityOfZion/neo-go/pkg/interop/storage" + "github.com/CityOfZion/neo-go/pkg/interop/util" +) + +const ( + decimals = 8 + multiplier = 100000000 +) + +var owner = util.FromAddress("AK2nJJpJr6o664CWJKi1QRXjqeic2zRp8y") + +// 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", + Decimals: decimals, + Owner: owner, + TotalSupply: 11000000 * multiplier, + CirculationKey: "TokenCirculation", + } +} + +// Main function = contract entry +func Main(operation string, args []interface{}) interface{} { + token := CreateToken() + + if operation == "name" { + return token.Name + } + if operation == "symbol" { + return token.Symbol + } + if operation == "decimals" { + return token.Decimals + } + + // The following operations need ctx + ctx := storage.GetContext() + + if operation == "totalSupply" { + return token.GetSupply(ctx) + } + if operation == "balanceOf" { + hodler := args[0].([]byte) + return token.BalanceOf(ctx, hodler) + } + 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 true +} + +// CheckArgs checks args array against a length indicator +func CheckArgs(args []interface{}, length int) bool { + if len(args) == length { + return true + } + + return false +} diff --git a/pkg/core/util.go b/pkg/core/util.go index 086f54e57..a5eab4b85 100644 --- a/pkg/core/util.go +++ b/pkg/core/util.go @@ -35,7 +35,7 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*Block, error) { NextConsensus: nextConsensus, Script: &transaction.Witness{ InvocationScript: []byte{}, - VerificationScript: []byte{byte(vm.Opusht)}, + VerificationScript: []byte{byte(vm.PUSHT)}, }, } @@ -82,7 +82,7 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*Block, error) { Scripts: []*transaction.Witness{ { InvocationScript: []byte{}, - VerificationScript: []byte{byte(vm.Opusht)}, + VerificationScript: []byte{byte(vm.PUSHT)}, }, }, }, @@ -97,7 +97,7 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*Block, error) { } func governingTokenTX() *transaction.Transaction { - admin, _ := util.Uint160FromScript([]byte{byte(vm.Opusht)}) + admin, _ := util.Uint160FromScript([]byte{byte(vm.PUSHT)}) registerTX := &transaction.RegisterTX{ AssetType: transaction.GoverningToken, Name: "[{\"lang\":\"zh-CN\",\"name\":\"小蚁股\"},{\"lang\":\"en\",\"name\":\"AntShare\"}]", @@ -120,7 +120,7 @@ func governingTokenTX() *transaction.Transaction { } func utilityTokenTX() *transaction.Transaction { - admin, _ := util.Uint160FromScript([]byte{byte(vm.Opushf)}) + admin, _ := util.Uint160FromScript([]byte{byte(vm.PUSHF)}) registerTX := &transaction.RegisterTX{ AssetType: transaction.UtilityToken, Name: "[{\"lang\":\"zh-CN\",\"name\":\"小蚁币\"},{\"lang\":\"en\",\"name\":\"AntCoin\"}]", diff --git a/pkg/interop/account/account.go b/pkg/interop/account/account.go new file mode 100644 index 000000000..c25903c1b --- /dev/null +++ b/pkg/interop/account/account.go @@ -0,0 +1,23 @@ +package account + +// Package account provides function signatures that can be used inside +// smart contracts that are written in the neo-go framework. + +// Account stubs a NEO account type. +type Account struct{} + +// GetScripHash returns the script hash of the given account. +func GetScriptHash(a Account) []byte { + return nil +} + +// GetVotes returns the votes of the given account which should be a slice of +// public key raw bytes. +func GetVotes(a Account) [][]byte { + return nil +} + +// GetBalance returns the balance of for the given account and asset id. +func GetBalance(a Account, assetID []byte) int { + return 0 +} diff --git a/pkg/interop/asset/asset.go b/pkg/interop/asset/asset.go new file mode 100644 index 000000000..a7a46db97 --- /dev/null +++ b/pkg/interop/asset/asset.go @@ -0,0 +1,53 @@ +package asset + +// Package asset provides function signatures that can be used inside +// smart contracts that are written in the neo-go framework. + +// Asset stubs a NEO asset type. +type Asset struct{} + +// GetAssetID returns the id of the given asset. +func GetAssetID(a Asset) []byte { + return nil +} + +// GetAssetType returns the type of the given asset. +func GetAssetType(a Asset) byte { + return 0x00 +} + +// GetAmount returns the amount of the given asset. +func GetAmount(a Asset) int { + return 0 +} + +// GetAvailable returns the available of the given asset. +func GetAvailable(a Asset) int { + return 0 +} + +// GetPrecision returns the precision of the given asset. +func GetPrecision(a Asset) byte { + return 0x00 +} + +// GetOwner returns the owner of the given asset. +func GetOwner(a Asset) []byte { + return nil +} + +// GetAdmin returns the admin of the given asset. +func GetAdmin(a Asset) []byte { + return nil +} + +// GetIssuer returns the issuer of the given asset. +func GetIssuer(a Asset) []byte { + return nil +} + +// Create registers a new asset on the blockchain. +func Create(assetType byte, name string, amount int, precision byte, owner, admin, issuer []byte) {} + +// Renew renews the existance of an asset by the given years. +func Renew(asset Asset, years int) {} diff --git a/pkg/interop/attribute/attribute.go b/pkg/interop/attribute/attribute.go new file mode 100644 index 000000000..6147b5be2 --- /dev/null +++ b/pkg/interop/attribute/attribute.go @@ -0,0 +1,17 @@ +package attribute + +// Package attribute provides function signatures that can be used inside +// smart contracts that are written in the neo-go framework. + +// Attribute stubs a NEO transaction attribute type. +type Attribute struct{} + +// GetUsage returns the usage of the given attribute. +func GetUsage(attr Attribute) byte { + return 0x00 +} + +// GetData returns the data of the given attribute. +func GetData(attr Attribute) []byte { + return nil +} diff --git a/pkg/interop/block/block.go b/pkg/interop/block/block.go new file mode 100644 index 000000000..2f3bf6738 --- /dev/null +++ b/pkg/interop/block/block.go @@ -0,0 +1,25 @@ +package block + +import "github.com/CityOfZion/neo-go/pkg/interop/transaction" + +// Package block provides function signatures that can be used inside +// smart contracts that are written in the neo-go framework. + +// Block stubs a NEO block type. +type Block struct{} + +// GetTransactionCount return the number of recorded transactions in the given block. +func GetTransactionCount(b Block) int { + return 0 +} + +// GetTransactions returns a slice of transactions recorded in the given block. +func GetTransactions(b Block) []transaction.Transaction { + return []transaction.Transaction{} +} + +// GetTransaction returns a transaction from the given a block hash of the +// transaction. +func GetTransaction(b Block, hash []byte) transaction.Transaction { + return transaction.Transaction{} +} diff --git a/pkg/interop/blockchain/blockchain.go b/pkg/interop/blockchain/blockchain.go new file mode 100644 index 000000000..d27bdaa07 --- /dev/null +++ b/pkg/interop/blockchain/blockchain.go @@ -0,0 +1,53 @@ +package blockchain + +import ( + "github.com/CityOfZion/neo-go/pkg/interop/account" + "github.com/CityOfZion/neo-go/pkg/interop/asset" + "github.com/CityOfZion/neo-go/pkg/interop/block" + "github.com/CityOfZion/neo-go/pkg/interop/contract" + "github.com/CityOfZion/neo-go/pkg/interop/header" + "github.com/CityOfZion/neo-go/pkg/interop/transaction" +) + +// Package blockchain provides function signatures that can be used inside +// smart contracts that are written in the neo-go framework. + +// GetHeight returns the height of te block recorded in the current execution scope. +func GetHeight() int { + return 0 +} + +// GetHeader returns the header found by the given hash or index. +func GetHeader(heightOrHash interface{}) header.Header { + return header.Header{} +} + +// GetBlock returns the block found by the given hash or index. +func GetBlock(heightOrHash interface{}) block.Block { + return block.Block{} +} + +// GetTransaction returns the transaction found by the given hash. +func GetTransaction(hash []byte) transaction.Transaction { + return transaction.Transaction{} +} + +// GetContract returns the contract found by the given script hash. +func GetContract(scriptHash []byte) contract.Contract { + return contract.Contract{} +} + +// GetAccount returns the account found by the given script hash. +func GetAccount(scriptHash []byte) account.Account { + return account.Account{} +} + +// GetValidators returns a slice of validator addresses. +func GetValidators() [][]byte { + return nil +} + +// GetAsset returns the asset found by the given asset id. +func GetAsset(assetID []byte) asset.Asset { + return asset.Asset{} +} diff --git a/pkg/interop/contract/contract.go b/pkg/interop/contract/contract.go new file mode 100644 index 000000000..55f60c0ad --- /dev/null +++ b/pkg/interop/contract/contract.go @@ -0,0 +1,55 @@ +package contract + +import "github.com/CityOfZion/neo-go/pkg/interop/storage" + +// Package contract provides function signatures that can be used inside +// smart contracts that are written in the neo-go framework. + +// Contract stubs a NEO contract type. +type Contract struct{} + +// GetScript returns the script of the given contract. +func GetScript(c Contract) []byte { + return nil +} + +// IsPayable returns whether the given contract is payable. +func IsPayable(c Contract) bool { + return false +} + +// GetStorageContext returns the storage context for the given contract. +func GetStorageContext(c Contract) storage.Context { + return storage.Context{} +} + +// Create creates a new contract. +// @FIXME What is the type of the returnType here? +func Create( + script []byte, + params []interface{}, + returnType byte, + properties interface{}, + name, + version, + author, + email, + description string) { +} + +// Migrate migrates a new contract. +// @FIXME What is the type of the returnType here? +func Migrate( + script []byte, + params []interface{}, + returnType byte, + properties interface{}, + name, + version, + author, + email, + description string) { +} + +// Destroy deletes a contract that is registered on the blockchain. +func Destroy(c Contract) {} diff --git a/pkg/vm/api/crypto/util.go b/pkg/interop/crypto/crypto.go similarity index 55% rename from pkg/vm/api/crypto/util.go rename to pkg/interop/crypto/crypto.go index d2f734564..6877fd46e 100644 --- a/pkg/vm/api/crypto/util.go +++ b/pkg/interop/crypto/crypto.go @@ -1,5 +1,8 @@ package crypto +// Package crypto provides function signatures that can be used inside +// smart contracts that are written in the neo-go framework. + // SHA1 computes the sha1 hash of b. func SHA1(b []byte) []byte { return nil @@ -10,12 +13,12 @@ func SHA256(b []byte) []byte { return nil } -// Hash160 .. +// Hash160 computes the sha256 + ripemd160 of b. func Hash160(b []byte) []byte { return nil } -// Hash256 .. +// Hash256 computes the sha256^2 hash of b. func Hash256(b []byte) []byte { return nil } diff --git a/pkg/interop/engine/engine.go b/pkg/interop/engine/engine.go new file mode 100644 index 000000000..057d42728 --- /dev/null +++ b/pkg/interop/engine/engine.go @@ -0,0 +1,29 @@ +package engine + +import "github.com/CityOfZion/neo-go/pkg/interop/transaction" + +// Package engine provides function signatures that can be used inside +// smart contracts that are written in the neo-go framework. + +// GetScriptContainer returns the transaction that is in the execution context. +func GetScriptContainer() transaction.Transaction { + return transaction.Transaction{} +} + +// GetExecutingScriptHash returns the script hash of the contract that is +// currently being executed. +func GetExecutingScriptHash() []byte { + return nil +} + +// GetCallingScriptHash returns the script hash of the contract that started +// the execution of the current script. +func GetCallingScriptHash() []byte { + return nil +} + +// GetEntryScriptHash returns the script hash of the contract that started the +// execution from the start. +func GetEntryScriptHash() []byte { + return nil +} diff --git a/pkg/interop/enumerator/enumerator.go b/pkg/interop/enumerator/enumerator.go new file mode 100644 index 000000000..8da72424d --- /dev/null +++ b/pkg/interop/enumerator/enumerator.go @@ -0,0 +1,29 @@ +package enumerator + +// Package enumerator provides function signatures that can be used inside +// smart contracts that are written in the neo-go framework. + +// TODO: Check enumerator use cases and add them to the examples folder. + +// Enumerator stubs a NEO enumerator type. +type Enumerator struct{} + +// Create creates a new enumerator from the given items. +func Create(items []interface{}) Enumerator { + return Enumerator{} +} + +// Next returns the next item in the iteration. +func Next(e Enumerator) interface{} { + return nil +} + +// Value returns the enumerator value. +func Value(e Enumerator) interface{} { + return nil +} + +// Concat concats the 2 given enumerators. +func Concat(a, b Enumerator) Enumerator { + return Enumerator{} +} diff --git a/pkg/interop/header/header.go b/pkg/interop/header/header.go new file mode 100644 index 000000000..44fc2d9e6 --- /dev/null +++ b/pkg/interop/header/header.go @@ -0,0 +1,47 @@ +package header + +// Package header provides function signatures that can be used inside +// smart contracts that are written in the neo-go framework. + +// Header stubs a NEO block header type. +type Header struct{} + +// GetIndex returns the index of the given header. +func GetIndex(h Header) int { + return 0 +} + +// GetHash returns the hash of the given header. +func GetHash(h Header) []byte { + return nil +} + +// GetPrevHash returns the previous hash of the given header. +func GetPrevHash(h Header) []byte { + return nil +} + +// GetTimestamp returns the timestamp of the given header. +func GetTimestamp(h Header) int { + return 0 +} + +// GetVersion returns the version of the given header. +func GetVersion(h Header) int { + return 0 +} + +// GetMerkleRoot returns the merkle root of the given header. +func GetMerkleRoot(h Header) []byte { + return nil +} + +// GetConsensusData returns the consensus data of the given header. +func GetConsensusData(h Header) int { + return 0 +} + +// GetNextConsensus returns the next consensus of the given header. +func GetNextConsensus(h Header) []byte { + return nil +} diff --git a/pkg/interop/input/input.go b/pkg/interop/input/input.go new file mode 100644 index 000000000..047637226 --- /dev/null +++ b/pkg/interop/input/input.go @@ -0,0 +1,17 @@ +package input + +// Package input provides function signatures that can be used inside +// smart contracts that are written in the neo-go framework. + +// Input stubs the input of a NEO transaction. +type Input struct{} + +// GetHash returns the hash of the given input. +func GetHash(in Input) []byte { + return nil +} + +// GetIndex returns the index of the given input. +func GetIndex(in Input) int { + return 0 +} diff --git a/pkg/interop/iterator/iterator.go b/pkg/interop/iterator/iterator.go new file mode 100644 index 000000000..c63d3b93d --- /dev/null +++ b/pkg/interop/iterator/iterator.go @@ -0,0 +1,28 @@ +package iterator + +// Package iterator provides function signatures that can be used inside +// smart contracts that are written in the neo-go framework. + +// Iterator stubs a NEO iterator object type. +type Iterator struct{} + +// Create creates an iterator from the given items. +func Create(items []interface{}) Iterator { + return Iterator{} +} + +// TODO: Better description for this. +// Key returns the iterator key. +func Key(it Iterator) interface{} { + return nil +} + +// Keys returns the iterator keys. +func Keys(it Iterator) []interface{} { + return nil +} + +// Values returns the iterator values. +func Values(it Iterator) []interface{} { + return nil +} diff --git a/pkg/interop/output/output.go b/pkg/interop/output/output.go new file mode 100644 index 000000000..6f40ac544 --- /dev/null +++ b/pkg/interop/output/output.go @@ -0,0 +1,22 @@ +package output + +// Package output provides function signatures that can be used inside +// smart contracts that are written in the neo-go framework. + +// Output stubs the output of a NEO transaction. +type Output struct{} + +// GetAssetID returns the asset id of the given output. +func GetAssetID(out Output) []byte { + return nil +} + +// GetValue returns the value of the given output. +func GetValue(out Output) int { + return 0 +} + +// GetScriptHash returns the script hash of the given output. +func GetScriptHash(out Output) []byte { + return nil +} diff --git a/pkg/interop/runtime/runtime.go b/pkg/interop/runtime/runtime.go new file mode 100644 index 000000000..fd07d7bd6 --- /dev/null +++ b/pkg/interop/runtime/runtime.go @@ -0,0 +1,48 @@ +package runtime + +// Package runtime provides function signatures that can be used inside +// smart contracts that are written in the neo-go framework. + +// CheckWitness verifies if the given hash is the invoker of the contract. +func CheckWitness(hash []byte) bool { + return true +} + +// Log instucts the VM to log the given message. +func Log(message string) {} + +// Notify an event to the VM. +func Notify(arg ...interface{}) int { + return 0 +} + +// GetTime returns the timestamp of the most recent block. +func GetTime() int { + return 0 +} + +// GetTrigger returns the smart contract invoke trigger which can be either +// verification or application. +func GetTrigger() byte { + return 0x00 +} + +// Application returns the Application trigger type +func Application() byte { + return 0x10 +} + +// Verification returns the Verification trigger type +func Verification() byte { + return 0x00 +} + +// Serialize serializes and item into a bytearray. +func Serialize(item interface{}) []byte { + return nil +} + +// Deserialize an item from a bytearray. +func Deserialize(b []byte) interface{} { + return nil +} diff --git a/pkg/interop/storage/storage.go b/pkg/interop/storage/storage.go new file mode 100644 index 000000000..2391fd550 --- /dev/null +++ b/pkg/interop/storage/storage.go @@ -0,0 +1,24 @@ +package storage + +import "github.com/CityOfZion/neo-go/pkg/interop/iterator" + +// Package storage provides function signatures that can be used inside +// smart contracts that are written in the neo-go framework. + +// Context represents the storage context +type Context struct{} + +// GetContext returns the storage context +func GetContext() Context { return Context{} } + +// Put value at given key +func Put(ctx Context, key interface{}, value interface{}) {} + +// Get value matching given key +func Get(ctx Context, key interface{}) interface{} { return 0 } + +// Delete key value pair from storage +func Delete(ctx Context, key interface{}) {} + +// Find returns an iterator.Iterator over the keys that matched the given key. +func Find(ctx Context, key interface{}) iterator.Iterator { return iterator.Iterator{} } diff --git a/pkg/interop/transaction/transaction.go b/pkg/interop/transaction/transaction.go new file mode 100644 index 000000000..550c796d4 --- /dev/null +++ b/pkg/interop/transaction/transaction.go @@ -0,0 +1,50 @@ +package transaction + +import ( + "github.com/CityOfZion/neo-go/pkg/interop/attribute" + "github.com/CityOfZion/neo-go/pkg/interop/input" + "github.com/CityOfZion/neo-go/pkg/interop/output" +) + +// Package transaction provides function signatures that can be used inside +// smart contracts that are written in the neo-go framework. + +// Transaction stubs a NEO transaction type. +type Transaction struct{} + +// GetHash returns the hash of the given transaction. +func GetHash(t Transaction) []byte { + return nil +} + +// GetType returns the type of the given transaction. +func GetType(t Transaction) byte { + return 0x00 +} + +// GetAttributes returns a slice of attributes for the given transaction. +func GetAttributes(t Transaction) []attribute.Attribute { + return []attribute.Attribute{} +} + +// FIXME: What is the correct return type for this? +// GetReferences returns a slice of references for the given transaction. +func GetReferences(t Transaction) []interface{} { + return []interface{}{} +} + +// FIXME: What is the correct return type for this? +// GetUnspentCoins returns the unspent coins for the given transaction. +func GetUnspentCoins(t Transaction) interface{} { + return 0 +} + +// GetInputs returns the inputs of the given transaction. +func GetInputs(t Transaction) []input.Input { + return []input.Input{} +} + +// GetOutputs returns the outputs of the given transaction. +func GetOutputs(t Transaction) []output.Output { + return []output.Output{} +} diff --git a/pkg/interop/util/util.go b/pkg/interop/util/util.go new file mode 100644 index 000000000..1f05d4941 --- /dev/null +++ b/pkg/interop/util/util.go @@ -0,0 +1,12 @@ +package util + +// FromAddress is an utility function that converts an NEO address to its hash. +func FromAddress(address string) []byte { + return nil +} + +// Equals compares a with b and will return true whether a and b +// are equal. +func Equals(a, b interface{}) bool { + return false +} diff --git a/pkg/smartcontract/contract.go b/pkg/smartcontract/contract.go index a56562475..33214000f 100644 --- a/pkg/smartcontract/contract.go +++ b/pkg/smartcontract/contract.go @@ -34,7 +34,7 @@ func CreateMultiSigRedeemScript(m int, publicKeys crypto.PublicKeys) ([]byte, er if err := vm.EmitInt(buf, int64(len(publicKeys))); err != nil { return nil, err } - if err := vm.EmitOpcode(buf, vm.Ocheckmultisig); err != nil { + if err := vm.EmitOpcode(buf, vm.CHECKMULTISIG); err != nil { return nil, err } diff --git a/pkg/smartcontract/contract_test.go b/pkg/smartcontract/contract_test.go index d8760743c..3e1c4dcf9 100644 --- a/pkg/smartcontract/contract_test.go +++ b/pkg/smartcontract/contract_test.go @@ -24,7 +24,7 @@ func TestCreateMultiSigRedeemScript(t *testing.T) { buf := bytes.NewBuffer(out) b, _ := buf.ReadByte() - assert.Equal(t, vm.Opush3, vm.Opcode(b)) + assert.Equal(t, vm.PUSH3, vm.Instruction(b)) for i := 0; i < len(validators); i++ { b, err := util.ReadVarBytes(buf) @@ -35,7 +35,7 @@ func TestCreateMultiSigRedeemScript(t *testing.T) { } b, _ = buf.ReadByte() - assert.Equal(t, vm.Opush3, vm.Opcode(b)) + assert.Equal(t, vm.PUSH3, vm.Instruction(b)) b, _ = buf.ReadByte() - assert.Equal(t, vm.Ocheckmultisig, vm.Opcode(b)) + assert.Equal(t, vm.CHECKMULTISIG, vm.Instruction(b)) } diff --git a/pkg/vm/README.md b/pkg/vm/README.md index 46e21b560..cff6635d4 100644 --- a/pkg/vm/README.md +++ b/pkg/vm/README.md @@ -117,7 +117,7 @@ You can invoke smart contracts with arguments. Take the following ***roll the di ``` package rollthedice -import "github.com/CityOfZion/neo-go/pkg/vm/api/runtime" +import "github.com/CityOfZion/neo-go/pkg/interop/runtime" func Main(method string, args []interface{}) int { if method == "rollDice" { diff --git a/pkg/vm/api/asset/asset.go b/pkg/vm/api/asset/asset.go deleted file mode 100644 index edc4aa99a..000000000 --- a/pkg/vm/api/asset/asset.go +++ /dev/null @@ -1,31 +0,0 @@ -package asset - -import "github.com/CityOfZion/neo-go/pkg/core" - -// GetAssetID returns the id of the given asset. -func GetAssetID(asset *core.AssetState) []byte { return nil } - -// TODO: Verify if we need to return a uint8 here. -// GetAssetType returns the type of the given asset. -func GetAssetType(asset *core.AssetState) uint8 { return 0x00 } - -// GetAmount returns the amount of the given asset. -func GetAmount(asset *core.AssetState) uint64 { return 0 } - -// GetAvailable returns the available amount of the given asset. -func GetAvailable(asset *core.AssetState) uint64 { return 0 } - -// GetPrecision returns the precision the given asset. -func GetPrecision(asset *core.AssetState) uint8 { return 0 } - -// GetOwner returns the owner the given asset. -func GetOwner(asset *core.AssetState) []byte { return nil } - -// GetIssuer returns the issuer the given asset. -func GetIssuer(asset *core.AssetState) []byte { return nil } - -// Create a new asset specified by the given parameters. -func Create(typ uint8, name string, amount uint64, owner, admin, issuer []byte) {} - -// Renew the given asset for the given x years. -func Renew(asset *core.AssetState, years uint32) {} diff --git a/pkg/vm/api/block/block.go b/pkg/vm/api/block/block.go deleted file mode 100644 index ad3b4d8a1..000000000 --- a/pkg/vm/api/block/block.go +++ /dev/null @@ -1,41 +0,0 @@ -package block - -import ( - "github.com/CityOfZion/neo-go/pkg/core" - "github.com/CityOfZion/neo-go/pkg/core/transaction" -) - -// GetTransactionCount returns the number of transactions that are recorded in -// the given block. -func GetTransactionCount(block *core.Block) int { return 0 } - -// GetTransactions returns a list of transactions that are recorded in this block. -func GetTransactions(block *core.Block) []*transaction.Transaction { return nil } - -// GetIndex returns the index of the given block. -func GetIndex(block *core.Block) uint32 { return 0 } - -// GetHash returns the hash of the given block. -func GetHash(block *core.Block) []byte { return nil } - -// GetHash returns the version of the given block. -func GetVersion(block *core.Block) uint32 { return 0 } - -// GetHash returns the previous hash of the given block. -func GetPrevHash(block *core.Block) []byte { return nil } - -// GetHash returns the merkle root of the given block. -func GetMerkleRoot(block *core.Block) []byte { return nil } - -// GetHash returns the timestamp of the given block. -func GetTimestamp(block *core.Block) uint32 { return 0 } - -// GetHash returns the next validator address of the given block. -func GetNextConsensus(block *core.Block) []byte { return nil } - -// GetConsensusData returns the consensus data of the given block. -func GetConsensusData(block *core.Block) uint64 { return 0 } - -// GetTransaction returns a specific transaction that is recorded in the given block -// by the given index. -func GetTransaction(block *core.Block, index int) *transaction.Transaction { return nil } diff --git a/pkg/vm/api/header/header.go b/pkg/vm/api/header/header.go deleted file mode 100644 index c4254bd94..000000000 --- a/pkg/vm/api/header/header.go +++ /dev/null @@ -1,24 +0,0 @@ -package header - -import "github.com/CityOfZion/neo-go/pkg/core" - -// GetIndex returns the index of the given header. -func GetIndex(header *core.Header) uint32 { return 0 } - -// GetHash returns the hash of the given header. -func GetHash(header *core.Header) []byte { return nil } - -// GetHash returns the version of the given header. -func GetVersion(header *core.Header) uint32 { return 0 } - -// GetHash returns the previous hash of the given header. -func GetPrevHash(header *core.Header) []byte { return nil } - -// GetHash returns the merkle root of the given header. -func GetMerkleRoot(header *core.Header) []byte { return nil } - -// GetHash returns the timestamp of the given header. -func GetTimestamp(header *core.Header) uint32 { return 0 } - -// GetHash returns the next validator address of the given header. -func GetNextConsensus(header *core.Header) []byte { return nil } diff --git a/pkg/vm/api/runtime/runtime.go b/pkg/vm/api/runtime/runtime.go deleted file mode 100644 index 685db4937..000000000 --- a/pkg/vm/api/runtime/runtime.go +++ /dev/null @@ -1,45 +0,0 @@ -package runtime - -// CheckWitness verifies if the invoker is the owner of the contract. -func CheckWitness(hash []byte) bool { - return true -} - -// GetTime returns the timestamp of the most recent block. -func GetTime() int { - return 0 -} - -// Notify an event to the VM. -func Notify(arg interface{}) int { - return 0 -} - -// Log instructs the VM to log the given message. -func Log(message string) {} - -// Application returns the application trigger type. -func Application() byte { - return 0x10 -} - -// Verification returns the verification trigger type. -func Verification() byte { - return 0x00 -} - -// GetTrigger return the current trigger type. The return in this function -// doesn't really mather, this is just an interop placeholder. -func GetTrigger() interface{} { - return 0 -} - -// Serialize serializes and item into a bytearray. -func Serialize(item interface{}) []byte { - return nil -} - -// Deserialize an item from a bytearray. -func Deserialize(b []byte) interface{} { - return nil -} diff --git a/pkg/vm/api/storage/storage.go b/pkg/vm/api/storage/storage.go deleted file mode 100644 index ed8cea536..000000000 --- a/pkg/vm/api/storage/storage.go +++ /dev/null @@ -1,19 +0,0 @@ -package storage - -// Context represents the storage context. -type Context interface{} - -// GetContext returns the storage context. -func GetContext() interface{} { return nil } - -// Put stores a value in to the storage. -func Put(ctx interface{}, key interface{}, value interface{}) {} - -// Get returns the value from the storage. -func Get(ctx interface{}, key interface{}) interface{} { return 0 } - -// Delete removes a stored key value pair. -func Delete(ctx interface{}, key interface{}) {} - -// Find entries somewhat matching the given key. -func Find(ctx interface{}, key interface{}) interface{} { return 0 } diff --git a/pkg/vm/api/transaction/transaction.go b/pkg/vm/api/transaction/transaction.go deleted file mode 100644 index e0744e217..000000000 --- a/pkg/vm/api/transaction/transaction.go +++ /dev/null @@ -1,27 +0,0 @@ -package transaction - -import "github.com/CityOfZion/neo-go/pkg/core/transaction" - -// GetType returns the type of the given transaction. -// TODO: Double check if the type returned should be of type uint8. -func GetType(tx *transaction.Transaction) uint8 { return 0x00 } - -// GetTXHash returns the hash of the given transaction. -func GetTXHash(tx *transaction.Transaction) []byte { return nil } - -// GetAttributes returns the attributes of the given transaction. -func GetAttributes(tx *transaction.Transaction) []*transaction.Attribute { return nil } - -// GetInputs returns the inputs of the given transaction. -func GetInputs(tx *transaction.Transaction) []*transaction.Input { return nil } - -// GetOutputs returns the outputs of the given transaction. -func GetOutputs(tx *transaction.Transaction) []*transaction.Output { return nil } - -// TODO: What does this return as data type? -// GetReferences returns the outputs of the given transaction. -// func GetReferences(tx *transaction.Transaction) { } - -// TODO: What does this return as data type? -// GetUnspentCoins returns the unspent coins of the given transaction. -// func GetUnspentCoins(tx *transaction.Transaction) { } diff --git a/pkg/vm/api/types/block.go b/pkg/vm/api/types/block.go deleted file mode 100644 index 928449db6..000000000 --- a/pkg/vm/api/types/block.go +++ /dev/null @@ -1,9 +0,0 @@ -package types - -// Block represents a block in the blockchain. -type Block struct{} - -// Index returns the height of the block. -func (b Block) Index() int { - return 0 -} diff --git a/pkg/vm/api/util/util.go b/pkg/vm/api/util/util.go deleted file mode 100644 index fee3d9b05..000000000 --- a/pkg/vm/api/util/util.go +++ /dev/null @@ -1,6 +0,0 @@ -package util - -// FromAddress returns the underlying bytes from the given address string. -func FromAddress(address string) []byte { - return nil -} diff --git a/pkg/vm/compiler/README.md b/pkg/vm/compiler/README.md index 3d2cbb44f..88287451e 100644 --- a/pkg/vm/compiler/README.md +++ b/pkg/vm/compiler/README.md @@ -68,24 +68,39 @@ By default the filename will be the name of your .go file with the .avm extensio You can dump the opcodes generated by the compiler with the following command: ``` -./bin/neo-go contract opdump -i mycontract.go +./bin/neo-go contract inspect -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 +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 @@ -118,8 +133,8 @@ Will output something like: package mycontract import ( - "github.com/CityOfZion/neo-go/pkg/vm/api/runtime" - "github.com/CityOfZion/neo-go/pkg/vm/api/util" + "github.com/CityOfZion/neo-go/pkg/interop/runtime" + "github.com/CityOfZion/neo-go/pkg/interop/util" ) var owner = util.FromAddress("AJX1jGfj3qPBbpAKjY527nPbnrnvSx9nCg") @@ -142,8 +157,8 @@ func Main() bool { package mytoken import ( - "github.com/CityOfZion/neo-go/pkg/vm/api/runtime" - "github.com/CityOfZion/neo-go/pkg/vm/api/storage" + "github.com/CityOfZion/neo-go/pkg/interop/runtime" + "github.com/CityOfZion/neo-go/pkg/interop/storage" ) var owner = util.FromAddress("AJX1jGfj3qPBbpAKjY527nPbnrnvSx9nCg") diff --git a/pkg/vm/compiler/analysis.go b/pkg/vm/compiler/analysis.go index 10ee90242..011901f2a 100644 --- a/pkg/vm/compiler/analysis.go +++ b/pkg/vm/compiler/analysis.go @@ -6,7 +6,6 @@ import ( "go/types" "log" - "github.com/CityOfZion/neo-go/pkg/vm" "golang.org/x/tools/go/loader" ) @@ -14,14 +13,8 @@ var ( // Go language builtin functions and custom builtin utility functions. builtinFuncs = []string{ "len", "append", "SHA256", - "SHA1", "Hash256", "Hash160", "FromAddress", - } - - // VM system calls that have no return value. - noRetSyscalls = []string{ - "Notify", "Log", "Put", "Register", "Delete", - "SetVotes", "ContractDestroy", "MerkleRoot", "Hash", - "PrevHash", "GetHeader", + "SHA1", "Hash256", "Hash160", + "FromAddress", "Equals", } ) @@ -202,19 +195,12 @@ 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 - } +func isSyscall(fun *funcScope) bool { + if fun.selector == nil { + return false } - return false + _, ok := syscalls[fun.selector.Name][fun.name] + return ok } func isStringType(t types.Type) bool { diff --git a/pkg/vm/compiler/codegen.go b/pkg/vm/compiler/codegen.go index df77461e1..080f72368 100644 --- a/pkg/vm/compiler/codegen.go +++ b/pkg/vm/compiler/codegen.go @@ -8,6 +8,8 @@ import ( "go/token" "go/types" "log" + "sort" + "strconv" "strings" "github.com/CityOfZion/neo-go/pkg/crypto" @@ -57,7 +59,7 @@ func (c *codegen) emitLoadConst(t types.TypeAndValue) { switch typ := t.Type.Underlying().(type) { case *types.Basic: switch typ.Kind() { - case types.Int, types.UntypedInt: + case types.Int, types.UntypedInt, types.Uint: val, _ := constant.Int64Val(t.Value) emitInt(c.prog, val) case types.String, types.UntypedString: @@ -87,18 +89,13 @@ func (c *codegen) emitLoadLocal(name string) { } func (c *codegen) emitLoadLocalPos(pos int) { - emitOpcode(c.prog, vm.Ofromaltstack) - emitOpcode(c.prog, vm.Odup) - emitOpcode(c.prog, vm.Otoaltstack) - + emitOpcode(c.prog, vm.DUPFROMALTSTACK) emitInt(c.prog, int64(pos)) - emitOpcode(c.prog, vm.Opickitem) + emitOpcode(c.prog, vm.PICKITEM) } func (c *codegen) emitStoreLocal(pos int) { - emitOpcode(c.prog, vm.Ofromaltstack) - emitOpcode(c.prog, vm.Odup) - emitOpcode(c.prog, vm.Otoaltstack) + emitOpcode(c.prog, vm.DUPFROMALTSTACK) if pos < 0 { log.Fatalf("invalid position to store local: %d", pos) @@ -106,19 +103,19 @@ func (c *codegen) emitStoreLocal(pos int) { emitInt(c.prog, int64(pos)) emitInt(c.prog, 2) - emitOpcode(c.prog, vm.Oroll) - emitOpcode(c.prog, vm.Osetitem) + emitOpcode(c.prog, vm.ROLL) + emitOpcode(c.prog, vm.SETITEM) } func (c *codegen) emitLoadField(i int) { emitInt(c.prog, int64(i)) - emitOpcode(c.prog, vm.Opickitem) + emitOpcode(c.prog, vm.PICKITEM) } func (c *codegen) emitStoreStructField(i int) { emitInt(c.prog, int64(i)) - emitOpcode(c.prog, vm.Orot) - emitOpcode(c.prog, vm.Osetitem) + emitOpcode(c.prog, vm.ROT) + emitOpcode(c.prog, vm.SETITEM) } // convertGlobals will traverse the AST and only convert global declarations. @@ -144,6 +141,10 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) { f, ok = c.funcs[decl.Name.Name] if ok { + // If this function is a syscall we will not convert it to bytecode. + if isSyscall(f) { + return + } c.setLabel(f.label) } else { f = c.newFunc(decl) @@ -155,8 +156,8 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) { // All globals copied into the scope of the function need to be added // to the stack size of the function. emitInt(c.prog, f.stackSize()+countGlobals(file)) - emitOpcode(c.prog, vm.Onewarray) - emitOpcode(c.prog, vm.Otoaltstack) + emitOpcode(c.prog, vm.NEWARRAY) + emitOpcode(c.prog, vm.TOALTSTACK) // We need to handle methods, which in Go, is just syntactic sugar. // The method receiver will be passed in as first argument. @@ -185,7 +186,7 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) { } // Load in all the global variables in to the scope of the function. // This is not necessary for syscalls. - if !isSyscall(f.name) { + if !isSyscall(f) { c.convertGlobals(file) } @@ -193,9 +194,9 @@ func (c *codegen) convertFuncDecl(file ast.Node, decl *ast.FuncDecl) { // If this function returns the void (no return stmt) we will cleanup its junk on the stack. if !hasReturnStmt(decl) { - emitOpcode(c.prog, vm.Ofromaltstack) - emitOpcode(c.prog, vm.Odrop) - emitOpcode(c.prog, vm.Oret) + emitOpcode(c.prog, vm.FROMALTSTACK) + emitOpcode(c.prog, vm.DROP) + emitOpcode(c.prog, vm.RET) } } @@ -249,6 +250,21 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { default: log.Fatal("nested selector assigns not supported yet") } + + // Assignments to index expressions. + // slice[0] = 10 + case *ast.IndexExpr: + ast.Walk(c, n.Rhs[i]) + name := t.X.(*ast.Ident).Name + c.emitLoadLocal(name) + // For now storm only supports basic index operations. Hence we + // cast this to an *ast.BasicLit (1, 2 , 3) + indexStr := t.Index.(*ast.BasicLit).Value + index, err := strconv.Atoi(indexStr) + if err != nil { + log.Fatal("failed to convert slice index to integer") + } + c.emitStoreStructField(index) } } return nil @@ -258,42 +274,39 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { log.Fatal("multiple returns not supported.") } - // @OPTIMIZE: We could skip these 3 instructions for each return statement. - // To be backwards compatible we will put them them in. - // See issue #65 (https://github.com/CityOfZion/neo-go/issues/65) l := c.newLabel() - emitJmp(c.prog, vm.Ojmp, int16(l)) c.setLabel(l) if len(n.Results) > 0 { ast.Walk(c, n.Results[0]) } - emitOpcode(c.prog, vm.Onop) // @OPTIMIZE - emitOpcode(c.prog, vm.Ofromaltstack) - emitOpcode(c.prog, vm.Odrop) // Cleanup the stack. - emitOpcode(c.prog, vm.Oret) + emitOpcode(c.prog, vm.FROMALTSTACK) + emitOpcode(c.prog, vm.DROP) // Cleanup the stack. + emitOpcode(c.prog, vm.RET) return nil case *ast.IfStmt: lIf := c.newLabel() lElse := c.newLabel() + lElseEnd := c.newLabel() + if n.Cond != nil { ast.Walk(c, n.Cond) - emitJmp(c.prog, vm.Ojmpifnot, int16(lElse)) + emitJmp(c.prog, vm.JMPIFNOT, int16(lElse)) } c.setLabel(lIf) ast.Walk(c, n.Body) - if n.Else != nil { - // TODO: handle else statements. - // emitJmp(c.prog, vm.Ojmp, int16(lEnd)) + emitJmp(c.prog, vm.JMP, int16(lElseEnd)) } + c.setLabel(lElse) if n.Else != nil { ast.Walk(c, n.Else) } + c.setLabel(lElseEnd) return nil case *ast.BasicLit: @@ -327,7 +340,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { c.emitLoadConst(c.typeInfo.Types[n.Elts[i]]) } emitInt(c.prog, int64(ln)) - emitOpcode(c.prog, vm.Opack) + emitOpcode(c.prog, vm.PACK) return nil } @@ -342,13 +355,13 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { switch n.Op { case token.LAND: ast.Walk(c, n.X) - emitJmp(c.prog, vm.Ojmpifnot, int16(len(c.l)-1)) + emitJmp(c.prog, vm.JMPIFNOT, int16(len(c.l)-1)) ast.Walk(c, n.Y) return nil case token.LOR: ast.Walk(c, n.X) - emitJmp(c.prog, vm.Ojmpif, int16(len(c.l)-2)) + emitJmp(c.prog, vm.JMPIF, int16(len(c.l) - 3)) ast.Walk(c, n.Y) return nil @@ -360,14 +373,33 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { // example: // const x = 10 // x + 2 will results into 12 - if tinfo := c.typeInfo.Types[n]; tinfo.Value != nil { + tinfo := c.typeInfo.Types[n] + if tinfo.Value != nil { c.emitLoadConst(tinfo) return nil } ast.Walk(c, n.X) ast.Walk(c, n.Y) - c.convertToken(n.Op) + + switch { + case n.Op == token.ADD: + // VM has separate opcodes for number and string concatenation + if isStringType(tinfo.Type) { + emitOpcode(c.prog, vm.CAT) + } else { + emitOpcode(c.prog, vm.ADD) + } + case n.Op == token.EQL: + // VM has separate opcodes for number and string equality + if isStringType(tinfo.Type) { + emitOpcode(c.prog, vm.EQUAL) + } else { + emitOpcode(c.prog, vm.NUMEQUAL) + } + default: + c.convertToken(n.Op) + } return nil } @@ -394,7 +426,10 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { // Dont forget to add 1 extra argument when its a method. numArgs++ } + f, ok = c.funcs[fun.Sel.Name] + // @FIXME this could cause runtime errors. + f.selector = fun.X.(*ast.Ident) if !ok { log.Fatalf("could not resolve function %s", fun.Sel.Name) } @@ -414,36 +449,30 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { // Do not swap for builtin functions. if !isBuiltin { if numArgs == 2 { - emitOpcode(c.prog, vm.Oswap) - } - if numArgs == 3 { + emitOpcode(c.prog, vm.SWAP) + } else if numArgs == 3 { emitInt(c.prog, 2) - emitOpcode(c.prog, vm.Oxswap) + emitOpcode(c.prog, vm.XSWAP) + } else { + for i := 1; i < numArgs; i++ { + emitInt(c.prog, int64(i)) + emitOpcode(c.prog, vm.ROLL) + } } } - // c# compiler adds a NOP (0x61) before every function call. Dont think its relevant - // and we could easily removed it, but to be consistent with the original compiler I - // will put them in. ^^ - emitOpcode(c.prog, vm.Onop) - // Check builtin first to avoid nil pointer on funcScope! switch { case isBuiltin: // Use the ident to check, builtins are not in func scopes. // We can be sure builtins are of type *ast.Ident. c.convertBuiltin(n) - case isSyscall(f.name): - c.convertSyscall(f.name) + case isSyscall(f): + c.convertSyscall(f.selector.Name, f.name) default: - emitCall(c.prog, vm.Ocall, int16(f.label)) + emitCall(c.prog, vm.CALL, int16(f.label)) } - // If we are not assigning this function to a variable we need to drop - // (cleanup) the top stack item. It's not a void but you get the point \o/. - if _, ok := c.scope.voidCalls[n]; ok { - emitOpcode(c.prog, vm.Odrop) - } return nil case *ast.SelectorExpr: @@ -462,7 +491,22 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { case *ast.UnaryExpr: ast.Walk(c, n.X) - c.convertToken(n.Op) + // From https://golang.org/ref/spec#Operators + // there can be only following unary operators + // "+" | "-" | "!" | "^" | "*" | "&" | "<-" . + // of which last three are not used in SC + switch n.Op { + case token.ADD: + // +10 == 10, no need to do anything in this case + case token.SUB: + emitOpcode(c.prog, vm.NEGATE) + case token.NOT: + emitOpcode(c.prog, vm.NOT) + case token.XOR: + emitOpcode(c.prog, vm.INVERT) + default: + log.Fatalf("invalid unary operator: %s", n.Op) + } return nil case *ast.IncDecStmt: @@ -490,7 +534,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { c.emitLoadField(int(val)) default: ast.Walk(c, n.Index) - emitOpcode(c.prog, vm.Opickitem) // just pickitem here + emitOpcode(c.prog, vm.PICKITEM) // just pickitem here } return nil @@ -508,14 +552,14 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { ast.Walk(c, n.Cond) // Jump if the condition is false - emitJmp(c.prog, vm.Ojmpifnot, int16(fend)) + emitJmp(c.prog, vm.JMPIFNOT, int16(fend)) // Walk body followed by the iterator (post stmt). ast.Walk(c, n.Body) ast.Walk(c, n.Post) // Jump back to condition. - emitJmp(c.prog, vm.Ojmp, int16(fstart)) + emitJmp(c.prog, vm.JMP, int16(fstart)) c.setLabel(fend) return nil @@ -531,13 +575,16 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor { return c } -func (c *codegen) convertSyscall(name string) { - api, ok := vm.Syscalls[name] +func (c *codegen) convertSyscall(api, name string) { + api, ok := syscalls[api][name] if !ok { log.Fatalf("unknown VM syscall api: %s", name) } emitSyscall(c.prog, api) - emitOpcode(c.prog, vm.Onop) // @OPTIMIZE + + // 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. + emitOpcode(c.prog, vm.NOP) } func (c *codegen) convertBuiltin(expr *ast.CallExpr) { @@ -554,20 +601,22 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) { arg := expr.Args[0] typ := c.typeInfo.Types[arg].Type if isStringType(typ) { - emitOpcode(c.prog, vm.Osize) + emitOpcode(c.prog, vm.SIZE) } else { - emitOpcode(c.prog, vm.Oarraysize) + emitOpcode(c.prog, vm.ARRAYSIZE) } case "append": - emitOpcode(c.prog, vm.Oappend) + emitOpcode(c.prog, vm.APPEND) case "SHA256": - emitOpcode(c.prog, vm.Osha256) + emitOpcode(c.prog, vm.SHA256) case "SHA1": - emitOpcode(c.prog, vm.Osha1) + emitOpcode(c.prog, vm.SHA1) case "Hash256": - emitOpcode(c.prog, vm.Ohash256) + emitOpcode(c.prog, vm.HASH256) case "Hash160": - emitOpcode(c.prog, vm.Ohash160) + emitOpcode(c.prog, vm.HASH160) + case "Equals": + emitOpcode(c.prog, vm.EQUAL) case "FromAddress": // We can be sure that this is a ast.BasicLit just containing a simple // address string. Note that the string returned from calling Value will @@ -601,10 +650,10 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit) { log.Fatalf("the given literal is not of type struct: %v", lit) } - emitOpcode(c.prog, vm.Onop) + emitOpcode(c.prog, vm.NOP) emitInt(c.prog, int64(strct.NumFields())) - emitOpcode(c.prog, vm.Onewstruct) - emitOpcode(c.prog, vm.Otoaltstack) + emitOpcode(c.prog, vm.NEWSTRUCT) + emitOpcode(c.prog, vm.TOALTSTACK) // We need to locally store all the fields, even if they are not initialized. // We will initialize all fields to their "zero" value. @@ -633,45 +682,58 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit) { c.emitLoadConst(typeAndVal) c.emitStoreLocal(i) } - emitOpcode(c.prog, vm.Ofromaltstack) + emitOpcode(c.prog, vm.FROMALTSTACK) } func (c *codegen) convertToken(tok token.Token) { switch tok { case token.ADD_ASSIGN: - emitOpcode(c.prog, vm.Oadd) + emitOpcode(c.prog, vm.ADD) case token.SUB_ASSIGN: - emitOpcode(c.prog, vm.Osub) + emitOpcode(c.prog, vm.SUB) case token.MUL_ASSIGN: - emitOpcode(c.prog, vm.Omul) + emitOpcode(c.prog, vm.MUL) case token.QUO_ASSIGN: - emitOpcode(c.prog, vm.Odiv) + emitOpcode(c.prog, vm.DIV) case token.ADD: - emitOpcode(c.prog, vm.Oadd) + emitOpcode(c.prog, vm.ADD) case token.SUB: - emitOpcode(c.prog, vm.Osub) + emitOpcode(c.prog, vm.SUB) case token.MUL: - emitOpcode(c.prog, vm.Omul) + emitOpcode(c.prog, vm.MUL) case token.QUO: - emitOpcode(c.prog, vm.Odiv) + emitOpcode(c.prog, vm.DIV) case token.LSS: - emitOpcode(c.prog, vm.Olt) + emitOpcode(c.prog, vm.LT) case token.LEQ: - emitOpcode(c.prog, vm.Olte) + emitOpcode(c.prog, vm.LTE) case token.GTR: - emitOpcode(c.prog, vm.Ogt) + emitOpcode(c.prog, vm.GT) case token.GEQ: - emitOpcode(c.prog, vm.Ogte) + emitOpcode(c.prog, vm.GTE) case token.EQL: - emitOpcode(c.prog, vm.Onumequal) + // TODO: this is wrong (and the next one also is), see issue #294 + // Changing it EQUAL is not that big of an improvement, so we're + // using NUMEQUAL for now + emitOpcode(c.prog, vm.NUMEQUAL) case token.NEQ: - emitOpcode(c.prog, vm.Onumnotequal) + emitOpcode(c.prog, vm.NUMNOTEQUAL) case token.DEC: - emitOpcode(c.prog, vm.Odec) + emitOpcode(c.prog, vm.DEC) case token.INC: - emitOpcode(c.prog, vm.Oinc) + emitOpcode(c.prog, vm.INC) case token.NOT: - emitOpcode(c.prog, vm.Onot) + emitOpcode(c.prog, vm.NOT) + case token.AND: + emitOpcode(c.prog, vm.AND) + case token.OR: + emitOpcode(c.prog, vm.OR) + case token.SHL: + emitOpcode(c.prog, vm.SHL) + case token.SHR: + emitOpcode(c.prog, vm.SHR) + case token.XOR: + emitOpcode(c.prog, vm.XOR) default: log.Fatalf("compiler could not convert token: %s", tok) } @@ -712,8 +774,16 @@ func CodeGen(info *buildInfo) (*bytes.Buffer, error) { // convert the entry point first c.convertFuncDecl(mainFile, main) + // sort map keys to generate code deterministically + keys := make([]*types.Package, 0, len(info.program.AllPackages)) + for p := range info.program.AllPackages { + keys = append(keys, p) + } + sort.Slice(keys, func(i, j int) bool { return keys[i].Path() < keys[j].Path() }) + // Generate the code for the program - for _, pkg := range info.program.AllPackages { + for _, k := range keys { + pkg := info.program.AllPackages[k] c.typeInfo = &pkg.Info for _, f := range pkg.Files { @@ -750,8 +820,8 @@ func (c *codegen) writeJumps() { b := c.prog.Bytes() for i, op := range b { j := i + 1 - switch vm.Opcode(op) { - case vm.Ojmp, vm.Ojmpifnot, vm.Ojmpif, vm.Ocall: + switch vm.Instruction(op) { + case vm.JMP, vm.JMPIFNOT, vm.JMPIF, vm.CALL: index := int16(binary.LittleEndian.Uint16(b[j : j+2])) if int(index) > len(c.l) || int(index) < 0 { continue diff --git a/pkg/vm/compiler/compiler.go b/pkg/vm/compiler/compiler.go index 28dfa0a3d..828e8983c 100644 --- a/pkg/vm/compiler/compiler.go +++ b/pkg/vm/compiler/compiler.go @@ -90,16 +90,15 @@ func CompileAndSave(src string, o *Options) error { if err != nil { return fmt.Errorf("Error while trying to compile smart contract file: %v", err) } - if o.Debug { - log.Println(hex.EncodeToString(b)) - } + + log.Println(hex.EncodeToString(b)) out := fmt.Sprintf("%s.%s", o.Outfile, o.Ext) return ioutil.WriteFile(out, b, os.ModePerm) } -// DumpOpcode compiles the program and dumps the opcode in a user friendly format. -func DumpOpcode(src string) error { +// CompileAndInspect compiles the program and dumps the opcode in a user friendly format. +func CompileAndInspect(src string) error { b, err := ioutil.ReadFile(src) if err != nil { return err @@ -111,8 +110,24 @@ func DumpOpcode(src string) error { w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0) fmt.Fprintln(w, "INDEX\tOPCODE\tDESC\t") - for i := 0; i < len(b); i++ { - fmt.Fprintf(w, "%d\t0x%2x\t%s\t\n", i, b[i], vm.Opcode(b[i])) + for i := 0; i <= len(b)-1; { + instr := vm.Instruction(b[i]) + if instr >= vm.PUSHBYTES1 && instr <= vm.PUSHBYTES75 { + fmt.Fprintf(w, "%d\t0x%x\t%s\t\n", i, b[i], fmt.Sprintf("PUSHBYTES%d", int(instr))) + for x := 0; x < int(instr); x++ { + fmt.Fprintf(w, "%d\t0x%x\t%s\t\n", i, b[i+1+x], string(b[i+1+x])) + } + i += int(instr) + 1 + continue + } + fmt.Fprintf(w, "%d\t0x%x\t%s\t\n", i, b[i], instr) + i++ + if instr == vm.JMP || instr == vm.JMPIF || instr == vm.JMPIFNOT || instr == vm.CALL { + for x := 0; x < 2; x++ { + fmt.Fprintf(w, "%d\t0x%x\t%d\t\n", i, b[i + x], b[i + x]) + } + i += 2 + } } w.Flush() return nil diff --git a/pkg/vm/compiler/compiler_test.go b/pkg/vm/compiler/compiler_test.go new file mode 100644 index 000000000..1ab46d1ae --- /dev/null +++ b/pkg/vm/compiler/compiler_test.go @@ -0,0 +1,57 @@ +package compiler_test + +import ( + "io/ioutil" + "os" + "path" + "testing" + + "github.com/CityOfZion/neo-go/pkg/vm/compiler" +) + +const examplePath = "../../../examples" + +func TestExamplesFolder(t *testing.T) { + infos, err := ioutil.ReadDir(examplePath) + if err != nil { + t.Fatal(err) + } + + for _, info := range infos { + infos, err := ioutil.ReadDir(path.Join(examplePath, info.Name())) + if err != nil { + t.Fatal(err) + } + if len(infos) == 0 { + t.Fatal("detected smart contract folder with no contract in it") + } + + filename := filterFilename(infos) + targetPath := path.Join(examplePath, info.Name(), filename) + if err := compileFile(targetPath); err != nil { + t.Fatal(err) + } + } +} + +func filterFilename(infos []os.FileInfo) string { + for _, info := range infos { + if !info.IsDir() { + return info.Name() + } + } + return "" +} + +func compileFile(src string) error { + o := compiler.Options{ + Outfile: "tmp/contract.avm", + } + + file, err := os.Open(src) + if err != nil { + return err + } + _, err = compiler.Compile(file, &o) + return err +} diff --git a/pkg/vm/compiler/emit.go b/pkg/vm/compiler/emit.go index 3221ac745..584979e5d 100644 --- a/pkg/vm/compiler/emit.go +++ b/pkg/vm/compiler/emit.go @@ -12,34 +12,34 @@ import ( "github.com/CityOfZion/neo-go/pkg/vm" ) -func emit(w *bytes.Buffer, op vm.Opcode, b []byte) error { - if err := w.WriteByte(byte(op)); err != nil { +func emit(w *bytes.Buffer, instr vm.Instruction, b []byte) error { + if err := w.WriteByte(byte(instr)); err != nil { return err } _, err := w.Write(b) return err } -func emitOpcode(w io.ByteWriter, op vm.Opcode) error { - return w.WriteByte(byte(op)) +func emitOpcode(w io.ByteWriter, instr vm.Instruction) error { + return w.WriteByte(byte(instr)) } func emitBool(w io.ByteWriter, ok bool) error { if ok { - return emitOpcode(w, vm.Opusht) + return emitOpcode(w, vm.PUSHT) } - return emitOpcode(w, vm.Opushf) + return emitOpcode(w, vm.PUSHF) } func emitInt(w *bytes.Buffer, i int64) error { if i == -1 { - return emitOpcode(w, vm.Opushm1) + return emitOpcode(w, vm.PUSHM1) } if i == 0 { - return emitOpcode(w, vm.Opushf) + return emitOpcode(w, vm.PUSHF) } if i > 0 && i < 16 { - val := vm.Opcode(int(vm.Opush1) - 1 + int(i)) + val := vm.Instruction(int(vm.PUSH1) - 1 + int(i)) return emitOpcode(w, val) } @@ -59,18 +59,18 @@ func emitBytes(w *bytes.Buffer, b []byte) error { ) switch { - case n <= int(vm.Opushbytes75): - return emit(w, vm.Opcode(n), b) + case n <= int(vm.PUSHBYTES75): + return emit(w, vm.Instruction(n), b) case n < 0x100: - err = emit(w, vm.Opushdata1, []byte{byte(n)}) + err = emit(w, vm.PUSHDATA1, []byte{byte(n)}) case n < 0x10000: buf := make([]byte, 2) binary.LittleEndian.PutUint16(buf, uint16(n)) - err = emit(w, vm.Opushdata2, buf) + err = emit(w, vm.PUSHDATA2, buf) default: buf := make([]byte, 4) binary.LittleEndian.PutUint32(buf, uint32(n)) - err = emit(w, vm.Opushdata4, buf) + err = emit(w, vm.PUSHDATA4, buf) } if err != nil { return err @@ -86,24 +86,24 @@ func emitSyscall(w *bytes.Buffer, api string) error { buf := make([]byte, len(api)+1) buf[0] = byte(len(api)) copy(buf[1:], []byte(api)) - return emit(w, vm.Osyscall, buf) + return emit(w, vm.SYSCALL, buf) } -func emitCall(w *bytes.Buffer, op vm.Opcode, label int16) error { - return emitJmp(w, op, label) +func emitCall(w *bytes.Buffer, instr vm.Instruction, label int16) error { + return emitJmp(w, instr, label) } -func emitJmp(w *bytes.Buffer, op vm.Opcode, label int16) error { - if !isOpcodeJmp(op) { - return fmt.Errorf("opcode %s is not a jump or call type", op) +func emitJmp(w *bytes.Buffer, instr vm.Instruction, label int16) error { + if !isInstrJmp(instr) { + return fmt.Errorf("opcode %s is not a jump or call type", instr) } buf := make([]byte, 2) binary.LittleEndian.PutUint16(buf, uint16(label)) - return emit(w, op, buf) + return emit(w, instr, buf) } -func isOpcodeJmp(op vm.Opcode) bool { - if op == vm.Ojmp || op == vm.Ojmpifnot || op == vm.Ojmpif || op == vm.Ocall { +func isInstrJmp(instr vm.Instruction) bool { + if instr == vm.JMP || instr == vm.JMPIFNOT || instr == vm.JMPIF || instr == vm.CALL { return true } return false diff --git a/pkg/vm/compiler/func_scope.go b/pkg/vm/compiler/func_scope.go index 7fdc5f843..a71fcda07 100644 --- a/pkg/vm/compiler/func_scope.go +++ b/pkg/vm/compiler/func_scope.go @@ -10,6 +10,10 @@ type funcScope struct { // identifier of the function. name string + // Selector of the function if there is any. Only functions imported + // from other packages should have a selector. + selector *ast.Ident + // The declaration of the function in the AST. Nil if this scope is not a function. decl *ast.FuncDecl diff --git a/pkg/vm/compiler/syscall.go b/pkg/vm/compiler/syscall.go new file mode 100644 index 000000000..62e24f4c9 --- /dev/null +++ b/pkg/vm/compiler/syscall.go @@ -0,0 +1,91 @@ +package compiler + +var syscalls = map[string]map[string]string{ + "storage": { + "GetContext": "Neo.Storage.GetContext", + "Put": "Neo.Storage.Put", + "Get": "Neo.Storage.Get", + "Delete": "Neo.Storage.Delete", + "Find": "Neo.Storage.Find", + }, + "runtime": { + "GetTrigger": "Neo.Runtime.GetTrigger", + "CheckWitness": "Neo.Runtime.CheckWitness", + "Notify": "Neo.Runtime.Notify", + "Log": "Neo.Runtime.Log", + "GetTime": "Neo.Runtime.GetTime", + "Serialize": "Neo.Runtime.Serialize", + "Deserialize": "Neo.Runtime.Deserialize", + }, + "blockchain": { + "GetHeight": "Neo.Blockchain.GetHeight", + "GetHeader": "Neo.Blockchain.GetHeader", + "GetBlock": "Neo.Blockchain.GetBlock", + "GetTransaction": "Neo.Blockchain.GetTransaction", + "GetContract": "Neo.Blockchain.GetContract", + "GetAccount": "Neo.Blockchain.GetAccount", + "GetValidators": "Neo.Blockchain.GetValidators", + "GetAsset": "Neo.Blockchain.GetAsset", + }, + "header": { + "GetIndex": "Neo.Header.GetIndex", + "GetHash": "Neo.Header.GetHash", + "GetPrevHash": "Neo.Header.GetPrevHash", + "GetTimestamp": "Neo.Header.GetTimestamp", + "GetVersion": "Neo.Header.GetVersion", + "GetMerkleRoot": "Neo.Header.GetMerkleRoot", + "GetConsensusData": "Neo.Header.GetConsensusData", + "GetNextConsensus": "Neo.Header.GetNextConsensus", + }, + "block": { + "GetTransactionCount": "Neo.Block.GetTransactionCount", + "GetTransactions": "Neo.Block.GetTransactions", + "GetTransaction": "Neo.Block.GetTransaction", + }, + "transaction": { + "GetHash": "Neo.Transaction.GetHash", + "GetType": "Neo.Transaction.GetType", + "GetAttributes": "Neo.Transaction.GetAttributes", + "GetInputs": "Neo.Transaction.GetInputs", + "GetOutputs": "Neo.Transaction.GetOutputs", + "GetReferences": "Neo.Transaction.GetReferences", + "GetUnspentCoins": "Neo.Transaction.GetUnspentCoins", + "GetScript": "Neo.Transaction.GetScript", + }, + "asset": { + "GetAssetID": "Neo.Asset.GetAssetID", + "GetAssetType": "Neo.Asset.GetAssetType", + "GetAmount": "Neo.Asset.GetAmount", + "Create": "Neo.Asset.Create", + "Renew": "Neo.Asset.Renew", + }, + "contract": { + "GetScript": "Neo.Contract.GetScript", + "IsPayable": "Neo.Contract.IsPayable", + "Create": "Neo.Contract.Create", + "Destroy": "Neo.Contract.Destroy", + "Migrate": "Neo.Contract.Migrate", + "GetStorageContext": "Neo.Contract.GetStorageContext", + }, + "input": { + "GetHash": "Neo.Input.GetHash", + "GetIndex": "Neo.Input.GetIndex", + }, + "output": { + "GetAssetID": "Neo.Output.GetAssetID", + "GetValue": "Neo.Output.GetValue", + "GetScriptHash": "Neo.Output.GetScriptHash", + }, + "engine": { + "GetScriptContainer": "System.ExecutionEngine.GetScriptContainer", + "GetCallingScriptHash": "System.ExecutionEngine.GetCallingScriptHash", + "GetEntryScriptHash": "System.ExecutionEngine.GetEntryScriptHash", + "GetExecutingScriptHash": "System.ExecutionEngine.GetExecutingScriptHash", + }, + "iterator": { + "Create": "Neo.Iterator.Create", + "Key": "Neo.Iterator.Key", + "Keys": "Neo.Iterator.Keys", + "Values": "Neo.Iterator.Values", + }, +} diff --git a/pkg/vm/context.go b/pkg/vm/context.go index ed4fef036..043c89acf 100644 --- a/pkg/vm/context.go +++ b/pkg/vm/context.go @@ -26,12 +26,12 @@ func NewContext(b []byte) *Context { } // Next return the next instruction to execute. -func (c *Context) Next() Opcode { +func (c *Context) Next() Instruction { c.ip++ if c.ip >= len(c.prog) { - return Oret + return RET } - return Opcode(c.prog[c.ip]) + return Instruction(c.prog[c.ip]) } // IP returns the absolute instruction without taking 0 into account. @@ -47,11 +47,11 @@ func (c *Context) LenInstr() int { } // CurrInstr returns the current instruction and opcode. -func (c *Context) CurrInstr() (int, Opcode) { +func (c *Context) CurrInstr() (int, Instruction) { if c.ip < 0 { - return c.ip, Opcode(0x00) + return c.ip, Instruction(0x00) } - return c.ip, Opcode(c.prog[c.ip]) + return c.ip, Instruction(c.prog[c.ip]) } // Copy returns an new exact copy of c. diff --git a/pkg/vm/emit.go b/pkg/vm/emit.go index 49b794e86..14556b039 100644 --- a/pkg/vm/emit.go +++ b/pkg/vm/emit.go @@ -11,8 +11,8 @@ import ( "github.com/CityOfZion/neo-go/pkg/util" ) -// Emit a VM Opcode with data to the given buffer. -func Emit(w *bytes.Buffer, op Opcode, b []byte) error { +// Emit a VM Instruction with data to the given buffer. +func Emit(w *bytes.Buffer, op Instruction, b []byte) error { if err := w.WriteByte(byte(op)); err != nil { return err } @@ -20,29 +20,29 @@ func Emit(w *bytes.Buffer, op Opcode, b []byte) error { return err } -// EmitOpcode emits a single VM Opcode the given buffer. -func EmitOpcode(w io.ByteWriter, op Opcode) error { +// EmitOpcode emits a single VM Instruction the given buffer. +func EmitOpcode(w io.ByteWriter, op Instruction) error { return w.WriteByte(byte(op)) } // EmitBool emits a bool type the given buffer. func EmitBool(w io.ByteWriter, ok bool) error { if ok { - return EmitOpcode(w, Opusht) + return EmitOpcode(w, PUSHT) } - return EmitOpcode(w, Opushf) + return EmitOpcode(w, PUSHF) } // EmitInt emits a int type to the given buffer. func EmitInt(w *bytes.Buffer, i int64) error { if i == -1 { - return EmitOpcode(w, Opushm1) + return EmitOpcode(w, PUSHM1) } if i == 0 { - return EmitOpcode(w, Opushf) + return EmitOpcode(w, PUSHF) } if i > 0 && i < 16 { - val := Opcode(int(Opush1) - 1 + int(i)) + val := Instruction(int(PUSH1) - 1 + int(i)) return EmitOpcode(w, val) } @@ -63,18 +63,18 @@ func EmitBytes(w *bytes.Buffer, b []byte) error { n = len(b) ) - if n <= int(Opushbytes75) { - return Emit(w, Opcode(n), b) + if n <= int(PUSHBYTES75) { + return Emit(w, Instruction(n), b) } else if n < 0x100 { - err = Emit(w, Opushdata1, []byte{byte(n)}) + err = Emit(w, PUSHDATA1, []byte{byte(n)}) } else if n < 0x10000 { buf := make([]byte, 2) binary.LittleEndian.PutUint16(buf, uint16(n)) - err = Emit(w, Opushdata2, buf) + err = Emit(w, PUSHDATA2, buf) } else { buf := make([]byte, 4) binary.LittleEndian.PutUint32(buf, uint32(n)) - err = Emit(w, Opushdata4, buf) + err = Emit(w, PUSHDATA4, buf) } if err != nil { return err @@ -92,17 +92,17 @@ func EmitSyscall(w *bytes.Buffer, api string) error { buf := make([]byte, len(api)+1) buf[0] = byte(len(api)) copy(buf[1:], []byte(api)) - return Emit(w, Osyscall, buf) + return Emit(w, SYSCALL, buf) } -// EmitCall emits a call Opcode with label to the given buffer. -func EmitCall(w *bytes.Buffer, op Opcode, label int16) error { +// EmitCall emits a call Instruction with label to the given buffer. +func EmitCall(w *bytes.Buffer, op Instruction, label int16) error { return EmitJmp(w, op, label) } -// EmitJmp emits a jump Opcode along with label to the given buffer. -func EmitJmp(w *bytes.Buffer, op Opcode, label int16) error { - if !isOpcodeJmp(op) { +// EmitJmp emits a jump Instruction along with label to the given buffer. +func EmitJmp(w *bytes.Buffer, op Instruction, label int16) error { + if !isInstructionJmp(op) { return fmt.Errorf("opcode %s is not a jump or call type", op.String()) } buf := make([]byte, 2) @@ -113,9 +113,9 @@ func EmitJmp(w *bytes.Buffer, op Opcode, label int16) error { // EmitAppCall emits an appcall, if tailCall is true, tailCall opcode will be // emitted instead. func EmitAppCall(w *bytes.Buffer, scriptHash util.Uint160, tailCall bool) error { - op := Oappcall + op := APPCALL if tailCall { - op = Otailcall + op = TAILCALL } return Emit(w, op, scriptHash.Bytes()) } @@ -142,8 +142,8 @@ func EmitAppCallWithOperation(w *bytes.Buffer, scriptHash util.Uint160, operatio return EmitAppCall(w, scriptHash, false) } -func isOpcodeJmp(op Opcode) bool { - if op == Ojmp || op == Ojmpifnot || op == Ojmpif || op == Ocall { +func isInstructionJmp(op Instruction) bool { + if op == JMP || op == JMPIFNOT || op == JMPIF || op == CALL { return true } return false diff --git a/pkg/vm/emit_test.go b/pkg/vm/emit_test.go index 2c127636c..0baf995bc 100644 --- a/pkg/vm/emit_test.go +++ b/pkg/vm/emit_test.go @@ -11,7 +11,7 @@ import ( func TestEmitInt(t *testing.T) { buf := new(bytes.Buffer) EmitInt(buf, 10) - assert.Equal(t, Opcode(buf.Bytes()[0]), Opush10) + assert.Equal(t, Instruction(buf.Bytes()[0]), PUSH10) buf.Reset() EmitInt(buf, 100) assert.Equal(t, buf.Bytes()[0], uint8(1)) @@ -26,8 +26,8 @@ func TestEmitBool(t *testing.T) { buf := new(bytes.Buffer) EmitBool(buf, true) EmitBool(buf, false) - assert.Equal(t, Opcode(buf.Bytes()[0]), Opush1) - assert.Equal(t, Opcode(buf.Bytes()[1]), Opush0) + assert.Equal(t, Instruction(buf.Bytes()[0]), PUSH1) + assert.Equal(t, Instruction(buf.Bytes()[1]), PUSH0) } func TestEmitString(t *testing.T) { @@ -48,7 +48,7 @@ func TestEmitSyscall(t *testing.T) { buf := new(bytes.Buffer) for _, syscall := range syscalls { EmitSyscall(buf, syscall) - assert.Equal(t, Opcode(buf.Bytes()[0]), Osyscall) + assert.Equal(t, Instruction(buf.Bytes()[0]), SYSCALL) assert.Equal(t, buf.Bytes()[1], uint8(len(syscall))) assert.Equal(t, buf.Bytes()[2:], []byte(syscall)) buf.Reset() @@ -57,8 +57,8 @@ func TestEmitSyscall(t *testing.T) { func TestEmitCall(t *testing.T) { buf := new(bytes.Buffer) - EmitCall(buf, Ojmp, 100) - assert.Equal(t, Opcode(buf.Bytes()[0]), Ojmp) + EmitCall(buf, JMP, 100) + assert.Equal(t, Instruction(buf.Bytes()[0]), JMP) label := binary.LittleEndian.Uint16(buf.Bytes()[1:3]) assert.Equal(t, label, uint16(100)) } diff --git a/pkg/vm/instruction_string.go b/pkg/vm/instruction_string.go new file mode 100644 index 000000000..da98524bb --- /dev/null +++ b/pkg/vm/instruction_string.go @@ -0,0 +1,118 @@ +// Code generated by "stringer -type=Instruction"; DO NOT EDIT. + +package vm + +import "strconv" + +const _Instruction_name = "PUSH0PUSHBYTES1PUSHBYTES75PUSHDATA1PUSHDATA2PUSHDATA4PUSHM1PUSH1PUSH2PUSH3PUSH4PUSH5PUSH6PUSH7PUSH8PUSH9PUSH10PUSH11PUSH12PUSH13PUSH14PUSH15PUSH16NOPJMPJMPIFJMPIFNOTCALLRETAPPCALLSYSCALLTAILCALLDUPFROMALTSTACKTOALTSTACKFROMALTSTACKXDROPXSWAPXTUCKDEPTHDROPDUPNIPOVERPICKROLLROTSWAPTUCKCATSUBSTRLEFTRIGHTSIZEINVERTANDORXOREQUALINCDECSIGNNEGATEABSNOTNZADDSUBMULDIVMODSHLSHRBOOLANDBOOLORNUMEQUALNUMNOTEQUALLTGTLTEGTEMINMAXWITHINSHA1SHA256HASH160HASH256CHECKSIGCHECKMULTISIGARRAYSIZEPACKUNPACKPICKITEMSETITEMNEWARRAYNEWSTRUCTAPPENDREVERSEREMOVETHROWTHROWIFNOT" + +var _Instruction_map = map[Instruction]string{ + 0: _Instruction_name[0:5], + 1: _Instruction_name[5:15], + 75: _Instruction_name[15:26], + 76: _Instruction_name[26:35], + 77: _Instruction_name[35:44], + 78: _Instruction_name[44:53], + 79: _Instruction_name[53:59], + 81: _Instruction_name[59:64], + 82: _Instruction_name[64:69], + 83: _Instruction_name[69:74], + 84: _Instruction_name[74:79], + 85: _Instruction_name[79:84], + 86: _Instruction_name[84:89], + 87: _Instruction_name[89:94], + 88: _Instruction_name[94:99], + 89: _Instruction_name[99:104], + 90: _Instruction_name[104:110], + 91: _Instruction_name[110:116], + 92: _Instruction_name[116:122], + 93: _Instruction_name[122:128], + 94: _Instruction_name[128:134], + 95: _Instruction_name[134:140], + 96: _Instruction_name[140:146], + 97: _Instruction_name[146:149], + 98: _Instruction_name[149:152], + 99: _Instruction_name[152:157], + 100: _Instruction_name[157:165], + 101: _Instruction_name[165:169], + 102: _Instruction_name[169:172], + 103: _Instruction_name[172:179], + 104: _Instruction_name[179:186], + 105: _Instruction_name[186:194], + 106: _Instruction_name[194:209], + 107: _Instruction_name[209:219], + 108: _Instruction_name[219:231], + 109: _Instruction_name[231:236], + 114: _Instruction_name[236:241], + 115: _Instruction_name[241:246], + 116: _Instruction_name[246:251], + 117: _Instruction_name[251:255], + 118: _Instruction_name[255:258], + 119: _Instruction_name[258:261], + 120: _Instruction_name[261:265], + 121: _Instruction_name[265:269], + 122: _Instruction_name[269:273], + 123: _Instruction_name[273:276], + 124: _Instruction_name[276:280], + 125: _Instruction_name[280:284], + 126: _Instruction_name[284:287], + 127: _Instruction_name[287:293], + 128: _Instruction_name[293:297], + 129: _Instruction_name[297:302], + 130: _Instruction_name[302:306], + 131: _Instruction_name[306:312], + 132: _Instruction_name[312:315], + 133: _Instruction_name[315:317], + 134: _Instruction_name[317:320], + 135: _Instruction_name[320:325], + 139: _Instruction_name[325:328], + 140: _Instruction_name[328:331], + 141: _Instruction_name[331:335], + 143: _Instruction_name[335:341], + 144: _Instruction_name[341:344], + 145: _Instruction_name[344:347], + 146: _Instruction_name[347:349], + 147: _Instruction_name[349:352], + 148: _Instruction_name[352:355], + 149: _Instruction_name[355:358], + 150: _Instruction_name[358:361], + 151: _Instruction_name[361:364], + 152: _Instruction_name[364:367], + 153: _Instruction_name[367:370], + 154: _Instruction_name[370:377], + 155: _Instruction_name[377:383], + 156: _Instruction_name[383:391], + 158: _Instruction_name[391:402], + 159: _Instruction_name[402:404], + 160: _Instruction_name[404:406], + 161: _Instruction_name[406:409], + 162: _Instruction_name[409:412], + 163: _Instruction_name[412:415], + 164: _Instruction_name[415:418], + 165: _Instruction_name[418:424], + 167: _Instruction_name[424:428], + 168: _Instruction_name[428:434], + 169: _Instruction_name[434:441], + 170: _Instruction_name[441:448], + 172: _Instruction_name[448:456], + 174: _Instruction_name[456:469], + 192: _Instruction_name[469:478], + 193: _Instruction_name[478:482], + 194: _Instruction_name[482:488], + 195: _Instruction_name[488:496], + 196: _Instruction_name[496:503], + 197: _Instruction_name[503:511], + 198: _Instruction_name[511:520], + 200: _Instruction_name[520:526], + 201: _Instruction_name[526:533], + 202: _Instruction_name[533:539], + 240: _Instruction_name[539:544], + 241: _Instruction_name[544:554], +} + +func (i Instruction) String() string { + if str, ok := _Instruction_map[i]; ok { + return str + } + return "Instruction(" + strconv.FormatInt(int64(i), 10) + ")" +} diff --git a/pkg/vm/instructions.go b/pkg/vm/instructions.go new file mode 100644 index 000000000..032c0a5c9 --- /dev/null +++ b/pkg/vm/instructions.go @@ -0,0 +1,130 @@ +package vm + +//go:generate stringer -type=Instruction + +// Instruction represents an single operation for the NEO virtual machine. +type Instruction byte + +// Viable list of supported instruction constants. +const ( + // Constants + PUSH0 Instruction = 0x00 + PUSHF Instruction = PUSH0 + PUSHBYTES1 Instruction = 0x01 + PUSHBYTES75 Instruction = 0x4B + PUSHDATA1 Instruction = 0x4C + PUSHDATA2 Instruction = 0x4D + PUSHDATA4 Instruction = 0x4E + PUSHM1 Instruction = 0x4F + PUSH1 Instruction = 0x51 + PUSHT Instruction = PUSH1 + PUSH2 Instruction = 0x52 + PUSH3 Instruction = 0x53 + PUSH4 Instruction = 0x54 + PUSH5 Instruction = 0x55 + PUSH6 Instruction = 0x56 + PUSH7 Instruction = 0x57 + PUSH8 Instruction = 0x58 + PUSH9 Instruction = 0x59 + PUSH10 Instruction = 0x5A + PUSH11 Instruction = 0x5B + PUSH12 Instruction = 0x5C + PUSH13 Instruction = 0x5D + PUSH14 Instruction = 0x5E + PUSH15 Instruction = 0x5F + PUSH16 Instruction = 0x60 + + // Flow control + NOP Instruction = 0x61 + JMP Instruction = 0x62 + JMPIF Instruction = 0x63 + JMPIFNOT Instruction = 0x64 + CALL Instruction = 0x65 + RET Instruction = 0x66 + APPCALL Instruction = 0x67 + SYSCALL Instruction = 0x68 + TAILCALL Instruction = 0x69 + + // Stack + DUPFROMALTSTACK Instruction = 0x6A + TOALTSTACK Instruction = 0x6B + FROMALTSTACK Instruction = 0x6C + XDROP Instruction = 0x6D + XSWAP Instruction = 0x72 + XTUCK Instruction = 0x73 + DEPTH Instruction = 0x74 + DROP Instruction = 0x75 + DUP Instruction = 0x76 + NIP Instruction = 0x77 + OVER Instruction = 0x78 + PICK Instruction = 0x79 + ROLL Instruction = 0x7A + ROT Instruction = 0x7B + SWAP Instruction = 0x7C + TUCK Instruction = 0x7D + + // Splice + CAT Instruction = 0x7E + SUBSTR Instruction = 0x7F + LEFT Instruction = 0x80 + RIGHT Instruction = 0x81 + SIZE Instruction = 0x82 + + // Bitwise logic + INVERT Instruction = 0x83 + AND Instruction = 0x84 + OR Instruction = 0x85 + XOR Instruction = 0x86 + EQUAL Instruction = 0x87 + + // Arithmetic + INC Instruction = 0x8B + DEC Instruction = 0x8C + SIGN Instruction = 0x8D + NEGATE Instruction = 0x8F + ABS Instruction = 0x90 + NOT Instruction = 0x91 + NZ Instruction = 0x92 + ADD Instruction = 0x93 + SUB Instruction = 0x94 + MUL Instruction = 0x95 + DIV Instruction = 0x96 + MOD Instruction = 0x97 + SHL Instruction = 0x98 + SHR Instruction = 0x99 + BOOLAND Instruction = 0x9A + BOOLOR Instruction = 0x9B + NUMEQUAL Instruction = 0x9C + NUMNOTEQUAL Instruction = 0x9E + LT Instruction = 0x9F + GT Instruction = 0xA0 + LTE Instruction = 0xA1 + GTE Instruction = 0xA2 + MIN Instruction = 0xA3 + MAX Instruction = 0xA4 + WITHIN Instruction = 0xA5 + + // Crypto + SHA1 Instruction = 0xA7 + SHA256 Instruction = 0xA8 + HASH160 Instruction = 0xA9 + HASH256 Instruction = 0xAA + CHECKSIG Instruction = 0xAC + CHECKMULTISIG Instruction = 0xAE + + // Array + ARRAYSIZE Instruction = 0xC0 + PACK Instruction = 0xC1 + UNPACK Instruction = 0xC2 + PICKITEM Instruction = 0xC3 + SETITEM Instruction = 0xC4 + NEWARRAY Instruction = 0xC5 + NEWSTRUCT Instruction = 0xC6 + APPEND Instruction = 0xC8 + REVERSE Instruction = 0xC9 + REMOVE Instruction = 0xCA + + // Exceptions + THROW Instruction = 0xF0 + THROWIFNOT Instruction = 0xF1 +) diff --git a/pkg/vm/opcode.go b/pkg/vm/opcode.go deleted file mode 100644 index 83cf6a6d7..000000000 --- a/pkg/vm/opcode.go +++ /dev/null @@ -1,131 +0,0 @@ -package vm - -//go:generate stringer -type=Opcode - -// Opcode is an single operational instruction for the GO NEO virtual machine. -type Opcode byte - -// List of supported opcodes. -const ( - // Constants - Opush0 Opcode = 0x00 // An empty array of bytes is pushed onto the stack. - Opushf = Opush0 - Opushbytes1 Opcode = 0x01 // 0x01-0x4B The next opcode bytes is data to be pushed onto the stack - Opushbytes75 Opcode = 0x4B - Opushdata1 Opcode = 0x4C // The next byte contains the number of bytes to be pushed onto the stack. - Opushdata2 Opcode = 0x4D // The next two bytes contain the number of bytes to be pushed onto the stack. - Opushdata4 Opcode = 0x4E // The next four bytes contain the number of bytes to be pushed onto the stack. - Opushm1 Opcode = 0x4F // The number -1 is pushed onto the stack. - Opush1 Opcode = 0x51 - Opusht = Opush1 - Opush2 Opcode = 0x52 // The number 2 is pushed onto the stack. - Opush3 Opcode = 0x53 // The number 3 is pushed onto the stack. - Opush4 Opcode = 0x54 // The number 4 is pushed onto the stack. - Opush5 Opcode = 0x55 // The number 5 is pushed onto the stack. - Opush6 Opcode = 0x56 // The number 6 is pushed onto the stack. - Opush7 Opcode = 0x57 // The number 7 is pushed onto the stack. - Opush8 Opcode = 0x58 // The number 8 is pushed onto the stack. - Opush9 Opcode = 0x59 // The number 9 is pushed onto the stack. - Opush10 Opcode = 0x5A // The number 10 is pushed onto the stack. - Opush11 Opcode = 0x5B // The number 11 is pushed onto the stack. - Opush12 Opcode = 0x5C // The number 12 is pushed onto the stack. - Opush13 Opcode = 0x5D // The number 13 is pushed onto the stack. - Opush14 Opcode = 0x5E // The number 14 is pushed onto the stack. - Opush15 Opcode = 0x5F // The number 15 is pushed onto the stack. - Opush16 Opcode = 0x60 // The number 16 is pushed onto the stack. - - // Flow control - Onop Opcode = 0x61 // No operation. - Ojmp Opcode = 0x62 - Ojmpif Opcode = 0x63 - Ojmpifnot Opcode = 0x64 - Ocall Opcode = 0x65 - Oret Opcode = 0x66 - Oappcall Opcode = 0x67 - Osyscall Opcode = 0x68 - Otailcall Opcode = 0x69 - - // The stack - Odupfromaltstack Opcode = 0x6A - Otoaltstack Opcode = 0x6B // Puts the input onto the top of the alt stack. Removes it from the main stack. - Ofromaltstack Opcode = 0x6C // Puts the input onto the top of the main stack. Removes it from the alt stack. - Oxdrop Opcode = 0x6D - Oxswap Opcode = 0x72 - Oxtuck Opcode = 0x73 - Odepth Opcode = 0x74 // Puts the number of stack items onto the stack. - Odrop Opcode = 0x75 // Removes the top stack item. - Odup Opcode = 0x76 // Duplicates the top stack item. - Onip Opcode = 0x77 // Removes the second-to-top stack item. - Oover Opcode = 0x78 // Copies the second-to-top stack item to the top. - Opick Opcode = 0x79 // The item n back in the stack is copied to the top. - Oroll Opcode = 0x7A // The item n back in the stack is moved to the top. - Orot Opcode = 0x7B // The top three items on the stack are rotated to the left. - Oswap Opcode = 0x7C // The top two items on the stack are swapped. - Otuck Opcode = 0x7D // The item at the top of the stack is copied and inserted before the second-to-top item. - - // Splice - Ocat Opcode = 0x7E // Concatenates two strings. - Osubstr Opcode = 0x7F // Returns a section of a string. - Oleft Opcode = 0x80 // Keeps only characters left of the specified point in a string. - Oright Opcode = 0x81 // Keeps only characters right of the specified point in a string. - Osize Opcode = 0x82 // Returns the length of the input string. - - // Bitwise logic - Oinvert Opcode = 0x83 // Flips all of the bits in the input. - Oand Opcode = 0x84 // Boolean and between each bit in the inputs. - Oor Opcode = 0x85 // Boolean or between each bit in the inputs. - Oxor Opcode = 0x86 // Boolean exclusive or between each bit in the inputs. - Oequal Opcode = 0x87 // Returns 1 if the inputs are exactly equal, 0 otherwise. - - // Arithmetic - // Note: Arithmetic inputs are limited to signed 32-bit integers, but may overflow their output. - Oinc Opcode = 0x8B // 1 is added to the input. - Odec Opcode = 0x8C // 1 is subtracted from the input. - Osign Opcode = 0x8D - Onegate Opcode = 0x8F // The sign of the input is flipped. - Oabs Opcode = 0x90 // The input is made positive. - Onot Opcode = 0x91 // If the input is 0 or 1, it is flipped. Otherwise the output will be 0. - Onz Opcode = 0x92 // Returns 0 if the input is 0. 1 otherwise. - Oadd Opcode = 0x93 // a is added to b. - Osub Opcode = 0x94 // b is subtracted from a. - Omul Opcode = 0x95 // a is multiplied by b. - Odiv Opcode = 0x96 // a is divided by b. - Omod Opcode = 0x97 // Returns the remainder after dividing a by b. - Oshl Opcode = 0x98 // Shifts a left b bits, preserving sign. - Oshr Opcode = 0x99 // Shifts a right b bits, preserving sign. - Obooland Opcode = 0x9A // If both a and b are not 0, the output is 1. Otherwise 0. - Oboolor Opcode = 0x9B // If a or b is not 0, the output is 1. Otherwise 0. - Onumequal Opcode = 0x9C // Returns 1 if the numbers are equal, 0 otherwise. - Onumnotequal Opcode = 0x9E // Returns 1 if the numbers are not equal, 0 otherwise. - Olt Opcode = 0x9F // Returns 1 if a is less than b, 0 otherwise. - Ogt Opcode = 0xA0 // Returns 1 if a is greater than b, 0 otherwise. - Olte Opcode = 0xA1 // Returns 1 if a is less than or equal to b, 0 otherwise. - Ogte Opcode = 0xA2 // Returns 1 if a is greater than or equal to b, 0 otherwise. - Omin Opcode = 0xA3 // Returns the smaller of a and b. - Omax Opcode = 0xA4 // Returns the larger of a and b. - Owithin Opcode = 0xA5 // Returns 1 if x is within the specified range (left-inclusive), 0 otherwise. - - // Crypto - Osha1 Opcode = 0xA7 // The input is hashed using SHA-1. - Osha256 Opcode = 0xA8 // The input is hashed using SHA-256. - Ohash160 Opcode = 0xA9 - Ohash256 Opcode = 0xAA - Ochecksig Opcode = 0xAC - Ocheckmultisig Opcode = 0xAE - - // array - Oarraysize Opcode = 0xC0 - Opack Opcode = 0xC1 - Ounpack Opcode = 0xC2 - Opickitem Opcode = 0xC3 - Osetitem Opcode = 0xC4 - Onewarray Opcode = 0xC5 // Pops size from stack and creates a new array with that size, and pushes the array into the stack - Onewstruct Opcode = 0xC6 - Oappend Opcode = 0xC8 - Oreverse Opcode = 0xC9 - Oremove Opcode = 0xCA - - // exceptions - Othrow Opcode = 0xF0 - Othrowifnot Opcode = 0xF1 -) diff --git a/pkg/vm/opcode_string.go b/pkg/vm/opcode_string.go deleted file mode 100644 index 84b34c5e3..000000000 --- a/pkg/vm/opcode_string.go +++ /dev/null @@ -1,118 +0,0 @@ -// Code generated by "stringer -type=Opcode"; DO NOT EDIT. - -package vm - -import "strconv" - -const _Opcode_name = "Opush0Opushbytes1Opushbytes75Opushdata1Opushdata2Opushdata4Opushm1Opush1Opush2Opush3Opush4Opush5Opush6Opush7Opush8Opush9Opush10Opush11Opush12Opush13Opush14Opush15Opush16OnopOjmpOjmpifOjmpifnotOcallOretOappcallOsyscallOtailcallOdupfromaltstackOtoaltstackOfromaltstackOxdropOxswapOxtuckOdepthOdropOdupOnipOoverOpickOrollOrotOswapOtuckOcatOsubstrOleftOrightOsizeOinvertOandOorOxorOequalOincOdecOsignOnegateOabsOnotOnzOaddOsubOmulOdivOmodOshlOshrOboolandOboolorOnumequalOnumnotequalOltOgtOlteOgteOminOmaxOwithinOsha1Osha256Ohash160Ohash256OchecksigOcheckmultisigOarraysizeOpackOunpackOpickitemOsetitemOnewarrayOnewstructOappendOreverseOremoveOthrowOthrowifnot" - -var _Opcode_map = map[Opcode]string{ - 0: _Opcode_name[0:6], - 1: _Opcode_name[6:17], - 75: _Opcode_name[17:29], - 76: _Opcode_name[29:39], - 77: _Opcode_name[39:49], - 78: _Opcode_name[49:59], - 79: _Opcode_name[59:66], - 81: _Opcode_name[66:72], - 82: _Opcode_name[72:78], - 83: _Opcode_name[78:84], - 84: _Opcode_name[84:90], - 85: _Opcode_name[90:96], - 86: _Opcode_name[96:102], - 87: _Opcode_name[102:108], - 88: _Opcode_name[108:114], - 89: _Opcode_name[114:120], - 90: _Opcode_name[120:127], - 91: _Opcode_name[127:134], - 92: _Opcode_name[134:141], - 93: _Opcode_name[141:148], - 94: _Opcode_name[148:155], - 95: _Opcode_name[155:162], - 96: _Opcode_name[162:169], - 97: _Opcode_name[169:173], - 98: _Opcode_name[173:177], - 99: _Opcode_name[177:183], - 100: _Opcode_name[183:192], - 101: _Opcode_name[192:197], - 102: _Opcode_name[197:201], - 103: _Opcode_name[201:209], - 104: _Opcode_name[209:217], - 105: _Opcode_name[217:226], - 106: _Opcode_name[226:242], - 107: _Opcode_name[242:253], - 108: _Opcode_name[253:266], - 109: _Opcode_name[266:272], - 114: _Opcode_name[272:278], - 115: _Opcode_name[278:284], - 116: _Opcode_name[284:290], - 117: _Opcode_name[290:295], - 118: _Opcode_name[295:299], - 119: _Opcode_name[299:303], - 120: _Opcode_name[303:308], - 121: _Opcode_name[308:313], - 122: _Opcode_name[313:318], - 123: _Opcode_name[318:322], - 124: _Opcode_name[322:327], - 125: _Opcode_name[327:332], - 126: _Opcode_name[332:336], - 127: _Opcode_name[336:343], - 128: _Opcode_name[343:348], - 129: _Opcode_name[348:354], - 130: _Opcode_name[354:359], - 131: _Opcode_name[359:366], - 132: _Opcode_name[366:370], - 133: _Opcode_name[370:373], - 134: _Opcode_name[373:377], - 135: _Opcode_name[377:383], - 139: _Opcode_name[383:387], - 140: _Opcode_name[387:391], - 141: _Opcode_name[391:396], - 143: _Opcode_name[396:403], - 144: _Opcode_name[403:407], - 145: _Opcode_name[407:411], - 146: _Opcode_name[411:414], - 147: _Opcode_name[414:418], - 148: _Opcode_name[418:422], - 149: _Opcode_name[422:426], - 150: _Opcode_name[426:430], - 151: _Opcode_name[430:434], - 152: _Opcode_name[434:438], - 153: _Opcode_name[438:442], - 154: _Opcode_name[442:450], - 155: _Opcode_name[450:457], - 156: _Opcode_name[457:466], - 158: _Opcode_name[466:478], - 159: _Opcode_name[478:481], - 160: _Opcode_name[481:484], - 161: _Opcode_name[484:488], - 162: _Opcode_name[488:492], - 163: _Opcode_name[492:496], - 164: _Opcode_name[496:500], - 165: _Opcode_name[500:507], - 167: _Opcode_name[507:512], - 168: _Opcode_name[512:519], - 169: _Opcode_name[519:527], - 170: _Opcode_name[527:535], - 172: _Opcode_name[535:544], - 174: _Opcode_name[544:558], - 192: _Opcode_name[558:568], - 193: _Opcode_name[568:573], - 194: _Opcode_name[573:580], - 195: _Opcode_name[580:589], - 196: _Opcode_name[589:597], - 197: _Opcode_name[597:606], - 198: _Opcode_name[606:616], - 200: _Opcode_name[616:623], - 201: _Opcode_name[623:631], - 202: _Opcode_name[631:638], - 240: _Opcode_name[638:644], - 241: _Opcode_name[644:655], -} - -func (i Opcode) String() string { - if str, ok := _Opcode_map[i]; ok { - return str - } - return "Opcode(" + strconv.FormatInt(int64(i), 10) + ")" -} diff --git a/pkg/vm/syscall.go b/pkg/vm/syscall.go deleted file mode 100644 index 69f2ee1e3..000000000 --- a/pkg/vm/syscall.go +++ /dev/null @@ -1,19 +0,0 @@ -package vm - -// Syscalls are a mapping between the syscall function name -// and the registered VM interop API. -var Syscalls = map[string]string{ - // Storage API - "GetContext": "Neo.Storage.GetContext", - "Put": "Neo.Storage.Put", - "Get": "Neo.Storage.Get", - "Delete": "Neo.Storage.Delete", - - // Runtime API - "GetTrigger": "Neo.Runtime.GetTrigger", - "CheckWitness": "Neo.Runtime.CheckWitness", - "GetCurrentBlock": "Neo.Runtime.GetCurrentBlock", - "GetTime": "Neo.Runtime.GetTime", - "Notify": "Neo.Runtime.Notify", - "Log": "Neo.Runtime.Log", -} diff --git a/pkg/vm/tests/syscall_test.go b/pkg/vm/tests/syscall_test.go index 5b3e4c883..a19e9dcb3 100644 --- a/pkg/vm/tests/syscall_test.go +++ b/pkg/vm/tests/syscall_test.go @@ -8,7 +8,7 @@ func TestStoragePutGet(t *testing.T) { src := ` package foo - import "github.com/CityOfZion/neo-go/pkg/vm/api/storage" + import "github.com/CityOfZion/neo-go/pkg/interop/storage" func Main() string { ctx := storage.GetContext() diff --git a/pkg/vm/tests/util_test.go b/pkg/vm/tests/util_test.go index db56be0a3..2da750d91 100644 --- a/pkg/vm/tests/util_test.go +++ b/pkg/vm/tests/util_test.go @@ -8,7 +8,7 @@ func TestSHA256(t *testing.T) { src := ` package foo import ( - "github.com/CityOfZion/neo-go/pkg/vm/api/crypto" + "github.com/CityOfZion/neo-go/pkg/interop/crypto" ) func Main() []byte { src := []byte{0x97} @@ -23,7 +23,7 @@ func TestSHA1(t *testing.T) { src := ` package foo import ( - "github.com/CityOfZion/neo-go/pkg/vm/api/crypto" + "github.com/CityOfZion/neo-go/pkg/interop/crypto" ) func Main() []byte { src := []byte{0x97} @@ -38,7 +38,7 @@ func TestHash160(t *testing.T) { src := ` package foo import ( - "github.com/CityOfZion/neo-go/pkg/vm/api/crypto" + "github.com/CityOfZion/neo-go/pkg/interop/crypto" ) func Main() []byte { src := []byte{0x97} @@ -53,7 +53,7 @@ func TestHash256(t *testing.T) { src := ` package foo import ( - "github.com/CityOfZion/neo-go/pkg/vm/api/crypto" + "github.com/CityOfZion/neo-go/pkg/interop/crypto" ) func Main() []byte { src := []byte{0x97} diff --git a/pkg/vm/vm.go b/pkg/vm/vm.go index f201ae569..420becec2 100644 --- a/pkg/vm/vm.go +++ b/pkg/vm/vm.go @@ -104,7 +104,7 @@ func (v *VM) PrintOps() { } else { cursor = "" } - fmt.Fprintf(w, "%d\t0x%2x\t%s\t%s\n", i, prog[i], Opcode(prog[i]).String(), cursor) + fmt.Fprintf(w, "%d\t0x%2x\t%s\t%s\n", i, prog[i], Instruction(prog[i]).String(), cursor) } w.Flush() @@ -228,7 +228,7 @@ func (v *VM) Step() { } // execute performs an instruction cycle in the VM. Acting on the instruction (opcode). -func (v *VM) execute(ctx *Context, op Opcode) { +func (v *VM) execute(ctx *Context, op Instruction) { // Instead of polluting the whole VM logic with error handling, we will recover // each panic at a central point, putting the VM in a fault state. defer func() { @@ -239,57 +239,57 @@ func (v *VM) execute(ctx *Context, op Opcode) { } }() - if op >= Opushbytes1 && op <= Opushbytes75 { + if op >= PUSHBYTES1 && op <= PUSHBYTES75 { b := ctx.readBytes(int(op)) v.estack.PushVal(b) return } switch op { - case Opushm1, Opush1, Opush2, Opush3, Opush4, Opush5, - Opush6, Opush7, Opush8, Opush9, Opush10, Opush11, - Opush12, Opush13, Opush14, Opush15, Opush16: - val := int(op) - int(Opush1) + 1 + case PUSHM1, PUSH1, PUSH2, PUSH3, PUSH4, PUSH5, + PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11, + PUSH12, PUSH13, PUSH14, PUSH15, PUSH16: + val := int(op) - int(PUSH1) + 1 v.estack.PushVal(val) - case Opush0: + case PUSH0: v.estack.PushVal(0) - case Opushdata1: + case PUSHDATA1: n := ctx.readByte() b := ctx.readBytes(int(n)) v.estack.PushVal(b) - case Opushdata2: + case PUSHDATA2: n := ctx.readUint16() b := ctx.readBytes(int(n)) v.estack.PushVal(b) - case Opushdata4: + case PUSHDATA4: n := ctx.readUint32() b := ctx.readBytes(int(n)) v.estack.PushVal(b) // Stack operations. - case Otoaltstack: + case TOALTSTACK: v.astack.Push(v.estack.Pop()) - case Ofromaltstack: + case FROMALTSTACK: v.estack.Push(v.astack.Pop()) - case Odupfromaltstack: + case DUPFROMALTSTACK: v.estack.Push(v.astack.Dup(0)) - case Odup: + case DUP: v.estack.Push(v.estack.Dup(0)) - case Oswap: + case SWAP: a := v.estack.Pop() b := v.estack.Pop() v.estack.Push(a) v.estack.Push(b) - case Oxswap: + case XSWAP: n := int(v.estack.Pop().BigInt().Int64()) if n < 0 { panic("XSWAP: invalid length") @@ -305,7 +305,7 @@ func (v *VM) execute(ctx *Context, op Opcode) { b.value = aval } - case Otuck: + case TUCK: n := int(v.estack.Pop().BigInt().Int64()) if n <= 0 { panic("OTUCK: invalid length") @@ -313,7 +313,7 @@ func (v *VM) execute(ctx *Context, op Opcode) { v.estack.InsertAt(v.estack.Peek(0), n) - case Orot: + case ROT: c := v.estack.Pop() b := v.estack.Pop() a := v.estack.Pop() @@ -322,21 +322,21 @@ func (v *VM) execute(ctx *Context, op Opcode) { v.estack.Push(c) v.estack.Push(a) - case Odepth: + case DEPTH: v.estack.PushVal(v.estack.Len()) - case Onip: + case NIP: elem := v.estack.Pop() _ = v.estack.Pop() v.estack.Push(elem) - case Oover: + case OVER: b := v.estack.Pop() a := v.estack.Peek(0) v.estack.Push(b) v.estack.Push(a) - case Oroll: + case ROLL: n := int(v.estack.Pop().BigInt().Int64()) if n < 0 { panic("negative stack item returned") @@ -345,105 +345,105 @@ func (v *VM) execute(ctx *Context, op Opcode) { v.estack.Push(v.estack.RemoveAt(n)) } - case Odrop: + case DROP: v.estack.Pop() - case Oequal: + case EQUAL: panic("TODO EQUAL") // Bit operations. - case Oand: + case AND: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).And(b, a)) - case Oor: + case OR: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Or(b, a)) - case Oxor: + case XOR: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Xor(b, a)) // Numeric operations. - case Oadd: + case ADD: a := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Add(a, b)) - case Osub: + case SUB: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Sub(a, b)) - case Odiv: + case DIV: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Div(a, b)) - case Omul: + case MUL: a := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Mul(a, b)) - case Omod: + case MOD: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Mod(a, b)) - case Oshl: + case SHL: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Lsh(a, uint(b.Int64()))) - case Oshr: + case SHR: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Rsh(a, uint(b.Int64()))) - case Obooland: + case BOOLAND: b := v.estack.Pop().Bool() a := v.estack.Pop().Bool() v.estack.PushVal(a && b) - case Oboolor: + case BOOLOR: b := v.estack.Pop().Bool() a := v.estack.Pop().Bool() v.estack.PushVal(a || b) - case Onumequal: + case NUMEQUAL: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(a.Cmp(b) == 0) - case Onumnotequal: + case NUMNOTEQUAL: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(a.Cmp(b) != 0) - case Olt: + case LT: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(a.Cmp(b) == -1) - case Ogt: + case GT: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(a.Cmp(b) == 1) - case Olte: + case LTE: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(a.Cmp(b) <= 0) - case Ogte: + case GTE: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() v.estack.PushVal(a.Cmp(b) >= 0) - case Omin: + case MIN: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() val := a @@ -452,7 +452,7 @@ func (v *VM) execute(ctx *Context, op Opcode) { } v.estack.PushVal(val) - case Omax: + case MAX: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() val := a @@ -461,52 +461,52 @@ func (v *VM) execute(ctx *Context, op Opcode) { } v.estack.PushVal(val) - case Owithin: + case WITHIN: b := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt() x := v.estack.Pop().BigInt() v.estack.PushVal(a.Cmp(x) <= 0 && x.Cmp(b) == -1) - case Oinc: + case INC: x := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Add(x, big.NewInt(1))) - case Odec: + case DEC: x := v.estack.Pop().BigInt() v.estack.PushVal(new(big.Int).Sub(x, big.NewInt(1))) - case Osign: + case SIGN: x := v.estack.Pop().BigInt() v.estack.PushVal(x.Sign()) - case Onegate: + case NEGATE: x := v.estack.Pop().BigInt() v.estack.PushVal(x.Neg(x)) - case Oabs: + case ABS: x := v.estack.Pop().BigInt() v.estack.PushVal(x.Abs(x)) - case Onot: + case NOT: x := v.estack.Pop().Bool() v.estack.PushVal(!x) - case Onz: + case NZ: panic("todo NZ") // x := v.estack.Pop().BigInt() // Object operations. - case Onewarray: + case NEWARRAY: n := v.estack.Pop().BigInt().Int64() items := make([]StackItem, n) v.estack.PushVal(&ArrayItem{items}) - case Onewstruct: + case NEWSTRUCT: n := v.estack.Pop().BigInt().Int64() items := make([]StackItem, n) v.estack.PushVal(&StructItem{items}) - case Oappend: + case APPEND: itemElem := v.estack.Pop() arrElem := v.estack.Pop() @@ -522,11 +522,11 @@ func (v *VM) execute(ctx *Context, op Opcode) { panic("APPEND: not of underlying type Array") } - case Oreverse: + case REVERSE: - case Oremove: + case REMOVE: - case Opack: + case PACK: n := int(v.estack.Pop().BigInt().Int64()) if n < 0 || n > v.estack.Len() { panic("OPACK: invalid length") @@ -539,10 +539,10 @@ func (v *VM) execute(ctx *Context, op Opcode) { v.estack.PushVal(items) - case Ounpack: + case UNPACK: panic("TODO") - case Opickitem: + case PICKITEM: var ( key = v.estack.Pop() obj = v.estack.Pop() @@ -562,7 +562,7 @@ func (v *VM) execute(ctx *Context, op Opcode) { panic("PICKITEM: unknown type") } - case Osetitem: + case SETITEM: var ( item = v.estack.Pop().value key = v.estack.Pop() @@ -582,7 +582,7 @@ func (v *VM) execute(ctx *Context, op Opcode) { panic(fmt.Sprintf("SETITEM: invalid item type %s", t)) } - case Oarraysize: + case ARRAYSIZE: elem := v.estack.Pop() // Cause there is no native (byte) item type here, hence we need to check // the type of the item for array size operations. @@ -595,7 +595,7 @@ func (v *VM) execute(ctx *Context, op Opcode) { panic("ARRAYSIZE: item not of type []StackItem") } - case Osize: + case SIZE: elem := v.estack.Pop() arr, ok := elem.value.Value().([]uint8) if !ok { @@ -603,7 +603,7 @@ func (v *VM) execute(ctx *Context, op Opcode) { } v.estack.PushVal(len(arr)) - case Ojmp, Ojmpif, Ojmpifnot: + case JMP, JMPIF, JMPIFNOT: var ( rOffset = int16(ctx.readUint16()) offset = ctx.ip + int(rOffset) - 3 // sizeOf(int16 + uint8) @@ -612,9 +612,9 @@ func (v *VM) execute(ctx *Context, op Opcode) { panic(fmt.Sprintf("JMP: invalid offset %d ip at %d", offset, ctx.ip)) } cond := true - if op > Ojmp { + if op > JMP { cond = v.estack.Pop().Bool() - if op == Ojmpifnot { + if op == JMPIFNOT { cond = !cond } } @@ -622,12 +622,12 @@ func (v *VM) execute(ctx *Context, op Opcode) { ctx.ip = offset } - case Ocall: + case CALL: v.istack.PushVal(ctx.Copy()) ctx.ip += 2 - v.execute(v.Context(), Ojmp) + v.execute(v.Context(), JMP) - case Osyscall: + case SYSCALL: api := ctx.readVarBytes() ifunc, ok := v.interop[string(api)] if !ok { @@ -637,7 +637,7 @@ func (v *VM) execute(ctx *Context, op Opcode) { panic(fmt.Sprintf("failed to invoke syscall: %s", err)) } - case Oappcall, Otailcall: + case APPCALL, TAILCALL: if len(v.scripts) == 0 { panic("script table is empty") } @@ -652,32 +652,32 @@ func (v *VM) execute(ctx *Context, op Opcode) { panic("could not find script") } - if op == Otailcall { + if op == TAILCALL { _ = v.istack.Pop() } v.LoadScript(script) - case Oret: + case RET: _ = v.istack.Pop() if v.istack.Len() == 0 { v.state = haltState } // Cryptographic operations. - case Osha1: + case SHA1: b := v.estack.Pop().Bytes() sha := sha1.New() sha.Write(b) v.estack.PushVal(sha.Sum(nil)) - case Osha256: + case SHA256: b := v.estack.Pop().Bytes() sha := sha256.New() sha.Write(b) v.estack.PushVal(sha.Sum(nil)) - case Ohash160: + case HASH160: b := v.estack.Pop().Bytes() sha := sha256.New() sha.Write(b) @@ -686,7 +686,7 @@ func (v *VM) execute(ctx *Context, op Opcode) { ripemd.Write(h) v.estack.PushVal(ripemd.Sum(nil)) - case Ohash256: + case HASH256: b := v.estack.Pop().Bytes() sha := sha256.New() sha.Write(b) @@ -695,19 +695,19 @@ func (v *VM) execute(ctx *Context, op Opcode) { sha.Write(h) v.estack.PushVal(sha.Sum(nil)) - case Ochecksig: + case CHECKSIG: // pubkey := v.estack.Pop().Bytes() // sig := v.estack.Pop().Bytes() - case Ocheckmultisig: + case CHECKMULTISIG: - case Onop: + case NOP: // unlucky ^^ - case Othrow: + case THROW: panic("THROW") - case Othrowifnot: + case THROWIFNOT: if !v.estack.Pop().Bool() { panic("THROWIFNOT") } diff --git a/pkg/vm/vm_test.go b/pkg/vm/vm_test.go index cc67101a8..257271056 100644 --- a/pkg/vm/vm_test.go +++ b/pkg/vm/vm_test.go @@ -20,7 +20,7 @@ func TestInteropHook(t *testing.T) { buf := new(bytes.Buffer) EmitSyscall(buf, "foo") - EmitOpcode(buf, Oret) + EmitOpcode(buf, RET) v.Load(buf.Bytes()) v.Run() assert.Equal(t, 1, v.estack.Len()) @@ -51,7 +51,7 @@ func TestPushBytes1to75(t *testing.T) { assert.IsType(t, elem.Bytes(), b) assert.Equal(t, 0, vm.estack.Len()) - vm.execute(nil, Oret) + vm.execute(nil, RET) assert.Equal(t, 0, vm.astack.Len()) assert.Equal(t, 0, vm.istack.Len()) @@ -61,7 +61,7 @@ func TestPushBytes1to75(t *testing.T) { func TestPushm1to16(t *testing.T) { var prog []byte - for i := int(Opushm1); i <= int(Opush16); i++ { + for i := int(PUSHM1); i <= int(PUSH16); i++ { if i == 80 { continue // opcode layout we got here. } @@ -69,7 +69,7 @@ func TestPushm1to16(t *testing.T) { } vm := load(prog) - for i := int(Opushm1); i <= int(Opush16); i++ { + for i := int(PUSHM1); i <= int(PUSH16); i++ { if i == 80 { continue // nice opcode layout we got here. } @@ -77,7 +77,7 @@ func TestPushm1to16(t *testing.T) { elem := vm.estack.Pop() assert.IsType(t, &BigIntegerItem{}, elem.value) - val := i - int(Opush1) + 1 + val := i - int(PUSH1) + 1 assert.Equal(t, elem.BigInt().Int64(), int64(val)) } } @@ -95,7 +95,7 @@ func TestPushData4(t *testing.T) { } func TestAdd(t *testing.T) { - prog := makeProgram(Oadd) + prog := makeProgram(ADD) vm := load(prog) vm.estack.PushVal(4) vm.estack.PushVal(2) @@ -104,7 +104,7 @@ func TestAdd(t *testing.T) { } func TestMul(t *testing.T) { - prog := makeProgram(Omul) + prog := makeProgram(MUL) vm := load(prog) vm.estack.PushVal(4) vm.estack.PushVal(2) @@ -113,7 +113,7 @@ func TestMul(t *testing.T) { } func TestDiv(t *testing.T) { - prog := makeProgram(Odiv) + prog := makeProgram(DIV) vm := load(prog) vm.estack.PushVal(4) vm.estack.PushVal(2) @@ -122,7 +122,7 @@ func TestDiv(t *testing.T) { } func TestSub(t *testing.T) { - prog := makeProgram(Osub) + prog := makeProgram(SUB) vm := load(prog) vm.estack.PushVal(4) vm.estack.PushVal(2) @@ -131,7 +131,7 @@ func TestSub(t *testing.T) { } func TestLT(t *testing.T) { - prog := makeProgram(Olt) + prog := makeProgram(LT) vm := load(prog) vm.estack.PushVal(4) vm.estack.PushVal(3) @@ -140,7 +140,7 @@ func TestLT(t *testing.T) { } func TestLTE(t *testing.T) { - prog := makeProgram(Olte) + prog := makeProgram(LTE) vm := load(prog) vm.estack.PushVal(2) vm.estack.PushVal(3) @@ -149,7 +149,7 @@ func TestLTE(t *testing.T) { } func TestGT(t *testing.T) { - prog := makeProgram(Ogt) + prog := makeProgram(GT) vm := load(prog) vm.estack.PushVal(9) vm.estack.PushVal(3) @@ -159,7 +159,7 @@ func TestGT(t *testing.T) { } func TestGTE(t *testing.T) { - prog := makeProgram(Ogte) + prog := makeProgram(GTE) vm := load(prog) vm.estack.PushVal(3) vm.estack.PushVal(3) @@ -168,7 +168,7 @@ func TestGTE(t *testing.T) { } func TestDepth(t *testing.T) { - prog := makeProgram(Odepth) + prog := makeProgram(DEPTH) vm := load(prog) vm.estack.PushVal(1) vm.estack.PushVal(2) @@ -178,7 +178,7 @@ func TestDepth(t *testing.T) { } func TestNumEqual(t *testing.T) { - prog := makeProgram(Onumequal) + prog := makeProgram(NUMEQUAL) vm := load(prog) vm.estack.PushVal(1) vm.estack.PushVal(2) @@ -187,7 +187,7 @@ func TestNumEqual(t *testing.T) { } func TestNumNotEqual(t *testing.T) { - prog := makeProgram(Onumnotequal) + prog := makeProgram(NUMNOTEQUAL) vm := load(prog) vm.estack.PushVal(2) vm.estack.PushVal(2) @@ -196,7 +196,7 @@ func TestNumNotEqual(t *testing.T) { } func TestINC(t *testing.T) { - prog := makeProgram(Oinc) + prog := makeProgram(INC) vm := load(prog) vm.estack.PushVal(1) vm.Run() @@ -204,13 +204,13 @@ func TestINC(t *testing.T) { } func TestAppCall(t *testing.T) { - prog := []byte{byte(Oappcall)} + prog := []byte{byte(APPCALL)} hash := util.Uint160{} prog = append(prog, hash.Bytes()...) - prog = append(prog, byte(Oret)) + prog = append(prog, byte(RET)) vm := load(prog) - vm.scripts[hash] = makeProgram(Odepth) + vm.scripts[hash] = makeProgram(DEPTH) vm.estack.PushVal(2) vm.Run() @@ -231,12 +231,12 @@ func TestSimpleCall(t *testing.T) { assert.Equal(t, result, int(vm.estack.Pop().BigInt().Int64())) } -func makeProgram(opcodes ...Opcode) []byte { - prog := make([]byte, len(opcodes)+1) // Oret +func makeProgram(opcodes ...Instruction) []byte { + prog := make([]byte, len(opcodes)+1) // RET for i := 0; i < len(opcodes); i++ { prog[i] = byte(opcodes[i]) } - prog[len(prog)-1] = byte(Oret) + prog[len(prog)-1] = byte(RET) return prog }