Merge pull request #1033 from nspcc-dev/feature/manifest

Add Manifest to state.Contract
This commit is contained in:
Roman Khimov 2020-06-11 11:23:40 +03:00 committed by GitHub
commit 5fed409ea7
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
38 changed files with 396 additions and 675 deletions

View file

@ -1,7 +1,6 @@
package smartcontract package smartcontract
import ( import (
"bufio"
"bytes" "bytes"
"context" "context"
"encoding/hex" "encoding/hex"
@ -22,6 +21,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/rpc/request" "github.com/nspcc-dev/neo-go/pkg/rpc/request"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/wallet" "github.com/nspcc-dev/neo-go/pkg/wallet"
@ -323,23 +323,22 @@ func initSmartContract(ctx *cli.Context) error {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
// Ask contract information and write a neo-go.yml file unless the -skip-details flag is set. m := ProjectConfig{
// TODO: Fix the missing neo-go.yml file with the `init` command when the package manager is in place. EntryPoint: manifest.Method{
if !ctx.Bool("skip-details") { Name: "Main",
details := parseContractDetails() Parameters: []manifest.Parameter{
details.ReturnType = smartcontract.ByteArrayType manifest.NewParameter("Method", smartcontract.StringType),
details.Parameters = make([]smartcontract.ParamType, 2) manifest.NewParameter("Arguments", smartcontract.ArrayType),
details.Parameters[0] = smartcontract.StringType },
details.Parameters[1] = smartcontract.ArrayType ReturnType: smartcontract.ByteArrayType,
},
project := &ProjectConfig{Contract: details} }
b, err := yaml.Marshal(project) b, err := yaml.Marshal(m)
if err != nil { if err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
if err := ioutil.WriteFile(filepath.Join(basePath, "neo-go.yml"), b, 0644); err != nil { if err := ioutil.WriteFile(filepath.Join(basePath, "neo-go.yml"), b, 0644); err != nil {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
}
} }
data := []byte(fmt.Sprintf(smartContractTmpl, contractName)) data := []byte(fmt.Sprintf(smartContractTmpl, contractName))
@ -375,7 +374,7 @@ func contractCompile(ctx *cli.Context) error {
if err != nil { if err != nil {
return err return err
} }
o.ContractDetails = &conf.Contract o.ContractFeatures = conf.GetFeatures()
} }
result, err := compiler.CompileAndSave(src, o) result, err := compiler.CompileAndSave(src, o)
@ -526,30 +525,35 @@ func testInvokeScript(ctx *cli.Context) error {
// ProjectConfig contains project metadata. // ProjectConfig contains project metadata.
type ProjectConfig struct { type ProjectConfig struct {
Version uint HasStorage bool
Contract smartcontract.ContractDetails `yaml:"project"` IsPayable bool
EntryPoint manifest.Method
Methods []manifest.Method
Events []manifest.Event
} }
func parseContractDetails() smartcontract.ContractDetails { // GetFeatures returns smartcontract features from the config.
details := smartcontract.ContractDetails{} func (p *ProjectConfig) GetFeatures() smartcontract.PropertyState {
reader := bufio.NewReader(os.Stdin) var fs smartcontract.PropertyState
if p.IsPayable {
fs |= smartcontract.IsPayable
}
if p.HasStorage {
fs |= smartcontract.HasStorage
}
return fs
}
fmt.Print("Author: ") // ToManifest converts project config to the manifest.
details.Author, _ = reader.ReadString('\n') func (p *ProjectConfig) ToManifest(script []byte) *manifest.Manifest {
h := hash.Hash160(script)
fmt.Print("Email: ") m := manifest.NewManifest(h)
details.Email, _ = reader.ReadString('\n') m.Features = p.GetFeatures()
m.ABI.Hash = h
fmt.Print("Version: ") m.ABI.EntryPoint = p.EntryPoint
details.Version, _ = reader.ReadString('\n') m.ABI.Methods = p.Methods
m.ABI.Events = p.Events
fmt.Print("Project name: ") return m
details.ProjectName, _ = reader.ReadString('\n')
fmt.Print("Description: ")
details.Description, _ = reader.ReadString('\n')
return details
} }
func inspect(ctx *cli.Context) error { func inspect(ctx *cli.Context) error {
@ -646,13 +650,12 @@ func contractDeploy(ctx *cli.Context) error {
return cli.NewExitError(err, 1) return cli.NewExitError(err, 1)
} }
txScript, err := request.CreateDeploymentScript(avm, &conf.Contract) m := conf.ToManifest(avm)
txScript, sysfee, err := request.CreateDeploymentScript(avm, m)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to create deployment script: %v", err), 1) return cli.NewExitError(fmt.Errorf("failed to create deployment script: %v", err), 1)
} }
sysfee := smartcontract.GetDeploymentPrice(request.DetailsToSCProperties(&conf.Contract))
txHash, err := c.SignAndPushInvocationTx(txScript, acc, sysfee, gas) txHash, err := c.SignAndPushInvocationTx(txScript, acc, sysfee, gas)
if err != nil { if err != nil {
return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1) return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1)

View file

@ -32,7 +32,7 @@ type Options struct {
ABIInfo string ABIInfo string
// Contract metadata. // Contract metadata.
ContractDetails *smartcontract.ContractDetails ContractFeatures smartcontract.PropertyState
} }
type buildInfo struct { type buildInfo struct {
@ -118,7 +118,7 @@ func CompileAndSave(src string, o *Options) ([]byte, error) {
if o.ABIInfo == "" { if o.ABIInfo == "" {
return b, err return b, err
} }
abi := di.convertToABI(b, o.ContractDetails) abi := di.convertToABI(b, o.ContractFeatures)
abiData, err := json.Marshal(abi) abiData, err := json.Marshal(abi)
if err != nil { if err != nil {
return b, err return b, err

View file

@ -311,14 +311,16 @@ func parsePairJSON(data []byte, sep string) (string, string, error) {
return ss[0], ss[1], nil return ss[0], ss[1], nil
} }
func (di *DebugInfo) convertToABI(contract []byte, cd *smartcontract.ContractDetails) ABI { // convertToABI converts contract to the ABI struct for debugger.
// Note: manifest is taken from the external source, however it can be generated ad-hoc. See #1038.
func (di *DebugInfo) convertToABI(contract []byte, fs smartcontract.PropertyState) ABI {
methods := make([]Method, 0) methods := make([]Method, 0)
for _, method := range di.Methods { for _, method := range di.Methods {
if method.Name.Name == di.EntryPoint { if method.Name.Name == di.EntryPoint {
methods = append(methods, Method{ methods = append(methods, Method{
Name: method.Name.Name, Name: method.Name.Name,
Parameters: method.Parameters, Parameters: method.Parameters,
ReturnType: cd.ReturnType.String(), ReturnType: method.ReturnType,
}) })
break break
} }
@ -333,14 +335,8 @@ func (di *DebugInfo) convertToABI(contract []byte, cd *smartcontract.ContractDet
return ABI{ return ABI{
Hash: hash.Hash160(contract), Hash: hash.Hash160(contract),
Metadata: Metadata{ Metadata: Metadata{
Author: cd.Author, HasStorage: fs&smartcontract.HasStorage != 0,
Email: cd.Email, IsPayable: fs&smartcontract.IsPayable != 0,
Version: cd.Version,
Title: cd.ProjectName,
Description: cd.Description,
HasStorage: cd.HasStorage,
HasDynamicInvocation: cd.HasDynamicInvocation,
IsPayable: cd.IsPayable,
}, },
EntryPoint: di.EntryPoint, EntryPoint: di.EntryPoint,
Functions: methods, Functions: methods,

View file

@ -117,33 +117,10 @@ func methodStruct() struct{} { return struct{}{} }
} }
t.Run("convert to ABI", func(t *testing.T) { t.Run("convert to ABI", func(t *testing.T) {
author := "Joe" actual := d.convertToABI(buf, smartcontract.HasStorage)
email := "Joe@ex.com"
version := "1.0"
title := "MyProj"
description := "Description"
actual := d.convertToABI(buf, &smartcontract.ContractDetails{
Author: author,
Email: email,
Version: version,
ProjectName: title,
Description: description,
HasStorage: true,
HasDynamicInvocation: false,
IsPayable: false,
ReturnType: smartcontract.BoolType,
Parameters: []smartcontract.ParamType{
smartcontract.StringType,
},
})
expected := ABI{ expected := ABI{
Hash: hash.Hash160(buf), Hash: hash.Hash160(buf),
Metadata: Metadata{ Metadata: Metadata{
Author: author,
Email: email,
Version: version,
Title: title,
Description: description,
HasStorage: true, HasStorage: true,
HasDynamicInvocation: false, HasDynamicInvocation: false,
IsPayable: false, IsPayable: false,

View file

@ -37,12 +37,9 @@ var syscalls = map[string]map[string]string{
"GetTransactionHeight": "System.Blockchain.GetTransactionHeight", "GetTransactionHeight": "System.Blockchain.GetTransactionHeight",
}, },
"contract": { "contract": {
"GetScript": "Neo.Contract.GetScript", "Create": "System.Contract.Create",
"IsPayable": "Neo.Contract.IsPayable", "Destroy": "System.Contract.Destroy",
"Create": "Neo.Contract.Create", "Update": "System.Contract.Update",
"Destroy": "Neo.Contract.Destroy",
"Migrate": "Neo.Contract.Migrate",
"GetStorageContext": "Neo.Contract.GetStorageContext",
}, },
"engine": { "engine": {
"GetScriptContainer": "System.ExecutionEngine.GetScriptContainer", "GetScriptContainer": "System.ExecutionEngine.GetScriptContainer",

View file

@ -8,6 +8,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/internal/random" "github.com/nspcc-dev/neo-go/pkg/internal/random"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -59,10 +60,19 @@ func TestCachedDaoContracts(t *testing.T) {
_, err := dao.GetContractState(sh) _, err := dao.GetContractState(sh)
require.NotNil(t, err) require.NotNil(t, err)
cs := &state.Contract{} m := manifest.NewManifest(hash.Hash160(script))
cs.Name = "test" m.ABI.EntryPoint.Name = "somename"
cs.Script = script m.ABI.EntryPoint.Parameters = []manifest.Parameter{
cs.ParamList = []smartcontract.ParamType{1, 2} manifest.NewParameter("first", smartcontract.IntegerType),
manifest.NewParameter("second", smartcontract.StringType),
}
m.ABI.EntryPoint.ReturnType = smartcontract.BoolType
cs := &state.Contract{
ID: 123,
Script: script,
Manifest: *m,
}
require.NoError(t, dao.PutContractState(cs)) require.NoError(t, dao.PutContractState(cs))
cs2, err := dao.GetContractState(sh) cs2, err := dao.GetContractState(sh)

View file

@ -31,6 +31,7 @@ type DAO interface {
GetHeaderHashes() ([]util.Uint256, error) GetHeaderHashes() ([]util.Uint256, error)
GetNEP5Balances(acc util.Uint160) (*state.NEP5Balances, error) GetNEP5Balances(acc util.Uint160) (*state.NEP5Balances, error)
GetNEP5TransferLog(acc util.Uint160, index uint32) (*state.NEP5TransferLog, error) GetNEP5TransferLog(acc util.Uint160, index uint32) (*state.NEP5TransferLog, error)
GetNextContractID() (int32, error)
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error) GetStorageItems(hash util.Uint160) (map[string]*state.StorageItem, error)
GetStorageItemsWithPrefix(hash util.Uint160, prefix []byte) (map[string]*state.StorageItem, error) GetStorageItemsWithPrefix(hash util.Uint160, prefix []byte) (map[string]*state.StorageItem, error)
@ -45,6 +46,7 @@ type DAO interface {
PutCurrentHeader(hashAndIndex []byte) error PutCurrentHeader(hashAndIndex []byte) error
PutNEP5Balances(acc util.Uint160, bs *state.NEP5Balances) error PutNEP5Balances(acc util.Uint160, bs *state.NEP5Balances) error
PutNEP5TransferLog(acc util.Uint160, index uint32, lg *state.NEP5TransferLog) error PutNEP5TransferLog(acc util.Uint160, index uint32, lg *state.NEP5TransferLog) error
PutNextContractID(id int32) error
PutStorageItem(scripthash util.Uint160, key []byte, si *state.StorageItem) error PutStorageItem(scripthash util.Uint160, key []byte, si *state.StorageItem) error
PutVersion(v string) error PutVersion(v string) error
StoreAsBlock(block *block.Block) error StoreAsBlock(block *block.Block) error
@ -169,6 +171,27 @@ func (dao *Simple) DeleteContractState(hash util.Uint160) error {
return dao.Store.Delete(key) return dao.Store.Delete(key)
} }
// GetNextContractID returns id for the next contract and increases stored id.
func (dao *Simple) GetNextContractID() (int32, error) {
key := storage.SYSContractID.Bytes()
data, err := dao.Store.Get(key)
if err != nil {
if err == storage.ErrKeyNotFound {
err = nil
}
return 0, err
}
return int32(binary.LittleEndian.Uint32(data)), nil
}
// PutNextContractID sets next contract id to id.
func (dao *Simple) PutNextContractID(id int32) error {
key := storage.SYSContractID.Bytes()
data := make([]byte, 4)
binary.LittleEndian.PutUint32(data, uint32(id))
return dao.Store.Put(key, data)
}
// -- end contracts. // -- end contracts.
// -- start nep5 balances. // -- start nep5 balances.

View file

@ -60,7 +60,7 @@ func TestPutAndGetAccountStateOrNew(t *testing.T) {
func TestPutAndGetContractState(t *testing.T) { func TestPutAndGetContractState(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore()) dao := NewSimple(storage.NewMemoryStore())
contractState := &state.Contract{Script: []byte{}, ParamList: []smartcontract.ParamType{}} contractState := &state.Contract{Script: []byte{}}
hash := contractState.ScriptHash() hash := contractState.ScriptHash()
err := dao.PutContractState(contractState) err := dao.PutContractState(contractState)
require.NoError(t, err) require.NoError(t, err)
@ -71,7 +71,7 @@ func TestPutAndGetContractState(t *testing.T) {
func TestDeleteContractState(t *testing.T) { func TestDeleteContractState(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore()) dao := NewSimple(storage.NewMemoryStore())
contractState := &state.Contract{Script: []byte{}, ParamList: []smartcontract.ParamType{}} contractState := &state.Contract{Script: []byte{}}
hash := contractState.ScriptHash() hash := contractState.ScriptHash()
err := dao.PutContractState(contractState) err := dao.PutContractState(contractState)
require.NoError(t, err) require.NoError(t, err)
@ -82,6 +82,17 @@ func TestDeleteContractState(t *testing.T) {
require.Nil(t, gotContractState) require.Nil(t, gotContractState)
} }
func TestSimple_GetNextContractID(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore())
id, err := dao.GetNextContractID()
require.NoError(t, err)
require.EqualValues(t, 0, id)
require.NoError(t, dao.PutNextContractID(10))
id, err = dao.GetNextContractID()
require.NoError(t, err)
require.EqualValues(t, 10, id)
}
func TestPutGetAppExecResult(t *testing.T) { func TestPutGetAppExecResult(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore()) dao := NewSimple(storage.NewMemoryStore())
hash := random.Uint256() hash := random.Uint256()

View file

@ -1,7 +1,6 @@
package core package core
import ( import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
@ -12,6 +11,9 @@ import (
// of 0.001 GAS = Fixed8(10^5). // of 0.001 GAS = Fixed8(10^5).
const interopGasRatio = 100000 const interopGasRatio = 100000
// StoragePrice is a price for storing 1 byte of storage.
const StoragePrice = 100000
// getPrice returns a price for executing op with the provided parameter. // getPrice returns a price for executing op with the provided parameter.
// Some SYSCALLs have variable price depending on their arguments. // Some SYSCALLs have variable price depending on their arguments.
func getPrice(v *vm.VM, op opcode.Opcode, parameter []byte) util.Fixed8 { func getPrice(v *vm.VM, op opcode.Opcode, parameter []byte) util.Fixed8 {
@ -41,8 +43,6 @@ func getSyscallPrice(v *vm.VM, id uint32) util.Fixed8 {
} }
const ( const (
neoContractCreate = 0x6ea56cf6 // Neo.Contract.Create
neoContractMigrate = 0x90621b47 // Neo.Contract.Migrate
systemStoragePut = 0x84183fe6 // System.Storage.Put systemStoragePut = 0x84183fe6 // System.Storage.Put
systemStoragePutEx = 0x3a9be173 // System.Storage.PutEx systemStoragePutEx = 0x3a9be173 // System.Storage.PutEx
neoStoragePut = 0xf541a152 // Neo.Storage.Put neoStoragePut = 0xf541a152 // Neo.Storage.Put
@ -51,8 +51,6 @@ func getSyscallPrice(v *vm.VM, id uint32) util.Fixed8 {
estack := v.Estack() estack := v.Estack()
switch id { switch id {
case neoContractCreate, neoContractMigrate:
return smartcontract.GetDeploymentPrice(smartcontract.PropertyState(estack.Peek(3).BigInt().Int64()))
case systemStoragePut, systemStoragePutEx, neoStoragePut: case systemStoragePut, systemStoragePutEx, neoStoragePut:
// price for storage PUT is 1 GAS per 1 KiB // price for storage PUT is 1 GAS per 1 KiB
keySize := len(estack.Peek(1).Bytes()) keySize := len(estack.Peek(1).Bytes())

View file

@ -23,66 +23,6 @@ func TestGetPrice(t *testing.T) {
v := SpawnVM(systemInterop) v := SpawnVM(systemInterop)
v.SetPriceGetter(getPrice) v.SetPriceGetter(getPrice)
t.Run("Neo.Contract.Create (no props)", func(t *testing.T) {
// Neo.Contract.Create: f66ca56e (requires push properties on fourth position)
v.Load([]byte{byte(opcode.PUSH0), byte(opcode.PUSH0), byte(opcode.PUSH0), byte(opcode.PUSH0),
byte(opcode.SYSCALL), 0xf6, 0x6c, 0xa5, 0x6e})
require.NoError(t, v.StepInto()) // push 0 - ContractPropertyState.NoProperty
require.NoError(t, v.StepInto()) // push 0
require.NoError(t, v.StepInto()) // push 0
require.NoError(t, v.StepInto()) // push 0
checkGas(t, util.Fixed8FromInt64(100), v)
})
t.Run("Neo.Contract.Create (has storage)", func(t *testing.T) {
// Neo.Contract.Create: f66ca56e (requires push properties on fourth position)
v.Load([]byte{byte(opcode.PUSH1), byte(opcode.PUSH0), byte(opcode.PUSH0), byte(opcode.PUSH0),
byte(opcode.SYSCALL), 0xf6, 0x6c, 0xa5, 0x6e})
require.NoError(t, v.StepInto()) // push 01 - ContractPropertyState.HasStorage
require.NoError(t, v.StepInto()) // push 0
require.NoError(t, v.StepInto()) // push 0
require.NoError(t, v.StepInto()) // push 0
checkGas(t, util.Fixed8FromInt64(500), v)
})
t.Run("Neo.Contract.Create (has dynamic invoke)", func(t *testing.T) {
// Neo.Contract.Create: f66ca56e (requires push properties on fourth position)
v.Load([]byte{byte(opcode.PUSH2), byte(opcode.PUSH0), byte(opcode.PUSH0), byte(opcode.PUSH0),
byte(opcode.SYSCALL), 0xf6, 0x6c, 0xa5, 0x6e})
require.NoError(t, v.StepInto()) // push 02 - ContractPropertyState.HasDynamicInvoke
require.NoError(t, v.StepInto()) // push 0
require.NoError(t, v.StepInto()) // push 0
require.NoError(t, v.StepInto()) // push 0
checkGas(t, util.Fixed8FromInt64(600), v)
})
t.Run("Neo.Contract.Create (has both storage and dynamic invoke)", func(t *testing.T) {
// Neo.Contract.Create: f66ca56e (requires push properties on fourth position)
v.Load([]byte{byte(opcode.PUSH3), byte(opcode.PUSH0), byte(opcode.PUSH0), byte(opcode.PUSH0),
byte(opcode.SYSCALL), 0xf6, 0x6c, 0xa5, 0x6e})
require.NoError(t, v.StepInto()) // push 03 - HasStorage and HasDynamicInvoke
require.NoError(t, v.StepInto()) // push 0
require.NoError(t, v.StepInto()) // push 0
require.NoError(t, v.StepInto()) // push 0
checkGas(t, util.Fixed8FromInt64(1000), v)
})
t.Run("Neo.Contract.Migrate", func(t *testing.T) {
// Neo.Contract.Migrate: 471b6290 (requires push properties on fourth position)
v.Load([]byte{byte(opcode.PUSH0), byte(opcode.PUSH0), byte(opcode.PUSH0), byte(opcode.PUSH0),
byte(opcode.SYSCALL), 0x47, 0x1b, 0x62, 0x90})
require.NoError(t, v.StepInto()) // push 0 - ContractPropertyState.NoProperty
require.NoError(t, v.StepInto()) // push 0
require.NoError(t, v.StepInto()) // push 0
require.NoError(t, v.StepInto()) // push 0
checkGas(t, util.Fixed8FromInt64(100), v)
})
t.Run("System.Storage.Put", func(t *testing.T) { t.Run("System.Storage.Put", func(t *testing.T) {
// System.Storage.Put: e63f1884 (requires push key and value) // System.Storage.Put: e63f1884 (requires push key and value)
v.Load([]byte{byte(opcode.PUSH3), byte(opcode.PUSH3), byte(opcode.PUSH0), v.Load([]byte{byte(opcode.PUSH3), byte(opcode.PUSH3), byte(opcode.PUSH0),

View file

@ -18,6 +18,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
@ -222,21 +223,20 @@ func TestCreateBasicChain(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
t.Logf("contractHash: %s", hash.Hash160(avm).StringLE()) t.Logf("contractHash: %s", hash.Hash160(avm).StringLE())
var props smartcontract.PropertyState
script := io.NewBufBinWriter() script := io.NewBufBinWriter()
emit.Bytes(script.BinWriter, []byte("Da contract dat hallos u")) m := manifest.NewManifest(hash.Hash160(avm))
emit.Bytes(script.BinWriter, []byte("joe@example.com")) m.ABI.EntryPoint.Name = "Main"
emit.Bytes(script.BinWriter, []byte("Random Guy")) m.ABI.EntryPoint.Parameters = []manifest.Parameter{
emit.Bytes(script.BinWriter, []byte("0.99")) manifest.NewParameter("method", smartcontract.StringType),
emit.Bytes(script.BinWriter, []byte("Helloer")) manifest.NewParameter("params", smartcontract.ArrayType),
props |= smartcontract.HasStorage }
emit.Int(script.BinWriter, int64(props)) m.ABI.EntryPoint.ReturnType = smartcontract.BoolType
emit.Int(script.BinWriter, int64(5)) m.Features = smartcontract.HasStorage
params := make([]byte, 1) bs, err := testserdes.EncodeBinary(m)
params[0] = byte(7) require.NoError(t, err)
emit.Bytes(script.BinWriter, params) emit.Bytes(script.BinWriter, bs)
emit.Bytes(script.BinWriter, avm) emit.Bytes(script.BinWriter, avm)
emit.Syscall(script.BinWriter, "Neo.Contract.Create") emit.Syscall(script.BinWriter, "System.Contract.Create")
txScript := script.Bytes() txScript := script.Bytes()
invFee := util.Fixed8FromFloat(100) invFee := util.Fixed8FromFloat(100)

View file

@ -28,7 +28,6 @@ type Context struct {
Block *block.Block Block *block.Block
Tx *transaction.Transaction Tx *transaction.Transaction
DAO *dao.Cached DAO *dao.Cached
LowerDAO dao.DAO
Notifications []state.NotificationEvent Notifications []state.NotificationEvent
Log *zap.Logger Log *zap.Logger
} }
@ -44,7 +43,6 @@ func NewContext(trigger trigger.Type, bc blockchainer.Blockchainer, d dao.DAO, n
Block: block, Block: block,
Tx: tx, Tx: tx,
DAO: dao, DAO: dao,
LowerDAO: d,
Notifications: nes, Notifications: nes,
Log: log, Log: log,
} }
@ -82,21 +80,12 @@ type ContractMD struct {
Manifest manifest.Manifest Manifest manifest.Manifest
ServiceName string ServiceName string
ServiceID uint32 ServiceID uint32
ContractID int32
Script []byte Script []byte
Hash util.Uint160 Hash util.Uint160
Methods map[string]MethodAndPrice Methods map[string]MethodAndPrice
} }
// GetContract returns script of the contract with the specified hash.
func (ic *Context) GetContract(h util.Uint160) ([]byte, bool) {
cs, err := ic.DAO.GetContractState(h)
if err != nil {
return nil, false
}
hasDynamicInvoke := (cs.Properties & smartcontract.HasDynamicInvoke) != 0
return cs.Script, hasDynamicInvoke
}
// NewContractMD returns Contract with the specified list of methods. // NewContractMD returns Contract with the specified list of methods.
func NewContractMD(name string) *ContractMD { func NewContractMD(name string) *ContractMD {
c := &ContractMD{ c := &ContractMD{

View file

@ -1,6 +1,7 @@
package runtime package runtime
import ( import (
"github.com/nspcc-dev/neo-go/pkg/core/dao"
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
@ -13,7 +14,7 @@ import (
// for verifying in the interop context. // for verifying in the interop context.
func CheckHashedWitness(ic *interop.Context, v vm.ScriptHashGetter, hash util.Uint160) (bool, error) { func CheckHashedWitness(ic *interop.Context, v vm.ScriptHashGetter, hash util.Uint160) (bool, error) {
if tx, ok := ic.Container.(*transaction.Transaction); ok { if tx, ok := ic.Container.(*transaction.Transaction); ok {
return checkScope(tx, v, hash) return checkScope(ic.DAO, tx, v, hash)
} }
// only for non-Transaction types (Block, etc.) // only for non-Transaction types (Block, etc.)
@ -29,7 +30,7 @@ func CheckHashedWitness(ic *interop.Context, v vm.ScriptHashGetter, hash util.Ui
return false, nil return false, nil
} }
func checkScope(tx *transaction.Transaction, v vm.ScriptHashGetter, hash util.Uint160) (bool, error) { func checkScope(d dao.DAO, tx *transaction.Transaction, v vm.ScriptHashGetter, hash util.Uint160) (bool, error) {
for _, c := range tx.Cosigners { for _, c := range tx.Cosigners {
if c.Account == hash { if c.Account == hash {
if c.Scopes == transaction.Global { if c.Scopes == transaction.Global {
@ -51,13 +52,11 @@ func checkScope(tx *transaction.Transaction, v vm.ScriptHashGetter, hash util.Ui
} }
} }
if c.Scopes&transaction.CustomGroups != 0 { if c.Scopes&transaction.CustomGroups != 0 {
return true, nil callingScriptHash := v.GetCallingScriptHash()
// TODO: we don't currently have Manifest field in ContractState if callingScriptHash.Equals(util.Uint160{}) {
/*callingScriptHash := v.GetCallingScriptHash()
if callingScriptHash.Equals(util.Uint160{}){
return false, nil return false, nil
} }
cs, err := ic.DAO.GetContractState(callingScriptHash) cs, err := d.GetContractState(callingScriptHash)
if err != nil { if err != nil {
return false, err return false, err
} }
@ -68,7 +67,7 @@ func checkScope(tx *transaction.Transaction, v vm.ScriptHashGetter, hash util.Ui
return true, nil return true, nil
} }
} }
}*/ }
} }
return false, nil return false, nil
} }

View file

@ -8,8 +8,10 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop"
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
) )
@ -68,48 +70,23 @@ func createContractStateFromVM(ic *interop.Context, v *vm.VM) (*state.Contract,
if len(script) > MaxContractScriptSize { if len(script) > MaxContractScriptSize {
return nil, errors.New("the script is too big") return nil, errors.New("the script is too big")
} }
paramBytes := v.Estack().Pop().Bytes() manifestBytes := v.Estack().Pop().Bytes()
if len(paramBytes) > MaxContractParametersNum { if len(manifestBytes) > manifest.MaxManifestSize {
return nil, errors.New("too many parameters for a script") return nil, errors.New("manifest is too big")
} }
paramList := make([]smartcontract.ParamType, len(paramBytes)) if !v.AddGas(util.Fixed8(StoragePrice * (len(script) + len(manifestBytes)))) {
for k, v := range paramBytes { return nil, errors.New("gas limit exceeded")
paramList[k] = smartcontract.ParamType(v)
} }
retType := smartcontract.ParamType(v.Estack().Pop().BigInt().Int64()) var m manifest.Manifest
properties := smartcontract.PropertyState(v.Estack().Pop().BigInt().Int64()) r := io.NewBinReaderFromBuf(manifestBytes)
name := v.Estack().Pop().Bytes() m.DecodeBinary(r)
if len(name) > MaxContractStringLen { if r.Err != nil {
return nil, errors.New("too big name") return nil, r.Err
} }
version := v.Estack().Pop().Bytes() return &state.Contract{
if len(version) > MaxContractStringLen { Script: script,
return nil, errors.New("too big version") Manifest: m,
} }, nil
author := v.Estack().Pop().Bytes()
if len(author) > MaxContractStringLen {
return nil, errors.New("too big author")
}
email := v.Estack().Pop().Bytes()
if len(email) > MaxContractStringLen {
return nil, errors.New("too big email")
}
desc := v.Estack().Pop().Bytes()
if len(desc) > MaxContractDescriptionLen {
return nil, errors.New("too big description")
}
contract := &state.Contract{
Script: script,
ParamList: paramList,
ReturnType: retType,
Properties: properties,
Name: string(name),
CodeVersion: string(version),
Author: string(author),
Email: string(email),
Description: string(desc),
}
return contract, nil
} }
// contractCreate creates a contract. // contractCreate creates a contract.
@ -119,65 +96,65 @@ func contractCreate(ic *interop.Context, v *vm.VM) error {
return err return err
} }
contract, err := ic.DAO.GetContractState(newcontract.ScriptHash()) contract, err := ic.DAO.GetContractState(newcontract.ScriptHash())
if contract != nil {
return errors.New("contract already exists")
}
id, err := ic.DAO.GetNextContractID()
if err != nil { if err != nil {
contract = newcontract return err
err := ic.DAO.PutContractState(contract)
if err != nil {
return err
}
} }
v.Estack().PushVal(stackitem.NewInterop(contract)) newcontract.ID = id
if err := ic.DAO.PutNextContractID(id); err != nil {
return err
}
if err := ic.DAO.PutContractState(newcontract); err != nil {
return err
}
v.Estack().PushVal(stackitem.NewInterop(newcontract))
return nil return nil
} }
// contractGetScript returns a script associated with a contract. // contractUpdate migrates a contract.
func contractGetScript(ic *interop.Context, v *vm.VM) error { func contractUpdate(ic *interop.Context, v *vm.VM) error {
csInterface := v.Estack().Pop().Value() contract, err := ic.DAO.GetContractState(v.GetCurrentScriptHash())
cs, ok := csInterface.(*state.Contract) if contract == nil {
if !ok { return errors.New("contract doesn't exist")
return fmt.Errorf("%T is not a contract state", cs)
} }
v.Estack().PushVal(cs.Script)
return nil
}
// contractIsPayable returns whether contract is payable.
func contractIsPayable(ic *interop.Context, v *vm.VM) error {
csInterface := v.Estack().Pop().Value()
cs, ok := csInterface.(*state.Contract)
if !ok {
return fmt.Errorf("%T is not a contract state", cs)
}
v.Estack().PushVal(cs.IsPayable())
return nil
}
// contractMigrate migrates a contract.
func contractMigrate(ic *interop.Context, v *vm.VM) error {
newcontract, err := createContractStateFromVM(ic, v) newcontract, err := createContractStateFromVM(ic, v)
if err != nil { if err != nil {
return err return err
} }
contract, err := ic.DAO.GetContractState(newcontract.ScriptHash()) if newcontract.Script != nil {
if err != nil { if l := len(newcontract.Script); l == 0 || l > MaxContractScriptSize {
contract = newcontract return errors.New("invalid script len")
err := ic.DAO.PutContractState(contract) }
h := newcontract.ScriptHash()
if h.Equals(contract.ScriptHash()) {
return errors.New("the script is the same")
} else if _, err := ic.DAO.GetContractState(h); err == nil {
return errors.New("contract already exists")
}
newcontract.ID = contract.ID
if err := ic.DAO.PutContractState(newcontract); err != nil {
return err
}
if err := ic.DAO.DeleteContractState(contract.ScriptHash()); err != nil {
return err
}
}
if contract.HasStorage() {
// TODO store items by ID #1037
hash := v.GetCurrentScriptHash()
siMap, err := ic.DAO.GetStorageItems(hash)
if err != nil { if err != nil {
return err return err
} }
if contract.HasStorage() { for k, v := range siMap {
hash := v.GetCurrentScriptHash() v.IsConst = false
siMap, err := ic.DAO.GetStorageItems(hash) err = ic.DAO.PutStorageItem(contract.ScriptHash(), []byte(k), v)
if err != nil { if err != nil {
return err return err
} }
for k, v := range siMap {
v.IsConst = false
err = ic.DAO.PutStorageItem(contract.ScriptHash(), []byte(k), v)
if err != nil {
return err
}
}
} }
} }
v.Estack().PushVal(stackitem.NewInterop(contract)) v.Estack().PushVal(stackitem.NewInterop(contract))

View file

@ -13,9 +13,10 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/state" "github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/internal/random"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger" "github.com/nspcc-dev/neo-go/pkg/smartcontract/trigger"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm" "github.com/nspcc-dev/neo-go/pkg/vm"
@ -211,28 +212,6 @@ func TestECDSAVerify(t *testing.T) {
}) })
} }
func TestContractGetScript(t *testing.T) {
v, contractState, context, chain := createVMAndContractState(t)
defer chain.Close()
v.Estack().PushVal(stackitem.NewInterop(contractState))
err := contractGetScript(context, v)
require.NoError(t, err)
script := v.Estack().Pop().Value()
require.Equal(t, contractState.Script, script)
}
func TestContractIsPayable(t *testing.T) {
v, contractState, context, chain := createVMAndContractState(t)
defer chain.Close()
v.Estack().PushVal(stackitem.NewInterop(contractState))
err := contractIsPayable(context, v)
require.NoError(t, err)
isPayable := v.Estack().Pop().Value()
require.Equal(t, contractState.IsPayable(), isPayable)
}
// Helper functions to create VM, InteropContext, TX, Account, Contract. // Helper functions to create VM, InteropContext, TX, Account, Contract.
func createVM(t *testing.T) (*vm.VM, *interop.Context, *Blockchain) { func createVM(t *testing.T) (*vm.VM, *interop.Context, *Blockchain) {
@ -265,16 +244,18 @@ func createVMAndPushTX(t *testing.T) (*vm.VM, *transaction.Transaction, *interop
func createVMAndContractState(t *testing.T) (*vm.VM, *state.Contract, *interop.Context, *Blockchain) { func createVMAndContractState(t *testing.T) (*vm.VM, *state.Contract, *interop.Context, *Blockchain) {
v := vm.New() v := vm.New()
script := []byte("testscript")
m := manifest.NewManifest(hash.Hash160(script))
m.ABI.EntryPoint.Parameters = []manifest.Parameter{
manifest.NewParameter("Name", smartcontract.StringType),
manifest.NewParameter("Amount", smartcontract.IntegerType),
manifest.NewParameter("Hash", smartcontract.Hash160Type),
}
m.ABI.EntryPoint.ReturnType = smartcontract.ArrayType
m.Features = smartcontract.HasStorage
contractState := &state.Contract{ contractState := &state.Contract{
Script: []byte("testscript"), Script: script,
ParamList: []smartcontract.ParamType{smartcontract.StringType, smartcontract.IntegerType, smartcontract.Hash160Type}, Manifest: *m,
ReturnType: smartcontract.ArrayType,
Properties: smartcontract.HasStorage,
Name: random.String(10),
CodeVersion: random.String(10),
Author: random.String(10),
Email: random.String(10),
Description: random.String(10),
} }
chain := newTestChain(t) chain := newTestChain(t)

View file

@ -179,62 +179,6 @@ func bcGetTransactionHeight(ic *interop.Context, v *vm.VM) error {
return nil return nil
} }
// popHeaderFromVM returns pointer to Header or error. It's main feature is
// proper treatment of Block structure, because C# code implicitly assumes
// that header APIs can also operate on blocks.
func popHeaderFromVM(v *vm.VM) (*block.Header, error) {
iface := v.Estack().Pop().Value()
header, ok := iface.(*block.Header)
if !ok {
block, ok := iface.(*block.Block)
if !ok {
return nil, errors.New("value is not a header or block")
}
return block.Header(), nil
}
return header, nil
}
// headerGetIndex returns block index from the header.
func headerGetIndex(ic *interop.Context, v *vm.VM) error {
header, err := popHeaderFromVM(v)
if err != nil {
return err
}
v.Estack().PushVal(header.Index)
return nil
}
// headerGetHash returns header hash of the passed header.
func headerGetHash(ic *interop.Context, v *vm.VM) error {
header, err := popHeaderFromVM(v)
if err != nil {
return err
}
v.Estack().PushVal(header.Hash().BytesBE())
return nil
}
// headerGetPrevHash returns previous header hash of the passed header.
func headerGetPrevHash(ic *interop.Context, v *vm.VM) error {
header, err := popHeaderFromVM(v)
if err != nil {
return err
}
v.Estack().PushVal(header.PrevHash.BytesBE())
return nil
}
// headerGetTimestamp returns timestamp of the passed header.
func headerGetTimestamp(ic *interop.Context, v *vm.VM) error {
header, err := popHeaderFromVM(v)
if err != nil {
return err
}
v.Estack().PushVal(header.Timestamp)
return nil
}
// engineGetScriptContainer returns transaction that contains the script being // engineGetScriptContainer returns transaction that contains the script being
// run. // run.
func engineGetScriptContainer(ic *interop.Context, v *vm.VM) error { func engineGetScriptContainer(ic *interop.Context, v *vm.VM) error {
@ -484,12 +428,21 @@ func contractCallExInternal(ic *interop.Context, v *vm.VM, h []byte, method stac
if err != nil { if err != nil {
return errors.New("invalid contract hash") return errors.New("invalid contract hash")
} }
script, _ := ic.GetContract(u) cs, err := ic.DAO.GetContractState(u)
if script == nil { if err != nil {
return errors.New("contract not found") return errors.New("contract not found")
} }
// TODO perform flags checking after #923 bs, err := method.TryBytes()
v.LoadScript(script) if err != nil {
return err
}
curr, err := ic.DAO.GetContractState(v.GetCurrentScriptHash())
if err == nil {
if !curr.Manifest.CanCall(&cs.Manifest, string(bs)) {
return errors.New("disallowed method call")
}
}
v.LoadScript(cs.Script)
v.Estack().PushVal(args) v.Estack().PushVal(args)
v.Estack().PushVal(method) v.Estack().PushVal(method)
return nil return nil
@ -520,25 +473,3 @@ func contractDestroy(ic *interop.Context, v *vm.VM) error {
} }
return nil return nil
} }
// contractGetStorageContext retrieves StorageContext of a contract.
func contractGetStorageContext(ic *interop.Context, v *vm.VM) error {
csInterface := v.Estack().Pop().Value()
cs, ok := csInterface.(*state.Contract)
if !ok {
return fmt.Errorf("%T is not a contract state", cs)
}
_, err := ic.DAO.GetContractState(cs.ScriptHash())
if err != nil {
return fmt.Errorf("non-existent contract")
}
_, err = ic.LowerDAO.GetContractState(cs.ScriptHash())
if err == nil {
return fmt.Errorf("contract was not created in this transaction")
}
stc := &StorageContext{
ScriptHash: cs.ScriptHash(),
}
v.Estack().PushVal(stackitem.NewInterop(stc))
return nil
}

View file

@ -70,8 +70,9 @@ var systemInterops = []interop.Function{
{Name: "System.Blockchain.GetTransactionHeight", Func: bcGetTransactionHeight, Price: 100}, {Name: "System.Blockchain.GetTransactionHeight", Func: bcGetTransactionHeight, Price: 100},
{Name: "System.Contract.Call", Func: contractCall, Price: 1}, {Name: "System.Contract.Call", Func: contractCall, Price: 1},
{Name: "System.Contract.CallEx", Func: contractCallEx, Price: 1}, {Name: "System.Contract.CallEx", Func: contractCallEx, Price: 1},
{Name: "System.Contract.Create", Func: contractCreate, Price: 0},
{Name: "System.Contract.Destroy", Func: contractDestroy, Price: 1}, {Name: "System.Contract.Destroy", Func: contractDestroy, Price: 1},
{Name: "System.Contract.GetStorageContext", Func: contractGetStorageContext, Price: 1}, {Name: "System.Contract.Update", Func: contractUpdate, Price: 0},
{Name: "System.Enumerator.Concat", Func: enumerator.Concat, Price: 1}, {Name: "System.Enumerator.Concat", Func: enumerator.Concat, Price: 1},
{Name: "System.Enumerator.Create", Func: enumerator.Create, Price: 1}, {Name: "System.Enumerator.Create", Func: enumerator.Create, Price: 1},
{Name: "System.Enumerator.Next", Func: enumerator.Next, Price: 1}, {Name: "System.Enumerator.Next", Func: enumerator.Next, Price: 1},
@ -104,12 +105,6 @@ var systemInterops = []interop.Function{
} }
var neoInterops = []interop.Function{ var neoInterops = []interop.Function{
{Name: "Neo.Contract.Create", Func: contractCreate, Price: 0},
{Name: "Neo.Contract.Destroy", Func: contractDestroy, Price: 1},
{Name: "Neo.Contract.GetScript", Func: contractGetScript, Price: 1},
{Name: "Neo.Contract.GetStorageContext", Func: contractGetStorageContext, Price: 1},
{Name: "Neo.Contract.IsPayable", Func: contractIsPayable, Price: 1},
{Name: "Neo.Contract.Migrate", Func: contractMigrate, Price: 0},
{Name: "Neo.Crypto.ECDsaVerify", Func: crypto.ECDSAVerify, Price: 1}, {Name: "Neo.Crypto.ECDsaVerify", Func: crypto.ECDSAVerify, Price: 1},
{Name: "Neo.Crypto.ECDsaCheckMultiSig", Func: crypto.ECDSACheckMultisig, Price: 1}, {Name: "Neo.Crypto.ECDsaCheckMultiSig", Func: crypto.ECDSACheckMultisig, Price: 1},
{Name: "Neo.Crypto.SHA256", Func: crypto.Sha256, Price: 1}, {Name: "Neo.Crypto.SHA256", Func: crypto.Sha256, Price: 1},

View file

@ -32,9 +32,6 @@ func TestUnexpectedNonInterops(t *testing.T) {
// All of these functions expect an interop item on the stack. // All of these functions expect an interop item on the stack.
funcs := []func(*interop.Context, *vm.VM) error{ funcs := []func(*interop.Context, *vm.VM) error{
contractGetScript,
contractGetStorageContext,
contractIsPayable,
storageContextAsReadOnly, storageContextAsReadOnly,
storageDelete, storageDelete,
storageFind, storageFind,

View file

@ -26,9 +26,9 @@ func Deploy(ic *interop.Context, _ *vm.VM) error {
} }
cs := &state.Contract{ cs := &state.Contract{
Script: md.Script, ID: md.ContractID,
ParamList: params, Script: md.Script,
ReturnType: md.Manifest.ABI.EntryPoint.ReturnType, Manifest: md.Manifest,
} }
if err := ic.DAO.PutContractState(cs); err != nil { if err := ic.DAO.PutContractState(cs); err != nil {
return err return err

View file

@ -20,6 +20,7 @@ type GAS struct {
} }
const gasSyscallName = "Neo.Native.Tokens.GAS" const gasSyscallName = "Neo.Native.Tokens.GAS"
const gasContractID = -2
// GASFactor is a divisor for finding GAS integral value. // GASFactor is a divisor for finding GAS integral value.
const GASFactor = NEOTotalSupply const GASFactor = NEOTotalSupply
@ -35,6 +36,7 @@ func NewGAS() *GAS {
nep5.factor = GASFactor nep5.factor = GASFactor
nep5.onPersist = chainOnPersist(g.onPersist, g.OnPersist) nep5.onPersist = chainOnPersist(g.onPersist, g.OnPersist)
nep5.incBalance = g.increaseBalance nep5.incBalance = g.increaseBalance
nep5.ContractID = gasContractID
g.nep5TokenNative = *nep5 g.nep5TokenNative = *nep5

View file

@ -34,6 +34,7 @@ type keyWithVotes struct {
const ( const (
neoSyscallName = "Neo.Native.Tokens.NEO" neoSyscallName = "Neo.Native.Tokens.NEO"
neoContractID = -2
// NEOTotalSupply is the total amount of NEO in the system. // NEOTotalSupply is the total amount of NEO in the system.
NEOTotalSupply = 100000000 NEOTotalSupply = 100000000
// prefixValidator is a prefix used to store validator's data. // prefixValidator is a prefix used to store validator's data.
@ -69,6 +70,7 @@ func NewNEO() *NEO {
nep5.factor = 1 nep5.factor = 1
nep5.onPersist = chainOnPersist(n.onPersist, n.OnPersist) nep5.onPersist = chainOnPersist(n.onPersist, n.OnPersist)
nep5.incBalance = n.increaseBalance nep5.incBalance = n.increaseBalance
nep5.ContractID = neoContractID
n.nep5TokenNative = *nep5 n.nep5TokenNative = *nep5

View file

@ -1,52 +1,38 @@
package state package state
import ( import (
"encoding/json"
"errors"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
) )
// Contract holds information about a smart contract in the NEO blockchain. // Contract holds information about a smart contract in the NEO blockchain.
type Contract struct { type Contract struct {
Script []byte ID int32
ParamList []smartcontract.ParamType Script []byte
ReturnType smartcontract.ParamType Manifest manifest.Manifest
Properties smartcontract.PropertyState
Name string
CodeVersion string
Author string
Email string
Description string
scriptHash util.Uint160 scriptHash util.Uint160
} }
// DecodeBinary implements Serializable interface. // DecodeBinary implements Serializable interface.
func (cs *Contract) DecodeBinary(br *io.BinReader) { func (cs *Contract) DecodeBinary(br *io.BinReader) {
cs.ID = int32(br.ReadU32LE())
cs.Script = br.ReadVarBytes() cs.Script = br.ReadVarBytes()
br.ReadArray(&cs.ParamList) cs.Manifest.DecodeBinary(br)
cs.ReturnType = smartcontract.ParamType(br.ReadB())
cs.Properties = smartcontract.PropertyState(br.ReadB())
cs.Name = br.ReadString()
cs.CodeVersion = br.ReadString()
cs.Author = br.ReadString()
cs.Email = br.ReadString()
cs.Description = br.ReadString()
cs.createHash() cs.createHash()
} }
// EncodeBinary implements Serializable interface. // EncodeBinary implements Serializable interface.
func (cs *Contract) EncodeBinary(bw *io.BinWriter) { func (cs *Contract) EncodeBinary(bw *io.BinWriter) {
bw.WriteU32LE(uint32(cs.ID))
bw.WriteVarBytes(cs.Script) bw.WriteVarBytes(cs.Script)
bw.WriteArray(cs.ParamList) cs.Manifest.EncodeBinary(bw)
bw.WriteB(byte(cs.ReturnType))
bw.WriteB(byte(cs.Properties))
bw.WriteString(cs.Name)
bw.WriteString(cs.CodeVersion)
bw.WriteString(cs.Author)
bw.WriteString(cs.Email)
bw.WriteString(cs.Description)
} }
// ScriptHash returns a contract script hash. // ScriptHash returns a contract script hash.
@ -64,15 +50,42 @@ func (cs *Contract) createHash() {
// HasStorage checks whether the contract has storage property set. // HasStorage checks whether the contract has storage property set.
func (cs *Contract) HasStorage() bool { func (cs *Contract) HasStorage() bool {
return (cs.Properties & smartcontract.HasStorage) != 0 return (cs.Manifest.Features & smartcontract.HasStorage) != 0
}
// HasDynamicInvoke checks whether the contract has dynamic invoke property set.
func (cs *Contract) HasDynamicInvoke() bool {
return (cs.Properties & smartcontract.HasDynamicInvoke) != 0
} }
// IsPayable checks whether the contract has payable property set. // IsPayable checks whether the contract has payable property set.
func (cs *Contract) IsPayable() bool { func (cs *Contract) IsPayable() bool {
return (cs.Properties & smartcontract.IsPayable) != 0 return (cs.Manifest.Features & smartcontract.IsPayable) != 0
}
type contractJSON struct {
ID int32 `json:"id"`
Script []byte `json:"script"`
Manifest *manifest.Manifest `json:"manifest"`
ScriptHash util.Uint160 `json:"hash"`
}
// MarshalJSON implements json.Marshaler.
func (cs *Contract) MarshalJSON() ([]byte, error) {
return json.Marshal(&contractJSON{
ID: cs.ID,
Script: cs.Script,
Manifest: &cs.Manifest,
ScriptHash: cs.ScriptHash(),
})
}
// UnmarshalJSON implements json.Unmarshaler.
func (cs *Contract) UnmarshalJSON(data []byte) error {
var cj contractJSON
if err := json.Unmarshal(data, &cj); err != nil {
return err
} else if cj.Manifest == nil {
return errors.New("empty manifest")
}
cs.ID = cj.ID
cs.Script = cj.Script
cs.Manifest = *cj.Manifest
cs.createHash()
return nil
} }

View file

@ -6,42 +6,46 @@ import (
"github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
) )
func TestEncodeDecodeContractState(t *testing.T) { func TestEncodeDecodeContractState(t *testing.T) {
script := []byte("testscript") script := []byte("testscript")
contract := &Contract{ h := hash.Hash160(script)
Script: script, m := manifest.NewManifest(h)
ParamList: []smartcontract.ParamType{smartcontract.StringType, smartcontract.IntegerType, smartcontract.Hash160Type}, m.ABI.Methods = []manifest.Method{{
ReturnType: smartcontract.BoolType, Name: "main",
Properties: smartcontract.HasStorage, Parameters: []manifest.Parameter{
Name: "Contrato", {
CodeVersion: "1.0.0", Name: "amount",
Author: "Joe Random", Type: smartcontract.IntegerType,
Email: "joe@example.com", },
Description: "Test contract", {
} Name: "hash",
Type: smartcontract.Hash160Type,
assert.Equal(t, hash.Hash160(script), contract.ScriptHash()) },
},
contractDecoded := &Contract{}
testserdes.EncodeDecodeBinary(t, contract, contractDecoded)
assert.Equal(t, contract.ScriptHash(), contractDecoded.ScriptHash())
}
func TestContractStateProperties(t *testing.T) {
flaggedContract := Contract{
Properties: smartcontract.HasStorage | smartcontract.HasDynamicInvoke | smartcontract.IsPayable,
}
nonFlaggedContract := Contract{
ReturnType: smartcontract.BoolType, ReturnType: smartcontract.BoolType,
}}
m.Features = smartcontract.HasStorage
contract := &Contract{
ID: 123,
Script: script,
Manifest: *m,
} }
assert.Equal(t, true, flaggedContract.HasStorage())
assert.Equal(t, true, flaggedContract.HasDynamicInvoke()) assert.Equal(t, h, contract.ScriptHash())
assert.Equal(t, true, flaggedContract.IsPayable())
assert.Equal(t, false, nonFlaggedContract.HasStorage()) t.Run("Serializable", func(t *testing.T) {
assert.Equal(t, false, nonFlaggedContract.HasDynamicInvoke()) contractDecoded := new(Contract)
assert.Equal(t, false, nonFlaggedContract.IsPayable()) testserdes.EncodeDecodeBinary(t, contract, contractDecoded)
assert.Equal(t, contract.ScriptHash(), contractDecoded.ScriptHash())
})
t.Run("JSON", func(t *testing.T) {
contractDecoded := new(Contract)
testserdes.MarshalUnmarshalJSON(t, contract, contractDecoded)
assert.Equal(t, contract.ScriptHash(), contractDecoded.ScriptHash())
})
} }

View file

@ -18,6 +18,7 @@ const (
IXHeaderHashList KeyPrefix = 0x80 IXHeaderHashList KeyPrefix = 0x80
SYSCurrentBlock KeyPrefix = 0xc0 SYSCurrentBlock KeyPrefix = 0xc0
SYSCurrentHeader KeyPrefix = 0xc1 SYSCurrentHeader KeyPrefix = 0xc1
SYSContractID KeyPrefix = 0xc2
SYSVersion KeyPrefix = 0xf0 SYSVersion KeyPrefix = 0xf0
) )

View file

@ -3,83 +3,32 @@ Package contract provides functions to work with contracts.
*/ */
package contract package contract
import "github.com/nspcc-dev/neo-go/pkg/interop/storage"
// Contract represents a Neo contract and is used in interop functions. It's // Contract represents a Neo contract and is used in interop functions. It's
// an opaque data structure that you can manipulate with using functions from // an opaque data structure that you can manipulate with using functions from
// this package. It's similar in function to the Contract class in the Neo .net // this package. It's similar in function to the Contract class in the Neo .net
// framework. // framework.
type Contract struct{} type Contract struct{}
// GetScript returns the script of the given contract. It uses
// `Neo.Contract.GetScript` syscall.
func GetScript(c Contract) []byte {
return nil
}
// IsPayable returns whether the given contract is payable (able to accept
// asset transfers to its address). It uses `Neo.Contract.IsPayable` syscall.
func IsPayable(c Contract) bool {
return false
}
// GetStorageContext returns storage context for the given contract. It only
// works for contracts created in this transaction (so you can't take a storage
// context for arbitrary contract). Refer to the `storage` package on how to
// use this context. This function uses `Neo.Contract.GetStorageContext` syscall.
func GetStorageContext(c Contract) storage.Context {
return storage.Context{}
}
// Create creates a new contract using a set of input parameters: // Create creates a new contract using a set of input parameters:
// script contract's bytecode (limited in length by 1M) // script contract's bytecode (limited in length by 1M)
// params contract's input parameter types, one byte per parameter, see // manifest contract's manifest (limited in length by 2 KiB)
// ParamType in the `smartcontract` package for value
// definitions. Maximum number of parameters: 252.
// returnType return value type, also a ParamType constant
// properties bit field with contract's permissions (storage, dynamic
// invoke, payable), see PropertyState in the `smartcontract`
// package
// name human-readable contract name (no longer than 252 bytes)
// version human-readable contract version (no longer than 252 bytes)
// author contract's author (no longer than 252 bytes)
// email contract's author/support e-mail (no longer than 252 bytes)
// description human-readable contract description (no longer than 64K bytes)
// It returns this new created Contract when successful (and fails transaction // It returns this new created Contract when successful (and fails transaction
// if not). It uses `Neo.Contract.Create` syscall. // if not). It uses `System.Contract.Create` syscall.
func Create( func Create(script []byte, manifest []byte) Contract {
script []byte,
params []byte,
returnType byte,
properties byte,
name,
version,
author,
email,
description string) Contract {
return Contract{} return Contract{}
} }
// Migrate migrates calling contract (that is the one that calls Migrate) to // Update updates script and manifest of the calling contract (that is the one that calls Update)
// the new contract. Its parameters have exactly the same semantics as for // to the new ones. Its parameters have exactly the same semantics as for
// Create. The old contract will be deleted by this call, if it has any storage // Create. The old contract will be deleted by this call, if it has any storage
// associated it will be migrated to the new contract. New contract is returned. // associated it will be migrated to the new contract. New contract is returned.
// This function uses `Neo.Contract.Migrate` syscall. // This function uses `System.Contract.Update` syscall.
func Migrate( func Update(script []byte, manifest []byte) Contract {
script []byte,
params []byte,
returnType byte,
properties byte,
name,
version,
author,
email,
description string) Contract {
return Contract{} return Contract{}
} }
// Destroy deletes calling contract (the one that calls Destroy) from the // Destroy deletes calling contract (the one that calls Destroy) from the
// blockchain, so it's only possible to do that from the contract itself and // blockchain, so it's only possible to do that from the contract itself and
// not by any outside code. When contract is deleted all associated storage // not by any outside code. When contract is deleted all associated storage
// items are deleted too. This function uses `Neo.Contract.Destroy` syscall. // items are deleted too. This function uses `System.Contract.Destroy` syscall.
func Destroy() {} func Destroy() {}

View file

@ -6,6 +6,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
@ -177,10 +178,10 @@ func (c *Client) GetConnectionCount() (int, error) {
} }
// GetContractState queries contract information, according to the contract script hash. // GetContractState queries contract information, according to the contract script hash.
func (c *Client) GetContractState(hash util.Uint160) (*result.ContractState, error) { func (c *Client) GetContractState(hash util.Uint160) (*state.Contract, error) {
var ( var (
params = request.NewRawParams(hash.StringLE()) params = request.NewRawParams(hash.StringLE())
resp = &result.ContractState{} resp = &state.Contract{}
) )
if err := c.performRequest("getcontractstate", params, resp); err != nil { if err := c.performRequest("getcontractstate", params, resp); err != nil {
return resp, err return resp, err

View file

@ -2,6 +2,7 @@ package client
import ( import (
"context" "context"
"encoding/base64"
"encoding/hex" "encoding/hex"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
@ -11,12 +12,15 @@ import (
"github.com/gorilla/websocket" "github.com/gorilla/websocket"
"github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/internal/testserdes" "github.com/nspcc-dev/neo-go/pkg/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/rpc/request" "github.com/nspcc-dev/neo-go/pkg/rpc/request"
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result" "github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
@ -293,39 +297,33 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
{ {
name: "positive", name: "positive",
invoke: func(c *Client) (interface{}, error) { invoke: func(c *Client) (interface{}, error) {
hash, err := util.Uint160DecodeStringLE("dc675afc61a7c0f7b3d2682bf6e1d8ed865a0e5f") hash, err := util.Uint160DecodeStringLE("1b4357bff5a01bdf2a6581247cf9ed1e24629176")
if err != nil { if err != nil {
panic(err) panic(err)
} }
return c.GetContractState(hash) return c.GetContractState(hash)
}, },
serverResponse: `{"jsonrpc":"2.0","id":1,"result":{"version":0,"hash":"0xdc675afc61a7c0f7b3d2682bf6e1d8ed865a0e5f","script":"X8VrbHZrAFJ6xGx2a1FSesRhB1dvb2xvbmdsdmtSUnrEA1dOR2x2a1NSesQAbHZrVFJ6xCEDVK5JgiEEbGZu/ruu6b0OtII0acmOdISUqSpx80axpmFsdmtVUnrEbHZrAMMGZGVwbG95h2x2a1ZSesRsdmtWw2QWAGx2a1XDYWXyAmx2a1dSesRi2AFsdmtVw2Fl2AFhbHZrAMMLdG90YWxTdXBwbHmHbHZrWFJ6xGx2a1jDZEAAYWgWTmVvLlN0b3JhZ2UuR2V0Q29udGV4dAZzdXBwbHlhfGgPTmVvLlN0b3JhZ2UuR2V0bHZrV1J6xGJwAWx2awDDBG5hbWWHbHZrWVJ6xGx2a1nDZBIAbHZrUsNsdmtXUnrEYkcBbHZrAMMGc3ltYm9sh2x2a1pSesRsdmtaw2QSAGx2a1PDbHZrV1J6xGIcAWx2awDDCGRlY2ltYWxzh2x2a1tSesRsdmtbw2QSAGx2a1TDbHZrV1J6xGLvAGx2awDDCWJhbGFuY2VPZodsdmtcUnrEbHZrXMNkQABhaBZOZW8uU3RvcmFnZS5HZXRDb250ZXh0bHZrUcNRw2F8aA9OZW8uU3RvcmFnZS5HZXRsdmtXUnrEYpMAbHZrUcMAw2FoGE5lby5SdW50aW1lLkNoZWNrV2l0bmVzcwCcbHZrXVJ6xGx2a13DZA4AAGx2a1dSesRiVQBsdmsAwwh0cmFuc2ZlcodsdmteUnrEbHZrXsNkLABsdmtRwwDDbHZrUcNRw2x2a1HDUsNhZdQDYVJyZckBbHZrV1J6xGIOAABsdmtXUnrEYgMAbHZrV8NhbHVmU8VrbHZrAFJ6xGFhaBZOZW8uU3RvcmFnZS5HZXRDb250ZXh0bHZrAMNhfGgPTmVvLlN0b3JhZ2UuR2V0YWVwA1GTbHZrUVJ6xGFoFk5lby5TdG9yYWdlLkdldENvbnRleHRsdmsAw2x2a1HDYWURA2FScmgPTmVvLlN0b3JhZ2UuUHV0YWFoFk5lby5TdG9yYWdlLkdldENvbnRleHQGc3VwcGx5YXxoD05lby5TdG9yYWdlLkdldGFl9AJRk2x2a1JSesRhaBZOZW8uU3RvcmFnZS5HZXRDb250ZXh0BnN1cHBseWx2a1LDYWWTAmFScmgPTmVvLlN0b3JhZ2UuUHV0YWFsdWZTxWtsdmsAUnrEYVFsdmtRUnrEYWgWTmVvLlN0b3JhZ2UuR2V0Q29udGV4dGx2awDDbHZrUcNhZUACYVJyaA9OZW8uU3RvcmFnZS5QdXRhYWgWTmVvLlN0b3JhZ2UuR2V0Q29udGV4dAZzdXBwbHlsdmtRw2FlAgJhUnJoD05lby5TdG9yYWdlLlB1dGFRbHZrUlJ6xGIDAGx2a1LDYWx1ZlnFa2x2awBSesRsdmtRUnrEbHZrUlJ6xGFhaBZOZW8uU3RvcmFnZS5HZXRDb250ZXh0bHZrAMNhfGgPTmVvLlN0b3JhZ2UuR2V0bHZrU1J6xGFoFk5lby5TdG9yYWdlLkdldENvbnRleHRsdmtRw2F8aA9OZW8uU3RvcmFnZS5HZXRsdmtUUnrEbHZrU8NhZXYBbHZrUsOUbHZrVVJ6xGx2a1TDYWVgAWx2a1LDk2x2a1ZSesRsdmtVwwCiZA0AbHZrUsMAomIEAABsdmtXUnrEbHZrV8Nk7ABhYWgWTmVvLlN0b3JhZ2UuR2V0Q29udGV4dGx2awDDbHZrVcNhZdgAYVJyaA9OZW8uU3RvcmFnZS5QdXRhYWgWTmVvLlN0b3JhZ2UuR2V0Q29udGV4dGx2a1HDbHZrVsNhZZwAYVJyaA9OZW8uU3RvcmFnZS5QdXRhVcV2ABNUcmFuc2ZlciBTdWNjZXNzZnVsxHZRbHZrAMPEdlJsdmtRw8R2U2x2a1LDxHZUYWgYTmVvLkJsb2NrY2hhaW4uR2V0SGVpZ2h0xGFoEk5lby5SdW50aW1lLk5vdGlmeWFRbHZrWFJ6xGIOAABsdmtYUnrEYgMAbHZrWMNhbHVmU8VrbHZrAFJ6xGFsdmsAw2x2a1FSesRsdmtRw2x2a1JSesRiAwBsdmtSw2FsdWZTxWtsdmsAUnrEYVFsdmsAw2pSelJ6xGx2a1HDbHZrUlJ6xGIDAGx2a1LDYWx1Zg==","parameters":["ByteArray"],"returntype":"ByteArray","name":"Woolong","code_version":"0.9.2","author":"lllwvlvwlll","email":"lllwvlvwlll@gmail.com","description":"GO NEO!!!","properties":{"storage":true,"dynamic_invoke":false}}}`, serverResponse: `{"id":1,"jsonrpc":"2.0","result":{"id":0,"script":"VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==","manifest":{"abi":{"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176","entryPoint":{"name":"Main","parameters":[{"name":"method","type":"String"},{"name":"params","type":"Array"}],"returnType":"Boolean"},"methods":[],"events":[]},"groups":[],"features":{"payable":false,"storage":true},"permissions":null,"trusts":[],"safeMethods":[],"extra":null},"hash":"0x1b4357bff5a01bdf2a6581247cf9ed1e24629176"}}`,
result: func(c *Client) interface{} { result: func(c *Client) interface{} {
hash, err := util.Uint160DecodeStringLE("dc675afc61a7c0f7b3d2682bf6e1d8ed865a0e5f") script, err := base64.StdEncoding.DecodeString("VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==")
if err != nil { if err != nil {
panic(err) panic(err)
} }
script, err := hex.DecodeString("5fc56b6c766b00527ac46c766b51527ac46107576f6f6c6f6e676c766b52527ac403574e476c766b53527ac4006c766b54527ac4210354ae498221046c666efebbaee9bd0eb4823469c98e748494a92a71f346b1a6616c766b55527ac46c766b00c3066465706c6f79876c766b56527ac46c766b56c36416006c766b55c36165f2026c766b57527ac462d8016c766b55c36165d801616c766b00c30b746f74616c537570706c79876c766b58527ac46c766b58c36440006168164e656f2e53746f726167652e476574436f6e7465787406737570706c79617c680f4e656f2e53746f726167652e4765746c766b57527ac46270016c766b00c3046e616d65876c766b59527ac46c766b59c36412006c766b52c36c766b57527ac46247016c766b00c30673796d626f6c876c766b5a527ac46c766b5ac36412006c766b53c36c766b57527ac4621c016c766b00c308646563696d616c73876c766b5b527ac46c766b5bc36412006c766b54c36c766b57527ac462ef006c766b00c30962616c616e63654f66876c766b5c527ac46c766b5cc36440006168164e656f2e53746f726167652e476574436f6e746578746c766b51c351c3617c680f4e656f2e53746f726167652e4765746c766b57527ac46293006c766b51c300c36168184e656f2e52756e74696d652e436865636b5769746e657373009c6c766b5d527ac46c766b5dc3640e00006c766b57527ac46255006c766b00c3087472616e73666572876c766b5e527ac46c766b5ec3642c006c766b51c300c36c766b51c351c36c766b51c352c36165d40361527265c9016c766b57527ac4620e00006c766b57527ac46203006c766b57c3616c756653c56b6c766b00527ac4616168164e656f2e53746f726167652e476574436f6e746578746c766b00c3617c680f4e656f2e53746f726167652e4765746165700351936c766b51527ac46168164e656f2e53746f726167652e476574436f6e746578746c766b00c36c766b51c361651103615272680f4e656f2e53746f726167652e507574616168164e656f2e53746f726167652e476574436f6e7465787406737570706c79617c680f4e656f2e53746f726167652e4765746165f40251936c766b52527ac46168164e656f2e53746f726167652e476574436f6e7465787406737570706c796c766b52c361659302615272680f4e656f2e53746f726167652e50757461616c756653c56b6c766b00527ac461516c766b51527ac46168164e656f2e53746f726167652e476574436f6e746578746c766b00c36c766b51c361654002615272680f4e656f2e53746f726167652e507574616168164e656f2e53746f726167652e476574436f6e7465787406737570706c796c766b51c361650202615272680f4e656f2e53746f726167652e50757461516c766b52527ac46203006c766b52c3616c756659c56b6c766b00527ac46c766b51527ac46c766b52527ac4616168164e656f2e53746f726167652e476574436f6e746578746c766b00c3617c680f4e656f2e53746f726167652e4765746c766b53527ac46168164e656f2e53746f726167652e476574436f6e746578746c766b51c3617c680f4e656f2e53746f726167652e4765746c766b54527ac46c766b53c3616576016c766b52c3946c766b55527ac46c766b54c3616560016c766b52c3936c766b56527ac46c766b55c300a2640d006c766b52c300a2620400006c766b57527ac46c766b57c364ec00616168164e656f2e53746f726167652e476574436f6e746578746c766b00c36c766b55c36165d800615272680f4e656f2e53746f726167652e507574616168164e656f2e53746f726167652e476574436f6e746578746c766b51c36c766b56c361659c00615272680f4e656f2e53746f726167652e5075746155c57600135472616e73666572205375636365737366756cc476516c766b00c3c476526c766b51c3c476536c766b52c3c476546168184e656f2e426c6f636b636861696e2e476574486569676874c46168124e656f2e52756e74696d652e4e6f7469667961516c766b58527ac4620e00006c766b58527ac46203006c766b58c3616c756653c56b6c766b00527ac4616c766b00c36c766b51527ac46c766b51c36c766b52527ac46203006c766b52c3616c756653c56b6c766b00527ac461516c766b00c36a527a527ac46c766b51c36c766b52527ac46203006c766b52c3616c7566") m := manifest.NewManifest(hash.Hash160(script))
if err != nil { m.ABI.EntryPoint.Name = "Main"
panic(err) m.ABI.EntryPoint.Parameters = []manifest.Parameter{
manifest.NewParameter("method", smartcontract.StringType),
manifest.NewParameter("params", smartcontract.ArrayType),
} }
return &result.ContractState{ m.ABI.EntryPoint.ReturnType = smartcontract.BoolType
Version: 0, m.Features = smartcontract.HasStorage
ScriptHash: hash, cs := &state.Contract{
Script: script, ID: 0,
ParamList: []smartcontract.ParamType{smartcontract.ByteArrayType}, Script: script,
ReturnType: smartcontract.ByteArrayType, Manifest: *m,
Name: "Woolong",
CodeVersion: "0.9.2",
Author: "lllwvlvwlll",
Email: "lllwvlvwlll@gmail.com",
Description: "GO NEO!!!",
Properties: result.Properties{
HasStorage: true,
HasDynamicInvoke: false,
IsPayable: false,
},
} }
_ = cs.ScriptHash()
return cs
}, },
}, },
}, },

View file

@ -5,49 +5,28 @@ import (
"fmt" "fmt"
"strconv" "strconv"
"github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/emit" "github.com/nspcc-dev/neo-go/pkg/vm/emit"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode" "github.com/nspcc-dev/neo-go/pkg/vm/opcode"
) )
// DetailsToSCProperties extract the fields needed from ContractDetails
// and converts them to smartcontract.PropertyState.
func DetailsToSCProperties(contract *smartcontract.ContractDetails) smartcontract.PropertyState {
var props smartcontract.PropertyState
if contract.HasStorage {
props |= smartcontract.HasStorage
}
if contract.HasDynamicInvocation {
props |= smartcontract.HasDynamicInvoke
}
if contract.IsPayable {
props |= smartcontract.IsPayable
}
return props
}
// CreateDeploymentScript returns a script that deploys given smart contract // CreateDeploymentScript returns a script that deploys given smart contract
// with its metadata. // with its metadata and system fee require for this.
func CreateDeploymentScript(avm []byte, contract *smartcontract.ContractDetails) ([]byte, error) { func CreateDeploymentScript(avm []byte, manif *manifest.Manifest) ([]byte, util.Fixed8, error) {
script := io.NewBufBinWriter() script := io.NewBufBinWriter()
emit.Bytes(script.BinWriter, []byte(contract.Description)) w := io.NewBufBinWriter()
emit.Bytes(script.BinWriter, []byte(contract.Email)) manif.EncodeBinary(w.BinWriter)
emit.Bytes(script.BinWriter, []byte(contract.Author)) rawManifest := w.Bytes()
emit.Bytes(script.BinWriter, []byte(contract.Version)) emit.Bytes(script.BinWriter, rawManifest)
emit.Bytes(script.BinWriter, []byte(contract.ProjectName))
emit.Int(script.BinWriter, int64(DetailsToSCProperties(contract)))
emit.Int(script.BinWriter, int64(contract.ReturnType))
params := make([]byte, len(contract.Parameters))
for k := range contract.Parameters {
params[k] = byte(contract.Parameters[k])
}
emit.Bytes(script.BinWriter, params)
emit.Bytes(script.BinWriter, avm) emit.Bytes(script.BinWriter, avm)
emit.Syscall(script.BinWriter, "Neo.Contract.Create") emit.Syscall(script.BinWriter, "System.Contract.Create")
return script.Bytes(), nil sysfee := util.Fixed8(core.StoragePrice * (len(avm) + len(rawManifest)))
return script.Bytes(), sysfee, nil
} }
// expandArrayIntoScript pushes all FuncParam parameters from the given array // expandArrayIntoScript pushes all FuncParam parameters from the given array

View file

@ -1,53 +0,0 @@
package result
import (
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
)
// ContractState wrapper used for the representation of
// state.Contract on the RPC Server.
type ContractState struct {
Version byte `json:"version"`
ScriptHash util.Uint160 `json:"hash"`
Script []byte `json:"script"`
ParamList []smartcontract.ParamType `json:"parameters"`
ReturnType smartcontract.ParamType `json:"returntype"`
Name string `json:"name"`
CodeVersion string `json:"code_version"`
Author string `json:"author"`
Email string `json:"email"`
Description string `json:"description"`
Properties Properties `json:"properties"`
}
// Properties response wrapper.
type Properties struct {
HasStorage bool `json:"storage"`
HasDynamicInvoke bool `json:"dynamic_invoke"`
IsPayable bool `json:"is_payable"`
}
// NewContractState creates a new Contract wrapper.
func NewContractState(c *state.Contract) ContractState {
properties := Properties{
HasStorage: c.HasStorage(),
HasDynamicInvoke: c.HasDynamicInvoke(),
IsPayable: c.IsPayable(),
}
return ContractState{
Version: 0,
ScriptHash: c.ScriptHash(),
Script: c.Script,
ParamList: c.ParamList,
ReturnType: c.ReturnType,
Properties: properties,
Name: c.Name,
CodeVersion: c.CodeVersion,
Author: c.Author,
Email: c.Email,
Description: c.Description,
}
}

View file

@ -738,11 +738,10 @@ func (s *Server) getContractState(reqParams request.Params) (interface{}, *respo
return nil, response.ErrInvalidParams return nil, response.ErrInvalidParams
} else { } else {
cs := s.chain.GetContractState(scriptHash) cs := s.chain.GetContractState(scriptHash)
if cs != nil { if cs == nil {
results = result.NewContractState(cs)
} else {
return nil, response.NewRPCError("Unknown contract", "", nil) return nil, response.NewRPCError("Unknown contract", "", nil)
} }
results = cs
} }
return results, nil return results, nil
} }

View file

@ -17,6 +17,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core" "github.com/nspcc-dev/neo-go/pkg/core"
"github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/blockchainer" "github.com/nspcc-dev/neo-go/pkg/core/blockchainer"
"github.com/nspcc-dev/neo-go/pkg/core/state"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address" "github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/internal/testchain" "github.com/nspcc-dev/neo-go/pkg/internal/testchain"
@ -55,12 +56,12 @@ var rpcTestCases = map[string][]rpcTestCase{
"getapplicationlog": { "getapplicationlog": {
{ {
name: "positive", name: "positive",
params: `["328190e78f1b5b8dcf86e71ec1894d6144f4e4cdb25abc20ea02b618851941dd"]`, params: `["136ef2ba8259d121a173da2588ae0fbb735f0f740820fded9cd8da8fee109b20"]`,
result: func(e *executor) interface{} { return &result.ApplicationLog{} }, result: func(e *executor) interface{} { return &result.ApplicationLog{} },
check: func(t *testing.T, e *executor, acc interface{}) { check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := acc.(*result.ApplicationLog) res, ok := acc.(*result.ApplicationLog)
require.True(t, ok) require.True(t, ok)
expectedTxHash, err := util.Uint256DecodeStringLE("328190e78f1b5b8dcf86e71ec1894d6144f4e4cdb25abc20ea02b618851941dd") expectedTxHash, err := util.Uint256DecodeStringLE("136ef2ba8259d121a173da2588ae0fbb735f0f740820fded9cd8da8fee109b20")
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, expectedTxHash, res.TxHash) assert.Equal(t, expectedTxHash, res.TxHash)
assert.Equal(t, 1, len(res.Executions)) assert.Equal(t, 1, len(res.Executions))
@ -88,13 +89,11 @@ var rpcTestCases = map[string][]rpcTestCase{
{ {
name: "positive", name: "positive",
params: fmt.Sprintf(`["%s"]`, testContractHash), params: fmt.Sprintf(`["%s"]`, testContractHash),
result: func(e *executor) interface{} { return &result.ContractState{} }, result: func(e *executor) interface{} { return &state.Contract{} },
check: func(t *testing.T, e *executor, cs interface{}) { check: func(t *testing.T, e *executor, cs interface{}) {
res, ok := cs.(*result.ContractState) res, ok := cs.(*state.Contract)
require.True(t, ok) require.True(t, ok)
assert.Equal(t, byte(0), res.Version) assert.Equal(t, testContractHash, res.ScriptHash().StringLE())
assert.Equal(t, testContractHash, res.ScriptHash.StringLE())
assert.Equal(t, "0.99", res.CodeVersion)
}, },
}, },
{ {
@ -484,7 +483,7 @@ var rpcTestCases = map[string][]rpcTestCase{
"gettransactionheight": { "gettransactionheight": {
{ {
name: "positive", name: "positive",
params: `["328190e78f1b5b8dcf86e71ec1894d6144f4e4cdb25abc20ea02b618851941dd"]`, params: `["136ef2ba8259d121a173da2588ae0fbb735f0f740820fded9cd8da8fee109b20"]`,
result: func(e *executor) interface{} { result: func(e *executor) interface{} {
h := 0 h := 0
return &h return &h

Binary file not shown.

View file

@ -1,18 +0,0 @@
package smartcontract
import "github.com/nspcc-dev/neo-go/pkg/util"
// GetDeploymentPrice returns contract deployment price based on its properties.
func GetDeploymentPrice(props PropertyState) util.Fixed8 {
fee := int64(100)
if props&HasStorage != 0 {
fee += 400
}
if props&HasDynamicInvoke != 0 {
fee += 500
}
return util.Fixed8FromInt64(fee)
}

View file

@ -3,6 +3,7 @@ package manifest
import ( import (
"encoding/json" "encoding/json"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
) )
@ -126,3 +127,23 @@ func (m *Manifest) UnmarshalJSON(data []byte) error {
return nil return nil
} }
// EncodeBinary implements io.Serializable.
func (m *Manifest) EncodeBinary(w *io.BinWriter) {
data, err := json.Marshal(m)
if err != nil {
w.Err = err
return
}
w.WriteVarBytes(data)
}
// DecodeBinary implements io.Serializable.
func (m *Manifest) DecodeBinary(r *io.BinReader) {
data := r.ReadVarBytes()
if r.Err != nil {
return
} else if err := json.Unmarshal(data, m); err != nil {
r.Err = err
}
}

View file

@ -22,10 +22,9 @@ type PropertyState byte
// List of supported properties. // List of supported properties.
const ( const (
HasStorage PropertyState = 1 << iota HasStorage PropertyState = 1 << iota
HasDynamicInvoke IsPayable PropertyState = 1 << 2
IsPayable NoProperties = 0
NoProperties = 0
) )
// Parameter represents a smart contract parameter. // Parameter represents a smart contract parameter.

View file

@ -39,9 +39,6 @@ type Context struct {
// Script hash of the prog. // Script hash of the prog.
scriptHash util.Uint160 scriptHash util.Uint160
// Whether it's allowed to make dynamic calls from this context.
hasDynamicInvoke bool
} }
var errNoInstParam = errors.New("failed to read instruction parameter") var errNoInstParam = errors.New("failed to read instruction parameter")

View file

@ -136,6 +136,12 @@ func (v *VM) SetGasLimit(max util.Fixed8) {
v.gasLimit = max v.gasLimit = max
} }
// AddGas consumes specified amount of gas. It returns true iff gas limit wasn't exceeded.
func (v *VM) AddGas(gas util.Fixed8) bool {
v.gasConsumed += gas
return v.gasLimit == 0 || v.gasConsumed <= v.gasLimit
}
// Estack returns the evaluation stack so interop hooks can utilize this. // Estack returns the evaluation stack so interop hooks can utilize this.
func (v *VM) Estack() *Stack { func (v *VM) Estack() *Stack {
return v.estack return v.estack
@ -262,16 +268,6 @@ func (v *VM) LoadScript(b []byte) {
v.istack.PushVal(ctx) v.istack.PushVal(ctx)
} }
// loadScriptWithHash if similar to the LoadScript method, but it also loads
// given script hash directly into the Context to avoid its recalculations. It's
// up to user of this function to make sure the script and hash match each other.
func (v *VM) loadScriptWithHash(b []byte, hash util.Uint160, hasDynamicInvoke bool) {
v.LoadScript(b)
ctx := v.Context()
ctx.scriptHash = hash
ctx.hasDynamicInvoke = hasDynamicInvoke
}
// Context returns the current executed context. Nil if there is no context, // Context returns the current executed context. Nil if there is no context,
// which implies no program is loaded. // which implies no program is loaded.
func (v *VM) Context() *Context { func (v *VM) Context() *Context {

View file

@ -98,6 +98,14 @@ func TestVM_SetPriceGetter(t *testing.T) {
}) })
} }
func TestAddGas(t *testing.T) {
v := New()
v.SetGasLimit(10)
require.True(t, v.AddGas(5))
require.True(t, v.AddGas(5))
require.False(t, v.AddGas(5))
}
func TestBytesToPublicKey(t *testing.T) { func TestBytesToPublicKey(t *testing.T) {
v := New() v := New()
cache := v.GetPublicKeys() cache := v.GetPublicKeys()