stackitem: introduce Convertible interface

We have a lot of native contract types that are converted to stack items
before serialization, then deserialized as stack items and converted back to
regular structures. stackitem.Convertible allows to remove a lot of repetitive
io.Serializable code.

This also introduces to/from converter in testserdes which unfortunately
required to change util tests to avoid circular references.
This commit is contained in:
Roman Khimov 2021-07-17 18:37:33 +03:00
parent 2d993d0da5
commit aab18c3083
23 changed files with 223 additions and 339 deletions

View file

@ -5,6 +5,7 @@ import (
"testing" "testing"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
@ -26,6 +27,15 @@ func EncodeDecodeBinary(t *testing.T, expected, actual io.Serializable) {
require.Equal(t, expected, actual) require.Equal(t, expected, actual)
} }
// ToFromStackItem checks if expected stays the same after converting to/from
// StackItem.
func ToFromStackItem(t *testing.T, expected, actual stackitem.Convertible) {
item, err := expected.ToStackItem()
require.NoError(t, err)
require.NoError(t, actual.FromStackItem(item))
require.Equal(t, expected, actual)
}
// EncodeBinary serializes a to a byte slice. // EncodeBinary serializes a to a byte slice.
func EncodeBinary(a io.Serializable) ([]byte, error) { func EncodeBinary(a io.Serializable) ([]byte, error) {
w := io.NewBufBinWriter() w := io.NewBufBinWriter()

View file

@ -361,24 +361,18 @@ func (bc *Blockchain) init() error {
if storedCS == nil { if storedCS == nil {
return fmt.Errorf("native contract %s is not stored", md.Name) return fmt.Errorf("native contract %s is not stored", md.Name)
} }
w := io.NewBufBinWriter() storedCSBytes, err := stackitem.SerializeConvertible(storedCS)
storedCS.EncodeBinary(w.BinWriter) if err != nil {
if w.Err != nil { return fmt.Errorf("failed to check native %s state against autogenerated one: %w", md.Name, err)
return fmt.Errorf("failed to check native %s state against autogenerated one: %w", md.Name, w.Err)
} }
buff := w.Bytes()
storedCSBytes := make([]byte, len(buff))
copy(storedCSBytes, buff)
w.Reset()
autogenCS := &state.Contract{ autogenCS := &state.Contract{
ContractBase: md.ContractBase, ContractBase: md.ContractBase,
UpdateCounter: storedCS.UpdateCounter, // it can be restored only from the DB, so use the stored value. UpdateCounter: storedCS.UpdateCounter, // it can be restored only from the DB, so use the stored value.
} }
autogenCS.EncodeBinary(w.BinWriter) autogenCSBytes, err := stackitem.SerializeConvertible(autogenCS)
if w.Err != nil { if err != nil {
return fmt.Errorf("failed to check native %s state against autogenerated one: %w", md.Name, w.Err) return fmt.Errorf("failed to check native %s state against autogenerated one: %w", md.Name, err)
} }
autogenCSBytes := w.Bytes()
if !bytes.Equal(storedCSBytes, autogenCSBytes) { if !bytes.Equal(storedCSBytes, autogenCSBytes) {
return fmt.Errorf("native %s: version mismatch (stored contract state differs from autogenerated one), "+ return fmt.Errorf("native %s: version mismatch (stored contract state differs from autogenerated one), "+
"try to resynchronize the node from the genesis", md.Name) "try to resynchronize the node from the genesis", md.Name)

View file

@ -18,7 +18,6 @@ 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/crypto/hash" "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/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/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
@ -272,10 +271,9 @@ func (s *Designate) GetDesignatedByRole(d dao.DAO, r noderoles.Role, index uint3
} }
} }
if resSi != nil { if resSi != nil {
reader := io.NewBinReaderFromBuf(resSi) err = stackitem.DeserializeConvertible(resSi, &ns)
ns.DecodeBinary(reader) if err != nil {
if reader.Err != nil { return nil, 0, err
return nil, 0, reader.Err
} }
} }
return keys.PublicKeys(ns), bestIndex, err return keys.PublicKeys(ns), bestIndex, err
@ -287,7 +285,7 @@ func (s *Designate) designateAsRole(ic *interop.Context, args []stackitem.Item)
panic(ErrInvalidRole) panic(ErrInvalidRole)
} }
var ns NodeList var ns NodeList
if err := ns.fromStackItem(args[1]); err != nil { if err := ns.FromStackItem(args[1]); err != nil {
panic(err) panic(err)
} }
@ -326,8 +324,9 @@ func (s *Designate) DesignateAsRole(ic *interop.Context, r noderoles.Role, pubs
return ErrAlreadyDesignated return ErrAlreadyDesignated
} }
sort.Sort(pubs) sort.Sort(pubs)
nl := NodeList(pubs)
s.rolesChangedFlag.Store(true) s.rolesChangedFlag.Store(true)
err := ic.DAO.PutStorageItem(s.ID, key, NodeList(pubs).Bytes()) err := putConvertibleToDAO(s.ID, ic.DAO, key, &nl)
if err != nil { if err != nil {
return err return err
} }

View file

@ -16,7 +16,6 @@ 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/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
"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/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag"
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
@ -155,7 +154,7 @@ func (m *Management) GetContract(d dao.DAO, hash util.Uint160) (*state.Contract,
func (m *Management) getContractFromDAO(d dao.DAO, hash util.Uint160) (*state.Contract, error) { func (m *Management) getContractFromDAO(d dao.DAO, hash util.Uint160) (*state.Contract, error) {
contract := new(state.Contract) contract := new(state.Contract)
key := makeContractKey(hash) key := makeContractKey(hash)
err := getSerializableFromDAO(m.ID, d, key, contract) err := getConvertibleFromDAO(m.ID, d, key, contract)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -487,14 +486,12 @@ func (m *Management) InitializeCache(d dao.DAO) error {
var initErr error var initErr error
d.Seek(m.ID, []byte{prefixContract}, func(_, v []byte) { d.Seek(m.ID, []byte{prefixContract}, func(_, v []byte) {
var cs state.Contract var cs = new(state.Contract)
r := io.NewBinReaderFromBuf(v) initErr = stackitem.DeserializeConvertible(v, cs)
cs.DecodeBinary(r) if initErr != nil {
if r.Err != nil {
initErr = r.Err
return return
} }
m.contracts[cs.Hash] = &cs m.contracts[cs.Hash] = cs
}) })
return initErr return initErr
} }
@ -529,7 +526,7 @@ func (m *Management) Initialize(ic *interop.Context) error {
// PutContractState saves given contract state into given DAO. // PutContractState saves given contract state into given DAO.
func (m *Management) PutContractState(d dao.DAO, cs *state.Contract) error { func (m *Management) PutContractState(d dao.DAO, cs *state.Contract) error {
key := makeContractKey(cs.Hash) key := makeContractKey(cs.Hash)
if err := putSerializableToDAO(m.ID, d, key, cs); err != nil { if err := putConvertibleToDAO(m.ID, d, key, cs); err != nil {
return err return err
} }
m.markUpdated(cs.Hash) m.markUpdated(cs.Hash)

View file

@ -697,7 +697,7 @@ func (n *NEO) RegisterCandidateInternal(ic *interop.Context, pub *keys.PublicKey
c = new(candidate).FromBytes(si) c = new(candidate).FromBytes(si)
c.Registered = true c.Registered = true
} }
return ic.DAO.PutStorageItem(n.ID, key, c.Bytes()) return putConvertibleToDAO(n.ID, ic.DAO, key, c)
} }
func (n *NEO) unregisterCandidate(ic *interop.Context, args []stackitem.Item) stackitem.Item { func (n *NEO) unregisterCandidate(ic *interop.Context, args []stackitem.Item) stackitem.Item {
@ -726,7 +726,7 @@ func (n *NEO) UnregisterCandidateInternal(ic *interop.Context, pub *keys.PublicK
if ok { if ok {
return err return err
} }
return ic.DAO.PutStorageItem(n.ID, key, c.Bytes()) return putConvertibleToDAO(n.ID, ic.DAO, key, c)
} }
func (n *NEO) vote(ic *interop.Context, args []stackitem.Item) stackitem.Item { func (n *NEO) vote(ic *interop.Context, args []stackitem.Item) stackitem.Item {
@ -819,7 +819,7 @@ func (n *NEO) ModifyAccountVotes(acc *state.NEOBalanceState, d dao.DAO, value *b
} }
} }
n.validators.Store(keys.PublicKeys(nil)) n.validators.Store(keys.PublicKeys(nil))
return d.PutStorageItem(n.ID, key, cd.Bytes()) return putConvertibleToDAO(n.ID, d, key, cd)
} }
return nil return nil
} }

View file

@ -11,41 +11,35 @@ type candidate struct {
Votes big.Int Votes big.Int
} }
// Bytes marshals c to byte array.
func (c *candidate) Bytes() []byte {
res, err := stackitem.Serialize(c.toStackItem())
if err != nil {
panic(err)
}
return res
}
// FromBytes unmarshals candidate from byte array. // FromBytes unmarshals candidate from byte array.
func (c *candidate) FromBytes(data []byte) *candidate { func (c *candidate) FromBytes(data []byte) *candidate {
item, err := stackitem.Deserialize(data) err := stackitem.DeserializeConvertible(data, c)
if err != nil { if err != nil {
panic(err) panic(err)
} }
return c.fromStackItem(item) return c
} }
func (c *candidate) toStackItem() stackitem.Item { // ToStackItem implements stackitem.Convertible. It never returns an error.
func (c *candidate) ToStackItem() (stackitem.Item, error) {
return stackitem.NewStruct([]stackitem.Item{ return stackitem.NewStruct([]stackitem.Item{
stackitem.NewBool(c.Registered), stackitem.NewBool(c.Registered),
stackitem.NewBigInteger(&c.Votes), stackitem.NewBigInteger(&c.Votes),
}) }), nil
} }
func (c *candidate) fromStackItem(item stackitem.Item) *candidate { // FromStackItem implements stackitem.Convertible.
func (c *candidate) FromStackItem(item stackitem.Item) error {
arr := item.(*stackitem.Struct).Value().([]stackitem.Item) arr := item.(*stackitem.Struct).Value().([]stackitem.Item)
vs, err := arr[1].TryInteger() vs, err := arr[1].TryInteger()
if err != nil { if err != nil {
panic(err) return err
} }
c.Registered, err = arr[0].TryBool() reg, err := arr[0].TryBool()
if err != nil { if err != nil {
panic(err) return err
} }
c.Registered = reg
c.Votes = *vs c.Votes = *vs
return c return nil
} }

View file

@ -4,7 +4,7 @@ import (
"math/big" "math/big"
"testing" "testing"
"github.com/stretchr/testify/require" "github.com/nspcc-dev/neo-go/internal/testserdes"
) )
func TestCandidate_Bytes(t *testing.T) { func TestCandidate_Bytes(t *testing.T) {
@ -12,7 +12,6 @@ func TestCandidate_Bytes(t *testing.T) {
Registered: true, Registered: true,
Votes: *big.NewInt(0x0F), Votes: *big.NewInt(0x0F),
} }
data := expected.Bytes() actual := new(candidate)
actual := new(candidate).FromBytes(data) testserdes.ToFromStackItem(t, expected, actual)
require.Equal(t, expected, actual)
} }

View file

