package state import ( "bytes" "math/big" "github.com/nspcc-dev/neo-go/pkg/config/limits" "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" ) // TokenTransferBatchSize is the maximum number of entries for TokenTransferLog. const TokenTransferBatchSize = 128 // TokenTransferLog is a serialized log of token transfers. type TokenTransferLog struct { Raw []byte buf *bytes.Buffer iow *io.BinWriter } // NEP17Transfer represents a single NEP-17 Transfer event. type NEP17Transfer struct { // Asset is a NEP-17 contract ID. Asset int32 // Counterparty is the address of the sender/receiver (the other side of the transfer). Counterparty util.Uint160 // Amount is the amount of tokens transferred. // It is negative when tokens are sent and positive if they are received. Amount *big.Int // Block is a number of block when the event occurred. Block uint32 // Timestamp is the timestamp of the block where transfer occurred. Timestamp uint64 // Tx is a hash the transaction. Tx util.Uint256 } // NEP11Transfer represents a single NEP-11 Transfer event. type NEP11Transfer struct { NEP17Transfer // ID is a NEP-11 token ID. ID []byte } // TokenTransferInfo stores a map of the contract IDs to the balance's last updated // block trackers along with the information about NEP-17 and NEP-11 transfer batch. type TokenTransferInfo struct { LastUpdated map[int32]uint32 // NextNEP11Batch stores the index of the next NEP-11 transfer batch. NextNEP11Batch uint32 // NextNEP17Batch stores the index of the next NEP-17 transfer batch. NextNEP17Batch uint32 // NextNEP11NewestTimestamp stores the block timestamp of the first NEP-11 transfer in raw. NextNEP11NewestTimestamp uint64 // NextNEP17NewestTimestamp stores the block timestamp of the first NEP-17 transfer in raw. NextNEP17NewestTimestamp uint64 // NewNEP11Batch is true if batch with the `NextNEP11Batch` index should be created. NewNEP11Batch bool // NewNEP17Batch is true if batch with the `NextNEP17Batch` index should be created. NewNEP17Batch bool } // NewTokenTransferInfo returns new TokenTransferInfo. func NewTokenTransferInfo() *TokenTransferInfo { return &TokenTransferInfo{ NewNEP11Batch: true, NewNEP17Batch: true, LastUpdated: make(map[int32]uint32), } } // DecodeBinary implements the io.Serializable interface. func (bs *TokenTransferInfo) DecodeBinary(r *io.BinReader) { bs.NextNEP11Batch = r.ReadU32LE() bs.NextNEP17Batch = r.ReadU32LE() bs.NextNEP11NewestTimestamp = r.ReadU64LE() bs.NextNEP17NewestTimestamp = r.ReadU64LE() bs.NewNEP11Batch = r.ReadBool() bs.NewNEP17Batch = r.ReadBool() lenBalances := r.ReadVarUint() m := make(map[int32]uint32, lenBalances) for i := 0; i < int(lenBalances); i++ { key := int32(r.ReadU32LE()) m[key] = r.ReadU32LE() } bs.LastUpdated = m } // EncodeBinary implements the io.Serializable interface. func (bs *TokenTransferInfo) EncodeBinary(w *io.BinWriter) { w.WriteU32LE(bs.NextNEP11Batch) w.WriteU32LE(bs.NextNEP17Batch) w.WriteU64LE(bs.NextNEP11NewestTimestamp) w.WriteU64LE(bs.NextNEP17NewestTimestamp) w.WriteBool(bs.NewNEP11Batch) w.WriteBool(bs.NewNEP17Batch) w.WriteVarUint(uint64(len(bs.LastUpdated))) for k, v := range bs.LastUpdated { w.WriteU32LE(uint32(k)) w.WriteU32LE(v) } } // Append appends a single transfer to a log. func (lg *TokenTransferLog) Append(tr io.Serializable) error { // The first entry, set up counter. if len(lg.Raw) == 0 { lg.Raw = append(lg.Raw, 0) } if lg.buf == nil { lg.buf = bytes.NewBuffer(lg.Raw) } if lg.iow == nil { lg.iow = io.NewBinWriterFromIO(lg.buf) } tr.EncodeBinary(lg.iow) if lg.iow.Err != nil { return lg.iow.Err } lg.Raw = lg.buf.Bytes() lg.Raw[0]++ return nil } // Reset resets the state of the log, clearing all entries, but keeping existing // buffer for future writes. func (lg *TokenTransferLog) Reset() { lg.Raw = lg.Raw[:0] lg.buf = nil lg.iow = nil } // ForEachNEP11 iterates over a transfer log returning on the first error. func (lg *TokenTransferLog) ForEachNEP11(f func(*NEP11Transfer) (bool, error)) (bool, error) { if lg == nil || len(lg.Raw) == 0 { return true, nil } transfers := make([]NEP11Transfer, lg.Size()) r := io.NewBinReaderFromBuf(lg.Raw[1:]) for i := 0; i < lg.Size(); i++ { transfers[i].DecodeBinary(r) } if r.Err != nil { return false, r.Err } for i := len(transfers) - 1; i >= 0; i-- { cont, err := f(&transfers[i]) if err != nil || !cont { return false, err } } return true, nil } // ForEachNEP17 iterates over a transfer log returning on the first error. func (lg *TokenTransferLog) ForEachNEP17(f func(*NEP17Transfer) (bool, error)) (bool, error) { if lg == nil || len(lg.Raw) == 0 { return true, nil } transfers := make([]NEP17Transfer, lg.Size()) r := io.NewBinReaderFromBuf(lg.Raw[1:]) for i := 0; i < lg.Size(); i++ { transfers[i].DecodeBinary(r) } if r.Err != nil { return false, r.Err } for i := len(transfers) - 1; i >= 0; i-- { cont, err := f(&transfers[i]) if err != nil || !cont { return false, err } } return true, nil } // Size returns the amount of the transfer written in the log. func (lg *TokenTransferLog) Size() int { if len(lg.Raw) == 0 { return 0 } return int(lg.Raw[0]) } // EncodeBinary implements the io.Serializable interface. func (t *NEP17Transfer) EncodeBinary(w *io.BinWriter) { var buf [bigint.MaxBytesLen]byte w.WriteU32LE(uint32(t.Asset)) w.WriteBytes(t.Tx[:]) w.WriteBytes(t.Counterparty[:]) w.WriteU32LE(t.Block) w.WriteU64LE(t.Timestamp) amount := bigint.ToPreallocatedBytes(t.Amount, buf[:]) w.WriteVarBytes(amount) } // DecodeBinary implements the io.Serializable interface. func (t *NEP17Transfer) DecodeBinary(r *io.BinReader) { t.Asset = int32(r.ReadU32LE()) r.ReadBytes(t.Tx[:]) r.ReadBytes(t.Counterparty[:]) t.Block = r.ReadU32LE() t.Timestamp = r.ReadU64LE() amount := r.ReadVarBytes(bigint.MaxBytesLen) t.Amount = bigint.FromBytes(amount) } // EncodeBinary implements the io.Serializable interface. func (t *NEP11Transfer) EncodeBinary(w *io.BinWriter) { t.NEP17Transfer.EncodeBinary(w) w.WriteVarBytes(t.ID) } // DecodeBinary implements the io.Serializable interface. func (t *NEP11Transfer) DecodeBinary(r *io.BinReader) { t.NEP17Transfer.DecodeBinary(r) t.ID = r.ReadVarBytes(limits.MaxStorageKeyLen) }