core: add Manifest to state.Contract
This commit is contained in:
parent
425277098c
commit
df958caf93
19 changed files with 229 additions and 338 deletions
|
@ -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)
|
||||||
|
|
|
@ -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)
|
||||||
|
|
|
@ -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"
|
||||||
|
@ -41,8 +40,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 +48,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,19 +223,18 @@ 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, "Neo.Contract.Create")
|
||||||
txScript := script.Bytes()
|
txScript := script.Bytes()
|
||||||
|
|
|
@ -94,8 +94,7 @@ func (ic *Context) GetContract(h util.Uint160) ([]byte, bool) {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, false
|
return nil, false
|
||||||
}
|
}
|
||||||
hasDynamicInvoke := (cs.Properties & smartcontract.HasDynamicInvoke) != 0
|
return cs.Script, cs.HasDynamicInvoke()
|
||||||
return cs.Script, hasDynamicInvoke
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewContractMD returns Contract with the specified list of methods.
|
// NewContractMD returns Contract with the specified list of methods.
|
||||||
|
|
|
@ -8,7 +8,8 @@ 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/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 +69,20 @@ 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))
|
var m manifest.Manifest
|
||||||
for k, v := range paramBytes {
|
r := io.NewBinReaderFromBuf(manifestBytes)
|
||||||
paramList[k] = smartcontract.ParamType(v)
|
m.DecodeBinary(r)
|
||||||
|
if r.Err != nil {
|
||||||
|
return nil, r.Err
|
||||||
}
|
}
|
||||||
retType := smartcontract.ParamType(v.Estack().Pop().BigInt().Int64())
|
return &state.Contract{
|
||||||
properties := smartcontract.PropertyState(v.Estack().Pop().BigInt().Int64())
|
|
||||||
name := v.Estack().Pop().Bytes()
|
|
||||||
if len(name) > MaxContractStringLen {
|
|
||||||
return nil, errors.New("too big name")
|
|
||||||
}
|
|
||||||
version := v.Estack().Pop().Bytes()
|
|
||||||
if len(version) > MaxContractStringLen {
|
|
||||||
return nil, errors.New("too big version")
|
|
||||||
}
|
|
||||||
author := v.Estack().Pop().Bytes()
|
|
||||||
if len(author) > MaxContractStringLen {
|
|
||||||
return nil, errors.New("too big author")
|
|
||||||
}
|
|
||||||
email := v.Estack().Pop().Bytes()
|
|
||||||
if len(email) > MaxContractStringLen {
|
|
||||||
return nil, errors.New("too big email")
|
|
||||||
}
|
|
||||||
desc := v.Estack().Pop().Bytes()
|
|
||||||
if len(desc) > MaxContractDescriptionLen {
|
|
||||||
return nil, errors.New("too big description")
|
|
||||||
}
|
|
||||||
contract := &state.Contract{
|
|
||||||
Script: script,
|
Script: script,
|
||||||
ParamList: paramList,
|
Manifest: m,
|
||||||
ReturnType: retType,
|
}, nil
|
||||||
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,14 +92,12 @@ 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 err != nil {
|
if contract != nil {
|
||||||
contract = newcontract
|
return errors.New("contract already exists")
|
||||||
err := ic.DAO.PutContractState(contract)
|
} else if err := ic.DAO.PutContractState(newcontract); err != nil {
|
||||||
if err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
}
|
v.Estack().PushVal(stackitem.NewInterop(newcontract))
|
||||||
v.Estack().PushVal(stackitem.NewInterop(contract))
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -154,18 +125,34 @@ func contractIsPayable(ic *interop.Context, v *vm.VM) error {
|
||||||
|
|
||||||
// contractMigrate migrates a contract.
|
// contractMigrate migrates a contract.
|
||||||
func contractMigrate(ic *interop.Context, v *vm.VM) error {
|
func contractMigrate(ic *interop.Context, v *vm.VM) error {
|
||||||
|
contract, err := ic.DAO.GetContractState(v.GetCurrentScriptHash())
|
||||||
|
if contract == nil {
|
||||||
|
return errors.New("contract doesn't exist")
|
||||||
|
}
|
||||||
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)
|
}
|
||||||
if err != nil {
|
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
|
return err
|
||||||
}
|
}
|
||||||
|
if err := ic.DAO.DeleteContractState(contract.ScriptHash()); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
if contract.HasStorage() {
|
if contract.HasStorage() {
|
||||||
|
// TODO store items by ID #1037
|
||||||
hash := v.GetCurrentScriptHash()
|
hash := v.GetCurrentScriptHash()
|
||||||
siMap, err := ic.DAO.GetStorageItems(hash)
|
siMap, err := ic.DAO.GetStorageItems(hash)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -179,7 +166,6 @@ func contractMigrate(ic *interop.Context, v *vm.VM) error {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
v.Estack().PushVal(stackitem.NewInterop(contract))
|
v.Estack().PushVal(stackitem.NewInterop(contract))
|
||||||
return contractDestroy(ic, v)
|
return contractDestroy(ic, v)
|
||||||
}
|
}
|
||||||
|
|
|
@ -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"
|
||||||
|
@ -265,16 +266,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)
|
||||||
|
|
|
@ -26,9 +26,9 @@ func Deploy(ic *interop.Context, _ *vm.VM) error {
|
||||||
}
|
}
|
||||||
|
|
||||||
cs := &state.Contract{
|
cs := &state.Contract{
|
||||||
|
ID: md.ContractID,
|
||||||
Script: md.Script,
|
Script: md.Script,
|
||||||
ParamList: params,
|
Manifest: md.Manifest,
|
||||||
ReturnType: md.Manifest.ABI.EntryPoint.ReturnType,
|
|
||||||
}
|
}
|
||||||
if err := ic.DAO.PutContractState(cs); err != nil {
|
if err := ic.DAO.PutContractState(cs); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
@ -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 {
|
||||||
|
ID int32
|
||||||
Script []byte
|
Script []byte
|
||||||
ParamList []smartcontract.ParamType
|
Manifest manifest.Manifest
|
||||||
ReturnType smartcontract.ParamType
|
|
||||||
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,47 @@ 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.
|
// HasDynamicInvoke checks whether the contract has dynamic invoke property set.
|
||||||
func (cs *Contract) HasDynamicInvoke() bool {
|
func (cs *Contract) HasDynamicInvoke() bool {
|
||||||
return (cs.Properties & smartcontract.HasDynamicInvoke) != 0
|
return (cs.Manifest.Features & 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{{
|
||||||
|
Name: "main",
|
||||||
|
Parameters: []manifest.Parameter{
|
||||||
|
{
|
||||||
|
Name: "amount",
|
||||||
|
Type: smartcontract.IntegerType,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Name: "hash",
|
||||||
|
Type: smartcontract.Hash160Type,
|
||||||
|
},
|
||||||
|
},
|
||||||
ReturnType: smartcontract.BoolType,
|
ReturnType: smartcontract.BoolType,
|
||||||
Properties: smartcontract.HasStorage,
|
}}
|
||||||
Name: "Contrato",
|
m.Features = smartcontract.HasStorage
|
||||||
CodeVersion: "1.0.0",
|
contract := &Contract{
|
||||||
Author: "Joe Random",
|
ID: 123,
|
||||||
Email: "joe@example.com",
|
Script: script,
|
||||||
Description: "Test contract",
|
Manifest: *m,
|
||||||
}
|
}
|
||||||
|
|
||||||
assert.Equal(t, hash.Hash160(script), contract.ScriptHash())
|
assert.Equal(t, h, contract.ScriptHash())
|
||||||
|
|
||||||
contractDecoded := &Contract{}
|
t.Run("Serializable", func(t *testing.T) {
|
||||||
|
contractDecoded := new(Contract)
|
||||||
testserdes.EncodeDecodeBinary(t, contract, contractDecoded)
|
testserdes.EncodeDecodeBinary(t, contract, contractDecoded)
|
||||||
assert.Equal(t, contract.ScriptHash(), contractDecoded.ScriptHash())
|
assert.Equal(t, contract.ScriptHash(), contractDecoded.ScriptHash())
|
||||||
}
|
})
|
||||||
|
t.Run("JSON", func(t *testing.T) {
|
||||||
func TestContractStateProperties(t *testing.T) {
|
contractDecoded := new(Contract)
|
||||||
flaggedContract := Contract{
|
testserdes.MarshalUnmarshalJSON(t, contract, contractDecoded)
|
||||||
Properties: smartcontract.HasStorage | smartcontract.HasDynamicInvoke | smartcontract.IsPayable,
|
assert.Equal(t, contract.ScriptHash(), contractDecoded.ScriptHash())
|
||||||
}
|
})
|
||||||
nonFlaggedContract := Contract{
|
|
||||||
ReturnType: smartcontract.BoolType,
|
|
||||||
}
|
|
||||||
assert.Equal(t, true, flaggedContract.HasStorage())
|
|
||||||
assert.Equal(t, true, flaggedContract.HasDynamicInvoke())
|
|
||||||
assert.Equal(t, true, flaggedContract.IsPayable())
|
|
||||||
assert.Equal(t, false, nonFlaggedContract.HasStorage())
|
|
||||||
assert.Equal(t, false, nonFlaggedContract.HasDynamicInvoke())
|
|
||||||
assert.Equal(t, false, nonFlaggedContract.IsPayable())
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -33,30 +33,10 @@ func GetStorageContext(c Contract) 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 `Neo.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{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -65,16 +45,7 @@ func Create(
|
||||||
// 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 `Neo.Contract.Migrate` syscall.
|
||||||
func Migrate(
|
func Migrate(script []byte, manifest []byte) Contract {
|
||||||
script []byte,
|
|
||||||
params []byte,
|
|
||||||
returnType byte,
|
|
||||||
properties byte,
|
|
||||||
name,
|
|
||||||
version,
|
|
||||||
author,
|
|
||||||
email,
|
|
||||||
description string) Contract {
|
|
||||||
return Contract{}
|
return Contract{}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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{
|
||||||
|
ID: 0,
|
||||||
Script: script,
|
Script: script,
|
||||||
ParamList: []smartcontract.ParamType{smartcontract.ByteArrayType},
|
Manifest: *m,
|
||||||
ReturnType: smartcontract.ByteArrayType,
|
|
||||||
Name: "Woolong",
|
|
||||||
CodeVersion: "0.9.2",
|
|
||||||
Author: "lllwvlvwlll",
|
|
||||||
Email: "lllwvlvwlll@gmail.com",
|
|
||||||
Description: "GO NEO!!!",
|
|
||||||
Properties: result.Properties{
|
|
||||||
HasStorage: true,
|
|
||||||
HasDynamicInvoke: false,
|
|
||||||
IsPayable: false,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
|
_ = cs.ScriptHash()
|
||||||
|
return cs
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
|
@ -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: `["9d84eee99b8fda7cba4931ae54b316c1c0468bd4526b8b4eb6cf62d771abe9c7"]`,
|
||||||
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("9d84eee99b8fda7cba4931ae54b316c1c0468bd4526b8b4eb6cf62d771abe9c7")
|
||||||
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: `["9d84eee99b8fda7cba4931ae54b316c1c0468bd4526b8b4eb6cf62d771abe9c7"]`,
|
||||||
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.
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue