Merge pull request #292 from nspcc-dev/neo-storm-chpicks

Cherry-picks from neo-storm. Closes #290.
This commit is contained in:
Roman Khimov 2019-08-16 19:23:56 +03:00 committed by GitHub
commit 0574bcf1fe
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
61 changed files with 2351 additions and 801 deletions

5
.gitignore vendored
View file

@ -13,6 +13,7 @@
# Added by CoZ developers # Added by CoZ developers
vendor/ vendor/
bin/ bin/
!examples/**/vendor
# text editors # text editors
# vscode # vscode
@ -32,3 +33,7 @@ chains/
chain/ chain/
blockchain/ blockchain/
blockchains/ blockchains/
# patch
*.orig
*.rej

View file

@ -126,7 +126,57 @@ ApplicationConfiguration:
``` ```
## Writing smart contracts in Go ## 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 # Contributing

View file

@ -1,19 +1,38 @@
package smartcontract package smartcontract
import ( import (
"bufio"
"bytes"
"context" "context"
"encoding/hex" "encoding/hex"
"encoding/json" "encoding/json"
"errors"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"os"
"path/filepath"
"github.com/CityOfZion/neo-go/pkg/rpc" "github.com/CityOfZion/neo-go/pkg/rpc"
"github.com/CityOfZion/neo-go/pkg/vm/compiler" "github.com/CityOfZion/neo-go/pkg/vm/compiler"
"github.com/urfave/cli" "github.com/urfave/cli"
) )
const ( var (
errNoInput = "Input file is mandatory and should be passed using -i flag." 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. // NewCommand returns a new contract command.
@ -53,13 +72,28 @@ func NewCommand() cli.Command {
}, },
}, },
{ {
Name: "opdump", Name: "init",
Usage: "dump the opcode of a .go file", Usage: "initialize a new smart-contract in a directory with boiler plate code",
Action: contractDumpOpcode, 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{ Flags: []cli.Flag{
cli.StringFlag{ cli.StringFlag{
Name: "in, i", 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 { func contractCompile(ctx *cli.Context) error {
src := ctx.String("in") src := ctx.String("in")
if len(src) == 0 { if len(src) == 0 {
@ -99,9 +172,8 @@ func testInvoke(ctx *cli.Context) error {
// For now we will hardcode the endpoint. // For now we will hardcode the endpoint.
// On the long term the internal VM will run the script. // On the long term the internal VM will run the script.
// TODO: remove RPC dependency, hardcoded node. // TODO: remove RPC dependency, hardcoded node.
endpoint := "http://seed5.bridgeprotocol.io:10332" endpoint := "http://node1.ams2.bridgeprotocol.io:10332"
opts := rpc.ClientOptions{} client, err := rpc.NewClient(context.TODO(), endpoint, rpc.ClientOptions{})
client, err := rpc.NewClient(context.TODO(), endpoint, opts)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
@ -122,12 +194,67 @@ func testInvoke(ctx *cli.Context) error {
return nil 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") src := ctx.String("in")
if len(src) == 0 { if len(src) == 0 {
return cli.NewExitError(errNoInput, 1) 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 cli.NewExitError(err, 1)
} }
return nil return nil

523
docs/runtime.md Normal file
View file

@ -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

22
examples/engine/engine.go Normal file
View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

Binary file not shown.

View file

@ -1,9 +1,9 @@
package tokensale package tokensale
import ( import (
"github.com/CityOfZion/neo-go/pkg/vm/api/runtime" "github.com/CityOfZion/neo-go/pkg/interop/runtime"
"github.com/CityOfZion/neo-go/pkg/vm/api/storage" "github.com/CityOfZion/neo-go/pkg/interop/storage"
"github.com/CityOfZion/neo-go/pkg/vm/api/util" "github.com/CityOfZion/neo-go/pkg/interop/util"
) )
const ( const (

View file

@ -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
}

70
examples/token/token.go Normal file
View file

@ -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
}

View file

@ -35,7 +35,7 @@ func createGenesisBlock(cfg config.ProtocolConfiguration) (*Block, error) {
NextConsensus: nextConsensus, NextConsensus: nextConsensus,
Script: &transaction.Witness{ Script: &transaction.Witness{
InvocationScript: []byte{}, 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{ Scripts: []*transaction.Witness{
{ {
InvocationScript: []byte{}, 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 { func governingTokenTX() *transaction.Transaction {
admin, _ := util.Uint160FromScript([]byte{byte(vm.Opusht)}) admin, _ := util.Uint160FromScript([]byte{byte(vm.PUSHT)})
registerTX := &transaction.RegisterTX{ registerTX := &transaction.RegisterTX{
AssetType: transaction.GoverningToken, AssetType: transaction.GoverningToken,
Name: "[{\"lang\":\"zh-CN\",\"name\":\"小蚁股\"},{\"lang\":\"en\",\"name\":\"AntShare\"}]", Name: "[{\"lang\":\"zh-CN\",\"name\":\"小蚁股\"},{\"lang\":\"en\",\"name\":\"AntShare\"}]",
@ -120,7 +120,7 @@ func governingTokenTX() *transaction.Transaction {
} }
func utilityTokenTX() *transaction.Transaction { func utilityTokenTX() *transaction.Transaction {
admin, _ := util.Uint160FromScript([]byte{byte(vm.Opushf)}) admin, _ := util.Uint160FromScript([]byte{byte(vm.PUSHF)})
registerTX := &transaction.RegisterTX{ registerTX := &transaction.RegisterTX{
AssetType: transaction.UtilityToken, AssetType: transaction.UtilityToken,
Name: "[{\"lang\":\"zh-CN\",\"name\":\"小蚁币\"},{\"lang\":\"en\",\"name\":\"AntCoin\"}]", Name: "[{\"lang\":\"zh-CN\",\"name\":\"小蚁币\"},{\"lang\":\"en\",\"name\":\"AntCoin\"}]",

View file

@ -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
}

View file

@ -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) {}

View file

@ -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
}

View file

@ -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{}
}

View file

@ -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{}
}

View file

@ -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) {}

View file

@ -1,5 +1,8 @@
package crypto 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. // SHA1 computes the sha1 hash of b.
func SHA1(b []byte) []byte { func SHA1(b []byte) []byte {
return nil return nil
@ -10,12 +13,12 @@ func SHA256(b []byte) []byte {
return nil return nil
} }
// Hash160 .. // Hash160 computes the sha256 + ripemd160 of b.
func Hash160(b []byte) []byte { func Hash160(b []byte) []byte {
return nil return nil
} }
// Hash256 .. // Hash256 computes the sha256^2 hash of b.
func Hash256(b []byte) []byte { func Hash256(b []byte) []byte {
return nil return nil
} }

View file

@ -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
}

View file

@ -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{}
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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
}

View file

@ -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{} }

View file

@ -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{}
}

12
pkg/interop/util/util.go Normal file
View file

@ -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
}

View file

@ -34,7 +34,7 @@ func CreateMultiSigRedeemScript(m int, publicKeys crypto.PublicKeys) ([]byte, er
if err := vm.EmitInt(buf, int64(len(publicKeys))); err != nil { if err := vm.EmitInt(buf, int64(len(publicKeys))); err != nil {
return nil, err return nil, err
} }
if err := vm.EmitOpcode(buf, vm.Ocheckmultisig); err != nil { if err := vm.EmitOpcode(buf, vm.CHECKMULTISIG); err != nil {
return nil, err return nil, err
} }

View file

@ -24,7 +24,7 @@ func TestCreateMultiSigRedeemScript(t *testing.T) {
buf := bytes.NewBuffer(out) buf := bytes.NewBuffer(out)
b, _ := buf.ReadByte() 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++ { for i := 0; i < len(validators); i++ {
b, err := util.ReadVarBytes(buf) b, err := util.ReadVarBytes(buf)
@ -35,7 +35,7 @@ func TestCreateMultiSigRedeemScript(t *testing.T) {
} }
b, _ = buf.ReadByte() b, _ = buf.ReadByte()
assert.Equal(t, vm.Opush3, vm.Opcode(b)) assert.Equal(t, vm.PUSH3, vm.Instruction(b))
b, _ = buf.ReadByte() b, _ = buf.ReadByte()
assert.Equal(t, vm.Ocheckmultisig, vm.Opcode(b)) assert.Equal(t, vm.CHECKMULTISIG, vm.Instruction(b))
} }

View file

@ -117,7 +117,7 @@ You can invoke smart contracts with arguments. Take the following ***roll the di
``` ```
package rollthedice 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 { func Main(method string, args []interface{}) int {
if method == "rollDice" { if method == "rollDice" {

View file

@ -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) {}

View file

@ -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 }

View file

@ -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 }

View file

@ -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
}

View file

@ -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 }

View file

@ -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) { }

View file

@ -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
}

View file

@ -1,6 +0,0 @@
package util
// FromAddress returns the underlying bytes from the given address string.
func FromAddress(address string) []byte {
return nil
}

View file

@ -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: 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: This will result in something like this:
``` ```
INDEX OPCODE DESC INDEX OPCODE DESC
0 0x52 OpPush2 0 0x54 PUSH4
1 0xc5 OpNewArray 1 0xc5 NEWARRAY
2 0x6b OpToAltStack 2 0x6b TOALTSTACK
3 0x 0 OpPush0 3 0x1 PUSHBYTES1
4 0x6c OpFromAltStack 3 0x2a *
5 0x76 OpDup 5 0x6a DUPFROMALTSTACK
6 0x6b OpToAltStack 6 0x0 PUSH0
7 0x 0 OpPush0 7 0x52 PUSH2
8 0x52 OpPush2 8 0x7a ROLL
9 0x7a OpRoll 9 0xc4 SETITEM
10 0xc4 OpSetItem 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 ### Test invoke a compiled contract
@ -118,8 +133,8 @@ Will output something like:
package mycontract package mycontract
import ( import (
"github.com/CityOfZion/neo-go/pkg/vm/api/runtime" "github.com/CityOfZion/neo-go/pkg/interop/runtime"
"github.com/CityOfZion/neo-go/pkg/vm/api/util" "github.com/CityOfZion/neo-go/pkg/interop/util"
) )
var owner = util.FromAddress("AJX1jGfj3qPBbpAKjY527nPbnrnvSx9nCg") var owner = util.FromAddress("AJX1jGfj3qPBbpAKjY527nPbnrnvSx9nCg")
@ -142,8 +157,8 @@ func Main() bool {
package mytoken package mytoken
import ( import (
"github.com/CityOfZion/neo-go/pkg/vm/api/runtime" "github.com/CityOfZion/neo-go/pkg/interop/runtime"
"github.com/CityOfZion/neo-go/pkg/vm/api/storage" "github.com/CityOfZion/neo-go/pkg/interop/storage"
) )
var owner = util.FromAddress("AJX1jGfj3qPBbpAKjY527nPbnrnvSx9nCg") var owner = util.FromAddress("AJX1jGfj3qPBbpAKjY527nPbnrnvSx9nCg")

View file

@ -6,7 +6,6 @@ import (
"go/types" "go/types"
"log" "log"
"github.com/CityOfZion/neo-go/pkg/vm"
"golang.org/x/tools/go/loader" "golang.org/x/tools/go/loader"
) )
@ -14,14 +13,8 @@ var (
// Go language builtin functions and custom builtin utility functions. // Go language builtin functions and custom builtin utility functions.
builtinFuncs = []string{ builtinFuncs = []string{
"len", "append", "SHA256", "len", "append", "SHA256",
"SHA1", "Hash256", "Hash160", "FromAddress", "SHA1", "Hash256", "Hash160",
} "FromAddress", "Equals",
// VM system calls that have no return value.
noRetSyscalls = []string{
"Notify", "Log", "Put", "Register", "Delete",
"SetVotes", "ContractDestroy", "MerkleRoot", "Hash",
"PrevHash", "GetHeader",
} }
) )
@ -202,20 +195,13 @@ func isByteArray(lit *ast.CompositeLit, tInfo *types.Info) bool {
return false return false
} }
func isSyscall(name string) bool { func isSyscall(fun *funcScope) bool {
_, ok := vm.Syscalls[name] if fun.selector == nil {
return ok
}
// isNoRetSyscall checks if the syscall has a return value.
func isNoRetSyscall(name string) bool {
for _, s := range noRetSyscalls {
if s == name {
return true
}
}
return false return false
} }
_, ok := syscalls[fun.selector.Name][fun.name]
return ok
}
func isStringType(t types.Type) bool { func isStringType(t types.Type) bool {
return t.String() == "string" return t.String() == "string"

View file

@ -8,6 +8,8 @@ import (
"go/token" "go/token"
"go/types" "go/types"
"log" "log"
"sort"
"strconv"
"strings" "strings"
"github.com/CityOfZion/neo-go/pkg/crypto" "github.com/CityOfZion/neo-go/pkg/crypto"
@ -57,7 +59,7 @@ func (c *codegen) emitLoadConst(t types.TypeAndValue) {
switch typ := t.Type.Underlying().(type) { switch typ := t.Type.Underlying().(type) {
case *types.Basic: case *types.Basic:
switch typ.Kind() { switch typ.Kind() {
case types.Int, types.UntypedInt: case types.Int, types.UntypedInt, types.Uint:
val, _ := constant.Int64Val(t.Value) val, _ := constant.Int64Val(t.Value)
emitInt(c.prog, val) emitInt(c.prog, val)
case types.String, types.UntypedString: case types.String, types.UntypedString:
@ -87,18 +89,13 @@ func (c *codegen) emitLoadLocal(name string) {
} }
func (c *codegen) emitLoadLocalPos(pos int) { func (c *codegen) emitLoadLocalPos(pos int) {
emitOpcode(c.prog, vm.Ofromaltstack) emitOpcode(c.prog, vm.DUPFROMALTSTACK)
emitOpcode(c.prog, vm.Odup)
emitOpcode(c.prog, vm.Otoaltstack)
emitInt(c.prog, int64(pos)) emitInt(c.prog, int64(pos))
emitOpcode(c.prog, vm.Opickitem) emitOpcode(c.prog, vm.PICKITEM)
} }
func (c *codegen) emitStoreLocal(pos int) { func (c *codegen) emitStoreLocal(pos int) {
emitOpcode(c.prog, vm.Ofromaltstack) emitOpcode(c.prog, vm.DUPFROMALTSTACK)
emitOpcode(c.prog, vm.Odup)
emitOpcode(c.prog, vm.Otoaltstack)
if pos < 0 { if pos < 0 {
log.Fatalf("invalid position to store local: %d", pos) 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, int64(pos))
emitInt(c.prog, 2) emitInt(c.prog, 2)
emitOpcode(c.prog, vm.Oroll) emitOpcode(c.prog, vm.ROLL)
emitOpcode(c.prog, vm.Osetitem) emitOpcode(c.prog, vm.SETITEM)
} }
func (c *codegen) emitLoadField(i int) { func (c *codegen) emitLoadField(i int) {
emitInt(c.prog, int64(i)) emitInt(c.prog, int64(i))
emitOpcode(c.prog, vm.Opickitem) emitOpcode(c.prog, vm.PICKITEM)
} }
func (c *codegen) emitStoreStructField(i int) { func (c *codegen) emitStoreStructField(i int) {
emitInt(c.prog, int64(i)) emitInt(c.prog, int64(i))
emitOpcode(c.prog, vm.Orot) emitOpcode(c.prog, vm.ROT)
emitOpcode(c.prog, vm.Osetitem) emitOpcode(c.prog, vm.SETITEM)
} }
// convertGlobals will traverse the AST and only convert global declarations. // 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] f, ok = c.funcs[decl.Name.Name]
if ok { if ok {
// If this function is a syscall we will not convert it to bytecode.
if isSyscall(f) {
return
}
c.setLabel(f.label) c.setLabel(f.label)
} else { } else {
f = c.newFunc(decl) 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 // All globals copied into the scope of the function need to be added
// to the stack size of the function. // to the stack size of the function.
emitInt(c.prog, f.stackSize()+countGlobals(file)) emitInt(c.prog, f.stackSize()+countGlobals(file))
emitOpcode(c.prog, vm.Onewarray) emitOpcode(c.prog, vm.NEWARRAY)
emitOpcode(c.prog, vm.Otoaltstack) emitOpcode(c.prog, vm.TOALTSTACK)
// We need to handle methods, which in Go, is just syntactic sugar. // We need to handle methods, which in Go, is just syntactic sugar.
// The method receiver will be passed in as first argument. // 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. // Load in all the global variables in to the scope of the function.
// This is not necessary for syscalls. // This is not necessary for syscalls.
if !isSyscall(f.name) { if !isSyscall(f) {
c.convertGlobals(file) 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 this function returns the void (no return stmt) we will cleanup its junk on the stack.
if !hasReturnStmt(decl) { if !hasReturnStmt(decl) {
emitOpcode(c.prog, vm.Ofromaltstack) emitOpcode(c.prog, vm.FROMALTSTACK)
emitOpcode(c.prog, vm.Odrop) emitOpcode(c.prog, vm.DROP)
emitOpcode(c.prog, vm.Oret) emitOpcode(c.prog, vm.RET)
} }
} }
@ -249,6 +250,21 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
default: default:
log.Fatal("nested selector assigns not supported yet") 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 return nil
@ -258,42 +274,39 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
log.Fatal("multiple returns not supported.") 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() l := c.newLabel()
emitJmp(c.prog, vm.Ojmp, int16(l))
c.setLabel(l) c.setLabel(l)
if len(n.Results) > 0 { if len(n.Results) > 0 {
ast.Walk(c, n.Results[0]) ast.Walk(c, n.Results[0])
} }
emitOpcode(c.prog, vm.Onop) // @OPTIMIZE emitOpcode(c.prog, vm.FROMALTSTACK)
emitOpcode(c.prog, vm.Ofromaltstack) emitOpcode(c.prog, vm.DROP) // Cleanup the stack.
emitOpcode(c.prog, vm.Odrop) // Cleanup the stack. emitOpcode(c.prog, vm.RET)
emitOpcode(c.prog, vm.Oret)
return nil return nil
case *ast.IfStmt: case *ast.IfStmt:
lIf := c.newLabel() lIf := c.newLabel()
lElse := c.newLabel() lElse := c.newLabel()
lElseEnd := c.newLabel()
if n.Cond != nil { if n.Cond != nil {
ast.Walk(c, n.Cond) ast.Walk(c, n.Cond)
emitJmp(c.prog, vm.Ojmpifnot, int16(lElse)) emitJmp(c.prog, vm.JMPIFNOT, int16(lElse))
} }
c.setLabel(lIf) c.setLabel(lIf)
ast.Walk(c, n.Body) ast.Walk(c, n.Body)
if n.Else != nil { if n.Else != nil {
// TODO: handle else statements. emitJmp(c.prog, vm.JMP, int16(lElseEnd))
// emitJmp(c.prog, vm.Ojmp, int16(lEnd))
} }
c.setLabel(lElse) c.setLabel(lElse)
if n.Else != nil { if n.Else != nil {
ast.Walk(c, n.Else) ast.Walk(c, n.Else)
} }
c.setLabel(lElseEnd)
return nil return nil
case *ast.BasicLit: case *ast.BasicLit:
@ -327,7 +340,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
c.emitLoadConst(c.typeInfo.Types[n.Elts[i]]) c.emitLoadConst(c.typeInfo.Types[n.Elts[i]])
} }
emitInt(c.prog, int64(ln)) emitInt(c.prog, int64(ln))
emitOpcode(c.prog, vm.Opack) emitOpcode(c.prog, vm.PACK)
return nil return nil
} }
@ -342,13 +355,13 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
switch n.Op { switch n.Op {
case token.LAND: case token.LAND:
ast.Walk(c, n.X) 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) ast.Walk(c, n.Y)
return nil return nil
case token.LOR: case token.LOR:
ast.Walk(c, n.X) 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) ast.Walk(c, n.Y)
return nil return nil
@ -360,14 +373,33 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
// example: // example:
// const x = 10 // const x = 10
// x + 2 will results into 12 // 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) c.emitLoadConst(tinfo)
return nil return nil
} }
ast.Walk(c, n.X) ast.Walk(c, n.X)
ast.Walk(c, n.Y) ast.Walk(c, n.Y)
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) c.convertToken(n.Op)
}
return nil 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. // Dont forget to add 1 extra argument when its a method.
numArgs++ numArgs++
} }
f, ok = c.funcs[fun.Sel.Name] f, ok = c.funcs[fun.Sel.Name]
// @FIXME this could cause runtime errors.
f.selector = fun.X.(*ast.Ident)
if !ok { if !ok {
log.Fatalf("could not resolve function %s", fun.Sel.Name) log.Fatalf("could not resolve function %s", fun.Sel.Name)
} }
@ -414,18 +449,17 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
// Do not swap for builtin functions. // Do not swap for builtin functions.
if !isBuiltin { if !isBuiltin {
if numArgs == 2 { if numArgs == 2 {
emitOpcode(c.prog, vm.Oswap) emitOpcode(c.prog, vm.SWAP)
} } else if numArgs == 3 {
if numArgs == 3 {
emitInt(c.prog, 2) 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! // Check builtin first to avoid nil pointer on funcScope!
switch { switch {
@ -433,17 +467,12 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
// Use the ident to check, builtins are not in func scopes. // Use the ident to check, builtins are not in func scopes.
// We can be sure builtins are of type *ast.Ident. // We can be sure builtins are of type *ast.Ident.
c.convertBuiltin(n) c.convertBuiltin(n)
case isSyscall(f.name): case isSyscall(f):
c.convertSyscall(f.name) c.convertSyscall(f.selector.Name, f.name)
default: 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 return nil
case *ast.SelectorExpr: case *ast.SelectorExpr:
@ -462,7 +491,22 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
case *ast.UnaryExpr: case *ast.UnaryExpr:
ast.Walk(c, n.X) 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 return nil
case *ast.IncDecStmt: case *ast.IncDecStmt:
@ -490,7 +534,7 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
c.emitLoadField(int(val)) c.emitLoadField(int(val))
default: default:
ast.Walk(c, n.Index) ast.Walk(c, n.Index)
emitOpcode(c.prog, vm.Opickitem) // just pickitem here emitOpcode(c.prog, vm.PICKITEM) // just pickitem here
} }
return nil return nil
@ -508,14 +552,14 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
ast.Walk(c, n.Cond) ast.Walk(c, n.Cond)
// Jump if the condition is false // 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). // Walk body followed by the iterator (post stmt).
ast.Walk(c, n.Body) ast.Walk(c, n.Body)
ast.Walk(c, n.Post) ast.Walk(c, n.Post)
// Jump back to condition. // Jump back to condition.
emitJmp(c.prog, vm.Ojmp, int16(fstart)) emitJmp(c.prog, vm.JMP, int16(fstart))
c.setLabel(fend) c.setLabel(fend)
return nil return nil
@ -531,13 +575,16 @@ func (c *codegen) Visit(node ast.Node) ast.Visitor {
return c return c
} }
func (c *codegen) convertSyscall(name string) { func (c *codegen) convertSyscall(api, name string) {
api, ok := vm.Syscalls[name] api, ok := syscalls[api][name]
if !ok { if !ok {
log.Fatalf("unknown VM syscall api: %s", name) log.Fatalf("unknown VM syscall api: %s", name)
} }
emitSyscall(c.prog, api) 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) { func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
@ -554,20 +601,22 @@ func (c *codegen) convertBuiltin(expr *ast.CallExpr) {
arg := expr.Args[0] arg := expr.Args[0]
typ := c.typeInfo.Types[arg].Type typ := c.typeInfo.Types[arg].Type
if isStringType(typ) { if isStringType(typ) {
emitOpcode(c.prog, vm.Osize) emitOpcode(c.prog, vm.SIZE)
} else { } else {
emitOpcode(c.prog, vm.Oarraysize) emitOpcode(c.prog, vm.ARRAYSIZE)
} }
case "append": case "append":
emitOpcode(c.prog, vm.Oappend) emitOpcode(c.prog, vm.APPEND)
case "SHA256": case "SHA256":
emitOpcode(c.prog, vm.Osha256) emitOpcode(c.prog, vm.SHA256)
case "SHA1": case "SHA1":
emitOpcode(c.prog, vm.Osha1) emitOpcode(c.prog, vm.SHA1)
case "Hash256": case "Hash256":
emitOpcode(c.prog, vm.Ohash256) emitOpcode(c.prog, vm.HASH256)
case "Hash160": case "Hash160":
emitOpcode(c.prog, vm.Ohash160) emitOpcode(c.prog, vm.HASH160)
case "Equals":
emitOpcode(c.prog, vm.EQUAL)
case "FromAddress": case "FromAddress":
// We can be sure that this is a ast.BasicLit just containing a simple // 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 // 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) 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())) emitInt(c.prog, int64(strct.NumFields()))
emitOpcode(c.prog, vm.Onewstruct) emitOpcode(c.prog, vm.NEWSTRUCT)
emitOpcode(c.prog, vm.Otoaltstack) emitOpcode(c.prog, vm.TOALTSTACK)
// We need to locally store all the fields, even if they are not initialized. // We need to locally store all the fields, even if they are not initialized.
// We will initialize all fields to their "zero" value. // We will initialize all fields to their "zero" value.
@ -633,45 +682,58 @@ func (c *codegen) convertStruct(lit *ast.CompositeLit) {
c.emitLoadConst(typeAndVal) c.emitLoadConst(typeAndVal)
c.emitStoreLocal(i) c.emitStoreLocal(i)
} }
emitOpcode(c.prog, vm.Ofromaltstack) emitOpcode(c.prog, vm.FROMALTSTACK)
} }
func (c *codegen) convertToken(tok token.Token) { func (c *codegen) convertToken(tok token.Token) {
switch tok { switch tok {
case token.ADD_ASSIGN: case token.ADD_ASSIGN:
emitOpcode(c.prog, vm.Oadd) emitOpcode(c.prog, vm.ADD)
case token.SUB_ASSIGN: case token.SUB_ASSIGN:
emitOpcode(c.prog, vm.Osub) emitOpcode(c.prog, vm.SUB)
case token.MUL_ASSIGN: case token.MUL_ASSIGN:
emitOpcode(c.prog, vm.Omul) emitOpcode(c.prog, vm.MUL)
case token.QUO_ASSIGN: case token.QUO_ASSIGN:
emitOpcode(c.prog, vm.Odiv) emitOpcode(c.prog, vm.DIV)
case token.ADD: case token.ADD:
emitOpcode(c.prog, vm.Oadd) emitOpcode(c.prog, vm.ADD)
case token.SUB: case token.SUB:
emitOpcode(c.prog, vm.Osub) emitOpcode(c.prog, vm.SUB)
case token.MUL: case token.MUL:
emitOpcode(c.prog, vm.Omul) emitOpcode(c.prog, vm.MUL)
case token.QUO: case token.QUO:
emitOpcode(c.prog, vm.Odiv) emitOpcode(c.prog, vm.DIV)
case token.LSS: case token.LSS:
emitOpcode(c.prog, vm.Olt) emitOpcode(c.prog, vm.LT)
case token.LEQ: case token.LEQ:
emitOpcode(c.prog, vm.Olte) emitOpcode(c.prog, vm.LTE)
case token.GTR: case token.GTR:
emitOpcode(c.prog, vm.Ogt) emitOpcode(c.prog, vm.GT)
case token.GEQ: case token.GEQ:
emitOpcode(c.prog, vm.Ogte) emitOpcode(c.prog, vm.GTE)
case token.EQL: 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: case token.NEQ:
emitOpcode(c.prog, vm.Onumnotequal) emitOpcode(c.prog, vm.NUMNOTEQUAL)
case token.DEC: case token.DEC:
emitOpcode(c.prog, vm.Odec) emitOpcode(c.prog, vm.DEC)
case token.INC: case token.INC:
emitOpcode(c.prog, vm.Oinc) emitOpcode(c.prog, vm.INC)
case token.NOT: 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: default:
log.Fatalf("compiler could not convert token: %s", tok) 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 // convert the entry point first
c.convertFuncDecl(mainFile, main) 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 // 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 c.typeInfo = &pkg.Info
for _, f := range pkg.Files { for _, f := range pkg.Files {
@ -750,8 +820,8 @@ func (c *codegen) writeJumps() {
b := c.prog.Bytes() b := c.prog.Bytes()
for i, op := range b { for i, op := range b {
j := i + 1 j := i + 1
switch vm.Opcode(op) { switch vm.Instruction(op) {
case vm.Ojmp, vm.Ojmpifnot, vm.Ojmpif, vm.Ocall: case vm.JMP, vm.JMPIFNOT, vm.JMPIF, vm.CALL:
index := int16(binary.LittleEndian.Uint16(b[j : j+2])) index := int16(binary.LittleEndian.Uint16(b[j : j+2]))
if int(index) > len(c.l) || int(index) < 0 { if int(index) > len(c.l) || int(index) < 0 {
continue continue

View file

@ -90,16 +90,15 @@ func CompileAndSave(src string, o *Options) error {
if err != nil { if err != nil {
return fmt.Errorf("Error while trying to compile smart contract file: %v", err) 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) out := fmt.Sprintf("%s.%s", o.Outfile, o.Ext)
return ioutil.WriteFile(out, b, os.ModePerm) return ioutil.WriteFile(out, b, os.ModePerm)
} }
// DumpOpcode compiles the program and dumps the opcode in a user friendly format. // CompileAndInspect compiles the program and dumps the opcode in a user friendly format.
func DumpOpcode(src string) error { func CompileAndInspect(src string) error {
b, err := ioutil.ReadFile(src) b, err := ioutil.ReadFile(src)
if err != nil { if err != nil {
return err return err
@ -111,8 +110,24 @@ func DumpOpcode(src string) error {
w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0) w := tabwriter.NewWriter(os.Stdout, 0, 0, 4, ' ', 0)
fmt.Fprintln(w, "INDEX\tOPCODE\tDESC\t") fmt.Fprintln(w, "INDEX\tOPCODE\tDESC\t")
for i := 0; i < len(b); i++ { for i := 0; i <= len(b)-1; {
fmt.Fprintf(w, "%d\t0x%2x\t%s\t\n", i, b[i], vm.Opcode(b[i])) 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() w.Flush()
return nil return nil

View file

@ -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
}

View file

@ -12,34 +12,34 @@ import (
"github.com/CityOfZion/neo-go/pkg/vm" "github.com/CityOfZion/neo-go/pkg/vm"
) )
func emit(w *bytes.Buffer, op vm.Opcode, b []byte) error { func emit(w *bytes.Buffer, instr vm.Instruction, b []byte) error {
if err := w.WriteByte(byte(op)); err != nil { if err := w.WriteByte(byte(instr)); err != nil {
return err return err
} }
_, err := w.Write(b) _, err := w.Write(b)
return err return err
} }
func emitOpcode(w io.ByteWriter, op vm.Opcode) error { func emitOpcode(w io.ByteWriter, instr vm.Instruction) error {
return w.WriteByte(byte(op)) return w.WriteByte(byte(instr))
} }
func emitBool(w io.ByteWriter, ok bool) error { func emitBool(w io.ByteWriter, ok bool) error {
if ok { 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 { func emitInt(w *bytes.Buffer, i int64) error {
if i == -1 { if i == -1 {
return emitOpcode(w, vm.Opushm1) return emitOpcode(w, vm.PUSHM1)
} }
if i == 0 { if i == 0 {
return emitOpcode(w, vm.Opushf) return emitOpcode(w, vm.PUSHF)
} }
if i > 0 && i < 16 { 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) return emitOpcode(w, val)
} }
@ -59,18 +59,18 @@ func emitBytes(w *bytes.Buffer, b []byte) error {
) )
switch { switch {
case n <= int(vm.Opushbytes75): case n <= int(vm.PUSHBYTES75):
return emit(w, vm.Opcode(n), b) return emit(w, vm.Instruction(n), b)
case n < 0x100: case n < 0x100:
err = emit(w, vm.Opushdata1, []byte{byte(n)}) err = emit(w, vm.PUSHDATA1, []byte{byte(n)})
case n < 0x10000: case n < 0x10000:
buf := make([]byte, 2) buf := make([]byte, 2)
binary.LittleEndian.PutUint16(buf, uint16(n)) binary.LittleEndian.PutUint16(buf, uint16(n))
err = emit(w, vm.Opushdata2, buf) err = emit(w, vm.PUSHDATA2, buf)
default: default:
buf := make([]byte, 4) buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, uint32(n)) binary.LittleEndian.PutUint32(buf, uint32(n))
err = emit(w, vm.Opushdata4, buf) err = emit(w, vm.PUSHDATA4, buf)
} }
if err != nil { if err != nil {
return err return err
@ -86,24 +86,24 @@ func emitSyscall(w *bytes.Buffer, api string) error {
buf := make([]byte, len(api)+1) buf := make([]byte, len(api)+1)
buf[0] = byte(len(api)) buf[0] = byte(len(api))
copy(buf[1:], []byte(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 { func emitCall(w *bytes.Buffer, instr vm.Instruction, label int16) error {
return emitJmp(w, op, label) return emitJmp(w, instr, label)
} }
func emitJmp(w *bytes.Buffer, op vm.Opcode, label int16) error { func emitJmp(w *bytes.Buffer, instr vm.Instruction, label int16) error {
if !isOpcodeJmp(op) { if !isInstrJmp(instr) {
return fmt.Errorf("opcode %s is not a jump or call type", op) return fmt.Errorf("opcode %s is not a jump or call type", instr)
} }
buf := make([]byte, 2) buf := make([]byte, 2)
binary.LittleEndian.PutUint16(buf, uint16(label)) binary.LittleEndian.PutUint16(buf, uint16(label))
return emit(w, op, buf) return emit(w, instr, buf)
} }
func isOpcodeJmp(op vm.Opcode) bool { func isInstrJmp(instr vm.Instruction) bool {
if op == vm.Ojmp || op == vm.Ojmpifnot || op == vm.Ojmpif || op == vm.Ocall { if instr == vm.JMP || instr == vm.JMPIFNOT || instr == vm.JMPIF || instr == vm.CALL {
return true return true
} }
return false return false

View file

@ -10,6 +10,10 @@ type funcScope struct {
// identifier of the function. // identifier of the function.
name string 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. // The declaration of the function in the AST. Nil if this scope is not a function.
decl *ast.FuncDecl decl *ast.FuncDecl

View file

@ -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",
},
}

View file

@ -26,12 +26,12 @@ func NewContext(b []byte) *Context {
} }
// Next return the next instruction to execute. // Next return the next instruction to execute.
func (c *Context) Next() Opcode { func (c *Context) Next() Instruction {
c.ip++ c.ip++
if c.ip >= len(c.prog) { 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. // 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. // CurrInstr returns the current instruction and opcode.
func (c *Context) CurrInstr() (int, Opcode) { func (c *Context) CurrInstr() (int, Instruction) {
if c.ip < 0 { 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. // Copy returns an new exact copy of c.

View file

@ -11,8 +11,8 @@ import (
"github.com/CityOfZion/neo-go/pkg/util" "github.com/CityOfZion/neo-go/pkg/util"
) )
// Emit a VM Opcode with data to the given buffer. // Emit a VM Instruction with data to the given buffer.
func Emit(w *bytes.Buffer, op Opcode, b []byte) error { func Emit(w *bytes.Buffer, op Instruction, b []byte) error {
if err := w.WriteByte(byte(op)); err != nil { if err := w.WriteByte(byte(op)); err != nil {
return err return err
} }
@ -20,29 +20,29 @@ func Emit(w *bytes.Buffer, op Opcode, b []byte) error {
return err return err
} }
// EmitOpcode emits a single VM Opcode the given buffer. // EmitOpcode emits a single VM Instruction the given buffer.
func EmitOpcode(w io.ByteWriter, op Opcode) error { func EmitOpcode(w io.ByteWriter, op Instruction) error {
return w.WriteByte(byte(op)) return w.WriteByte(byte(op))
} }
// EmitBool emits a bool type the given buffer. // EmitBool emits a bool type the given buffer.
func EmitBool(w io.ByteWriter, ok bool) error { func EmitBool(w io.ByteWriter, ok bool) error {
if ok { 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. // EmitInt emits a int type to the given buffer.
func EmitInt(w *bytes.Buffer, i int64) error { func EmitInt(w *bytes.Buffer, i int64) error {
if i == -1 { if i == -1 {
return EmitOpcode(w, Opushm1) return EmitOpcode(w, PUSHM1)
} }
if i == 0 { if i == 0 {
return EmitOpcode(w, Opushf) return EmitOpcode(w, PUSHF)
} }
if i > 0 && i < 16 { if i > 0 && i < 16 {
val := Opcode(int(Opush1) - 1 + int(i)) val := Instruction(int(PUSH1) - 1 + int(i))
return EmitOpcode(w, val) return EmitOpcode(w, val)
} }
@ -63,18 +63,18 @@ func EmitBytes(w *bytes.Buffer, b []byte) error {
n = len(b) n = len(b)
) )
if n <= int(Opushbytes75) { if n <= int(PUSHBYTES75) {
return Emit(w, Opcode(n), b) return Emit(w, Instruction(n), b)
} else if n < 0x100 { } else if n < 0x100 {
err = Emit(w, Opushdata1, []byte{byte(n)}) err = Emit(w, PUSHDATA1, []byte{byte(n)})
} else if n < 0x10000 { } else if n < 0x10000 {
buf := make([]byte, 2) buf := make([]byte, 2)
binary.LittleEndian.PutUint16(buf, uint16(n)) binary.LittleEndian.PutUint16(buf, uint16(n))
err = Emit(w, Opushdata2, buf) err = Emit(w, PUSHDATA2, buf)
} else { } else {
buf := make([]byte, 4) buf := make([]byte, 4)
binary.LittleEndian.PutUint32(buf, uint32(n)) binary.LittleEndian.PutUint32(buf, uint32(n))
err = Emit(w, Opushdata4, buf) err = Emit(w, PUSHDATA4, buf)
} }
if err != nil { if err != nil {
return err return err
@ -92,17 +92,17 @@ func EmitSyscall(w *bytes.Buffer, api string) error {
buf := make([]byte, len(api)+1) buf := make([]byte, len(api)+1)
buf[0] = byte(len(api)) buf[0] = byte(len(api))
copy(buf[1:], []byte(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. // EmitCall emits a call Instruction with label to the given buffer.
func EmitCall(w *bytes.Buffer, op Opcode, label int16) error { func EmitCall(w *bytes.Buffer, op Instruction, label int16) error {
return EmitJmp(w, op, label) return EmitJmp(w, op, label)
} }
// EmitJmp emits a jump Opcode along with label to the given buffer. // EmitJmp emits a jump Instruction along with label to the given buffer.
func EmitJmp(w *bytes.Buffer, op Opcode, label int16) error { func EmitJmp(w *bytes.Buffer, op Instruction, label int16) error {
if !isOpcodeJmp(op) { if !isInstructionJmp(op) {
return fmt.Errorf("opcode %s is not a jump or call type", op.String()) return fmt.Errorf("opcode %s is not a jump or call type", op.String())
} }
buf := make([]byte, 2) 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 // EmitAppCall emits an appcall, if tailCall is true, tailCall opcode will be
// emitted instead. // emitted instead.
func EmitAppCall(w *bytes.Buffer, scriptHash util.Uint160, tailCall bool) error { func EmitAppCall(w *bytes.Buffer, scriptHash util.Uint160, tailCall bool) error {
op := Oappcall op := APPCALL
if tailCall { if tailCall {
op = Otailcall op = TAILCALL
} }
return Emit(w, op, scriptHash.Bytes()) return Emit(w, op, scriptHash.Bytes())
} }
@ -142,8 +142,8 @@ func EmitAppCallWithOperation(w *bytes.Buffer, scriptHash util.Uint160, operatio
return EmitAppCall(w, scriptHash, false) return EmitAppCall(w, scriptHash, false)
} }
func isOpcodeJmp(op Opcode) bool { func isInstructionJmp(op Instruction) bool {
if op == Ojmp || op == Ojmpifnot || op == Ojmpif || op == Ocall { if op == JMP || op == JMPIFNOT || op == JMPIF || op == CALL {
return true return true
} }
return false return false

View file

@ -11,7 +11,7 @@ import (
func TestEmitInt(t *testing.T) { func TestEmitInt(t *testing.T) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
EmitInt(buf, 10) EmitInt(buf, 10)
assert.Equal(t, Opcode(buf.Bytes()[0]), Opush10) assert.Equal(t, Instruction(buf.Bytes()[0]), PUSH10)
buf.Reset() buf.Reset()
EmitInt(buf, 100) EmitInt(buf, 100)
assert.Equal(t, buf.Bytes()[0], uint8(1)) assert.Equal(t, buf.Bytes()[0], uint8(1))
@ -26,8 +26,8 @@ func TestEmitBool(t *testing.T) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
EmitBool(buf, true) EmitBool(buf, true)
EmitBool(buf, false) EmitBool(buf, false)
assert.Equal(t, Opcode(buf.Bytes()[0]), Opush1) assert.Equal(t, Instruction(buf.Bytes()[0]), PUSH1)
assert.Equal(t, Opcode(buf.Bytes()[1]), Opush0) assert.Equal(t, Instruction(buf.Bytes()[1]), PUSH0)
} }
func TestEmitString(t *testing.T) { func TestEmitString(t *testing.T) {
@ -48,7 +48,7 @@ func TestEmitSyscall(t *testing.T) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
for _, syscall := range syscalls { for _, syscall := range syscalls {
EmitSyscall(buf, syscall) 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()[1], uint8(len(syscall)))
assert.Equal(t, buf.Bytes()[2:], []byte(syscall)) assert.Equal(t, buf.Bytes()[2:], []byte(syscall))
buf.Reset() buf.Reset()
@ -57,8 +57,8 @@ func TestEmitSyscall(t *testing.T) {
func TestEmitCall(t *testing.T) { func TestEmitCall(t *testing.T) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
EmitCall(buf, Ojmp, 100) EmitCall(buf, JMP, 100)
assert.Equal(t, Opcode(buf.Bytes()[0]), Ojmp) assert.Equal(t, Instruction(buf.Bytes()[0]), JMP)
label := binary.LittleEndian.Uint16(buf.Bytes()[1:3]) label := binary.LittleEndian.Uint16(buf.Bytes()[1:3])
assert.Equal(t, label, uint16(100)) assert.Equal(t, label, uint16(100))
} }

View file

@ -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) + ")"
}

130
pkg/vm/instructions.go Normal file
View file

@ -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
)

View file

@ -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
)

View file

@ -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) + ")"
}

View file

@ -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",
}

View file

@ -8,7 +8,7 @@ func TestStoragePutGet(t *testing.T) {
src := ` src := `
package foo package foo
import "github.com/CityOfZion/neo-go/pkg/vm/api/storage" import "github.com/CityOfZion/neo-go/pkg/interop/storage"
func Main() string { func Main() string {
ctx := storage.GetContext() ctx := storage.GetContext()

View file

@ -8,7 +8,7 @@ func TestSHA256(t *testing.T) {
src := ` src := `
package foo package foo
import ( import (
"github.com/CityOfZion/neo-go/pkg/vm/api/crypto" "github.com/CityOfZion/neo-go/pkg/interop/crypto"
) )
func Main() []byte { func Main() []byte {
src := []byte{0x97} src := []byte{0x97}
@ -23,7 +23,7 @@ func TestSHA1(t *testing.T) {
src := ` src := `
package foo package foo
import ( import (
"github.com/CityOfZion/neo-go/pkg/vm/api/crypto" "github.com/CityOfZion/neo-go/pkg/interop/crypto"
) )
func Main() []byte { func Main() []byte {
src := []byte{0x97} src := []byte{0x97}
@ -38,7 +38,7 @@ func TestHash160(t *testing.T) {
src := ` src := `
package foo package foo
import ( import (
"github.com/CityOfZion/neo-go/pkg/vm/api/crypto" "github.com/CityOfZion/neo-go/pkg/interop/crypto"
) )
func Main() []byte { func Main() []byte {
src := []byte{0x97} src := []byte{0x97}
@ -53,7 +53,7 @@ func TestHash256(t *testing.T) {
src := ` src := `
package foo package foo
import ( import (
"github.com/CityOfZion/neo-go/pkg/vm/api/crypto" "github.com/CityOfZion/neo-go/pkg/interop/crypto"
) )
func Main() []byte { func Main() []byte {
src := []byte{0x97} src := []byte{0x97}

View file

@ -104,7 +104,7 @@ func (v *VM) PrintOps() {
} else { } else {
cursor = "" 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() w.Flush()
@ -228,7 +228,7 @@ func (v *VM) Step() {
} }
// execute performs an instruction cycle in the VM. Acting on the instruction (opcode). // 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 // 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. // each panic at a central point, putting the VM in a fault state.
defer func() { 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)) b := ctx.readBytes(int(op))
v.estack.PushVal(b) v.estack.PushVal(b)
return return
} }
switch op { switch op {
case Opushm1, Opush1, Opush2, Opush3, Opush4, Opush5, case PUSHM1, PUSH1, PUSH2, PUSH3, PUSH4, PUSH5,
Opush6, Opush7, Opush8, Opush9, Opush10, Opush11, PUSH6, PUSH7, PUSH8, PUSH9, PUSH10, PUSH11,
Opush12, Opush13, Opush14, Opush15, Opush16: PUSH12, PUSH13, PUSH14, PUSH15, PUSH16:
val := int(op) - int(Opush1) + 1 val := int(op) - int(PUSH1) + 1
v.estack.PushVal(val) v.estack.PushVal(val)
case Opush0: case PUSH0:
v.estack.PushVal(0) v.estack.PushVal(0)
case Opushdata1: case PUSHDATA1:
n := ctx.readByte() n := ctx.readByte()
b := ctx.readBytes(int(n)) b := ctx.readBytes(int(n))
v.estack.PushVal(b) v.estack.PushVal(b)
case Opushdata2: case PUSHDATA2:
n := ctx.readUint16() n := ctx.readUint16()
b := ctx.readBytes(int(n)) b := ctx.readBytes(int(n))
v.estack.PushVal(b) v.estack.PushVal(b)
case Opushdata4: case PUSHDATA4:
n := ctx.readUint32() n := ctx.readUint32()
b := ctx.readBytes(int(n)) b := ctx.readBytes(int(n))
v.estack.PushVal(b) v.estack.PushVal(b)
// Stack operations. // Stack operations.
case Otoaltstack: case TOALTSTACK:
v.astack.Push(v.estack.Pop()) v.astack.Push(v.estack.Pop())
case Ofromaltstack: case FROMALTSTACK:
v.estack.Push(v.astack.Pop()) v.estack.Push(v.astack.Pop())
case Odupfromaltstack: case DUPFROMALTSTACK:
v.estack.Push(v.astack.Dup(0)) v.estack.Push(v.astack.Dup(0))
case Odup: case DUP:
v.estack.Push(v.estack.Dup(0)) v.estack.Push(v.estack.Dup(0))
case Oswap: case SWAP:
a := v.estack.Pop() a := v.estack.Pop()
b := v.estack.Pop() b := v.estack.Pop()
v.estack.Push(a) v.estack.Push(a)
v.estack.Push(b) v.estack.Push(b)
case Oxswap: case XSWAP:
n := int(v.estack.Pop().BigInt().Int64()) n := int(v.estack.Pop().BigInt().Int64())
if n < 0 { if n < 0 {
panic("XSWAP: invalid length") panic("XSWAP: invalid length")
@ -305,7 +305,7 @@ func (v *VM) execute(ctx *Context, op Opcode) {
b.value = aval b.value = aval
} }
case Otuck: case TUCK:
n := int(v.estack.Pop().BigInt().Int64()) n := int(v.estack.Pop().BigInt().Int64())
if n <= 0 { if n <= 0 {
panic("OTUCK: invalid length") panic("OTUCK: invalid length")
@ -313,7 +313,7 @@ func (v *VM) execute(ctx *Context, op Opcode) {
v.estack.InsertAt(v.estack.Peek(0), n) v.estack.InsertAt(v.estack.Peek(0), n)
case Orot: case ROT:
c := v.estack.Pop() c := v.estack.Pop()
b := v.estack.Pop() b := v.estack.Pop()
a := 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(c)
v.estack.Push(a) v.estack.Push(a)
case Odepth: case DEPTH:
v.estack.PushVal(v.estack.Len()) v.estack.PushVal(v.estack.Len())
case Onip: case NIP:
elem := v.estack.Pop() elem := v.estack.Pop()
_ = v.estack.Pop() _ = v.estack.Pop()
v.estack.Push(elem) v.estack.Push(elem)
case Oover: case OVER:
b := v.estack.Pop() b := v.estack.Pop()
a := v.estack.Peek(0) a := v.estack.Peek(0)
v.estack.Push(b) v.estack.Push(b)
v.estack.Push(a) v.estack.Push(a)
case Oroll: case ROLL:
n := int(v.estack.Pop().BigInt().Int64()) n := int(v.estack.Pop().BigInt().Int64())
if n < 0 { if n < 0 {
panic("negative stack item returned") panic("negative stack item returned")
@ -345,105 +345,105 @@ func (v *VM) execute(ctx *Context, op Opcode) {
v.estack.Push(v.estack.RemoveAt(n)) v.estack.Push(v.estack.RemoveAt(n))
} }
case Odrop: case DROP:
v.estack.Pop() v.estack.Pop()
case Oequal: case EQUAL:
panic("TODO EQUAL") panic("TODO EQUAL")
// Bit operations. // Bit operations.
case Oand: case AND:
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
v.estack.PushVal(new(big.Int).And(b, a)) v.estack.PushVal(new(big.Int).And(b, a))
case Oor: case OR:
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
v.estack.PushVal(new(big.Int).Or(b, a)) v.estack.PushVal(new(big.Int).Or(b, a))
case Oxor: case XOR:
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
v.estack.PushVal(new(big.Int).Xor(b, a)) v.estack.PushVal(new(big.Int).Xor(b, a))
// Numeric operations. // Numeric operations.
case Oadd: case ADD:
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
v.estack.PushVal(new(big.Int).Add(a, b)) v.estack.PushVal(new(big.Int).Add(a, b))
case Osub: case SUB:
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
v.estack.PushVal(new(big.Int).Sub(a, b)) v.estack.PushVal(new(big.Int).Sub(a, b))
case Odiv: case DIV:
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
v.estack.PushVal(new(big.Int).Div(a, b)) v.estack.PushVal(new(big.Int).Div(a, b))
case Omul: case MUL:
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
v.estack.PushVal(new(big.Int).Mul(a, b)) v.estack.PushVal(new(big.Int).Mul(a, b))
case Omod: case MOD:
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
v.estack.PushVal(new(big.Int).Mod(a, b)) v.estack.PushVal(new(big.Int).Mod(a, b))
case Oshl: case SHL:
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
v.estack.PushVal(new(big.Int).Lsh(a, uint(b.Int64()))) v.estack.PushVal(new(big.Int).Lsh(a, uint(b.Int64())))
case Oshr: case SHR:
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
v.estack.PushVal(new(big.Int).Rsh(a, uint(b.Int64()))) v.estack.PushVal(new(big.Int).Rsh(a, uint(b.Int64())))
case Obooland: case BOOLAND:
b := v.estack.Pop().Bool() b := v.estack.Pop().Bool()
a := v.estack.Pop().Bool() a := v.estack.Pop().Bool()
v.estack.PushVal(a && b) v.estack.PushVal(a && b)
case Oboolor: case BOOLOR:
b := v.estack.Pop().Bool() b := v.estack.Pop().Bool()
a := v.estack.Pop().Bool() a := v.estack.Pop().Bool()
v.estack.PushVal(a || b) v.estack.PushVal(a || b)
case Onumequal: case NUMEQUAL:
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
v.estack.PushVal(a.Cmp(b) == 0) v.estack.PushVal(a.Cmp(b) == 0)
case Onumnotequal: case NUMNOTEQUAL:
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
v.estack.PushVal(a.Cmp(b) != 0) v.estack.PushVal(a.Cmp(b) != 0)
case Olt: case LT:
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
v.estack.PushVal(a.Cmp(b) == -1) v.estack.PushVal(a.Cmp(b) == -1)
case Ogt: case GT:
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
v.estack.PushVal(a.Cmp(b) == 1) v.estack.PushVal(a.Cmp(b) == 1)
case Olte: case LTE:
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
v.estack.PushVal(a.Cmp(b) <= 0) v.estack.PushVal(a.Cmp(b) <= 0)
case Ogte: case GTE:
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
v.estack.PushVal(a.Cmp(b) >= 0) v.estack.PushVal(a.Cmp(b) >= 0)
case Omin: case MIN:
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
val := a val := a
@ -452,7 +452,7 @@ func (v *VM) execute(ctx *Context, op Opcode) {
} }
v.estack.PushVal(val) v.estack.PushVal(val)
case Omax: case MAX:
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
val := a val := a
@ -461,52 +461,52 @@ func (v *VM) execute(ctx *Context, op Opcode) {
} }
v.estack.PushVal(val) v.estack.PushVal(val)
case Owithin: case WITHIN:
b := v.estack.Pop().BigInt() b := v.estack.Pop().BigInt()
a := v.estack.Pop().BigInt() a := v.estack.Pop().BigInt()
x := v.estack.Pop().BigInt() x := v.estack.Pop().BigInt()
v.estack.PushVal(a.Cmp(x) <= 0 && x.Cmp(b) == -1) v.estack.PushVal(a.Cmp(x) <= 0 && x.Cmp(b) == -1)
case Oinc: case INC:
x := v.estack.Pop().BigInt() x := v.estack.Pop().BigInt()
v.estack.PushVal(new(big.Int).Add(x, big.NewInt(1))) v.estack.PushVal(new(big.Int).Add(x, big.NewInt(1)))
case Odec: case DEC:
x := v.estack.Pop().BigInt() x := v.estack.Pop().BigInt()
v.estack.PushVal(new(big.Int).Sub(x, big.NewInt(1))) v.estack.PushVal(new(big.Int).Sub(x, big.NewInt(1)))
case Osign: case SIGN:
x := v.estack.Pop().BigInt() x := v.estack.Pop().BigInt()
v.estack.PushVal(x.Sign()) v.estack.PushVal(x.Sign())
case Onegate: case NEGATE:
x := v.estack.Pop().BigInt() x := v.estack.Pop().BigInt()
v.estack.PushVal(x.Neg(x)) v.estack.PushVal(x.Neg(x))
case Oabs: case ABS:
x := v.estack.Pop().BigInt() x := v.estack.Pop().BigInt()
v.estack.PushVal(x.Abs(x)) v.estack.PushVal(x.Abs(x))
case Onot: case NOT:
x := v.estack.Pop().Bool() x := v.estack.Pop().Bool()
v.estack.PushVal(!x) v.estack.PushVal(!x)
case Onz: case NZ:
panic("todo NZ") panic("todo NZ")
// x := v.estack.Pop().BigInt() // x := v.estack.Pop().BigInt()
// Object operations. // Object operations.
case Onewarray: case NEWARRAY:
n := v.estack.Pop().BigInt().Int64() n := v.estack.Pop().BigInt().Int64()
items := make([]StackItem, n) items := make([]StackItem, n)
v.estack.PushVal(&ArrayItem{items}) v.estack.PushVal(&ArrayItem{items})
case Onewstruct: case NEWSTRUCT:
n := v.estack.Pop().BigInt().Int64() n := v.estack.Pop().BigInt().Int64()
items := make([]StackItem, n) items := make([]StackItem, n)
v.estack.PushVal(&StructItem{items}) v.estack.PushVal(&StructItem{items})
case Oappend: case APPEND:
itemElem := v.estack.Pop() itemElem := v.estack.Pop()
arrElem := 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") 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()) n := int(v.estack.Pop().BigInt().Int64())
if n < 0 || n > v.estack.Len() { if n < 0 || n > v.estack.Len() {
panic("OPACK: invalid length") panic("OPACK: invalid length")
@ -539,10 +539,10 @@ func (v *VM) execute(ctx *Context, op Opcode) {
v.estack.PushVal(items) v.estack.PushVal(items)
case Ounpack: case UNPACK:
panic("TODO") panic("TODO")
case Opickitem: case PICKITEM:
var ( var (
key = v.estack.Pop() key = v.estack.Pop()
obj = v.estack.Pop() obj = v.estack.Pop()
@ -562,7 +562,7 @@ func (v *VM) execute(ctx *Context, op Opcode) {
panic("PICKITEM: unknown type") panic("PICKITEM: unknown type")
} }
case Osetitem: case SETITEM:
var ( var (
item = v.estack.Pop().value item = v.estack.Pop().value
key = v.estack.Pop() 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)) panic(fmt.Sprintf("SETITEM: invalid item type %s", t))
} }
case Oarraysize: case ARRAYSIZE:
elem := v.estack.Pop() elem := v.estack.Pop()
// Cause there is no native (byte) item type here, hence we need to check // Cause there is no native (byte) item type here, hence we need to check
// the type of the item for array size operations. // 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") panic("ARRAYSIZE: item not of type []StackItem")
} }
case Osize: case SIZE:
elem := v.estack.Pop() elem := v.estack.Pop()
arr, ok := elem.value.Value().([]uint8) arr, ok := elem.value.Value().([]uint8)
if !ok { if !ok {
@ -603,7 +603,7 @@ func (v *VM) execute(ctx *Context, op Opcode) {
} }
v.estack.PushVal(len(arr)) v.estack.PushVal(len(arr))
case Ojmp, Ojmpif, Ojmpifnot: case JMP, JMPIF, JMPIFNOT:
var ( var (
rOffset = int16(ctx.readUint16()) rOffset = int16(ctx.readUint16())
offset = ctx.ip + int(rOffset) - 3 // sizeOf(int16 + uint8) 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)) panic(fmt.Sprintf("JMP: invalid offset %d ip at %d", offset, ctx.ip))
} }
cond := true cond := true
if op > Ojmp { if op > JMP {
cond = v.estack.Pop().Bool() cond = v.estack.Pop().Bool()
if op == Ojmpifnot { if op == JMPIFNOT {
cond = !cond cond = !cond
} }
} }
@ -622,12 +622,12 @@ func (v *VM) execute(ctx *Context, op Opcode) {
ctx.ip = offset ctx.ip = offset
} }
case Ocall: case CALL:
v.istack.PushVal(ctx.Copy()) v.istack.PushVal(ctx.Copy())
ctx.ip += 2 ctx.ip += 2
v.execute(v.Context(), Ojmp) v.execute(v.Context(), JMP)
case Osyscall: case SYSCALL:
api := ctx.readVarBytes() api := ctx.readVarBytes()
ifunc, ok := v.interop[string(api)] ifunc, ok := v.interop[string(api)]
if !ok { if !ok {
@ -637,7 +637,7 @@ func (v *VM) execute(ctx *Context, op Opcode) {
panic(fmt.Sprintf("failed to invoke syscall: %s", err)) panic(fmt.Sprintf("failed to invoke syscall: %s", err))
} }
case Oappcall, Otailcall: case APPCALL, TAILCALL:
if len(v.scripts) == 0 { if len(v.scripts) == 0 {
panic("script table is empty") panic("script table is empty")
} }
@ -652,32 +652,32 @@ func (v *VM) execute(ctx *Context, op Opcode) {
panic("could not find script") panic("could not find script")
} }
if op == Otailcall { if op == TAILCALL {
_ = v.istack.Pop() _ = v.istack.Pop()
} }
v.LoadScript(script) v.LoadScript(script)
case Oret: case RET:
_ = v.istack.Pop() _ = v.istack.Pop()
if v.istack.Len() == 0 { if v.istack.Len() == 0 {
v.state = haltState v.state = haltState
} }
// Cryptographic operations. // Cryptographic operations.
case Osha1: case SHA1:
b := v.estack.Pop().Bytes() b := v.estack.Pop().Bytes()
sha := sha1.New() sha := sha1.New()
sha.Write(b) sha.Write(b)
v.estack.PushVal(sha.Sum(nil)) v.estack.PushVal(sha.Sum(nil))
case Osha256: case SHA256:
b := v.estack.Pop().Bytes() b := v.estack.Pop().Bytes()
sha := sha256.New() sha := sha256.New()
sha.Write(b) sha.Write(b)
v.estack.PushVal(sha.Sum(nil)) v.estack.PushVal(sha.Sum(nil))
case Ohash160: case HASH160:
b := v.estack.Pop().Bytes() b := v.estack.Pop().Bytes()
sha := sha256.New() sha := sha256.New()
sha.Write(b) sha.Write(b)
@ -686,7 +686,7 @@ func (v *VM) execute(ctx *Context, op Opcode) {
ripemd.Write(h) ripemd.Write(h)
v.estack.PushVal(ripemd.Sum(nil)) v.estack.PushVal(ripemd.Sum(nil))
case Ohash256: case HASH256:
b := v.estack.Pop().Bytes() b := v.estack.Pop().Bytes()
sha := sha256.New() sha := sha256.New()
sha.Write(b) sha.Write(b)
@ -695,19 +695,19 @@ func (v *VM) execute(ctx *Context, op Opcode) {
sha.Write(h) sha.Write(h)
v.estack.PushVal(sha.Sum(nil)) v.estack.PushVal(sha.Sum(nil))
case Ochecksig: case CHECKSIG:
// pubkey := v.estack.Pop().Bytes() // pubkey := v.estack.Pop().Bytes()
// sig := v.estack.Pop().Bytes() // sig := v.estack.Pop().Bytes()
case Ocheckmultisig: case CHECKMULTISIG:
case Onop: case NOP:
// unlucky ^^ // unlucky ^^
case Othrow: case THROW:
panic("THROW") panic("THROW")
case Othrowifnot: case THROWIFNOT:
if !v.estack.Pop().Bool() { if !v.estack.Pop().Bool() {
panic("THROWIFNOT") panic("THROWIFNOT")
} }

View file

@ -20,7 +20,7 @@ func TestInteropHook(t *testing.T) {
buf := new(bytes.Buffer) buf := new(bytes.Buffer)
EmitSyscall(buf, "foo") EmitSyscall(buf, "foo")
EmitOpcode(buf, Oret) EmitOpcode(buf, RET)
v.Load(buf.Bytes()) v.Load(buf.Bytes())
v.Run() v.Run()
assert.Equal(t, 1, v.estack.Len()) assert.Equal(t, 1, v.estack.Len())
@ -51,7 +51,7 @@ func TestPushBytes1to75(t *testing.T) {
assert.IsType(t, elem.Bytes(), b) assert.IsType(t, elem.Bytes(), b)
assert.Equal(t, 0, vm.estack.Len()) 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.astack.Len())
assert.Equal(t, 0, vm.istack.Len()) assert.Equal(t, 0, vm.istack.Len())
@ -61,7 +61,7 @@ func TestPushBytes1to75(t *testing.T) {
func TestPushm1to16(t *testing.T) { func TestPushm1to16(t *testing.T) {
var prog []byte var prog []byte
for i := int(Opushm1); i <= int(Opush16); i++ { for i := int(PUSHM1); i <= int(PUSH16); i++ {
if i == 80 { if i == 80 {
continue // opcode layout we got here. continue // opcode layout we got here.
} }
@ -69,7 +69,7 @@ func TestPushm1to16(t *testing.T) {
} }
vm := load(prog) vm := load(prog)
for i := int(Opushm1); i <= int(Opush16); i++ { for i := int(PUSHM1); i <= int(PUSH16); i++ {
if i == 80 { if i == 80 {
continue // nice opcode layout we got here. continue // nice opcode layout we got here.
} }
@ -77,7 +77,7 @@ func TestPushm1to16(t *testing.T) {
elem := vm.estack.Pop() elem := vm.estack.Pop()
assert.IsType(t, &BigIntegerItem{}, elem.value) assert.IsType(t, &BigIntegerItem{}, elem.value)
val := i - int(Opush1) + 1 val := i - int(PUSH1) + 1
assert.Equal(t, elem.BigInt().Int64(), int64(val)) assert.Equal(t, elem.BigInt().Int64(), int64(val))
} }
} }
@ -95,7 +95,7 @@ func TestPushData4(t *testing.T) {
} }
func TestAdd(t *testing.T) { func TestAdd(t *testing.T) {
prog := makeProgram(Oadd) prog := makeProgram(ADD)
vm := load(prog) vm := load(prog)
vm.estack.PushVal(4) vm.estack.PushVal(4)
vm.estack.PushVal(2) vm.estack.PushVal(2)
@ -104,7 +104,7 @@ func TestAdd(t *testing.T) {
} }
func TestMul(t *testing.T) { func TestMul(t *testing.T) {
prog := makeProgram(Omul) prog := makeProgram(MUL)
vm := load(prog) vm := load(prog)
vm.estack.PushVal(4) vm.estack.PushVal(4)
vm.estack.PushVal(2) vm.estack.PushVal(2)
@ -113,7 +113,7 @@ func TestMul(t *testing.T) {
} }
func TestDiv(t *testing.T) { func TestDiv(t *testing.T) {
prog := makeProgram(Odiv) prog := makeProgram(DIV)
vm := load(prog) vm := load(prog)
vm.estack.PushVal(4) vm.estack.PushVal(4)
vm.estack.PushVal(2) vm.estack.PushVal(2)
@ -122,7 +122,7 @@ func TestDiv(t *testing.T) {
} }
func TestSub(t *testing.T) { func TestSub(t *testing.T) {
prog := makeProgram(Osub) prog := makeProgram(SUB)
vm := load(prog) vm := load(prog)
vm.estack.PushVal(4) vm.estack.PushVal(4)
vm.estack.PushVal(2) vm.estack.PushVal(2)
@ -131,7 +131,7 @@ func TestSub(t *testing.T) {
} }
func TestLT(t *testing.T) { func TestLT(t *testing.T) {
prog := makeProgram(Olt) prog := makeProgram(LT)
vm := load(prog) vm := load(prog)
vm.estack.PushVal(4) vm.estack.PushVal(4)
vm.estack.PushVal(3) vm.estack.PushVal(3)
@ -140,7 +140,7 @@ func TestLT(t *testing.T) {
} }
func TestLTE(t *testing.T) { func TestLTE(t *testing.T) {
prog := makeProgram(Olte) prog := makeProgram(LTE)
vm := load(prog) vm := load(prog)
vm.estack.PushVal(2) vm.estack.PushVal(2)
vm.estack.PushVal(3) vm.estack.PushVal(3)
@ -149,7 +149,7 @@ func TestLTE(t *testing.T) {
} }
func TestGT(t *testing.T) { func TestGT(t *testing.T) {
prog := makeProgram(Ogt) prog := makeProgram(GT)
vm := load(prog) vm := load(prog)
vm.estack.PushVal(9) vm.estack.PushVal(9)
vm.estack.PushVal(3) vm.estack.PushVal(3)
@ -159,7 +159,7 @@ func TestGT(t *testing.T) {
} }
func TestGTE(t *testing.T) { func TestGTE(t *testing.T) {
prog := makeProgram(Ogte) prog := makeProgram(GTE)
vm := load(prog) vm := load(prog)
vm.estack.PushVal(3) vm.estack.PushVal(3)
vm.estack.PushVal(3) vm.estack.PushVal(3)
@ -168,7 +168,7 @@ func TestGTE(t *testing.T) {
} }
func TestDepth(t *testing.T) { func TestDepth(t *testing.T) {
prog := makeProgram(Odepth) prog := makeProgram(DEPTH)
vm := load(prog) vm := load(prog)
vm.estack.PushVal(1) vm.estack.PushVal(1)
vm.estack.PushVal(2) vm.estack.PushVal(2)
@ -178,7 +178,7 @@ func TestDepth(t *testing.T) {
} }
func TestNumEqual(t *testing.T) { func TestNumEqual(t *testing.T) {
prog := makeProgram(Onumequal) prog := makeProgram(NUMEQUAL)
vm := load(prog) vm := load(prog)
vm.estack.PushVal(1) vm.estack.PushVal(1)
vm.estack.PushVal(2) vm.estack.PushVal(2)
@ -187,7 +187,7 @@ func TestNumEqual(t *testing.T) {
} }
func TestNumNotEqual(t *testing.T) { func TestNumNotEqual(t *testing.T) {
prog := makeProgram(Onumnotequal) prog := makeProgram(NUMNOTEQUAL)
vm := load(prog) vm := load(prog)
vm.estack.PushVal(2) vm.estack.PushVal(2)
vm.estack.PushVal(2) vm.estack.PushVal(2)
@ -196,7 +196,7 @@ func TestNumNotEqual(t *testing.T) {
} }
func TestINC(t *testing.T) { func TestINC(t *testing.T) {
prog := makeProgram(Oinc) prog := makeProgram(INC)
vm := load(prog) vm := load(prog)
vm.estack.PushVal(1) vm.estack.PushVal(1)
vm.Run() vm.Run()
@ -204,13 +204,13 @@ func TestINC(t *testing.T) {
} }
func TestAppCall(t *testing.T) { func TestAppCall(t *testing.T) {
prog := []byte{byte(Oappcall)} prog := []byte{byte(APPCALL)}
hash := util.Uint160{} hash := util.Uint160{}
prog = append(prog, hash.Bytes()...) prog = append(prog, hash.Bytes()...)
prog = append(prog, byte(Oret)) prog = append(prog, byte(RET))
vm := load(prog) vm := load(prog)
vm.scripts[hash] = makeProgram(Odepth) vm.scripts[hash] = makeProgram(DEPTH)
vm.estack.PushVal(2) vm.estack.PushVal(2)
vm.Run() vm.Run()
@ -231,12 +231,12 @@ func TestSimpleCall(t *testing.T) {
assert.Equal(t, result, int(vm.estack.Pop().BigInt().Int64())) assert.Equal(t, result, int(vm.estack.Pop().BigInt().Int64()))
} }
func makeProgram(opcodes ...Opcode) []byte { func makeProgram(opcodes ...Instruction) []byte {
prog := make([]byte, len(opcodes)+1) // Oret prog := make([]byte, len(opcodes)+1) // RET
for i := 0; i < len(opcodes); i++ { for i := 0; i < len(opcodes); i++ {
prog[i] = byte(opcodes[i]) prog[i] = byte(opcodes[i])
} }
prog[len(prog)-1] = byte(Oret) prog[len(prog)-1] = byte(RET)
return prog return prog
} }