@ -414,7 +414,7 @@ func (n *Notary) setMaxNotValidBeforeDelta(ic *interop.Context, args []stackitem
func (n *Notary) GetDepositFor(dao dao.DAO, acc util.Uint160) *state.Deposit { func (n *Notary) GetDepositFor(dao dao.DAO, acc util.Uint160) *state.Deposit {
key := append([]byte{prefixDeposit}, acc.BytesBE()...) key := append([]byte{prefixDeposit}, acc.BytesBE()...)
deposit := new(state.Deposit) deposit := new(state.Deposit)
err := getSerializableFromDAO(n.ID, dao, key, deposit) err := getConvertibleFromDAO(n.ID, dao, key, deposit)
if err == nil { if err == nil {
return deposit return deposit
} }
@ -427,7 +427,7 @@ func (n *Notary) GetDepositFor(dao dao.DAO, acc util.Uint160) *state.Deposit {
// putDepositFor puts deposit on the balance of the specified account in the storage. // putDepositFor puts deposit on the balance of the specified account in the storage.
func (n *Notary) putDepositFor(dao dao.DAO, deposit *state.Deposit, acc util.Uint160) error { func (n *Notary) putDepositFor(dao dao.DAO, deposit *state.Deposit, acc util.Uint160) error {
key := append([]byte{prefixDeposit}, acc.BytesBE()...) key := append([]byte{prefixDeposit}, acc.BytesBE()...)
return putSerializableToDAO(n.ID, dao, key, deposit) return putConvertibleToDAO(n.ID, dao, key, deposit)
} }
// removeDepositFor removes deposit from the storage. // removeDepositFor removes deposit from the storage.

View file

@ -170,7 +170,7 @@ func (o *Oracle) PostPersist(ic *interop.Context) error {
} }
reqKey := makeRequestKey(resp.ID) reqKey := makeRequestKey(resp.ID)
req := new(state.OracleRequest) req := new(state.OracleRequest)
if err := o.getSerializableFromDAO(ic.DAO, reqKey, req); err != nil { if err := o.getConvertibleFromDAO(ic.DAO, reqKey, req); err != nil {
continue continue
} }
if err := ic.DAO.DeleteStorageItem(o.ID, reqKey); err != nil { if err := ic.DAO.DeleteStorageItem(o.ID, reqKey); err != nil {
@ -182,7 +182,7 @@ func (o *Oracle) PostPersist(ic *interop.Context) error {
idKey := makeIDListKey(req.URL) idKey := makeIDListKey(req.URL)
idList := new(IDList) idList := new(IDList)
if err := o.getSerializableFromDAO(ic.DAO, idKey, idList); err != nil { if err := o.getConvertibleFromDAO(ic.DAO, idKey, idList); err != nil {
return err return err
} }
if !idList.Remove(resp.ID) { if !idList.Remove(resp.ID) {
@ -193,7 +193,7 @@ func (o *Oracle) PostPersist(ic *interop.Context) error {
if len(*idList) == 0 { if len(*idList) == 0 {
err = ic.DAO.DeleteStorageItem(o.ID, idKey) err = ic.DAO.DeleteStorageItem(o.ID, idKey)
} else { } else {
err = ic.DAO.PutStorageItem(o.ID, idKey, idList.Bytes()) err = putConvertibleToDAO(o.ID, ic.DAO, idKey, idList)
} }
if err != nil { if err != nil {
return err return err
@ -398,7 +398,7 @@ func (o *Oracle) RequestInternal(ic *interop.Context, url string, filter *string
// PutRequestInternal puts oracle request with the specified id to d. // PutRequestInternal puts oracle request with the specified id to d.
func (o *Oracle) PutRequestInternal(id uint64, req *state.OracleRequest, d dao.DAO) error { func (o *Oracle) PutRequestInternal(id uint64, req *state.OracleRequest, d dao.DAO) error {
reqKey := makeRequestKey(id) reqKey := makeRequestKey(id)
if err := d.PutStorageItem(o.ID, reqKey, req.Bytes()); err != nil { if err := putConvertibleToDAO(o.ID, d, reqKey, req); err != nil {
return err return err
} }
o.newRequests[id] = req o.newRequests[id] = req
@ -406,14 +406,14 @@ func (o *Oracle) PutRequestInternal(id uint64, req *state.OracleRequest, d dao.D
// Add request ID to the id list. // Add request ID to the id list.
lst := new(IDList) lst := new(IDList)
key := makeIDListKey(req.URL) key := makeIDListKey(req.URL)
if err := o.getSerializableFromDAO(d, key, lst); err != nil && !errors.Is(err, storage.ErrKeyNotFound) { if err := o.getConvertibleFromDAO(d, key, lst); err != nil && !errors.Is(err, storage.ErrKeyNotFound) {
return err return err
} }
if len(*lst) >= maxRequestsCount { if len(*lst) >= maxRequestsCount {
return fmt.Errorf("there are too many pending requests for %s url", req.URL) return fmt.Errorf("there are too many pending requests for %s url", req.URL)
} }
*lst = append(*lst, id) *lst = append(*lst, id)
return d.PutStorageItem(o.ID, key, lst.Bytes()) return putConvertibleToDAO(o.ID, d, key, lst)
} }
// GetScriptHash returns script hash or oracle nodes. // GetScriptHash returns script hash or oracle nodes.
@ -431,14 +431,14 @@ func (o *Oracle) GetOracleNodes(d dao.DAO) (keys.PublicKeys, error) {
func (o *Oracle) GetRequestInternal(d dao.DAO, id uint64) (*state.OracleRequest, error) { func (o *Oracle) GetRequestInternal(d dao.DAO, id uint64) (*state.OracleRequest, error) {
key := makeRequestKey(id) key := makeRequestKey(id)
req := new(state.OracleRequest) req := new(state.OracleRequest)
return req, o.getSerializableFromDAO(d, key, req) return req, o.getConvertibleFromDAO(d, key, req)
} }
// GetIDListInternal returns request by ID and key under which it is stored. // GetIDListInternal returns request by ID and key under which it is stored.
func (o *Oracle) GetIDListInternal(d dao.DAO, url string) (*IDList, error) { func (o *Oracle) GetIDListInternal(d dao.DAO, url string) (*IDList, error) {
key := makeIDListKey(url) key := makeIDListKey(url)
idList := new(IDList) idList := new(IDList)
return idList, o.getSerializableFromDAO(d, key, idList) return idList, o.getConvertibleFromDAO(d, key, idList)
} }
func (o *Oracle) verify(ic *interop.Context, _ []stackitem.Item) stackitem.Item { func (o *Oracle) verify(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
@ -493,11 +493,10 @@ func (o *Oracle) getRequests(d dao.DAO) (map[uint64]*state.OracleRequest, error)
if len(k) != 8 { if len(k) != 8 {
return nil, errors.New("invalid request ID") return nil, errors.New("invalid request ID")
} }
r := io.NewBinReaderFromBuf(si)
req := new(state.OracleRequest) req := new(state.OracleRequest)
req.DecodeBinary(r) err = stackitem.DeserializeConvertible(si, req)
if r.Err != nil { if err != nil {
return nil, r.Err return nil, err
} }
id := binary.BigEndian.Uint64([]byte(k)) id := binary.BigEndian.Uint64([]byte(k))
reqs[id] = req reqs[id] = req
@ -516,8 +515,8 @@ func makeIDListKey(url string) []byte {
return append(prefixIDList, hash.Hash160([]byte(url)).BytesBE()...) return append(prefixIDList, hash.Hash160([]byte(url)).BytesBE()...)
} }
func (o *Oracle) getSerializableFromDAO(d dao.DAO, key []byte, item io.Serializable) error { func (o *Oracle) getConvertibleFromDAO(d dao.DAO, key []byte, item stackitem.Convertible) error {
return getSerializableFromDAO(o.ID, d, key, item) return getConvertibleFromDAO(o.ID, d, key, item)
} }
// updateCache updates cached Oracle values if they've been changed. // updateCache updates cached Oracle values if they've been changed.

View file

@ -6,7 +6,6 @@ import (
"math/big" "math/big"
"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/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
) )
@ -16,36 +15,17 @@ type IDList []uint64
// NodeList represents list or oracle nodes. // NodeList represents list or oracle nodes.
type NodeList keys.PublicKeys type NodeList keys.PublicKeys
// Bytes return l serizalized to a byte-slice. // ToStackItem implements stackitem.Convertible. It never returns an error.
func (l IDList) Bytes() []byte { func (l IDList) ToStackItem() (stackitem.Item, error) {
w := io.NewBufBinWriter()
l.EncodeBinary(w.BinWriter)
return w.Bytes()
}
// EncodeBinary implements io.Serializable.
func (l IDList) EncodeBinary(w *io.BinWriter) {
stackitem.EncodeBinary(l.toStackItem(), w)
}
// DecodeBinary implements io.Serializable.
func (l *IDList) DecodeBinary(r *io.BinReader) {
item := stackitem.DecodeBinary(r)
if r.Err != nil || item == nil {
return
}
r.Err = l.fromStackItem(item)
}
func (l IDList) toStackItem() stackitem.Item {
arr := make([]stackitem.Item, len(l)) arr := make([]stackitem.Item, len(l))
for i := range l { for i := range l {
arr[i] = stackitem.NewBigInteger(new(big.Int).SetUint64(l[i])) arr[i] = stackitem.NewBigInteger(new(big.Int).SetUint64(l[i]))
} }
return stackitem.NewArray(arr) return stackitem.NewArray(arr), nil
} }
func (l *IDList) fromStackItem(it stackitem.Item) error { // FromStackItem implements stackitem.Convertible.
func (l *IDList) FromStackItem(it stackitem.Item) error {
arr, ok := it.Value().([]stackitem.Item) arr, ok := it.Value().([]stackitem.Item)
if !ok { if !ok {
return errors.New("not an array") return errors.New("not an array")
@ -75,36 +55,17 @@ func (l *IDList) Remove(id uint64) bool {
return false return false
} }
// Bytes return l serizalized to a byte-slice. // ToStackItem implements stackitem.Convertible. It never returns an error.
func (l NodeList) Bytes() []byte { func (l NodeList) ToStackItem() (stackitem.Item, error) {
w := io.NewBufBinWriter()
l.EncodeBinary(w.BinWriter)
return w.Bytes()
}
// EncodeBinary implements io.Serializable.
func (l NodeList) EncodeBinary(w *io.BinWriter) {
stackitem.EncodeBinary(l.toStackItem(), w)
}
// DecodeBinary implements io.Serializable.
func (l *NodeList) DecodeBinary(r *io.BinReader) {
item := stackitem.DecodeBinary(r)
if r.Err != nil || item == nil {
return
}
r.Err = l.fromStackItem(item)
}
func (l NodeList) toStackItem() stackitem.Item {
arr := make([]stackitem.Item, len(l)) arr := make([]stackitem.Item, len(l))
for i := range l { for i := range l {
arr[i] = stackitem.NewByteArray(l[i].Bytes()) arr[i] = stackitem.NewByteArray(l[i].Bytes())
} }
return stackitem.NewArray(arr) return stackitem.NewArray(arr), nil
} }
func (l *NodeList) fromStackItem(it stackitem.Item) error { // FromStackItem implements stackitem.Convertible.
func (l *NodeList) FromStackItem(it stackitem.Item) error {
arr, ok := it.Value().([]stackitem.Item) arr, ok := it.Value().([]stackitem.Item)
if !ok { if !ok {
return errors.New("not an array") return errors.New("not an array")

View file

@ -5,32 +5,26 @@ import (
"github.com/nspcc-dev/neo-go/internal/testserdes" "github.com/nspcc-dev/neo-go/internal/testserdes"
"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/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func getInvalidTestFunc(actual io.Serializable, value interface{}) func(t *testing.T) { func getInvalidTestFunc(actual stackitem.Convertible, value interface{}) func(t *testing.T) {
return func(t *testing.T) { return func(t *testing.T) {
w := io.NewBufBinWriter()
it := stackitem.Make(value) it := stackitem.Make(value)
stackitem.EncodeBinary(it, w.BinWriter) require.Error(t, actual.FromStackItem(it))
require.NoError(t, w.Err)
require.Error(t, testserdes.DecodeBinary(w.Bytes(), actual))
} }
} }
func TestIDList_EncodeBinary(t *testing.T) { func TestIDListToFromSI(t *testing.T) {
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
l := &IDList{1, 4, 5} l := &IDList{1, 4, 5}
testserdes.EncodeDecodeBinary(t, l, new(IDList)) var l2 = new(IDList)
testserdes.ToFromStackItem(t, l, l2)
}) })
t.Run("Invalid", func(t *testing.T) { t.Run("Invalid", func(t *testing.T) {
t.Run("NotArray", getInvalidTestFunc(new(IDList), []byte{})) t.Run("NotArray", getInvalidTestFunc(new(IDList), []byte{}))
t.Run("InvalidElement", getInvalidTestFunc(new(IDList), []stackitem.Item{stackitem.Null{}})) t.Run("InvalidElement", getInvalidTestFunc(new(IDList), []stackitem.Item{stackitem.Null{}}))
t.Run("NotStackItem", func(t *testing.T) {
require.Error(t, testserdes.DecodeBinary([]byte{0x77}, new(IDList)))
})
}) })
} }
@ -50,22 +44,20 @@ func TestIDList_Remove(t *testing.T) {
require.Equal(t, IDList{1}, l) require.Equal(t, IDList{1}, l)
} }
func TestNodeList_EncodeBinary(t *testing.T) { func TestNodeListToFromSI(t *testing.T) {
priv, err := keys.NewPrivateKey() priv, err := keys.NewPrivateKey()
require.NoError(t, err) require.NoError(t, err)
pub := priv.PublicKey() pub := priv.PublicKey()
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
l := &NodeList{pub} l := &NodeList{pub}
testserdes.EncodeDecodeBinary(t, l, new(NodeList)) var l2 = new(NodeList)
testserdes.ToFromStackItem(t, l, l2)
}) })
t.Run("Invalid", func(t *testing.T) { t.Run("Invalid", func(t *testing.T) {
t.Run("NotArray", getInvalidTestFunc(new(NodeList), []byte{})) t.Run("NotArray", getInvalidTestFunc(new(NodeList), []byte{}))
t.Run("InvalidElement", getInvalidTestFunc(new(NodeList), []stackitem.Item{stackitem.Null{}})) t.Run("InvalidElement", getInvalidTestFunc(new(NodeList), []stackitem.Item{stackitem.Null{}}))
t.Run("InvalidKey", getInvalidTestFunc(new(NodeList), t.Run("InvalidKey", getInvalidTestFunc(new(NodeList),
[]stackitem.Item{stackitem.NewByteArray([]byte{0x9})})) []stackitem.Item{stackitem.NewByteArray([]byte{0x9})}))
t.Run("NotStackItem", func(t *testing.T) {
require.Error(t, testserdes.DecodeBinary([]byte{0x77}, new(NodeList)))
})
}) })
} }

View file

@ -8,30 +8,26 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/dao" "github.com/nspcc-dev/neo-go/pkg/core/dao"
"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/encoding/bigint" "github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
) )
var intOne = big.NewInt(1) var intOne = big.NewInt(1)
func getSerializableFromDAO(id int32, d dao.DAO, key []byte, item io.Serializable) error { func getConvertibleFromDAO(id int32, d dao.DAO, key []byte, conv stackitem.Convertible) error {
si := d.GetStorageItem(id, key) si := d.GetStorageItem(id, key)
if si == nil { if si == nil {
return storage.ErrKeyNotFound return storage.ErrKeyNotFound
} }
r := io.NewBinReaderFromBuf(si) return stackitem.DeserializeConvertible(si, conv)
item.DecodeBinary(r)
return r.Err
} }
func putSerializableToDAO(id int32, d dao.DAO, key []byte, item io.Serializable) error { func putConvertibleToDAO(id int32, d dao.DAO, key []byte, conv stackitem.Convertible) error {
w := io.NewBufBinWriter() data, err := stackitem.SerializeConvertible(conv)
item.EncodeBinary(w.BinWriter) if err != nil {
if w.Err != nil { return err
return w.Err
} }
return d.PutStorageItem(id, key, w.Bytes()) return d.PutStorageItem(id, key, data)
} }
func setIntWithKey(id int32, dao dao.DAO, key []byte, value int64) error { func setIntWithKey(id int32, dao dao.DAO, key []byte, value int64) error {

View file

@ -35,25 +35,6 @@ type NativeContract struct {
UpdateHistory []uint32 `json:"updatehistory"` UpdateHistory []uint32 `json:"updatehistory"`
} }
// DecodeBinary implements Serializable interface.
func (c *Contract) DecodeBinary(r *io.BinReader) {
si := stackitem.DecodeBinary(r)
if r.Err != nil {
return
}
r.Err = c.FromStackItem(si)
}
// EncodeBinary implements Serializable interface.
func (c *Contract) EncodeBinary(w *io.BinWriter) {
si, err := c.ToStackItem()
if err != nil {
w.Err = err
return
}
stackitem.EncodeBinary(si, w)
}
// ToStackItem converts state.Contract to stackitem.Item. // ToStackItem converts state.Contract to stackitem.Item.
func (c *Contract) ToStackItem() (stackitem.Item, error) { func (c *Contract) ToStackItem() (stackitem.Item, error) {
rawNef, err := c.NEF.Bytes() rawNef, err := c.NEF.Bytes()

View file

@ -14,7 +14,7 @@ import (
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestEncodeDecodeContractState(t *testing.T) { func TestContractStateToFromSI(t *testing.T) {
script := []byte("testscript") script := []byte("testscript")
h := hash.Hash160(script) h := hash.Hash160(script)
@ -52,9 +52,9 @@ func TestEncodeDecodeContractState(t *testing.T) {
} }
contract.NEF.Checksum = contract.NEF.CalculateChecksum() contract.NEF.Checksum = contract.NEF.CalculateChecksum()
t.Run("Serializable", func(t *testing.T) { t.Run("Convertible", func(t *testing.T) {
contractDecoded := new(Contract) contractDecoded := new(Contract)
testserdes.EncodeDecodeBinary(t, contract, contractDecoded) testserdes.ToFromStackItem(t, contract, contractDecoded)
}) })
t.Run("JSON", func(t *testing.T) { t.Run("JSON", func(t *testing.T) {
contractDecoded := new(Contract) contractDecoded := new(Contract)

View file

@ -6,7 +6,6 @@ import (
"math" "math"
"math/big" "math/big"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
) )
@ -16,28 +15,17 @@ type Deposit struct {
Till uint32 Till uint32
} }
// EncodeBinary implements io.Serializable interface. // ToStackItem implements stackitem.Convertible interface. It never returns an
func (d *Deposit) EncodeBinary(w *io.BinWriter) { // error.
stackitem.EncodeBinary(d.toStackItem(), w) func (d *Deposit) ToStackItem() (stackitem.Item, error) {
}
// DecodeBinary implements io.Serializable interface.
func (d *Deposit) DecodeBinary(r *io.BinReader) {
si := stackitem.DecodeBinary(r)
if r.Err != nil {
return
}
r.Err = d.fromStackItem(si)
}
func (d *Deposit) toStackItem() stackitem.Item {
return stackitem.NewStruct([]stackitem.Item{ return stackitem.NewStruct([]stackitem.Item{
stackitem.NewBigInteger(d.Amount), stackitem.NewBigInteger(d.Amount),
stackitem.Make(d.Till), stackitem.Make(d.Till),
}) }), nil
} }
func (d *Deposit) fromStackItem(it stackitem.Item) error { // FromStackItem implements stackitem.Convertible interface.
func (d *Deposit) FromStackItem(it stackitem.Item) error {
items, ok := it.Value().([]stackitem.Item) items, ok := it.Value().([]stackitem.Item)
if !ok { if !ok {
return errors.New("not a struct") return errors.New("not a struct")

View file

@ -11,49 +11,44 @@ import (
func TestEncodeDecodeDeposit(t *testing.T) { func TestEncodeDecodeDeposit(t *testing.T) {
d := &Deposit{Amount: big.NewInt(100500), Till: 888} d := &Deposit{Amount: big.NewInt(100500), Till: 888}
depo := new(Deposit) depo := new(Deposit)
testserdes.EncodeDecodeBinary(t, d, depo) testserdes.ToFromStackItem(t, d, depo)
item := stackitem.Make(42)
data, err := stackitem.Serialize(item)
require.NoError(t, err)
require.Error(t, testserdes.DecodeBinary(data, depo))
} }
func TestDepositFromStackItem(t *testing.T) { func TestDepositFromStackItem(t *testing.T) {
var d Deposit var d Deposit
item := stackitem.Make(42) item := stackitem.Make(42)
require.Error(t, d.fromStackItem(item)) require.Error(t, d.FromStackItem(item))
item = stackitem.NewStruct(nil) item = stackitem.NewStruct(nil)
require.Error(t, d.fromStackItem(item)) require.Error(t, d.FromStackItem(item))
item = stackitem.NewStruct([]stackitem.Item{ item = stackitem.NewStruct([]stackitem.Item{
stackitem.NewStruct(nil), stackitem.NewStruct(nil),
stackitem.NewStruct(nil), stackitem.NewStruct(nil),
}) })
require.Error(t, d.fromStackItem(item)) require.Error(t, d.FromStackItem(item))
item = stackitem.NewStruct([]stackitem.Item{ item = stackitem.NewStruct([]stackitem.Item{
stackitem.Make(777), stackitem.Make(777),
stackitem.NewStruct(nil), stackitem.NewStruct(nil),
}) })
require.Error(t, d.fromStackItem(item)) require.Error(t, d.FromStackItem(item))
item = stackitem.NewStruct([]stackitem.Item{ item = stackitem.NewStruct([]stackitem.Item{
stackitem.Make(777), stackitem.Make(777),
stackitem.Make(-1), stackitem.Make(-1),
}) })
require.Error(t, d.fromStackItem(item)) require.Error(t, d.FromStackItem(item))
item = stackitem.NewStruct([]stackitem.Item{ item = stackitem.NewStruct([]stackitem.Item{
stackitem.Make(777), stackitem.Make(777),
stackitem.Make("somenonu64value"), stackitem.Make("somenonu64value"),
}) })
require.Error(t, d.fromStackItem(item)) require.Error(t, d.FromStackItem(item))
item = stackitem.NewStruct([]stackitem.Item{ item = stackitem.NewStruct([]stackitem.Item{
stackitem.Make(777), stackitem.Make(777),
stackitem.Make(888), stackitem.Make(888),
}) })
require.NoError(t, d.fromStackItem(item)) require.NoError(t, d.FromStackItem(item))
} }

View file

@ -7,7 +7,6 @@ import (
"math/big" "math/big"
"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/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
) )
@ -26,99 +25,81 @@ type NEOBalanceState struct {
// NEP17BalanceStateFromBytes converts serialized NEP17BalanceState to structure. // NEP17BalanceStateFromBytes converts serialized NEP17BalanceState to structure.
func NEP17BalanceStateFromBytes(b []byte) (*NEP17BalanceState, error) { func NEP17BalanceStateFromBytes(b []byte) (*NEP17BalanceState, error) {
balance := new(NEP17BalanceState) balance := new(NEP17BalanceState)
if len(b) == 0 { err := balanceFromBytes(b, balance)
return balance, nil if err != nil {
} return nil, err
r := io.NewBinReaderFromBuf(b)
balance.DecodeBinary(r)
if r.Err != nil {
return nil, r.Err
} }
return balance, nil return balance, nil
} }
// Bytes returns serialized NEP17BalanceState. // Bytes returns serialized NEP17BalanceState.
func (s *NEP17BalanceState) Bytes() []byte { func (s *NEP17BalanceState) Bytes() []byte {
w := io.NewBufBinWriter() return balanceToBytes(s)
s.EncodeBinary(w.BinWriter) }
if w.Err != nil {
panic(w.Err) func balanceFromBytes(b []byte, item stackitem.Convertible) error {
if len(b) == 0 {
return nil
} }
return w.Bytes() return stackitem.DeserializeConvertible(b, item)
} }
func (s *NEP17BalanceState) toStackItem() stackitem.Item { func balanceToBytes(item stackitem.Convertible) []byte {
return stackitem.NewStruct([]stackitem.Item{stackitem.NewBigInteger(&s.Balance)}) data, err := stackitem.SerializeConvertible(item)
} if err != nil {
panic(err)
func (s *NEP17BalanceState) fromStackItem(item stackitem.Item) {
s.Balance = *item.(*stackitem.Struct).Value().([]stackitem.Item)[0].Value().(*big.Int)
}
// EncodeBinary implements io.Serializable interface.
func (s *NEP17BalanceState) EncodeBinary(w *io.BinWriter) {
si := s.toStackItem()
stackitem.EncodeBinary(si, w)
}
// DecodeBinary implements io.Serializable interface.
func (s *NEP17BalanceState) DecodeBinary(r *io.BinReader) {
si := stackitem.DecodeBinary(r)
if r.Err != nil {
return
} }
s.fromStackItem(si) return data
}
// ToStackItem implements stackitem.Convertible. It never returns an error.
func (s *NEP17BalanceState) ToStackItem() (stackitem.Item, error) {
return stackitem.NewStruct([]stackitem.Item{stackitem.NewBigInteger(&s.Balance)}), nil
}
// FromStackItem implements stackitem.Convertible.
func (s *NEP17BalanceState) FromStackItem(item stackitem.Item) error {
items, ok := item.Value().([]stackitem.Item)
if !ok {
return errors.New("not a struct")
}
if len(items) < 1 {
return errors.New("no balance value")
}
balance, err := items[0].TryInteger()
if err != nil {
return fmt.Errorf("invalid balance: %w", err)
}
s.Balance = *balance
return nil
} }
// NEOBalanceStateFromBytes converts serialized NEOBalanceState to structure. // NEOBalanceStateFromBytes converts serialized NEOBalanceState to structure.
func NEOBalanceStateFromBytes(b []byte) (*NEOBalanceState, error) { func NEOBalanceStateFromBytes(b []byte) (*NEOBalanceState, error) {
balance := new(NEOBalanceState) balance := new(NEOBalanceState)
if len(b) == 0 { err := balanceFromBytes(b, balance)
return balance, nil if err != nil {
} return nil, err
r := io.NewBinReaderFromBuf(b)
balance.DecodeBinary(r)
if r.Err != nil {
return nil, r.Err
} }
return balance, nil return balance, nil
} }
// Bytes returns serialized NEOBalanceState. // Bytes returns serialized NEOBalanceState.
func (s *NEOBalanceState) Bytes() []byte { func (s *NEOBalanceState) Bytes() []byte {
w := io.NewBufBinWriter() return balanceToBytes(s)
s.EncodeBinary(w.BinWriter)
if w.Err != nil {
panic(w.Err)
}
return w.Bytes()
} }
// EncodeBinary implements io.Serializable interface. // ToStackItem implements stackitem.Convertible interface. It never returns an error.
func (s *NEOBalanceState) EncodeBinary(w *io.BinWriter) { func (s *NEOBalanceState) ToStackItem() (stackitem.Item, error) {
si := s.toStackItem() resItem, _ := s.NEP17BalanceState.ToStackItem()
stackitem.EncodeBinary(si, w) result := resItem.(*stackitem.Struct)
}
// DecodeBinary implements io.Serializable interface.
func (s *NEOBalanceState) DecodeBinary(r *io.BinReader) {
si := stackitem.DecodeBinary(r)
if r.Err != nil {
return
}
r.Err = s.FromStackItem(si)
}
func (s *NEOBalanceState) toStackItem() stackitem.Item {
result := s.NEP17BalanceState.toStackItem().(*stackitem.Struct)
result.Append(stackitem.NewBigInteger(big.NewInt(int64(s.BalanceHeight)))) result.Append(stackitem.NewBigInteger(big.NewInt(int64(s.BalanceHeight))))
if s.VoteTo != nil { if s.VoteTo != nil {
result.Append(stackitem.NewByteArray(s.VoteTo.Bytes())) result.Append(stackitem.NewByteArray(s.VoteTo.Bytes()))
} else { } else {
result.Append(stackitem.Null{}) result.Append(stackitem.Null{})
} }
return result return result, nil
} }
// FromStackItem converts stackitem.Item to NEOBalanceState. // FromStackItem converts stackitem.Item to NEOBalanceState.

View file

@ -5,7 +5,6 @@ import (
"math/big" "math/big"
"unicode/utf8" "unicode/utf8"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
) )
@ -21,28 +20,9 @@ type OracleRequest struct {
UserData []byte UserData []byte
} }
// Bytes return o serizalized to a byte-slice. // ToStackItem implements stackitem.Convertible interface. It never returns an
func (o *OracleRequest) Bytes() []byte { // error.
w := io.NewBufBinWriter() func (o *OracleRequest) ToStackItem() (stackitem.Item, error) {
o.EncodeBinary(w.BinWriter)
return w.Bytes()
}
// EncodeBinary implements io.Serializable.
func (o *OracleRequest) EncodeBinary(w *io.BinWriter) {
stackitem.EncodeBinary(o.toStackItem(), w)
}
// DecodeBinary implements io.Serializable.
func (o *OracleRequest) DecodeBinary(r *io.BinReader) {
item := stackitem.DecodeBinary(r)
if r.Err != nil || item == nil {
return
}
r.Err = o.fromStackItem(item)
}
func (o *OracleRequest) toStackItem() stackitem.Item {
filter := stackitem.Item(stackitem.Null{}) filter := stackitem.Item(stackitem.Null{})
if o.Filter != nil { if o.Filter != nil {
filter = stackitem.Make(*o.Filter) filter = stackitem.Make(*o.Filter)
@ -55,10 +35,11 @@ func (o *OracleRequest) toStackItem() stackitem.Item {
stackitem.NewByteArray(o.CallbackContract.BytesBE()), stackitem.NewByteArray(o.CallbackContract.BytesBE()),
stackitem.Make(o.CallbackMethod), stackitem.Make(o.CallbackMethod),
stackitem.NewByteArray(o.UserData), stackitem.NewByteArray(o.UserData),
}) }), nil
} }
func (o *OracleRequest) fromStackItem(it stackitem.Item) error { // FromStackItem implements stackitem.Convertible interface.
func (o *OracleRequest) FromStackItem(it stackitem.Item) error {
arr, ok := it.Value().([]stackitem.Item) arr, ok := it.Value().([]stackitem.Item)
if !ok || len(arr) < 7 { if !ok || len(arr) < 7 {
return errors.New("not an array of needed length") return errors.New("not an array of needed length")

View file

@ -6,12 +6,11 @@ import (
"github.com/nspcc-dev/neo-go/internal/random" "github.com/nspcc-dev/neo-go/internal/random"
"github.com/nspcc-dev/neo-go/internal/testserdes" "github.com/nspcc-dev/neo-go/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestOracleRequest_EncodeBinary(t *testing.T) { func TestOracleRequestToFromSI(t *testing.T) {
t.Run("Valid", func(t *testing.T) { t.Run("Valid", func(t *testing.T) {
r := &OracleRequest{ r := &OracleRequest{
OriginalTxID: random.Uint256(), OriginalTxID: random.Uint256(),
@ -21,25 +20,19 @@ func TestOracleRequest_EncodeBinary(t *testing.T) {
CallbackMethod: "method", CallbackMethod: "method",
UserData: []byte{1, 2, 3}, UserData: []byte{1, 2, 3},
} }
testserdes.EncodeDecodeBinary(t, r, new(OracleRequest)) testserdes.ToFromStackItem(t, r, new(OracleRequest))
t.Run("WithFilter", func(t *testing.T) { t.Run("WithFilter", func(t *testing.T) {
s := "filter" s := "filter"
r.Filter = &s r.Filter = &s
testserdes.EncodeDecodeBinary(t, r, new(OracleRequest)) testserdes.ToFromStackItem(t, r, new(OracleRequest))
}) })
}) })
t.Run("Invalid", func(t *testing.T) { t.Run("Invalid", func(t *testing.T) {
w := io.NewBufBinWriter() var res = new(OracleRequest)
t.Run("NotArray", func(t *testing.T) { t.Run("NotArray", func(t *testing.T) {
w.Reset()
it := stackitem.NewByteArray([]byte{}) it := stackitem.NewByteArray([]byte{})
stackitem.EncodeBinary(it, w.BinWriter) require.Error(t, res.FromStackItem(it))
require.Error(t, testserdes.DecodeBinary(w.Bytes(), new(OracleRequest)))
})
t.Run("NotStackItem", func(t *testing.T) {
w.Reset()
require.Error(t, testserdes.DecodeBinary([]byte{0x77}, new(OracleRequest)))
}) })
items := []stackitem.Item{ items := []stackitem.Item{
@ -54,12 +47,10 @@ func TestOracleRequest_EncodeBinary(t *testing.T) {
arrItem := stackitem.NewArray(items) arrItem := stackitem.NewArray(items)
runInvalid := func(i int, elem stackitem.Item) func(t *testing.T) { runInvalid := func(i int, elem stackitem.Item) func(t *testing.T) {
return func(t *testing.T) { return func(t *testing.T) {
w.Reset()
before := items[i] before := items[i]
items[i] = elem items[i] = elem
stackitem.EncodeBinary(arrItem, w.BinWriter) require.Error(t, res.FromStackItem(arrItem))
items[i] = before items[i] = before
require.Error(t, testserdes.DecodeBinary(w.Bytes(), new(OracleRequest)))
} }
} }
t.Run("TxID", func(t *testing.T) { t.Run("TxID", func(t *testing.T) {

View file

@ -1,21 +1,22 @@
package util package util_test
import ( import (
"encoding/hex" "encoding/hex"
"testing" "testing"
"github.com/nspcc-dev/neo-go/internal/testserdes" "github.com/nspcc-dev/neo-go/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestUint160UnmarshalJSON(t *testing.T) { func TestUint160UnmarshalJSON(t *testing.T) {
str := "0263c1de100292813b5e075e585acc1bae963b2d" str := "0263c1de100292813b5e075e585acc1bae963b2d"
expected, err := Uint160DecodeStringLE(str) expected, err := util.Uint160DecodeStringLE(str)
assert.NoError(t, err) assert.NoError(t, err)
// UnmarshalJSON decodes hex-strings // UnmarshalJSON decodes hex-strings
var u1, u2 Uint160 var u1, u2 util.Uint160
assert.NoError(t, u1.UnmarshalJSON([]byte(`"`+str+`"`))) assert.NoError(t, u1.UnmarshalJSON([]byte(`"`+str+`"`)))
assert.True(t, expected.Equals(u1)) assert.True(t, expected.Equals(u1))
@ -27,25 +28,25 @@ func TestUint160UnmarshalJSON(t *testing.T) {
func TestUInt160DecodeString(t *testing.T) { func TestUInt160DecodeString(t *testing.T) {
hexStr := "2d3b96ae1bcc5a585e075e3b81920210dec16302" hexStr := "2d3b96ae1bcc5a585e075e3b81920210dec16302"
val, err := Uint160DecodeStringBE(hexStr) val, err := util.Uint160DecodeStringBE(hexStr)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, hexStr, val.String()) assert.Equal(t, hexStr, val.String())
valLE, err := Uint160DecodeStringLE(hexStr) valLE, err := util.Uint160DecodeStringLE(hexStr)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, val, valLE.Reverse()) assert.Equal(t, val, valLE.Reverse())
_, err = Uint160DecodeStringBE(hexStr[1:]) _, err = util.Uint160DecodeStringBE(hexStr[1:])
assert.Error(t, err) assert.Error(t, err)
_, err = Uint160DecodeStringLE(hexStr[1:]) _, err = util.Uint160DecodeStringLE(hexStr[1:])
assert.Error(t, err) assert.Error(t, err)
hexStr = "zz3b96ae1bcc5a585e075e3b81920210dec16302" hexStr = "zz3b96ae1bcc5a585e075e3b81920210dec16302"
_, err = Uint160DecodeStringBE(hexStr) _, err = util.Uint160DecodeStringBE(hexStr)
assert.Error(t, err) assert.Error(t, err)
_, err = Uint160DecodeStringLE(hexStr) _, err = util.Uint160DecodeStringLE(hexStr)
assert.Error(t, err) assert.Error(t, err)
} }
@ -54,18 +55,18 @@ func TestUint160DecodeBytes(t *testing.T) {
b, err := hex.DecodeString(hexStr) b, err := hex.DecodeString(hexStr)
require.NoError(t, err) require.NoError(t, err)
val, err := Uint160DecodeBytesBE(b) val, err := util.Uint160DecodeBytesBE(b)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, hexStr, val.String()) assert.Equal(t, hexStr, val.String())
valLE, err := Uint160DecodeBytesLE(b) valLE, err := util.Uint160DecodeBytesLE(b)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, val, valLE.Reverse()) assert.Equal(t, val, valLE.Reverse())
_, err = Uint160DecodeBytesLE(b[1:]) _, err = util.Uint160DecodeBytesLE(b[1:])
assert.Error(t, err) assert.Error(t, err)
_, err = Uint160DecodeBytesBE(b[1:]) _, err = util.Uint160DecodeBytesBE(b[1:])
assert.Error(t, err) assert.Error(t, err)
} }
@ -73,10 +74,10 @@ func TestUInt160Equals(t *testing.T) {
a := "2d3b96ae1bcc5a585e075e3b81920210dec16302" a := "2d3b96ae1bcc5a585e075e3b81920210dec16302"
b := "4d3b96ae1bcc5a585e075e3b81920210dec16302" b := "4d3b96ae1bcc5a585e075e3b81920210dec16302"
ua, err := Uint160DecodeStringBE(a) ua, err := util.Uint160DecodeStringBE(a)
require.NoError(t, err) require.NoError(t, err)
ub, err := Uint160DecodeStringBE(b) ub, err := util.Uint160DecodeStringBE(b)
require.NoError(t, err) require.NoError(t, err)
assert.False(t, ua.Equals(ub), "%s and %s cannot be equal", ua, ub) assert.False(t, ua.Equals(ub), "%s and %s cannot be equal", ua, ub)
assert.True(t, ua.Equals(ua), "%s and %s must be equal", ua, ua) assert.True(t, ua.Equals(ua), "%s and %s must be equal", ua, ua)
@ -86,11 +87,11 @@ func TestUInt160Less(t *testing.T) {
a := "2d3b96ae1bcc5a585e075e3b81920210dec16302" a := "2d3b96ae1bcc5a585e075e3b81920210dec16302"
b := "2d3b96ae1bcc5a585e075e3b81920210dec16303" b := "2d3b96ae1bcc5a585e075e3b81920210dec16303"
ua, err := Uint160DecodeStringBE(a) ua, err := util.Uint160DecodeStringBE(a)
assert.Nil(t, err) assert.Nil(t, err)
ua2, err := Uint160DecodeStringBE(a) ua2, err := util.Uint160DecodeStringBE(a)
assert.Nil(t, err) assert.Nil(t, err)
ub, err := Uint160DecodeStringBE(b) ub, err := util.Uint160DecodeStringBE(b)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, true, ua.Less(ub)) assert.Equal(t, true, ua.Less(ub))
assert.Equal(t, false, ua.Less(ua2)) assert.Equal(t, false, ua.Less(ua2))
@ -101,7 +102,7 @@ func TestUInt160String(t *testing.T) {
hexStr := "b28427088a3729b2536d10122960394e8be6721f" hexStr := "b28427088a3729b2536d10122960394e8be6721f"
hexRevStr := "1f72e68b4e39602912106d53b229378a082784b2" hexRevStr := "1f72e68b4e39602912106d53b229378a082784b2"
val, err := Uint160DecodeStringBE(hexStr) val, err := util.Uint160DecodeStringBE(hexStr)
assert.Nil(t, err) assert.Nil(t, err)
assert.Equal(t, hexStr, val.String()) assert.Equal(t, hexStr, val.String())
@ -110,7 +111,7 @@ func TestUInt160String(t *testing.T) {
func TestUint160_Reverse(t *testing.T) { func TestUint160_Reverse(t *testing.T) {
hexStr := "b28427088a3729b2536d10122960394e8be6721f" hexStr := "b28427088a3729b2536d10122960394e8be6721f"
val, err := Uint160DecodeStringBE(hexStr) val, err := util.Uint160DecodeStringBE(hexStr)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, hexStr, val.Reverse().StringLE()) assert.Equal(t, hexStr, val.Reverse().StringLE())

View file

@ -1,21 +1,22 @@
package util package util_test
import ( import (
"encoding/hex" "encoding/hex"
"testing" "testing"
"github.com/nspcc-dev/neo-go/internal/testserdes" "github.com/nspcc-dev/neo-go/internal/testserdes"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/stretchr/testify/assert" "github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require" "github.com/stretchr/testify/require"
) )
func TestUint256UnmarshalJSON(t *testing.T) { func TestUint256UnmarshalJSON(t *testing.T) {
str := "f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d" str := "f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d"
expected, err := Uint256DecodeStringLE(str) expected, err := util.Uint256DecodeStringLE(str)
require.NoError(t, err) require.NoError(t, err)
// UnmarshalJSON decodes hex-strings // UnmarshalJSON decodes hex-strings
var u1, u2 Uint256 var u1, u2 util.Uint256
require.NoError(t, u1.UnmarshalJSON([]byte(`"`+str+`"`))) require.NoError(t, u1.UnmarshalJSON([]byte(`"`+str+`"`)))
assert.True(t, expected.Equals(u1)) assert.True(t, expected.Equals(u1))
@ -28,33 +29,33 @@ func TestUint256UnmarshalJSON(t *testing.T) {
func TestUint256DecodeString(t *testing.T) { func TestUint256DecodeString(t *testing.T) {
hexStr := "f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d" hexStr := "f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d"
val, err := Uint256DecodeStringLE(hexStr) val, err := util.Uint256DecodeStringLE(hexStr)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, hexStr, val.StringLE()) assert.Equal(t, hexStr, val.StringLE())
valBE, err := Uint256DecodeStringBE(hexStr) valBE, err := util.Uint256DecodeStringBE(hexStr)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, val, valBE.Reverse()) assert.Equal(t, val, valBE.Reverse())
bs, err := hex.DecodeString(hexStr) bs, err := hex.DecodeString(hexStr)
require.NoError(t, err) require.NoError(t, err)
val1, err := Uint256DecodeBytesBE(bs) val1, err := util.Uint256DecodeBytesBE(bs)
assert.NoError(t, err) assert.NoError(t, err)
assert.Equal(t, hexStr, val1.String()) assert.Equal(t, hexStr, val1.String())
assert.Equal(t, val, val1.Reverse()) assert.Equal(t, val, val1.Reverse())
_, err = Uint256DecodeStringLE(hexStr[1:]) _, err = util.Uint256DecodeStringLE(hexStr[1:])
assert.Error(t, err) assert.Error(t, err)
_, err = Uint256DecodeStringBE(hexStr[1:]) _, err = util.Uint256DecodeStringBE(hexStr[1:])
assert.Error(t, err) assert.Error(t, err)
hexStr = "zzz7308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d" hexStr = "zzz7308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d"
_, err = Uint256DecodeStringLE(hexStr) _, err = util.Uint256DecodeStringLE(hexStr)
assert.Error(t, err) assert.Error(t, err)
_, err = Uint256DecodeStringBE(hexStr) _, err = util.Uint256DecodeStringBE(hexStr)
assert.Error(t, err) assert.Error(t, err)
} }
@ -63,11 +64,11 @@ func TestUint256DecodeBytes(t *testing.T) {
b, err := hex.DecodeString(hexStr) b, err := hex.DecodeString(hexStr)
require.NoError(t, err) require.NoError(t, err)
val, err := Uint256DecodeBytesLE(b) val, err := util.Uint256DecodeBytesLE(b)
require.NoError(t, err) require.NoError(t, err)
assert.Equal(t, hexStr, val.StringLE()) assert.Equal(t, hexStr, val.StringLE())
_, err = Uint256DecodeBytesBE(b[1:]) _, err = util.Uint256DecodeBytesBE(b[1:])
assert.Error(t, err) assert.Error(t, err)
} }
@ -75,10 +76,10 @@ func TestUInt256Equals(t *testing.T) {
a := "f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d" a := "f037308fa0ab18155bccfc08485468c112409ea5064595699e98c545f245f32d"
b := "e287c5b29a1b66092be6803c59c765308ac20287e1b4977fd399da5fc8f66ab5" b := "e287c5b29a1b66092be6803c59c765308ac20287e1b4977fd399da5fc8f66ab5"
ua, err := Uint256DecodeStringLE(a) ua, err := util.Uint256DecodeStringLE(a)
require.NoError(t, err) require.NoError(t, err)
ub, err := Uint256DecodeStringLE(b) ub, err := util.Uint256DecodeStringLE(b)
require.NoError(t, err) require.NoError(t, err)
assert.False(t, ua.Equals(ub), "%s and %s cannot be equal", ua, ub) assert.False(t, ua.Equals(ub), "%s and %s cannot be equal", ua, ub)
assert.True(t, ua.Equals(ua), "%s and %s must be equal", ua, ua) assert.True(t, ua.Equals(ua), "%s and %s must be equal", ua, ua)
@ -86,11 +87,11 @@ func TestUInt256Equals(t *testing.T) {
} }
func TestUint256_Serializable(t *testing.T) { func TestUint256_Serializable(t *testing.T) {
a := Uint256{ a := util.Uint256{
1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16,
17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32,
} }
var b Uint256 var b util.Uint256
testserdes.EncodeDecodeBinary(t, &a, &b) testserdes.EncodeDecodeBinary(t, &a, &b)
} }

View file

@ -52,6 +52,12 @@ type Item interface {
Convert(Type) (Item, error) Convert(Type) (Item, error)
} }
// Convertible is something that can be converted to/from Item.
type Convertible interface {
ToStackItem() (Item, error)
FromStackItem(Item) error
}
var ( var (
// ErrInvalidConversion is returned on attempt to make an incorrect // ErrInvalidConversion is returned on attempt to make an incorrect
// conversion between item types. // conversion between item types.

View file

@ -254,3 +254,21 @@ func decodeBinary(r *io.BinReader, allowInvalid bool) Item {
return nil return nil
} }
} }
// SerializeConvertible serializes Convertible into a slice of bytes.
func SerializeConvertible(conv Convertible) ([]byte, error) {
item, err := conv.ToStackItem()
if err != nil {
return nil, err
}
return Serialize(item)
}
// DeserializeConvertible deserializes Convertible from a slice of bytes.
func DeserializeConvertible(data []byte, conv Convertible) error {
item, err := Deserialize(data)
if err != nil {
return err
}
return conv.FromStackItem(item)
}