state: use big.Int for NEP5 balances and transfer amounts

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.
This commit is contained in:
Roman Khimov 2021-02-16 20:16:02 +03:00
parent 38842531ca
commit eb1986d2fc
4 changed files with 51 additions and 24 deletions

View file

@ -3,6 +3,7 @@ package core
import ( import (
"fmt" "fmt"
"math" "math"
"math/big"
"sort" "sort"
"sync" "sync"
"sync/atomic" "sync/atomic"
@ -944,20 +945,23 @@ func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, transfer *state.NEP
return return
} }
bs := balances.Trackers[transfer.Asset] bs := balances.Trackers[transfer.Asset]
bs.Balance -= transfer.Amount if bs.Balance == nil {
if bs.Balance != 0 { return
}
bs.Balance.Sub(bs.Balance, transfer.Amount)
if bs.Balance.Sign() > 0 {
bs.LastUpdatedBlock = transfer.Block bs.LastUpdatedBlock = transfer.Block
balances.Trackers[transfer.Asset] = bs balances.Trackers[transfer.Asset] = bs
} else { } else {
delete(balances.Trackers, transfer.Asset) delete(balances.Trackers, transfer.Asset)
} }
transfer.Amount = -transfer.Amount transfer.Amount.Neg(transfer.Amount)
isBig, err := cache.AppendNEP5Transfer(transfer.From, balances.NextTransferBatch, transfer) isBig, err := cache.AppendNEP5Transfer(transfer.From, balances.NextTransferBatch, transfer)
if err != nil { if err != nil {
return return
} }
transfer.Amount = -transfer.Amount transfer.Amount.Neg(transfer.Amount)
if isBig { if isBig {
balances.NextTransferBatch++ balances.NextTransferBatch++
} }
@ -971,7 +975,10 @@ func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, transfer *state.NEP
return return
} }
bs := balances.Trackers[transfer.Asset] bs := balances.Trackers[transfer.Asset]
bs.Balance += transfer.Amount if bs.Balance == nil {
bs.Balance = new(big.Int)
}
bs.Balance.Add(bs.Balance, transfer.Amount)
bs.LastUpdatedBlock = transfer.Block bs.LastUpdatedBlock = transfer.Block
balances.Trackers[transfer.Asset] = bs balances.Trackers[transfer.Asset] = bs

View file

