diff --git a/pkg/core/native/nonfungible.go b/pkg/core/native/nonfungible.go deleted file mode 100644 index 1f08b4ded..000000000 --- a/pkg/core/native/nonfungible.go +++ /dev/null @@ -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] -} diff --git a/pkg/core/native/nonfungible_test.go b/pkg/core/native/nonfungible_test.go deleted file mode 100644 index 9b7e9a25a..000000000 --- a/pkg/core/native/nonfungible_test.go +++ /dev/null @@ -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)) -} diff --git a/pkg/core/state/nonfungible.go b/pkg/core/state/nonfungible.go deleted file mode 100644 index 6dd1d29ab..000000000 --- a/pkg/core/state/nonfungible.go +++ /dev/null @@ -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 -} diff --git a/pkg/core/state/nonfungible_test.go b/pkg/core/state/nonfungible_test.go deleted file mode 100644 index 25c638a35..000000000 --- a/pkg/core/state/nonfungible_test.go +++ /dev/null @@ -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()) -}