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
import (
"bufio"
"bytes"
"context"
"encoding/hex"
@ -22,6 +21,7 @@ import (
"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/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/vm"
"github.com/nspcc-dev/neo-go/pkg/wallet"
@ -323,23 +323,22 @@ func initSmartContract(ctx *cli.Context) error {
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()
details.ReturnType = smartcontract.ByteArrayType
details.Parameters = make([]smartcontract.ParamType, 2)
details.Parameters[0] = smartcontract.StringType
details.Parameters[1] = smartcontract.ArrayType
project := &ProjectConfig{Contract: details}
b, err := yaml.Marshal(project)
if err != nil {
return cli.NewExitError(err, 1)
}
if err := ioutil.WriteFile(filepath.Join(basePath, "neo-go.yml"), b, 0644); err != nil {
return cli.NewExitError(err, 1)
}
m := ProjectConfig{
EntryPoint: manifest.Method{
Name: "Main",
Parameters: []manifest.Parameter{
manifest.NewParameter("Method", smartcontract.StringType),
manifest.NewParameter("Arguments", smartcontract.ArrayType),
},
ReturnType: smartcontract.ByteArrayType,
},
}
b, err := yaml.Marshal(m)
if err != nil {
return cli.NewExitError(err, 1)
}
if err := ioutil.WriteFile(filepath.Join(basePath, "neo-go.yml"), b, 0644); err != nil {
return cli.NewExitError(err, 1)
}
data := []byte(fmt.Sprintf(smartContractTmpl, contractName))
@ -375,7 +374,7 @@ func contractCompile(ctx *cli.Context) error {
if err != nil {
return err
}
o.ContractDetails = &conf.Contract
o.ContractFeatures = conf.GetFeatures()
}
result, err := compiler.CompileAndSave(src, o)
@ -526,30 +525,35 @@ func testInvokeScript(ctx *cli.Context) error {
// ProjectConfig contains project metadata.
type ProjectConfig struct {
Version uint
Contract smartcontract.ContractDetails `yaml:"project"`
HasStorage bool
IsPayable bool
EntryPoint manifest.Method
Methods []manifest.Method
Events []manifest.Event
}
func parseContractDetails() smartcontract.ContractDetails {
details := smartcontract.ContractDetails{}
reader := bufio.NewReader(os.Stdin)
// GetFeatures returns smartcontract features from the config.
func (p *ProjectConfig) GetFeatures() smartcontract.PropertyState {
var fs smartcontract.PropertyState
if p.IsPayable {
fs |= smartcontract.IsPayable
}
if p.HasStorage {
fs |= smartcontract.HasStorage
}
return fs
}
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
// ToManifest converts project config to the manifest.
func (p *ProjectConfig) ToManifest(script []byte) *manifest.Manifest {
h := hash.Hash160(script)
m := manifest.NewManifest(h)
m.Features = p.GetFeatures()
m.ABI.Hash = h
m.ABI.EntryPoint = p.EntryPoint
m.ABI.Methods = p.Methods
m.ABI.Events = p.Events
return m
}
func inspect(ctx *cli.Context) error {
@ -646,13 +650,12 @@ func contractDeploy(ctx *cli.Context) error {
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 {
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)
if err != nil {
return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1)

View file

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

View file

@ -311,14 +311,16 @@ func parsePairJSON(data []byte, sep string) (string, string, error) {
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)
for _, method := range di.Methods {
if method.Name.Name == di.EntryPoint {
methods = append(methods, Method{
Name: method.Name.Name,
Parameters: method.Parameters,
ReturnType: cd.ReturnType.String(),
ReturnType: method.ReturnType,
})
break
}
@ -333,14 +335,8 @@ func (di *DebugInfo) convertToABI(contract []byte, cd *smartcontract.ContractDet
return ABI{
Hash: hash.Hash160(contract),
Metadata: Metadata{
Author: cd.Author,
Email: cd.Email,
Version: cd.Version,
Title: cd.ProjectName,
Description: cd.Description,
HasStorage: cd.HasStorage,
HasDynamicInvocation: cd.HasDynamicInvocation,
IsPayable: cd.IsPayable,
HasStorage: fs&smartcontract.HasStorage != 0,
IsPayable: fs&smartcontract.IsPayable != 0,
},
EntryPoint: di.EntryPoint,
Functions: methods,

View file

@ -117,33 +117,10 @@ func methodStruct() struct{} { return struct{}{} }
}
t.Run("convert to ABI", func(t *testing.T) {
author := "Joe"
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,
},
})
actual := d.convertToABI(buf, smartcontract.HasStorage)
expected := ABI{
Hash: hash.Hash160(buf),
Metadata: Metadata{
Author: author,
Email: email,
Version: version,
Title: title,
Description: description,
HasStorage: true,
HasDynamicInvocation: false,
IsPayable: false,

View file

@ -37,12 +37,9 @@ var syscalls = map[string]map[string]string{
"GetTransactionHeight": "System.Blockchain.GetTransactionHeight",
},
"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",
"Create": "System.Contract.Create",
"Destroy": "System.Contract.Destroy",
"Update": "System.Contract.Update",
},
"engine": {
"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/internal/random"
"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/require"
)
@ -59,10 +60,19 @@ func TestCachedDaoContracts(t *testing.T) {
_, err := dao.GetContractState(sh)
require.NotNil(t, err)
cs := &state.Contract{}
cs.Name = "test"
cs.Script = script
cs.ParamList = []smartcontract.ParamType{1, 2}
m := manifest.NewManifest(hash.Hash160(script))
m.ABI.EntryPoint.Name = "somename"
m.ABI.EntryPoint.Parameters = []manifest.Parameter{
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))
cs2, err := dao.GetContractState(sh)

View file

@ -31,6 +31,7 @@ type DAO interface {
GetHeaderHashes() ([]util.Uint256, error)
GetNEP5Balances(acc util.Uint160) (*state.NEP5Balances, error)
GetNEP5TransferLog(acc util.Uint160, index uint32) (*state.NEP5TransferLog, error)
GetNextContractID() (int32, error)
GetStorageItem(scripthash util.Uint160, key []byte) *state.StorageItem
GetStorageItems(hash util.Uint160) (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
PutNEP5Balances(acc util.Uint160, bs *state.NEP5Balances) 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
PutVersion(v string) error
StoreAsBlock(block *block.Block) error
@ -169,6 +171,27 @@ func (dao *Simple) DeleteContractState(hash util.Uint160) error {
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.
// -- start nep5 balances.

View file

@ -60,7 +60,7 @@ func TestPutAndGetAccountStateOrNew(t *testing.T) {
func TestPutAndGetContractState(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore())
contractState := &state.Contract{Script: []byte{}, ParamList: []smartcontract.ParamType{}}
contractState := &state.Contract{Script: []byte{}}
hash := contractState.ScriptHash()
err := dao.PutContractState(contractState)
require.NoError(t, err)
@ -71,7 +71,7 @@ func TestPutAndGetContractState(t *testing.T) {
func TestDeleteContractState(t *testing.T) {
dao := NewSimple(storage.NewMemoryStore())
contractState := &state.Contract{Script: []byte{}, ParamList: []smartcontract.ParamType{}}
contractState := &state.Contract{Script: []byte{}}
hash := contractState.ScriptHash()
err := dao.PutContractState(contractState)
require.NoError(t, err)
@ -82,6 +82,17 @@ func TestDeleteContractState(t *testing.T) {
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) {
dao := NewSimple(storage.NewMemoryStore())
hash := random.Uint256()

View file

@ -1,7 +1,6 @@
package core
import (
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
@ -12,6 +11,9 @@ import (
// of 0.001 GAS = Fixed8(10^5).
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.
// Some SYSCALLs have variable price depending on their arguments.
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 (
neoContractCreate = 0x6ea56cf6 // Neo.Contract.Create
neoContractMigrate = 0x90621b47 // Neo.Contract.Migrate
systemStoragePut = 0x84183fe6 // System.Storage.Put
systemStoragePutEx = 0x3a9be173 // System.Storage.PutEx
neoStoragePut = 0xf541a152 // Neo.Storage.Put
@ -51,8 +51,6 @@ func getSyscallPrice(v *vm.VM, id uint32) util.Fixed8 {
estack := v.Estack()
switch id {
case neoContractCreate, neoContractMigrate:
return smartcontract.GetDeploymentPrice(smartcontract.PropertyState(estack.Peek(3).BigInt().Int64()))
case systemStoragePut, systemStoragePutEx, neoStoragePut:
// price for storage PUT is 1 GAS per 1 KiB
keySize := len(estack.Peek(1).Bytes())

View file

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

View file

@ -28,7 +28,6 @@ type Context struct {
Block *block.Block
Tx *transaction.Transaction
DAO *dao.Cached
LowerDAO dao.DAO
Notifications []state.NotificationEvent
Log *zap.Logger
}
@ -44,7 +43,6 @@ func NewContext(trigger trigger.Type, bc blockchainer.Blockchainer, d dao.DAO, n
Block: block,
Tx: tx,
DAO: dao,
LowerDAO: d,
Notifications: nes,
Log: log,
}
@ -82,21 +80,12 @@ type ContractMD struct {
Manifest manifest.Manifest
ServiceName string
ServiceID uint32
ContractID int32
Script []byte
Hash util.Uint160
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.
func NewContractMD(name string) *ContractMD {
c := &ContractMD{

View file

@ -1,6 +1,7 @@
package runtime
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/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
@ -13,7 +14,7 @@ import (
// for verifying in the interop context.
func CheckHashedWitness(ic *interop.Context, v vm.ScriptHashGetter, hash util.Uint160) (bool, error) {
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.)
@ -29,7 +30,7 @@ func CheckHashedWitness(ic *interop.Context, v vm.ScriptHashGetter, hash util.Ui
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 {
if c.Account == hash {
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 {
return true, nil
// TODO: we don't currently have Manifest field in ContractState
/*callingScriptHash := v.GetCallingScriptHash()
if callingScriptHash.Equals(util.Uint160{}){
callingScriptHash := v.GetCallingScriptHash()
if callingScriptHash.Equals(util.Uint160{}) {
return false, nil
}
cs, err := ic.DAO.GetContractState(callingScriptHash)
cs, err := d.GetContractState(callingScriptHash)
if err != nil {
return false, err
}
@ -68,7 +67,7 @@ func checkScope(tx *transaction.Transaction, v vm.ScriptHashGetter, hash util.Ui
return true, 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/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/util"
"github.com/nspcc-dev/neo-go/pkg/vm"
"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 {
return nil, errors.New("the script is too big")
}
paramBytes := v.Estack().Pop().Bytes()
if len(paramBytes) > MaxContractParametersNum {
return nil, errors.New("too many parameters for a script")
manifestBytes := v.Estack().Pop().Bytes()
if len(manifestBytes) > manifest.MaxManifestSize {
return nil, errors.New("manifest is too big")
}
paramList := make([]smartcontract.ParamType, len(paramBytes))
for k, v := range paramBytes {
paramList[k] = smartcontract.ParamType(v)
if !v.AddGas(util.Fixed8(StoragePrice * (len(script) + len(manifestBytes)))) {
return nil, errors.New("gas limit exceeded")
}
retType := smartcontract.ParamType(v.Estack().Pop().BigInt().Int64())
properties := smartcontract.PropertyState(v.Estack().Pop().BigInt().Int64())
name := v.Estack().Pop().Bytes()
if len(name) > MaxContractStringLen {
return nil, errors.New("too big name")
var m manifest.Manifest
r := io.NewBinReaderFromBuf(manifestBytes)
m.DecodeBinary(r)
if r.Err != nil {
return nil, r.Err
}
version := v.Estack().Pop().Bytes()
if len(version) > MaxContractStringLen {
return nil, errors.New("too big version")
}
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
return &state.Contract{
Script: script,
Manifest: m,
}, nil
}
// contractCreate creates a contract.
@ -119,65 +96,65 @@ func contractCreate(ic *interop.Context, v *vm.VM) error {
return err
}
contract, err := ic.DAO.GetContractState(newcontract.ScriptHash())
if contract != nil {
return errors.New("contract already exists")
}
id, err := ic.DAO.GetNextContractID()
if err != nil {
contract = newcontract
err := ic.DAO.PutContractState(contract)
if err != nil {
return err
}
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
}
// contractGetScript returns a script associated with a contract.
func contractGetScript(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)
// contractUpdate migrates a contract.
func contractUpdate(ic *interop.Context, v *vm.VM) error {
contract, err := ic.DAO.GetContractState(v.GetCurrentScriptHash())
if contract == nil {
return errors.New("contract doesn't exist")
}
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)
if err != nil {
return err
}
contract, err := ic.DAO.GetContractState(newcontract.ScriptHash())
if err != nil {
contract = newcontract
err := ic.DAO.PutContractState(contract)
if newcontract.Script != nil {
if l := len(newcontract.Script); l == 0 || l > MaxContractScriptSize {
return errors.New("invalid script len")
}
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 {
return err
}
if contract.HasStorage() {
hash := v.GetCurrentScriptHash()
siMap, err := ic.DAO.GetStorageItems(hash)
for k, v := range siMap {
v.IsConst = false
err = ic.DAO.PutStorageItem(contract.ScriptHash(), []byte(k), v)
if err != nil {
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))

View file

@ -13,9 +13,10 @@ import (
"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/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/internal/random"
"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/util"
"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.
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) {
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{
Script: []byte("testscript"),
ParamList: []smartcontract.ParamType{smartcontract.StringType, smartcontract.IntegerType, smartcontract.Hash160Type},
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),
Script: script,
Manifest: *m,
}
chain := newTestChain(t)

View file

@ -179,62 +179,6 @@ func bcGetTransactionHeight(ic *interop.Context, v *vm.VM) error {
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
// run.
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 {
return errors.New("invalid contract hash")
}
script, _ := ic.GetContract(u)
if script == nil {
cs, err := ic.DAO.GetContractState(u)
if err != nil {
return errors.New("contract not found")
}
// TODO perform flags checking after #923
v.LoadScript(script)
bs, err := method.TryBytes()
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(method)
return nil
@ -520,25 +473,3 @@ func contractDestroy(ic *interop.Context, v *vm.VM) error {
}
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.Contract.Call", Func: contractCall, 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.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.Create", Func: enumerator.Create, Price: 1},
{Name: "System.Enumerator.Next", Func: enumerator.Next, Price: 1},
@ -104,12 +105,6 @@ var systemInterops = []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.ECDsaCheckMultiSig", Func: crypto.ECDSACheckMultisig, 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.
funcs := []func(*interop.Context, *vm.VM) error{
contractGetScript,
contractGetStorageContext,
contractIsPayable,
storageContextAsReadOnly,
storageDelete,
storageFind,

View file

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

View file

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

View file

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

View file

@ -1,52 +1,38 @@
package state
import (
"encoding/json"
"errors"
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
"github.com/nspcc-dev/neo-go/pkg/io"
"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"
)
// Contract holds information about a smart contract in the NEO blockchain.
type Contract struct {
Script []byte
ParamList []smartcontract.ParamType
ReturnType smartcontract.ParamType
Properties smartcontract.PropertyState
Name string
CodeVersion string
Author string
Email string
Description string
ID int32
Script []byte
Manifest manifest.Manifest
scriptHash util.Uint160
}
// DecodeBinary implements Serializable interface.
func (cs *Contract) DecodeBinary(br *io.BinReader) {
cs.ID = int32(br.ReadU32LE())
cs.Script = br.ReadVarBytes()
br.ReadArray(&cs.ParamList)
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.Manifest.DecodeBinary(br)
cs.createHash()
}
// EncodeBinary implements Serializable interface.
func (cs *Contract) EncodeBinary(bw *io.BinWriter) {
bw.WriteU32LE(uint32(cs.ID))
bw.WriteVarBytes(cs.Script)
bw.WriteArray(cs.ParamList)
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)
cs.Manifest.EncodeBinary(bw)
}
// ScriptHash returns a contract script hash.
@ -64,15 +50,42 @@ func (cs *Contract) createHash() {
// HasStorage checks whether the contract has storage property set.
func (cs *Contract) HasStorage() bool {
return (cs.Properties & smartcontract.HasStorage) != 0
}
// HasDynamicInvoke checks whether the contract has dynamic invoke property set.
func (cs *Contract) HasDynamicInvoke() bool {
return (cs.Properties & smartcontract.HasDynamicInvoke) != 0
return (cs.Manifest.Features & smartcontract.HasStorage) != 0
}
// IsPayable checks whether the contract has payable property set.
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/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
"github.com/stretchr/testify/assert"
)
func TestEncodeDecodeContractState(t *testing.T) {
script := []byte("testscript")
contract := &Contract{
Script: script,
ParamList: []smartcontract.ParamType{smartcontract.StringType, smartcontract.IntegerType, smartcontract.Hash160Type},
ReturnType: smartcontract.BoolType,
Properties: smartcontract.HasStorage,
Name: "Contrato",
CodeVersion: "1.0.0",
Author: "Joe Random",
Email: "joe@example.com",
Description: "Test contract",
}
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{
h := hash.Hash160(script)
m := manifest.NewManifest(h)
m.ABI.Methods = []manifest.Method{{
Name: "main",
Parameters: []manifest.Parameter{
{
Name: "amount",
Type: smartcontract.IntegerType,
},
{
Name: "hash",
Type: smartcontract.Hash160Type,
},
},
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, true, flaggedContract.IsPayable())
assert.Equal(t, false, nonFlaggedContract.HasStorage())
assert.Equal(t, false, nonFlaggedContract.HasDynamicInvoke())
assert.Equal(t, false, nonFlaggedContract.IsPayable())
assert.Equal(t, h, contract.ScriptHash())
t.Run("Serializable", func(t *testing.T) {
contractDecoded := new(Contract)
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
SYSCurrentBlock KeyPrefix = 0xc0
SYSCurrentHeader KeyPrefix = 0xc1
SYSContractID KeyPrefix = 0xc2
SYSVersion KeyPrefix = 0xf0
)

View file

@ -3,83 +3,32 @@ Package contract provides functions to work with contracts.
*/
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
// 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
// framework.
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:
// script contract's bytecode (limited in length by 1M)
// params contract's input parameter types, one byte per parameter, see
// 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)
// manifest contract's manifest (limited in length by 2 KiB)
// It returns this new created Contract when successful (and fails transaction
// if not). It uses `Neo.Contract.Create` syscall.
func Create(
script []byte,
params []byte,
returnType byte,
properties byte,
name,
version,
author,
email,
description string) Contract {
// if not). It uses `System.Contract.Create` syscall.
func Create(script []byte, manifest []byte) Contract {
return Contract{}
}
// Migrate migrates calling contract (that is the one that calls Migrate) to
// the new contract. Its parameters have exactly the same semantics as for
// Update updates script and manifest of the calling contract (that is the one that calls Update)
// 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
// associated it will be migrated to the new contract. New contract is returned.
// This function uses `Neo.Contract.Migrate` syscall.
func Migrate(
script []byte,
params []byte,
returnType byte,
properties byte,
name,
version,
author,
email,
description string) Contract {
// This function uses `System.Contract.Update` syscall.
func Update(script []byte, manifest []byte) Contract {
return Contract{}
}
// 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
// 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() {}

View file

@ -6,6 +6,7 @@ import (
"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/state"
"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/io"
@ -177,10 +178,10 @@ func (c *Client) GetConnectionCount() (int, error) {
}
// 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 (
params = request.NewRawParams(hash.StringLE())
resp = &result.ContractState{}
resp = &state.Contract{}
)
if err := c.performRequest("getcontractstate", params, resp); err != nil {
return resp, err

View file

@ -2,6 +2,7 @@ package client
import (
"context"
"encoding/base64"
"encoding/hex"
"net/http"
"net/http/httptest"
@ -11,12 +12,15 @@ import (
"github.com/gorilla/websocket"
"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/crypto/hash"
"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/rpc/request"
"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/manifest"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/stretchr/testify/assert"
@ -293,39 +297,33 @@ var rpcClientTestCases = map[string][]rpcClientTestCase{
{
name: "positive",
invoke: func(c *Client) (interface{}, error) {
hash, err := util.Uint160DecodeStringLE("dc675afc61a7c0f7b3d2682bf6e1d8ed865a0e5f")
hash, err := util.Uint160DecodeStringLE("1b4357bff5a01bdf2a6581247cf9ed1e24629176")
if err != nil {
panic(err)
}
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{} {
hash, err := util.Uint160DecodeStringLE("dc675afc61a7c0f7b3d2682bf6e1d8ed865a0e5f")
script, err := base64.StdEncoding.DecodeString("VgJXHwIMDWNvbnRyYWN0IGNhbGx4eVMTwEEFB5IWIXhKDANQdXSXJyQAAAAQVUGEGNYNIXJqeRDOeRHOU0FSoUH1IUURQCOPAgAASgwLdG90YWxTdXBwbHmXJxEAAABFAkBCDwBAI28CAABKDAhkZWNpbWFsc5cnDQAAAEUSQCNWAgAASgwEbmFtZZcnEgAAAEUMBFJ1YmxAIzwCAABKDAZzeW1ib2yXJxEAAABFDANSVUJAIyECAABKDAliYWxhbmNlT2aXJ2IAAAAQVUGEGNYNIXN5EM50bMoAFLQnIwAAAAwPaW52YWxpZCBhZGRyZXNzEVVBNtNSBiFFENsgQGtsUEEfLnsHIXUMCWJhbGFuY2VPZmxtUxPAQQUHkhYhRW1AI7IBAABKDAh0cmFuc2ZlcpcnKwEAABBVQYQY1g0hdnkQzncHbwfKABS0JyoAAAAMFmludmFsaWQgJ2Zyb20nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRHOdwhvCMoAFLQnKAAAAAwUaW52YWxpZCAndG8nIGFkZHJlc3MRVUE201IGIUUQ2yBAeRLOdwlvCRC1JyIAAAAMDmludmFsaWQgYW1vdW50EVVBNtNSBiFFENsgQG5vB1BBHy57ByF3Cm8Kbwm1JyYAAAAMEmluc3VmZmljaWVudCBmdW5kcxFVQTbTUgYhRRDbIEBvCm8Jn3cKbm8HbwpTQVKhQfUhbm8IUEEfLnsHIXcLbwtvCZ53C25vCG8LU0FSoUH1IQwIdHJhbnNmZXJvB28IbwlUFMBBBQeSFiFFEUAjewAAAEoMBGluaXSXJ1AAAAAQVUGEGNYNIXcMEFVBh8PSZCF3DQJAQg8Adw5vDG8Nbw5TQVKhQfUhDAh0cmFuc2ZlcgwA2zBvDW8OVBTAQQUHkhYhRRFAIyMAAAAMEWludmFsaWQgb3BlcmF0aW9uQTbTUgY6IwUAAABFQA==")
if err != nil {
panic(err)
}
script, err := hex.DecodeString("5fc56b6c766b00527ac46c766b51527ac46107576f6f6c6f6e676c766b52527ac403574e476c766b53527ac4006c766b54527ac4210354ae498221046c666efebbaee9bd0eb4823469c98e748494a92a71f346b1a6616c766b55527ac46c766b00c3066465706c6f79876c766b56527ac46c766b56c36416006c766b55c36165f2026c766b57527ac462d8016c766b55c36165d801616c766b00c30b746f74616c537570706c79876c766b58527ac46c766b58c36440006168164e656f2e53746f726167652e476574436f6e7465787406737570706c79617c680f4e656f2e53746f726167652e4765746c766b57527ac46270016c766b00c3046e616d65876c766b59527ac46c766b59c36412006c766b52c36c766b57527ac46247016c766b00c30673796d626f6c876c766b5a527ac46c766b5ac36412006c766b53c36c766b57527ac4621c016c766b00c308646563696d616c73876c766b5b527ac46c766b5bc36412006c766b54c36c766b57527ac462ef006c766b00c30962616c616e63654f66876c766b5c527ac46c766b5cc36440006168164e656f2e53746f726167652e476574436f6e746578746c766b51c351c3617c680f4e656f2e53746f726167652e4765746c766b57527ac46293006c766b51c300c36168184e656f2e52756e74696d652e436865636b5769746e657373009c6c766b5d527ac46c766b5dc3640e00006c766b57527ac46255006c766b00c3087472616e73666572876c766b5e527ac46c766b5ec3642c006c766b51c300c36c766b51c351c36c766b51c352c36165d40361527265c9016c766b57527ac4620e00006c766b57527ac46203006c766b57c3616c756653c56b6c766b00527ac4616168164e656f2e53746f726167652e476574436f6e746578746c766b00c3617c680f4e656f2e53746f726167652e4765746165700351936c766b51527ac46168164e656f2e53746f726167652e476574436f6e746578746c766b00c36c766b51c361651103615272680f4e656f2e53746f726167652e507574616168164e656f2e53746f726167652e476574436f6e7465787406737570706c79617c680f4e656f2e53746f726167652e4765746165f40251936c766b52527ac46168164e656f2e53746f726167652e476574436f6e7465787406737570706c796c766b52c361659302615272680f4e656f2e53746f726167652e50757461616c756653c56b6c766b00527ac461516c766b51527ac46168164e656f2e53746f726167652e476574436f6e746578746c766b00c36c766b51c361654002615272680f4e656f2e53746f726167652e507574616168164e656f2e53746f726167652e476574436f6e7465787406737570706c796c766b51c361650202615272680f4e656f2e53746f726167652e50757461516c766b52527ac46203006c766b52c3616c756659c56b6c766b00527ac46c766b51527ac46c766b52527ac4616168164e656f2e53746f726167652e476574436f6e746578746c766b00c3617c680f4e656f2e53746f726167652e4765746c766b53527ac46168164e656f2e53746f726167652e476574436f6e746578746c766b51c3617c680f4e656f2e53746f726167652e4765746c766b54527ac46c766b53c3616576016c766b52c3946c766b55527ac46c766b54c3616560016c766b52c3936c766b56527ac46c766b55c300a2640d006c766b52c300a2620400006c766b57527ac46c766b57c364ec00616168164e656f2e53746f726167652e476574436f6e746578746c766b00c36c766b55c36165d800615272680f4e656f2e53746f726167652e507574616168164e656f2e53746f726167652e476574436f6e746578746c766b51c36c766b56c361659c00615272680f4e656f2e53746f726167652e5075746155c57600135472616e73666572205375636365737366756cc476516c766b00c3c476526c766b51c3c476536c766b52c3c476546168184e656f2e426c6f636b636861696e2e476574486569676874c46168124e656f2e52756e74696d652e4e6f7469667961516c766b58527ac4620e00006c766b58527ac46203006c766b58c3616c756653c56b6c766b00527ac4616c766b00c36c766b51527ac46c766b51c36c766b52527ac46203006c766b52c3616c756653c56b6c766b00527ac461516c766b00c36a527a527ac46c766b51c36c766b52527ac46203006c766b52c3616c7566")
if err != nil {
panic(err)
m := manifest.NewManifest(hash.Hash160(script))
m.ABI.EntryPoint.Name = "Main"
m.ABI.EntryPoint.Parameters = []manifest.Parameter{
manifest.NewParameter("method", smartcontract.StringType),
manifest.NewParameter("params", smartcontract.ArrayType),
}
return &result.ContractState{
Version: 0,
ScriptHash: hash,
Script: script,
ParamList: []smartcontract.ParamType{smartcontract.ByteArrayType},
ReturnType: smartcontract.ByteArrayType,
Name: "Woolong",
CodeVersion: "0.9.2",
Author: "lllwvlvwlll",
Email: "lllwvlvwlll@gmail.com",
Description: "GO NEO!!!",
Properties: result.Properties{
HasStorage: true,
HasDynamicInvoke: false,
IsPayable: false,
},
m.ABI.EntryPoint.ReturnType = smartcontract.BoolType
m.Features = smartcontract.HasStorage
cs := &state.Contract{
ID: 0,
Script: script,
Manifest: *m,
}
_ = cs.ScriptHash()
return cs
},
},
},

View file

@ -5,49 +5,28 @@ import (
"fmt"
"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/io"
"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/vm/emit"
"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
// with its metadata.
func CreateDeploymentScript(avm []byte, contract *smartcontract.ContractDetails) ([]byte, error) {
// with its metadata and system fee require for this.
func CreateDeploymentScript(avm []byte, manif *manifest.Manifest) ([]byte, util.Fixed8, error) {
script := io.NewBufBinWriter()
emit.Bytes(script.BinWriter, []byte(contract.Description))
emit.Bytes(script.BinWriter, []byte(contract.Email))
emit.Bytes(script.BinWriter, []byte(contract.Author))
emit.Bytes(script.BinWriter, []byte(contract.Version))
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)
w := io.NewBufBinWriter()
manif.EncodeBinary(w.BinWriter)
rawManifest := w.Bytes()
emit.Bytes(script.BinWriter, rawManifest)
emit.Bytes(script.BinWriter, avm)
emit.Syscall(script.BinWriter, "Neo.Contract.Create")
return script.Bytes(), nil
emit.Syscall(script.BinWriter, "System.Contract.Create")
sysfee := util.Fixed8(core.StoragePrice * (len(avm) + len(rawManifest)))
return script.Bytes(), sysfee, nil
}
// 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
} else {
cs := s.chain.GetContractState(scriptHash)
if cs != nil {
results = result.NewContractState(cs)
} else {
if cs == nil {
return nil, response.NewRPCError("Unknown contract", "", nil)
}
results = cs
}
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/block"
"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/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/internal/testchain"
@ -55,12 +56,12 @@ var rpcTestCases = map[string][]rpcTestCase{
"getapplicationlog": {
{
name: "positive",
params: `["328190e78f1b5b8dcf86e71ec1894d6144f4e4cdb25abc20ea02b618851941dd"]`,
params: `["136ef2ba8259d121a173da2588ae0fbb735f0f740820fded9cd8da8fee109b20"]`,
result: func(e *executor) interface{} { return &result.ApplicationLog{} },
check: func(t *testing.T, e *executor, acc interface{}) {
res, ok := acc.(*result.ApplicationLog)
require.True(t, ok)
expectedTxHash, err := util.Uint256DecodeStringLE("328190e78f1b5b8dcf86e71ec1894d6144f4e4cdb25abc20ea02b618851941dd")
expectedTxHash, err := util.Uint256DecodeStringLE("136ef2ba8259d121a173da2588ae0fbb735f0f740820fded9cd8da8fee109b20")
require.NoError(t, err)
assert.Equal(t, expectedTxHash, res.TxHash)
assert.Equal(t, 1, len(res.Executions))
@ -88,13 +89,11 @@ var rpcTestCases = map[string][]rpcTestCase{
{
name: "positive",
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{}) {
res, ok := cs.(*result.ContractState)
res, ok := cs.(*state.Contract)
require.True(t, ok)
assert.Equal(t, byte(0), res.Version)
assert.Equal(t, testContractHash, res.ScriptHash.StringLE())
assert.Equal(t, "0.99", res.CodeVersion)
assert.Equal(t, testContractHash, res.ScriptHash().StringLE())
},
},
{
@ -484,7 +483,7 @@ var rpcTestCases = map[string][]rpcTestCase{
"gettransactionheight": {
{
name: "positive",
params: `["328190e78f1b5b8dcf86e71ec1894d6144f4e4cdb25abc20ea02b618851941dd"]`,
params: `["136ef2ba8259d121a173da2588ae0fbb735f0f740820fded9cd8da8fee109b20"]`,
result: func(e *executor) interface{} {
h := 0
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 (
"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/util"
)
@ -126,3 +127,23 @@ func (m *Manifest) UnmarshalJSON(data []byte) error {
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.
const (
HasStorage PropertyState = 1 << iota
HasDynamicInvoke
IsPayable
NoProperties = 0
HasStorage PropertyState = 1 << iota
IsPayable PropertyState = 1 << 2
NoProperties = 0
)
// Parameter represents a smart contract parameter.

View file

@ -39,9 +39,6 @@ type Context struct {
// Script hash of the prog.
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")

View file

@ -136,6 +136,12 @@ func (v *VM) SetGasLimit(max util.Fixed8) {
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.
func (v *VM) Estack() *Stack {
return v.estack
@ -262,16 +268,6 @@ func (v *VM) LoadScript(b []byte) {
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,
// which implies no program is loaded.
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) {
v := New()
cache := v.GetPublicKeys()