forked from TrueCloudLab/neoneo-go
Merge pull request #1033 from nspcc-dev/feature/manifest
Add Manifest to state.Contract
This commit is contained in:
commit
5fed409ea7
38 changed files with 396 additions and 675 deletions
|
@ -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)
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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",
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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()
|
||||||
|
|
|
@ -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())
|
||||||
|
|
|
@ -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),
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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{
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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))
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
|
|
|
@ -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},
|
||||||
|
|
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
|
|
||||||
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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())
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
|
@ -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() {}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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,
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
Binary file not shown.
|
@ -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)
|
|
||||||
}
|
|
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -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.
|
||||||
|
|
|
@ -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")
|
||||||
|
|
16
pkg/vm/vm.go
16
pkg/vm/vm.go
|
@ -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 {
|
||||||
|
|
|
@ -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()
|
||||||
|
|
Loading…
Reference in a new issue