2020-03-05 07:45:50 +00:00
|
|
|
package state
|
|
|
|
|
|
|
|
import (
|
2020-10-29 19:10:40 +00:00
|
|
|
"errors"
|
|
|
|
"math/big"
|
|
|
|
|
2020-03-05 07:45:50 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
2020-03-05 14:11:58 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
2020-10-29 19:10:40 +00:00
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
2020-03-05 07:45:50 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// NEP5Tracker contains info about a single account in a NEP5 contract.
|
|
|
|
type NEP5Tracker struct {
|
|
|
|
// Balance is the current balance of the account.
|
2021-02-16 17:16:02 +00:00
|
|
|
Balance *big.Int
|
2020-03-05 07:45:50 +00:00
|
|
|
// LastUpdatedBlock is a number of block when last `transfer` to or from the
|
|
|
|
// account occured.
|
|
|
|
LastUpdatedBlock uint32
|
|
|
|
}
|
|
|
|
|
2020-08-04 13:55:45 +00:00
|
|
|
// TransferLog is a log of NEP5 token transfers for the specific command.
|
|
|
|
type TransferLog struct {
|
2020-03-05 14:11:58 +00:00
|
|
|
Raw []byte
|
|
|
|
}
|
|
|
|
|
|
|
|
// NEP5TransferSize is a size of a marshaled NEP5Transfer struct in bytes.
|
2021-02-16 17:16:02 +00:00
|
|
|
const NEP5TransferSize = util.Uint160Size*3 + amountSize + 4 + 4 + util.Uint256Size + 4
|
2020-03-05 14:11:58 +00:00
|
|
|
|
|
|
|
// NEP5Transfer represents a single NEP5 Transfer event.
|
|
|
|
type NEP5Transfer struct {
|
|
|
|
// Asset is a NEP5 contract hash.
|
|
|
|
Asset util.Uint160
|
|
|
|
// Address is the address of the sender.
|
|
|
|
From util.Uint160
|
|
|
|
// To is the address of the receiver.
|
|
|
|
To util.Uint160
|
|
|
|
// Amount is the amount of tokens transferred.
|
|
|
|
// It is negative when tokens are sent and positive if they are received.
|
2021-02-16 17:16:02 +00:00
|
|
|
Amount *big.Int
|
2020-03-05 14:11:58 +00:00
|
|
|
// Block is a number of block when the event occured.
|
|
|
|
Block uint32
|
|
|
|
// Timestamp is the timestamp of the block where transfer occured.
|
|
|
|
Timestamp uint32
|
|
|
|
// Tx is a hash the transaction.
|
|
|
|
Tx util.Uint256
|
2020-08-03 07:43:53 +00:00
|
|
|
// Index is the index of this transfer in the corresponding tx.
|
|
|
|
Index uint32
|
2020-03-05 14:11:58 +00:00
|
|
|
}
|
|
|
|
|
2021-02-16 17:16:02 +00:00
|
|
|
const amountSize = 32
|
|
|
|
|
2020-03-11 15:22:46 +00:00
|
|
|
// NEP5Balances is a map of the NEP5 contract hashes
|
|
|
|
// to the corresponding structures.
|
|
|
|
type NEP5Balances struct {
|
|
|
|
Trackers map[util.Uint160]NEP5Tracker
|
2020-03-12 09:43:21 +00:00
|
|
|
// NextTransferBatch stores an index of the next transfer batch.
|
|
|
|
NextTransferBatch uint32
|
2020-03-11 15:22:46 +00:00
|
|
|
}
|
|
|
|
|
2020-07-21 10:06:33 +00:00
|
|
|
// NEP5Metadata is a metadata for NEP5 contracts.
|
|
|
|
type NEP5Metadata struct {
|
|
|
|
Decimals int64
|
|
|
|
}
|
|
|
|
|
2020-03-11 15:22:46 +00:00
|
|
|
// NewNEP5Balances returns new NEP5Balances.
|
|
|
|
func NewNEP5Balances() *NEP5Balances {
|
|
|
|
return &NEP5Balances{
|
|
|
|
Trackers: make(map[util.Uint160]NEP5Tracker),
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// DecodeBinary implements io.Serializable interface.
|
|
|
|
func (bs *NEP5Balances) DecodeBinary(r *io.BinReader) {
|
2020-03-12 09:43:21 +00:00
|
|
|
bs.NextTransferBatch = r.ReadU32LE()
|
2020-03-11 15:22:46 +00:00
|
|
|
lenBalances := r.ReadVarUint()
|
|
|
|
m := make(map[util.Uint160]NEP5Tracker, lenBalances)
|
|
|
|
for i := 0; i < int(lenBalances); i++ {
|
|
|
|
var key util.Uint160
|
|
|
|
var tr NEP5Tracker
|
|
|
|
r.ReadBytes(key[:])
|
|
|
|
tr.DecodeBinary(r)
|
|
|
|
m[key] = tr
|
|
|
|
}
|
|
|
|
bs.Trackers = m
|
|
|
|
}
|
|
|
|
|
|
|
|
// EncodeBinary implements io.Serializable interface.
|
|
|
|
func (bs *NEP5Balances) EncodeBinary(w *io.BinWriter) {
|
2020-03-12 09:43:21 +00:00
|
|
|
w.WriteU32LE(bs.NextTransferBatch)
|
2020-03-11 15:22:46 +00:00
|
|
|
w.WriteVarUint(uint64(len(bs.Trackers)))
|
|
|
|
for k, v := range bs.Trackers {
|
|
|
|
w.WriteBytes(k[:])
|
|
|
|
v.EncodeBinary(w)
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-07-21 10:06:33 +00:00
|
|
|
// DecodeBinary implements io.Serializable interface.
|
|
|
|
func (bs *NEP5Metadata) DecodeBinary(r *io.BinReader) {
|
|
|
|
bs.Decimals = int64(r.ReadU64LE())
|
|
|
|
}
|
|
|
|
|
|
|
|
// EncodeBinary implements io.Serializable interface.
|
|
|
|
func (bs *NEP5Metadata) EncodeBinary(w *io.BinWriter) {
|
|
|
|
w.WriteU64LE(uint64(bs.Decimals))
|
|
|
|
}
|
|
|
|
|
2020-03-05 14:11:58 +00:00
|
|
|
// Append appends single transfer to a log.
|
2020-08-04 13:55:45 +00:00
|
|
|
func (lg *TransferLog) Append(tr io.Serializable) error {
|
2020-03-05 14:11:58 +00:00
|
|
|
w := io.NewBufBinWriter()
|
|
|
|
tr.EncodeBinary(w.BinWriter)
|
|
|
|
if w.Err != nil {
|
|
|
|
return w.Err
|
|
|
|
}
|
|
|
|
lg.Raw = append(lg.Raw, w.Bytes()...)
|
|
|
|
return nil
|
2020-03-05 12:16:03 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// ForEach iterates over transfer log returning on first error.
|
2020-09-08 12:29:07 +00:00
|
|
|
func (lg *TransferLog) ForEach(size int, tr io.Serializable, f func() (bool, error)) (bool, error) {
|
2020-03-05 12:16:03 +00:00
|
|
|
if lg == nil {
|
2020-09-08 12:29:07 +00:00
|
|
|
return true, nil
|
2020-03-05 12:16:03 +00:00
|
|
|
}
|
2020-09-08 09:57:45 +00:00
|
|
|
for i := len(lg.Raw); i > 0; i -= size {
|
|
|
|
r := io.NewBinReaderFromBuf(lg.Raw[i-size : i])
|
2020-03-05 12:16:03 +00:00
|
|
|
tr.DecodeBinary(r)
|
|
|
|
if r.Err != nil {
|
2020-09-08 12:29:07 +00:00
|
|
|
return false, r.Err
|
|
|
|
}
|
|
|
|
cont, err := f()
|
|
|
|
if err != nil {
|
|
|
|
return false, err
|
|
|
|
}
|
|
|
|
if !cont {
|
|
|
|
return false, nil
|
2020-03-05 12:16:03 +00:00
|
|
|
}
|
|
|
|
}
|
2020-09-08 12:29:07 +00:00
|
|
|
return true, nil
|
2020-03-05 14:11:58 +00:00
|
|
|
}
|
|
|
|
|
2020-08-04 13:55:45 +00:00
|
|
|
// Size returns an amount of transfer written in log provided size of a single transfer.
|
|
|
|
func (lg *TransferLog) Size() int {
|
|
|
|
return len(lg.Raw)
|
2020-03-12 09:32:24 +00:00
|
|
|
}
|
|
|
|
|
2020-03-05 07:45:50 +00:00
|
|
|
// EncodeBinary implements io.Serializable interface.
|
|
|
|
func (t *NEP5Tracker) EncodeBinary(w *io.BinWriter) {
|
2021-02-16 17:16:02 +00:00
|
|
|
w.WriteVarBytes(emit.IntToBytes(t.Balance))
|
2020-03-05 07:45:50 +00:00
|
|
|
w.WriteU32LE(t.LastUpdatedBlock)
|
|
|
|
}
|
|
|
|
|
|
|
|
// DecodeBinary implements io.Serializable interface.
|
|
|
|
func (t *NEP5Tracker) DecodeBinary(r *io.BinReader) {
|
2021-02-16 17:16:02 +00:00
|
|
|
t.Balance = emit.BytesToInt(r.ReadVarBytes(amountSize))
|
2020-03-05 07:45:50 +00:00
|
|
|
t.LastUpdatedBlock = r.ReadU32LE()
|
|
|
|
}
|
2020-03-05 14:11:58 +00:00
|
|
|
|
2020-10-29 19:10:40 +00:00
|
|
|
func parseUint160(addr []byte) util.Uint160 {
|
|
|
|
if u, err := util.Uint160DecodeBytesBE(addr); err == nil {
|
|
|
|
return u
|
|
|
|
}
|
|
|
|
return util.Uint160{}
|
|
|
|
}
|
|
|
|
|
|
|
|
// NEP5TransferFromNotification creates NEP5Transfer structure from the given
|
|
|
|
// notification (and using given context) if it's possible to parse it as
|
|
|
|
// NEP5 transfer.
|
|
|
|
func NEP5TransferFromNotification(ne NotificationEvent, txHash util.Uint256, height uint32, time uint32, index uint32) (*NEP5Transfer, error) {
|
|
|
|
arr, ok := ne.Item.Value().([]vm.StackItem)
|
|
|
|
if !ok || len(arr) != 4 {
|
|
|
|
return nil, errors.New("no array or wrong element count")
|
|
|
|
}
|
|
|
|
op, ok := arr[0].Value().([]byte)
|
|
|
|
if !ok || string(op) != "transfer" {
|
|
|
|
return nil, errors.New("not a 'transfer' event")
|
|
|
|
}
|
|
|
|
from, ok := arr[1].Value().([]byte)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("wrong 'from' type")
|
|
|
|
}
|
|
|
|
to, ok := arr[2].Value().([]byte)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("wrong 'to' type")
|
|
|
|
}
|
|
|
|
amount, ok := arr[3].Value().(*big.Int)
|
|
|
|
if !ok {
|
|
|
|
bs, ok := arr[3].Value().([]byte)
|
|
|
|
if !ok {
|
|
|
|
return nil, errors.New("wrong amount type")
|
|
|
|
}
|
|
|
|
amount = emit.BytesToInt(bs)
|
|
|
|
}
|
|
|
|
toAddr := parseUint160(to)
|
|
|
|
fromAddr := parseUint160(from)
|
|
|
|
transfer := &NEP5Transfer{
|
|
|
|
Asset: ne.ScriptHash,
|
|
|
|
From: fromAddr,
|
|
|
|
To: toAddr,
|
2021-02-16 17:16:02 +00:00
|
|
|
Amount: amount,
|
2020-10-29 19:10:40 +00:00
|
|
|
Block: height,
|
|
|
|
Timestamp: time,
|
|
|
|
Tx: txHash,
|
|
|
|
Index: index,
|
|
|
|
}
|
|
|
|
return transfer, nil
|
|
|
|
}
|
|
|
|
|
2020-03-05 14:11:58 +00:00
|
|
|
// EncodeBinary implements io.Serializable interface.
|
|
|
|
// Note: change NEP5TransferSize constant when changing this function.
|
|
|
|
func (t *NEP5Transfer) EncodeBinary(w *io.BinWriter) {
|
|
|
|
w.WriteBytes(t.Asset[:])
|
|
|
|
w.WriteBytes(t.Tx[:])
|
|
|
|
w.WriteBytes(t.From[:])
|
|
|
|
w.WriteBytes(t.To[:])
|
|
|
|
w.WriteU32LE(t.Block)
|
|
|
|
w.WriteU32LE(t.Timestamp)
|
2021-02-16 17:16:02 +00:00
|
|
|
am := emit.IntToBytes(t.Amount)
|
|
|
|
if len(am) > amountSize {
|
|
|
|
panic("bad integer length")
|
|
|
|
}
|
|
|
|
fillerLen := amountSize - len(am)
|
|
|
|
w.WriteBytes(am)
|
|
|
|
var filler byte
|
|
|
|
if t.Amount.Sign() < 0 {
|
|
|
|
filler = 0xff
|
|
|
|
}
|
|
|
|
for i := 0; i < fillerLen; i++ {
|
|
|
|
w.WriteB(filler)
|
|
|
|
}
|
2020-08-03 07:43:53 +00:00
|
|
|
w.WriteU32LE(t.Index)
|
2020-03-05 14:11:58 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
// DecodeBinary implements io.Serializable interface.
|
|
|
|
func (t *NEP5Transfer) DecodeBinary(r *io.BinReader) {
|
|
|
|
r.ReadBytes(t.Asset[:])
|
|
|
|
r.ReadBytes(t.Tx[:])
|
|
|
|
r.ReadBytes(t.From[:])
|
|
|
|
r.ReadBytes(t.To[:])
|
|
|
|
t.Block = r.ReadU32LE()
|
|
|
|
t.Timestamp = r.ReadU32LE()
|
2021-02-16 17:16:02 +00:00
|
|
|
amount := make([]byte, amountSize)
|
|
|
|
r.ReadBytes(amount)
|
|
|
|
t.Amount = emit.BytesToInt(amount)
|
2020-08-03 07:43:53 +00:00
|
|
|
t.Index = r.ReadU32LE()
|
2020-03-05 14:11:58 +00:00
|
|
|
}
|