forked from TrueCloudLab/neoneo-go
core: remove native nonfungible token
This commit is contained in:
parent
99b37efc31
commit
c9099fa555
4 changed files with 0 additions and 723 deletions
|
@ -1,412 +0,0 @@
|
|||
package native
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"math/big"
|
||||
"sort"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/dao"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/contract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/interop/runtime"
|
||||
istorage "github.com/nspcc-dev/neo-go/pkg/core/interop/storage"
|
||||
"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/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/callflag"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
type nonfungible struct {
|
||||
interop.ContractMD
|
||||
|
||||
tokenSymbol string
|
||||
tokenDecimals byte
|
||||
|
||||
onTransferred func(nftTokenState)
|
||||
getTokenKey func([]byte) []byte
|
||||
newTokenState func() nftTokenState
|
||||
}
|
||||
|
||||
type nftTokenState interface {
|
||||
io.Serializable
|
||||
ToStackItem() stackitem.Item
|
||||
FromStackItem(stackitem.Item) error
|
||||
ToMap() *stackitem.Map
|
||||
ID() []byte
|
||||
Base() *state.NFTTokenState
|
||||
}
|
||||
|
||||
const (
|
||||
prefixNFTTotalSupply = 11
|
||||
prefixNFTAccount = 7
|
||||
prefixNFTToken = 5
|
||||
)
|
||||
|
||||
var (
|
||||
nftTotalSupplyKey = []byte{prefixNFTTotalSupply}
|
||||
)
|
||||
|
||||
func newNonFungible(name string, id int32, symbol string, decimals byte) *nonfungible {
|
||||
n := &nonfungible{
|
||||
ContractMD: *interop.NewContractMD(name, id),
|
||||
|
||||
tokenSymbol: symbol,
|
||||
tokenDecimals: decimals,
|
||||
|
||||
getTokenKey: func(tokenID []byte) []byte {
|
||||
return append([]byte{prefixNFTToken}, tokenID...)
|
||||
},
|
||||
newTokenState: func() nftTokenState {
|
||||
return new(state.NFTTokenState)
|
||||
},
|
||||
}
|
||||
n.Manifest.SupportedStandards = []string{manifest.NEP11StandardName}
|
||||
|
||||
desc := newDescriptor("symbol", smartcontract.StringType)
|
||||
md := newMethodAndPrice(n.symbol, 0, callflag.NoneFlag)
|
||||
n.AddMethod(md, desc)
|
||||
|
||||
desc = newDescriptor("decimals", smartcontract.IntegerType)
|
||||
md = newMethodAndPrice(n.decimals, 0, callflag.NoneFlag)
|
||||
n.AddMethod(md, desc)
|
||||
|
||||
desc = newDescriptor("totalSupply", smartcontract.IntegerType)
|
||||
md = newMethodAndPrice(n.totalSupply, 1<<15, callflag.ReadStates)
|
||||
n.AddMethod(md, desc)
|
||||
|
||||
desc = newDescriptor("ownerOf", smartcontract.Hash160Type,
|
||||
manifest.NewParameter("tokenId", smartcontract.ByteArrayType))
|
||||
md = newMethodAndPrice(n.OwnerOf, 1<<15, callflag.ReadStates)
|
||||
n.AddMethod(md, desc)
|
||||
|
||||
desc = newDescriptor("balanceOf", smartcontract.IntegerType,
|
||||
manifest.NewParameter("owner", smartcontract.Hash160Type))
|
||||
md = newMethodAndPrice(n.BalanceOf, 1<<15, callflag.ReadStates)
|
||||
n.AddMethod(md, desc)
|
||||
|
||||
desc = newDescriptor("properties", smartcontract.MapType,
|
||||
manifest.NewParameter("tokenId", smartcontract.ByteArrayType))
|
||||
md = newMethodAndPrice(n.Properties, 1<<15, callflag.ReadStates)
|
||||
n.AddMethod(md, desc)
|
||||
|
||||
desc = newDescriptor("tokens", smartcontract.InteropInterfaceType)
|
||||
md = newMethodAndPrice(n.tokens, 1<<15, callflag.ReadStates)
|
||||
n.AddMethod(md, desc)
|
||||
|
||||
desc = newDescriptor("tokensOf", smartcontract.InteropInterfaceType,
|
||||
manifest.NewParameter("owner", smartcontract.Hash160Type))
|
||||
md = newMethodAndPrice(n.tokensOf, 1<<15, callflag.ReadStates)
|
||||
n.AddMethod(md, desc)
|
||||
|
||||
desc = newDescriptor("transfer", smartcontract.BoolType,
|
||||
manifest.NewParameter("to", smartcontract.Hash160Type),
|
||||
manifest.NewParameter("tokenId", smartcontract.ByteArrayType),
|
||||
manifest.NewParameter("data", smartcontract.AnyType))
|
||||
md = newMethodAndPrice(n.transfer, 1<<17, callflag.States|callflag.AllowNotify)
|
||||
md.StorageFee = 50
|
||||
n.AddMethod(md, desc)
|
||||
|
||||
n.AddEvent("Transfer",
|
||||
manifest.NewParameter("from", smartcontract.Hash160Type),
|
||||
manifest.NewParameter("to", smartcontract.Hash160Type),
|
||||
manifest.NewParameter("amount", smartcontract.IntegerType),
|
||||
manifest.NewParameter("tokenId", smartcontract.ByteArrayType))
|
||||
|
||||
return n
|
||||
}
|
||||
|
||||
// Initialize implements interop.Contract interface.
|
||||
func (n nonfungible) Initialize(ic *interop.Context) error {
|
||||
return setIntWithKey(n.ID, ic.DAO, nftTotalSupplyKey, 0)
|
||||
}
|
||||
|
||||
func (n *nonfungible) symbol(_ *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||
return stackitem.NewByteArray([]byte(n.tokenSymbol))
|
||||
}
|
||||
|
||||
func (n *nonfungible) decimals(_ *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||
return stackitem.NewBigInteger(big.NewInt(int64(n.tokenDecimals)))
|
||||
}
|
||||
|
||||
func (n *nonfungible) totalSupply(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
|
||||
return stackitem.NewBigInteger(n.TotalSupply(ic.DAO))
|
||||
}
|
||||
|
||||
func (n *nonfungible) TotalSupply(d dao.DAO) *big.Int {
|
||||
si := d.GetStorageItem(n.ID, nftTotalSupplyKey)
|
||||
if si == nil {
|
||||
panic(errors.New("total supply is not initialized"))
|
||||
}
|
||||
return bigint.FromBytes(si)
|
||||
}
|
||||
|
||||
func (n *nonfungible) setTotalSupply(d dao.DAO, ts *big.Int) {
|
||||
err := d.PutStorageItem(n.ID, nftTotalSupplyKey, bigint.ToBytes(ts))
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *nonfungible) tokenState(d dao.DAO, tokenID []byte) (nftTokenState, []byte, error) {
|
||||
key := n.getTokenKey(tokenID)
|
||||
s := n.newTokenState()
|
||||
err := getSerializableFromDAO(n.ID, d, key, s)
|
||||
return s, key, err
|
||||
}
|
||||
|
||||
func (n *nonfungible) accountState(d dao.DAO, owner util.Uint160) (*state.NFTAccountState, []byte, error) {
|
||||
acc := new(state.NFTAccountState)
|
||||
keyAcc := makeNFTAccountKey(owner)
|
||||
err := getSerializableFromDAO(n.ID, d, keyAcc, acc)
|
||||
return acc, keyAcc, err
|
||||
}
|
||||
|
||||
func (n *nonfungible) putAccountState(d dao.DAO, key []byte, acc *state.NFTAccountState) {
|
||||
var err error
|
||||
if acc.Balance.Sign() == 0 {
|
||||
err = d.DeleteStorageItem(n.ID, key)
|
||||
} else {
|
||||
err = putSerializableToDAO(n.ID, d, key, acc)
|
||||
}
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
func (n *nonfungible) OwnerOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
tokenID, err := args[0].TryBytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s, _, err := n.tokenState(ic.DAO, tokenID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return stackitem.NewByteArray(s.Base().Owner.BytesBE())
|
||||
}
|
||||
|
||||
func (n *nonfungible) Properties(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
tokenID, err := args[0].TryBytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
s, _, err := n.tokenState(ic.DAO, tokenID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return s.ToMap()
|
||||
}
|
||||
|
||||
func (n *nonfungible) BalanceOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
owner := toUint160(args[0])
|
||||
s, _, err := n.accountState(ic.DAO, owner)
|
||||
if err != nil {
|
||||
if errors.Is(err, storage.ErrKeyNotFound) {
|
||||
return stackitem.NewBigInteger(big.NewInt(0))
|
||||
}
|
||||
panic(err)
|
||||
}
|
||||
return stackitem.NewBigInteger(&s.Balance)
|
||||
}
|
||||
|
||||
func (n *nonfungible) tokens(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
prefix := []byte{prefixNFTToken}
|
||||
siMap, err := ic.DAO.GetStorageItemsWithPrefix(n.ID, prefix)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
filteredMap := stackitem.NewMap()
|
||||
for k, v := range siMap {
|
||||
filteredMap.Add(stackitem.NewByteArray(append(prefix, []byte(k)...)), stackitem.NewByteArray(v))
|
||||
}
|
||||
sort.Slice(filteredMap.Value().([]stackitem.MapElement), func(i, j int) bool {
|
||||
return bytes.Compare(filteredMap.Value().([]stackitem.MapElement)[i].Key.Value().([]byte),
|
||||
filteredMap.Value().([]stackitem.MapElement)[j].Key.Value().([]byte)) == -1
|
||||
})
|
||||
iter := istorage.NewIterator(filteredMap, 1, istorage.FindValuesOnly|istorage.FindDeserialize|istorage.FindPick1)
|
||||
return stackitem.NewInterop(iter)
|
||||
}
|
||||
|
||||
func (n *nonfungible) tokensOf(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
owner := toUint160(args[0])
|
||||
s, _, err := n.accountState(ic.DAO, owner)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
arr := make([]stackitem.Item, len(s.Tokens))
|
||||
for i := range arr {
|
||||
arr[i] = stackitem.NewByteArray(s.Tokens[i])
|
||||
}
|
||||
iter := newArrayIterator(arr)
|
||||
return stackitem.NewInterop(iter)
|
||||
}
|
||||
|
||||
var _ = (*nonfungible).mint // fix unused warning
|
||||
|
||||
func (n *nonfungible) mint(ic *interop.Context, s nftTokenState) {
|
||||
key := n.getTokenKey(s.ID())
|
||||
if ic.DAO.GetStorageItem(n.ID, key) != nil {
|
||||
panic("token is already minted")
|
||||
}
|
||||
if err := putSerializableToDAO(n.ID, ic.DAO, key, s); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
owner := s.Base().Owner
|
||||
acc, keyAcc, err := n.accountState(ic.DAO, owner)
|
||||
if err != nil && !errors.Is(err, storage.ErrKeyNotFound) {
|
||||
panic(err)
|
||||
}
|
||||
acc.Add(s.ID())
|
||||
n.putAccountState(ic.DAO, keyAcc, acc)
|
||||
|
||||
ts := n.TotalSupply(ic.DAO)
|
||||
ts.Add(ts, intOne)
|
||||
n.setTotalSupply(ic.DAO, ts)
|
||||
n.postTransfer(ic, nil, &owner, s.ID(), stackitem.Null{})
|
||||
}
|
||||
|
||||
func (n *nonfungible) postTransfer(ic *interop.Context, from, to *util.Uint160, tokenID []byte, data stackitem.Item) {
|
||||
ne := state.NotificationEvent{
|
||||
ScriptHash: n.Hash,
|
||||
Name: "Transfer",
|
||||
Item: stackitem.NewArray([]stackitem.Item{
|
||||
addrToStackItem(from),
|
||||
addrToStackItem(to),
|
||||
stackitem.NewBigInteger(intOne),
|
||||
stackitem.NewByteArray(tokenID),
|
||||
}),
|
||||
}
|
||||
ic.Notifications = append(ic.Notifications, ne)
|
||||
if to == nil {
|
||||
return
|
||||
}
|
||||
cs, err := ic.GetContract(*to)
|
||||
if err != nil {
|
||||
return
|
||||
}
|
||||
|
||||
fromArg := stackitem.Item(stackitem.Null{})
|
||||
if from != nil {
|
||||
fromArg = stackitem.NewByteArray((*from).BytesBE())
|
||||
}
|
||||
args := []stackitem.Item{
|
||||
fromArg,
|
||||
stackitem.NewBigInteger(intOne),
|
||||
stackitem.NewByteArray(tokenID),
|
||||
data,
|
||||
}
|
||||
if err := contract.CallFromNative(ic, n.Hash, cs, manifest.MethodOnNEP11Payment, args, false); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
}
|
||||
|
||||
var _ = (*nonfungible).burn // fix unused warning
|
||||
|
||||
func (n *nonfungible) burn(ic *interop.Context, tokenID []byte) {
|
||||
key := n.getTokenKey(tokenID)
|
||||
n.burnByKey(ic, key)
|
||||
}
|
||||
|
||||
func (n *nonfungible) burnByKey(ic *interop.Context, key []byte) {
|
||||
token := n.newTokenState()
|
||||
err := getSerializableFromDAO(n.ID, ic.DAO, key, token)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
if err := ic.DAO.DeleteStorageItem(n.ID, key); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
owner := token.Base().Owner
|
||||
acc, keyAcc, err := n.accountState(ic.DAO, owner)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
id := token.ID()
|
||||
acc.Remove(id)
|
||||
n.putAccountState(ic.DAO, keyAcc, acc)
|
||||
|
||||
ts := n.TotalSupply(ic.DAO)
|
||||
ts.Sub(ts, intOne)
|
||||
n.setTotalSupply(ic.DAO, ts)
|
||||
n.postTransfer(ic, &owner, nil, id, stackitem.Null{})
|
||||
}
|
||||
|
||||
func (n *nonfungible) transfer(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||
to := toUint160(args[0])
|
||||
tokenID, err := args[1].TryBytes()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
token, tokenKey, err := n.tokenState(ic.DAO, tokenID)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
from := token.Base().Owner
|
||||
ok, err := runtime.CheckHashedWitness(ic, from)
|
||||
if err != nil || !ok {
|
||||
return stackitem.NewBool(false)
|
||||
}
|
||||
if from != to {
|
||||
acc, key, err := n.accountState(ic.DAO, from)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
acc.Remove(tokenID)
|
||||
n.putAccountState(ic.DAO, key, acc)
|
||||
|
||||
token.Base().Owner = to
|
||||
n.onTransferred(token)
|
||||
err = putSerializableToDAO(n.ID, ic.DAO, tokenKey, token)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
acc, key, err = n.accountState(ic.DAO, to)
|
||||
if err != nil && !errors.Is(err, storage.ErrKeyNotFound) {
|
||||
panic(err)
|
||||
}
|
||||
acc.Add(tokenID)
|
||||
n.putAccountState(ic.DAO, key, acc)
|
||||
}
|
||||
n.postTransfer(ic, &from, &to, tokenID, args[2])
|
||||
return stackitem.NewBool(true)
|
||||
}
|
||||
|
||||
func makeNFTAccountKey(owner util.Uint160) []byte {
|
||||
return append([]byte{prefixNFTAccount}, owner.BytesBE()...)
|
||||
}
|
||||
|
||||
type arrayWrapper struct {
|
||||
index int
|
||||
value []stackitem.Item
|
||||
}
|
||||
|
||||
func newArrayIterator(arr []stackitem.Item) *arrayWrapper {
|
||||
return &arrayWrapper{
|
||||
index: -1,
|
||||
value: arr,
|
||||
}
|
||||
}
|
||||
|
||||
func (a *arrayWrapper) Next() bool {
|
||||
if next := a.index + 1; next < len(a.value) {
|
||||
a.index = next
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
func (a *arrayWrapper) Value() stackitem.Item {
|
||||
return a.value[a.index]
|
||||
}
|
|
@ -1,14 +0,0 @@
|
|||
package native
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest/standard"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNonfungibleNEP11(t *testing.T) {
|
||||
n := newNonFungible("NFToken", -100, "SYM", 1)
|
||||
require.NoError(t, standard.Check(&n.ContractMD.Manifest, manifest.NEP11StandardName))
|
||||
}
|
|
@ -1,174 +0,0 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"errors"
|
||||
"math/big"
|
||||
"sort"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
)
|
||||
|
||||
// NFTTokenState represents state of nonfungible token.
|
||||
type NFTTokenState struct {
|
||||
Owner util.Uint160
|
||||
Name string
|
||||
}
|
||||
|
||||
// NFTAccountState represents state of nonfunglible account.
|
||||
type NFTAccountState struct {
|
||||
NEP17BalanceState
|
||||
Tokens [][]byte
|
||||
}
|
||||
|
||||
// Base returns base class.
|
||||
func (s *NFTTokenState) Base() *NFTTokenState {
|
||||
return s
|
||||
}
|
||||
|
||||
// ToStackItem converts NFTTokenState to stackitem.
|
||||
func (s *NFTTokenState) ToStackItem() stackitem.Item {
|
||||
owner := s.Owner
|
||||
return stackitem.NewStruct([]stackitem.Item{
|
||||
stackitem.NewByteArray(owner.BytesBE()),
|
||||
stackitem.NewByteArray([]byte(s.Name)),
|
||||
})
|
||||
}
|
||||
|
||||
// EncodeBinary implements io.Serializable.
|
||||
func (s *NFTTokenState) EncodeBinary(w *io.BinWriter) {
|
||||
stackitem.EncodeBinaryStackItem(s.ToStackItem(), w)
|
||||
}
|
||||
|
||||
// FromStackItem converts stackitem to NFTTokenState.
|
||||
func (s *NFTTokenState) FromStackItem(item stackitem.Item) error {
|
||||
arr, ok := item.Value().([]stackitem.Item)
|
||||
if !ok || len(arr) < 2 {
|
||||
return errors.New("invalid stack item")
|
||||
}
|
||||
|
||||
bs, err := arr[0].TryBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
owner, err := util.Uint160DecodeBytesBE(bs)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
name, err := stackitem.ToString(arr[1])
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
s.Owner = owner
|
||||
s.Name = name
|
||||
return nil
|
||||
}
|
||||
|
||||
// DecodeBinary implements io.Serializable.
|
||||
func (s *NFTTokenState) DecodeBinary(r *io.BinReader) {
|
||||
item := stackitem.DecodeBinaryStackItem(r)
|
||||
if r.Err == nil {
|
||||
r.Err = s.FromStackItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
// ToMap converts NFTTokenState to Map stackitem.
|
||||
func (s *NFTTokenState) ToMap() *stackitem.Map {
|
||||
return stackitem.NewMapWithValue([]stackitem.MapElement{
|
||||
{
|
||||
Key: stackitem.NewByteArray([]byte("name")),
|
||||
Value: stackitem.NewByteArray([]byte(s.Name)),
|
||||
},
|
||||
})
|
||||
}
|
||||
|
||||
// ID returns token id.
|
||||
func (s *NFTTokenState) ID() []byte {
|
||||
return []byte(s.Name)
|
||||
}
|
||||
|
||||
// ToStackItem converts NFTAccountState to stackitem.
|
||||
func (s *NFTAccountState) ToStackItem() stackitem.Item {
|
||||
st := s.NEP17BalanceState.toStackItem().(*stackitem.Struct)
|
||||
arr := make([]stackitem.Item, len(s.Tokens))
|
||||
for i := range arr {
|
||||
arr[i] = stackitem.NewByteArray(s.Tokens[i])
|
||||
}
|
||||
st.Append(stackitem.NewArray(arr))
|
||||
return st
|
||||
}
|
||||
|
||||
// FromStackItem converts stackitem to NFTAccountState.
|
||||
func (s *NFTAccountState) FromStackItem(item stackitem.Item) error {
|
||||
s.NEP17BalanceState.fromStackItem(item)
|
||||
arr := item.Value().([]stackitem.Item)
|
||||
if len(arr) < 2 {
|
||||
return errors.New("invalid stack item")
|
||||
}
|
||||
arr, ok := arr[1].Value().([]stackitem.Item)
|
||||
if !ok {
|
||||
return errors.New("invalid stack item")
|
||||
}
|
||||
s.Tokens = make([][]byte, len(arr))
|
||||
for i := range s.Tokens {
|
||||
bs, err := arr[i].TryBytes()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
s.Tokens[i] = bs
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// EncodeBinary implements io.Serializable.
|
||||
func (s *NFTAccountState) EncodeBinary(w *io.BinWriter) {
|
||||
stackitem.EncodeBinaryStackItem(s.ToStackItem(), w)
|
||||
}
|
||||
|
||||
// DecodeBinary implements io.Serializable.
|
||||
func (s *NFTAccountState) DecodeBinary(r *io.BinReader) {
|
||||
item := stackitem.DecodeBinaryStackItem(r)
|
||||
if r.Err == nil {
|
||||
r.Err = s.FromStackItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
func (s *NFTAccountState) index(tokenID []byte) (int, bool) {
|
||||
lt := len(s.Tokens)
|
||||
index := sort.Search(lt, func(i int) bool {
|
||||
return bytes.Compare(s.Tokens[i], tokenID) >= 0
|
||||
})
|
||||
return index, index < lt && bytes.Equal(s.Tokens[index], tokenID)
|
||||
}
|
||||
|
||||
// Add adds token id to the set of account tokens
|
||||
// and returns true on success.
|
||||
func (s *NFTAccountState) Add(tokenID []byte) bool {
|
||||
index, isPresent := s.index(tokenID)
|
||||
if isPresent {
|
||||
return false
|
||||
}
|
||||
|
||||
s.Balance.Add(&s.Balance, big.NewInt(1))
|
||||
s.Tokens = append(s.Tokens, []byte{})
|
||||
copy(s.Tokens[index+1:], s.Tokens[index:])
|
||||
s.Tokens[index] = tokenID
|
||||
return true
|
||||
}
|
||||
|
||||
// Remove removes token id to the set of account tokens
|
||||
// and returns true on success.
|
||||
func (s *NFTAccountState) Remove(tokenID []byte) bool {
|
||||
index, isPresent := s.index(tokenID)
|
||||
if !isPresent {
|
||||
return false
|
||||
}
|
||||
|
||||
s.Balance.Sub(&s.Balance, big.NewInt(1))
|
||||
copy(s.Tokens[index:], s.Tokens[index+1:])
|
||||
s.Tokens = s.Tokens[:len(s.Tokens)-1]
|
||||
return true
|
||||
}
|
|
@ -1,123 +0,0 @@
|
|||
package state
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/internal/random"
|
||||
"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/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func newStruct(args ...interface{}) *stackitem.Struct {
|
||||
arr := make([]stackitem.Item, len(args))
|
||||
for i := range args {
|
||||
arr[i] = stackitem.Make(args[i])
|
||||
}
|
||||
return stackitem.NewStruct(arr)
|
||||
}
|
||||
|
||||
func TestNFTTokenState_Serializable(t *testing.T) {
|
||||
t.Run("valid", func(t *testing.T) {
|
||||
s := &NFTTokenState{
|
||||
Owner: random.Uint160(),
|
||||
Name: "random name",
|
||||
}
|
||||
id := s.ID()
|
||||
actual := new(NFTTokenState)
|
||||
testserdes.EncodeDecodeBinary(t, s, actual)
|
||||
require.Equal(t, id, actual.ID())
|
||||
})
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
errCases := []struct {
|
||||
name string
|
||||
item stackitem.Item
|
||||
}{
|
||||
{"invalid type", stackitem.NewByteArray([]byte{1, 2, 3})},
|
||||
{"invalid owner type",
|
||||
newStruct(stackitem.NewArray(nil), "name", "desc")},
|
||||
{"invalid owner uint160", newStruct("123", "name", "desc")},
|
||||
{"invalid name",
|
||||
newStruct(random.Uint160().BytesBE(), []byte{0x80}, "desc")},
|
||||
}
|
||||
|
||||
for _, tc := range errCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
w := io.NewBufBinWriter()
|
||||
stackitem.EncodeBinaryStackItem(tc.item, w.BinWriter)
|
||||
require.NoError(t, w.Err)
|
||||
require.Error(t, testserdes.DecodeBinary(w.Bytes(), new(NFTTokenState)))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNFTTokenState_ToMap(t *testing.T) {
|
||||
s := &NFTTokenState{
|
||||
Owner: random.Uint160(),
|
||||
Name: "random name",
|
||||
}
|
||||
m := s.ToMap()
|
||||
|
||||
elems := m.Value().([]stackitem.MapElement)
|
||||
i := m.Index(stackitem.Make("name"))
|
||||
require.True(t, i < len(elems))
|
||||
require.Equal(t, []byte("random name"), elems[i].Value.Value())
|
||||
}
|
||||
|
||||
func TestNFTAccountState_Serializable(t *testing.T) {
|
||||
t.Run("good", func(t *testing.T) {
|
||||
s := &NFTAccountState{
|
||||
NEP17BalanceState: NEP17BalanceState{
|
||||
Balance: *big.NewInt(10),
|
||||
},
|
||||
Tokens: [][]byte{
|
||||
{1, 2, 3},
|
||||
{3},
|
||||
},
|
||||
}
|
||||
testserdes.EncodeDecodeBinary(t, s, new(NFTAccountState))
|
||||
})
|
||||
t.Run("invalid", func(t *testing.T) {
|
||||
errCases := []struct {
|
||||
name string
|
||||
item stackitem.Item
|
||||
}{
|
||||
{"small size", newStruct(42)},
|
||||
{"not an array", newStruct(11, stackitem.NewByteArray([]byte{}))},
|
||||
{"not an array",
|
||||
newStruct(11, stackitem.NewArray([]stackitem.Item{
|
||||
stackitem.NewArray(nil),
|
||||
}))},
|
||||
}
|
||||
|
||||
for _, tc := range errCases {
|
||||
t.Run(tc.name, func(t *testing.T) {
|
||||
w := io.NewBufBinWriter()
|
||||
stackitem.EncodeBinaryStackItem(tc.item, w.BinWriter)
|
||||
require.NoError(t, w.Err)
|
||||
require.Error(t, testserdes.DecodeBinary(w.Bytes(), new(NFTAccountState)))
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func TestNFTAccountState_AddRemove(t *testing.T) {
|
||||
var s NFTAccountState
|
||||
require.True(t, s.Add([]byte{1, 2, 3}))
|
||||
require.EqualValues(t, 1, s.Balance.Int64())
|
||||
require.True(t, s.Add([]byte{1}))
|
||||
require.EqualValues(t, 2, s.Balance.Int64())
|
||||
|
||||
require.False(t, s.Add([]byte{1, 2, 3}))
|
||||
require.EqualValues(t, 2, s.Balance.Int64())
|
||||
|
||||
require.True(t, s.Remove([]byte{1}))
|
||||
require.EqualValues(t, 1, s.Balance.Int64())
|
||||
require.False(t, s.Remove([]byte{1}))
|
||||
require.EqualValues(t, 1, s.Balance.Int64())
|
||||
require.True(t, s.Remove([]byte{1, 2, 3}))
|
||||
require.EqualValues(t, 0, s.Balance.Int64())
|
||||
}
|
Loading…
Reference in a new issue