@ -13,7 +13,7 @@ import (
// NEP5Tracker contains info about a single account in a NEP5 contract. // NEP5Tracker contains info about a single account in a NEP5 contract.
type NEP5Tracker struct { type NEP5Tracker struct {
// Balance is the current balance of the account. // Balance is the current balance of the account.
Balance int64 Balance *big.Int
// LastUpdatedBlock is a number of block when last `transfer` to or from the // LastUpdatedBlock is a number of block when last `transfer` to or from the
// account occured. // account occured.
LastUpdatedBlock uint32 LastUpdatedBlock uint32
@ -25,7 +25,7 @@ type TransferLog struct {
} }
// NEP5TransferSize is a size of a marshaled NEP5Transfer struct in bytes. // NEP5TransferSize is a size of a marshaled NEP5Transfer struct in bytes.
const NEP5TransferSize = util.Uint160Size*3 + 8 + 4 + 4 + util.Uint256Size + 4 const NEP5TransferSize = util.Uint160Size*3 + amountSize + 4 + 4 + util.Uint256Size + 4
// NEP5Transfer represents a single NEP5 Transfer event. // NEP5Transfer represents a single NEP5 Transfer event.
type NEP5Transfer struct { type NEP5Transfer struct {
@ -37,7 +37,7 @@ type NEP5Transfer struct {
To util.Uint160 To util.Uint160
// Amount is the amount of tokens transferred. // Amount is the amount of tokens transferred.
// It is negative when tokens are sent and positive if they are received. // It is negative when tokens are sent and positive if they are received.
Amount int64 Amount *big.Int
// Block is a number of block when the event occured. // Block is a number of block when the event occured.
Block uint32 Block uint32
// Timestamp is the timestamp of the block where transfer occured. // Timestamp is the timestamp of the block where transfer occured.
@ -48,6 +48,8 @@ type NEP5Transfer struct {
Index uint32 Index uint32
} }
const amountSize = 32
// NEP5Balances is a map of the NEP5 contract hashes // NEP5Balances is a map of the NEP5 contract hashes
// to the corresponding structures. // to the corresponding structures.
type NEP5Balances struct { type NEP5Balances struct {
@ -143,13 +145,13 @@ func (lg *TransferLog) Size() int {
// EncodeBinary implements io.Serializable interface. // EncodeBinary implements io.Serializable interface.
func (t *NEP5Tracker) EncodeBinary(w *io.BinWriter) { func (t *NEP5Tracker) EncodeBinary(w *io.BinWriter) {
w.WriteU64LE(uint64(t.Balance)) w.WriteVarBytes(emit.IntToBytes(t.Balance))
w.WriteU32LE(t.LastUpdatedBlock) w.WriteU32LE(t.LastUpdatedBlock)
} }
// DecodeBinary implements io.Serializable interface. // DecodeBinary implements io.Serializable interface.
func (t *NEP5Tracker) DecodeBinary(r *io.BinReader) { func (t *NEP5Tracker) DecodeBinary(r *io.BinReader) {
t.Balance = int64(r.ReadU64LE()) t.Balance = emit.BytesToInt(r.ReadVarBytes(amountSize))
t.LastUpdatedBlock = r.ReadU32LE() t.LastUpdatedBlock = r.ReadU32LE()
} }
@ -194,7 +196,7 @@ func NEP5TransferFromNotification(ne NotificationEvent, txHash util.Uint256, hei
Asset: ne.ScriptHash, Asset: ne.ScriptHash,
From: fromAddr, From: fromAddr,
To: toAddr, To: toAddr,
Amount: amount.Int64(), Amount: amount,
Block: height, Block: height,
Timestamp: time, Timestamp: time,
Tx: txHash, Tx: txHash,
@ -212,7 +214,19 @@ func (t *NEP5Transfer) EncodeBinary(w *io.BinWriter) {
w.WriteBytes(t.To[:]) w.WriteBytes(t.To[:])
w.WriteU32LE(t.Block) w.WriteU32LE(t.Block)
w.WriteU32LE(t.Timestamp) w.WriteU32LE(t.Timestamp)
w.WriteU64LE(uint64(t.Amount)) 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) w.WriteU32LE(t.Index)
} }
@ -224,6 +238,8 @@ func (t *NEP5Transfer) DecodeBinary(r *io.BinReader) {
r.ReadBytes(t.To[:]) r.ReadBytes(t.To[:])
t.Block = r.ReadU32LE() t.Block = r.ReadU32LE()
t.Timestamp = r.ReadU32LE() t.Timestamp = r.ReadU32LE()
t.Amount = int64(r.ReadU64LE()) amount := make([]byte, amountSize)
r.ReadBytes(amount)
t.Amount = emit.BytesToInt(amount)
t.Index = r.ReadU32LE() t.Index = r.ReadU32LE()
} }

View file

@ -1,6 +1,7 @@
package state package state
import ( import (
"math/big"
"math/rand" "math/rand"
"testing" "testing"
"time" "time"
@ -41,7 +42,7 @@ func TestNEP5TransferLog_Append(t *testing.T) {
func TestNEP5Tracker_EncodeBinary(t *testing.T) { func TestNEP5Tracker_EncodeBinary(t *testing.T) {
expected := &NEP5Tracker{ expected := &NEP5Tracker{
Balance: int64(rand.Uint64()), Balance: big.NewInt(int64(rand.Uint64())),
LastUpdatedBlock: rand.Uint32(), LastUpdatedBlock: rand.Uint32(),
} }
@ -53,7 +54,7 @@ func TestNEP5Transfer_DecodeBinary(t *testing.T) {
Asset: util.Uint160{1, 2, 3}, Asset: util.Uint160{1, 2, 3},
From: util.Uint160{5, 6, 7}, From: util.Uint160{5, 6, 7},
To: util.Uint160{8, 9, 10}, To: util.Uint160{8, 9, 10},
Amount: 42, Amount: big.NewInt(42),
Block: 12345, Block: 12345,
Timestamp: 54321, Timestamp: 54321,
Tx: util.Uint256{8, 5, 3}, Tx: util.Uint256{8, 5, 3},
@ -70,7 +71,7 @@ func TestNEP5TransferSize(t *testing.T) {
func randomTransfer(r *rand.Rand) *NEP5Transfer { func randomTransfer(r *rand.Rand) *NEP5Transfer {
return &NEP5Transfer{ return &NEP5Transfer{
Amount: int64(r.Uint64()), Amount: big.NewInt(int64(r.Uint64())),
Block: r.Uint32(), Block: r.Uint32(),
Asset: random.Uint160(), Asset: random.Uint160(),
From: random.Uint160(), From: random.Uint160(),

View file

@ -771,7 +771,7 @@ func (s *Server) getNEP5Balances(ps request.Params) (interface{}, *response.Erro
} }
if as != nil { if as != nil {
for h, bal := range as.Trackers { for h, bal := range as.Trackers {
amount := strconv.FormatInt(bal.Balance, 10) amount := bal.Balance.String()
bs.Balances = append(bs.Balances, result.NEP5Balance{ bs.Balances = append(bs.Balances, result.NEP5Balance{
Asset: h, Asset: h,
Amount: amount, Amount: amount,
@ -824,14 +824,15 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
NotifyIndex: tr.Index, NotifyIndex: tr.Index,
} }
if tr.Amount > 0 { // token was received if tr.Amount.Sign() > 0 { // token was received
transfer.Amount = strconv.FormatInt(tr.Amount, 10) transfer.Amount = tr.Amount.String()
if !tr.From.Equals(util.Uint160{}) { if !tr.From.Equals(util.Uint160{}) {
transfer.Address = address.Uint160ToString(tr.From) transfer.Address = address.Uint160ToString(tr.From)
} }
bs.Received = append(bs.Received, transfer) bs.Received = append(bs.Received, transfer)
} else { } else {
transfer.Amount = strconv.FormatInt(-tr.Amount, 10) tr.Amount.Neg(tr.Amount)
transfer.Amount = tr.Amount.String()
if !tr.To.Equals(util.Uint160{}) { if !tr.To.Equals(util.Uint160{}) {
transfer.Address = address.Uint160ToString(tr.To) transfer.Address = address.Uint160ToString(tr.To)
} }
@ -888,12 +889,14 @@ func uint160ToString(u util.Uint160) string {
func appendNEP5ToTransferTx(transfer *result.TransferTx, nepTr *state.NEP5Transfer) { func appendNEP5ToTransferTx(transfer *result.TransferTx, nepTr *state.NEP5Transfer) {
var event result.TransferTxEvent var event result.TransferTxEvent
event.Asset = nepTr.Asset.StringLE() event.Asset = nepTr.Asset.StringLE()
if nepTr.Amount > 0 { // token was received if nepTr.Amount.Sign() > 0 { // token was received
event.Value = strconv.FormatInt(nepTr.Amount, 10) event.Value = nepTr.Amount.String()
event.Type = "receive" event.Type = "receive"
event.Address = uint160ToString(nepTr.From) event.Address = uint160ToString(nepTr.From)
} else { } else {
event.Value = strconv.FormatInt(-nepTr.Amount, 10) nepTr.Amount.Neg(nepTr.Amount)
event.Value = nepTr.Amount.String()
nepTr.Amount.Neg(nepTr.Amount)
event.Type = "send" event.Type = "send"
event.Address = uint160ToString(nepTr.To) event.Address = uint160ToString(nepTr.To)
} }
@ -1327,7 +1330,7 @@ func (s *Server) getBlockTransferTx(ps request.Params) (interface{}, *response.E
Asset: nepTr.Asset.StringLE(), Asset: nepTr.Asset.StringLE(),
From: uint160ToString(nepTr.From), From: uint160ToString(nepTr.From),
To: uint160ToString(nepTr.To), To: uint160ToString(nepTr.To),
Value: strconv.FormatInt(nepTr.Amount, 10), Value: nepTr.Amount.String(),
}) })
index++ index++
} }