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

View file

@ -13,7 +13,7 @@ import (
// NEP5Tracker contains info about a single account in a NEP5 contract.
type NEP5Tracker struct {
// 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
// account occured.
LastUpdatedBlock uint32
@ -25,7 +25,7 @@ type TransferLog struct {
}
// 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.
type NEP5Transfer struct {
@ -37,7 +37,7 @@ type NEP5Transfer struct {
To util.Uint160
// Amount is the amount of tokens transferred.
// 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 uint32
// Timestamp is the timestamp of the block where transfer occured.
@ -48,6 +48,8 @@ type NEP5Transfer struct {
Index uint32
}
const amountSize = 32
// NEP5Balances is a map of the NEP5 contract hashes
// to the corresponding structures.
type NEP5Balances struct {
@ -143,13 +145,13 @@ func (lg *TransferLog) Size() int {
// EncodeBinary implements io.Serializable interface.
func (t *NEP5Tracker) EncodeBinary(w *io.BinWriter) {
w.WriteU64LE(uint64(t.Balance))
w.WriteVarBytes(emit.IntToBytes(t.Balance))
w.WriteU32LE(t.LastUpdatedBlock)
}
// DecodeBinary implements io.Serializable interface.
func (t *NEP5Tracker) DecodeBinary(r *io.BinReader) {
t.Balance = int64(r.ReadU64LE())
t.Balance = emit.BytesToInt(r.ReadVarBytes(amountSize))
t.LastUpdatedBlock = r.ReadU32LE()
}
@ -194,7 +196,7 @@ func NEP5TransferFromNotification(ne NotificationEvent, txHash util.Uint256, hei
Asset: ne.ScriptHash,
From: fromAddr,
To: toAddr,
Amount: amount.Int64(),
Amount: amount,
Block: height,
Timestamp: time,
Tx: txHash,
@ -212,7 +214,19 @@ func (t *NEP5Transfer) EncodeBinary(w *io.BinWriter) {
w.WriteBytes(t.To[:])
w.WriteU32LE(t.Block)
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)
}
@ -224,6 +238,8 @@ func (t *NEP5Transfer) DecodeBinary(r *io.BinReader) {
r.ReadBytes(t.To[:])
t.Block = 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()
}

View file

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

View file

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