From eb1986d2fc781f5ef7bcca89cc899b5c01e9ea24 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 16 Feb 2021 20:16:02 +0300 Subject: [PATCH] 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. --- pkg/core/blockchain.go | 17 ++++++++++++----- pkg/core/state/nep5.go | 32 ++++++++++++++++++++++++-------- pkg/core/state/nep5_test.go | 7 ++++--- pkg/rpc/server/server.go | 19 +++++++++++-------- 4 files changed, 51 insertions(+), 24 deletions(-) diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 1644f101b..511044fc6 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -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 diff --git a/pkg/core/state/nep5.go b/pkg/core/state/nep5.go index d289ec00e..a87685293 100644 --- a/pkg/core/state/nep5.go +++ b/pkg/core/state/nep5.go @@ -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() } diff --git a/pkg/core/state/nep5_test.go b/pkg/core/state/nep5_test.go index 19f055d1c..a2c8ea764 100644 --- a/pkg/core/state/nep5_test.go +++ b/pkg/core/state/nep5_test.go @@ -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(), diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index 623a49e4d..b1ba0636c 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -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++ }