mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-13 15:45:01 +00:00
eb1986d2fc
In general, NEP5 contracts are not limited to int64. And we have an example of pnWETH Flamingo token now (with 18 decimals) that easily overflows int64, so for correctness we need to store big.Int. And as TransferLog is shared for different purposes I've decided to not make it variable-length on Neo 2.
245 lines
6.5 KiB
Go
245 lines
6.5 KiB
Go
package state
|
|
|
|
import (
|
|
"errors"
|
|
"math/big"
|
|
|
|
"github.com/nspcc-dev/neo-go/pkg/io"
|
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm"
|
|
"github.com/nspcc-dev/neo-go/pkg/vm/emit"
|
|
)
|
|
|
|
// NEP5Tracker contains info about a single account in a NEP5 contract.
|
|
type NEP5Tracker struct {
|
|
// Balance is the current balance of the account.
|
|
Balance *big.Int
|
|
// LastUpdatedBlock is a number of block when last `transfer` to or from the
|
|
// account occured.
|
|
LastUpdatedBlock uint32
|
|
}
|
|
|
|
// TransferLog is a log of NEP5 token transfers for the specific command.
|
|
type TransferLog struct {
|
|
Raw []byte
|
|
}
|
|
|
|
// NEP5TransferSize is a size of a marshaled NEP5Transfer struct in bytes.
|
|
const NEP5TransferSize = util.Uint160Size*3 + amountSize + 4 + 4 + util.Uint256Size + 4
|
|
|
|
// 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.
|
|
Amount *big.Int
|
|
// 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
|
|
// Index is the index of this transfer in the corresponding tx.
|
|
Index uint32
|
|
}
|
|
|
|
const amountSize = 32
|
|
|
|
// NEP5Balances is a map of the NEP5 contract hashes
|
|
// to the corresponding structures.
|
|
type NEP5Balances struct {
|
|
Trackers map[util.Uint160]NEP5Tracker
|
|
// NextTransferBatch stores an index of the next transfer batch.
|
|
NextTransferBatch uint32
|
|
}
|
|
|
|
// NEP5Metadata is a metadata for NEP5 contracts.
|
|
type NEP5Metadata struct {
|
|
Decimals int64
|
|
}
|
|
|
|
// 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) {
|
|
bs.NextTransferBatch = r.ReadU32LE()
|
|
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) {
|
|
w.WriteU32LE(bs.NextTransferBatch)
|
|
w.WriteVarUint(uint64(len(bs.Trackers)))
|
|
for k, v := range bs.Trackers {
|
|
w.WriteBytes(k[:])
|
|
v.EncodeBinary(w)
|
|
}
|
|
}
|
|
|
|
// 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))
|
|
}
|
|
|
|
// Append appends single transfer to a log.
|
|
func (lg *TransferLog) Append(tr io.Serializable) error {
|
|
w := io.NewBufBinWriter()
|
|
tr.EncodeBinary(w.BinWriter)
|
|
if w.Err != nil {
|
|
return w.Err
|
|
}
|
|
lg.Raw = append(lg.Raw, w.Bytes()...)
|
|
return nil
|
|
}
|
|
|
|
// ForEach iterates over transfer log returning on first error.
|
|
func (lg *TransferLog) ForEach(size int, tr io.Serializable, f func() (bool, error)) (bool, error) {
|
|
if lg == nil {
|
|
return true, nil
|
|
}
|
|
for i := len(lg.Raw); i > 0; i -= size {
|
|
r := io.NewBinReaderFromBuf(lg.Raw[i-size : i])
|
|
tr.DecodeBinary(r)
|
|
if r.Err != nil {
|
|
return false, r.Err
|
|
}
|
|
cont, err := f()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if !cont {
|
|
return false, nil
|
|
}
|
|
}
|
|
return true, nil
|
|
}
|
|
|
|
// Size returns an amount of transfer written in log provided size of a single transfer.
|
|
func (lg *TransferLog) Size() int {
|
|
return len(lg.Raw)
|
|
}
|
|
|
|
// EncodeBinary implements io.Serializable interface.
|
|
func (t *NEP5Tracker) EncodeBinary(w *io.BinWriter) {
|
|
w.WriteVarBytes(emit.IntToBytes(t.Balance))
|
|
w.WriteU32LE(t.LastUpdatedBlock)
|
|
}
|
|
|
|
// DecodeBinary implements io.Serializable interface.
|
|
func (t *NEP5Tracker) DecodeBinary(r *io.BinReader) {
|
|
t.Balance = emit.BytesToInt(r.ReadVarBytes(amountSize))
|
|
t.LastUpdatedBlock = r.ReadU32LE()
|
|
}
|
|
|
|
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,
|
|
Amount: amount,
|
|
Block: height,
|
|
Timestamp: time,
|
|
Tx: txHash,
|
|
Index: index,
|
|
}
|
|
return transfer, nil
|
|
}
|
|
|
|
// 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)
|
|
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)
|
|
}
|
|
w.WriteU32LE(t.Index)
|
|
}
|
|
|
|
// 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()
|
|
amount := make([]byte, amountSize)
|
|
r.ReadBytes(amount)
|
|
t.Amount = emit.BytesToInt(amount)
|
|
t.Index = r.ReadU32LE()
|
|
}
|