Merge pull request #1165 from nspcc-dev/neo3/state/nep5transfer

core: store NEP5 transfers and balances as big.Int
This commit is contained in:
Roman Khimov 2020-07-09 16:55:51 +03:00 committed by GitHub
commit 1fa2d4bc6a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
15 changed files with 113 additions and 80 deletions

View file

@ -694,7 +694,7 @@ func (bc *Blockchain) handleNotification(note *state.NotificationEvent, d *dao.C
} }
amount = bigint.FromBytes(bs) amount = bigint.FromBytes(bs)
} }
bc.processNEP5Transfer(d, h, b, note.ScriptHash, from, to, amount.Int64()) bc.processNEP5Transfer(d, h, b, note.ScriptHash, from, to, amount)
} }
func parseUint160(addr []byte) util.Uint160 { func parseUint160(addr []byte) util.Uint160 {
@ -704,7 +704,7 @@ func parseUint160(addr []byte) util.Uint160 {
return util.Uint160{} return util.Uint160{}
} }
func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b *block.Block, sc util.Uint160, from, to []byte, amount int64) { func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b *block.Block, sc util.Uint160, from, to []byte, amount *big.Int) {
toAddr := parseUint160(to) toAddr := parseUint160(to)
fromAddr := parseUint160(from) fromAddr := parseUint160(from)
transfer := &state.NEP5Transfer{ transfer := &state.NEP5Transfer{
@ -721,11 +721,10 @@ func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b *
return return
} }
bs := balances.Trackers[sc] bs := balances.Trackers[sc]
bs.Balance -= amount bs.Balance = *new(big.Int).Sub(&bs.Balance, amount)
bs.LastUpdatedBlock = b.Index bs.LastUpdatedBlock = b.Index
balances.Trackers[sc] = bs balances.Trackers[sc] = bs
transfer.Amount = *new(big.Int).Sub(&transfer.Amount, amount)
transfer.Amount = -amount
isBig, err := cache.AppendNEP5Transfer(fromAddr, balances.NextTransferBatch, transfer) isBig, err := cache.AppendNEP5Transfer(fromAddr, balances.NextTransferBatch, transfer)
if err != nil { if err != nil {
return return
@ -743,11 +742,11 @@ func (bc *Blockchain) processNEP5Transfer(cache *dao.Cached, h util.Uint256, b *
return return
} }
bs := balances.Trackers[sc] bs := balances.Trackers[sc]
bs.Balance += amount bs.Balance = *new(big.Int).Add(&bs.Balance, amount)
bs.LastUpdatedBlock = b.Index bs.LastUpdatedBlock = b.Index
balances.Trackers[sc] = bs balances.Trackers[sc] = bs
transfer.Amount = amount transfer.Amount = *amount
isBig, err := cache.AppendNEP5Transfer(toAddr, balances.NextTransferBatch, transfer) isBig, err := cache.AppendNEP5Transfer(toAddr, balances.NextTransferBatch, transfer)
if err != nil { if err != nil {
return return
@ -788,23 +787,24 @@ func (bc *Blockchain) GetNEP5Balances(acc util.Uint160) *state.NEP5Balances {
} }
// GetUtilityTokenBalance returns utility token (GAS) balance for the acc. // GetUtilityTokenBalance returns utility token (GAS) balance for the acc.
func (bc *Blockchain) GetUtilityTokenBalance(acc util.Uint160) int64 { func (bc *Blockchain) GetUtilityTokenBalance(acc util.Uint160) *big.Int {
bs, err := bc.dao.GetNEP5Balances(acc) bs, err := bc.dao.GetNEP5Balances(acc)
if err != nil { if err != nil {
return 0 return big.NewInt(0)
} }
return bs.Trackers[bc.contracts.GAS.Hash].Balance balance := bs.Trackers[bc.contracts.GAS.Hash].Balance
return &balance
} }
// GetGoverningTokenBalance returns governing token (NEO) balance and the height // GetGoverningTokenBalance returns governing token (NEO) balance and the height
// of the last balance change for the account. // of the last balance change for the account.
func (bc *Blockchain) GetGoverningTokenBalance(acc util.Uint160) (int64, uint32) { func (bc *Blockchain) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint32) {
bs, err := bc.dao.GetNEP5Balances(acc) bs, err := bc.dao.GetNEP5Balances(acc)
if err != nil { if err != nil {
return 0, 0 return big.NewInt(0), 0
} }
neo := bs.Trackers[bc.contracts.NEO.Hash] neo := bs.Trackers[bc.contracts.NEO.Hash]
return neo.Balance, neo.LastUpdatedBlock return &neo.Balance, neo.LastUpdatedBlock
} }
// LastBatch returns last persisted storage batch. // LastBatch returns last persisted storage batch.
@ -1062,7 +1062,7 @@ func (bc *Blockchain) UnsubscribeFromExecutions(ch chan<- *state.AppExecResult)
// amount of NEO between specified blocks. The amount of NEO being passed is in // amount of NEO between specified blocks. The amount of NEO being passed is in
// its natural non-divisible form (1 NEO as 1, 2 NEO as 2, no multiplication by // its natural non-divisible form (1 NEO as 1, 2 NEO as 2, no multiplication by
// 10⁸ is needed as for Fixed8). // 10⁸ is needed as for Fixed8).
func (bc *Blockchain) CalculateClaimable(value int64, startHeight, endHeight uint32) int64 { func (bc *Blockchain) CalculateClaimable(value *big.Int, startHeight, endHeight uint32) *big.Int {
var amount int64 var amount int64
di := uint32(bc.decrementInterval) di := uint32(bc.decrementInterval)
@ -1088,7 +1088,7 @@ func (bc *Blockchain) CalculateClaimable(value int64, startHeight, endHeight uin
amount += int64(iend-istart) * int64(bc.generationAmount[ustart]) amount += int64(iend-istart) * int64(bc.generationAmount[ustart])
} }
return amount * value return new(big.Int).Mul(big.NewInt(amount), value)
} }
// FeePerByte returns transaction network fee per byte. // FeePerByte returns transaction network fee per byte.
@ -1148,7 +1148,7 @@ func (bc *Blockchain) verifyTx(t *transaction.Transaction, block *block.Block) e
} }
balance := bc.GetUtilityTokenBalance(t.Sender) balance := bc.GetUtilityTokenBalance(t.Sender)
need := t.SystemFee + t.NetworkFee need := t.SystemFee + t.NetworkFee
if balance < need { if balance.Cmp(big.NewInt(need)) < 0 {
return errors.Errorf("insufficient funds: balance is %v, need: %v", balance, need) return errors.Errorf("insufficient funds: balance is %v, need: %v", balance, need)
} }
size := io.GetVarSize(t) size := io.GetVarSize(t)

View file

@ -1,6 +1,7 @@
package core package core
import ( import (
"math/big"
"testing" "testing"
"time" "time"
@ -196,23 +197,23 @@ func TestGetClaimable(t *testing.T) {
require.NoError(t, err) require.NoError(t, err)
t.Run("first generation period", func(t *testing.T) { t.Run("first generation period", func(t *testing.T) {
amount := bc.CalculateClaimable(1, 0, 2) amount := bc.CalculateClaimable(big.NewInt(1), 0, 2)
require.EqualValues(t, 8, amount) require.EqualValues(t, big.NewInt(8), amount)
}) })
t.Run("a number of full periods", func(t *testing.T) { t.Run("a number of full periods", func(t *testing.T) {
amount := bc.CalculateClaimable(1, 0, 6) amount := bc.CalculateClaimable(big.NewInt(1), 0, 6)
require.EqualValues(t, 4+4+3+3+2+2, amount) require.EqualValues(t, big.NewInt(4+4+3+3+2+2), amount)
}) })
t.Run("start from the 2-nd block", func(t *testing.T) { t.Run("start from the 2-nd block", func(t *testing.T) {
amount := bc.CalculateClaimable(1, 1, 7) amount := bc.CalculateClaimable(big.NewInt(1), 1, 7)
require.EqualValues(t, 4+3+3+2+2+1, amount) require.EqualValues(t, big.NewInt(4+3+3+2+2+1), amount)
}) })
t.Run("end height after generation has ended", func(t *testing.T) { t.Run("end height after generation has ended", func(t *testing.T) {
amount := bc.CalculateClaimable(1, 1, 10) amount := bc.CalculateClaimable(big.NewInt(1), 1, 10)
require.EqualValues(t, 4+3+3+2+2+1+1, amount) require.EqualValues(t, big.NewInt(4+3+3+2+2+1+1), amount)
}) })
} }

View file

@ -1,6 +1,8 @@
package blockchainer package blockchainer
import ( import (
"math/big"
"github.com/nspcc-dev/neo-go/pkg/config" "github.com/nspcc-dev/neo-go/pkg/config"
"github.com/nspcc-dev/neo-go/pkg/core/block" "github.com/nspcc-dev/neo-go/pkg/core/block"
"github.com/nspcc-dev/neo-go/pkg/core/mempool" "github.com/nspcc-dev/neo-go/pkg/core/mempool"
@ -19,13 +21,13 @@ type Blockchainer interface {
AddHeaders(...*block.Header) error AddHeaders(...*block.Header) error
AddBlock(*block.Block) error AddBlock(*block.Block) error
BlockHeight() uint32 BlockHeight() uint32
CalculateClaimable(value int64, startHeight, endHeight uint32) int64 CalculateClaimable(value *big.Int, startHeight, endHeight uint32) *big.Int
Close() Close()
HeaderHeight() uint32 HeaderHeight() uint32
GetBlock(hash util.Uint256) (*block.Block, error) GetBlock(hash util.Uint256) (*block.Block, error)
GetContractState(hash util.Uint160) *state.Contract GetContractState(hash util.Uint160) *state.Contract
GetEnrollments() ([]state.Validator, error) GetEnrollments() ([]state.Validator, error)
GetGoverningTokenBalance(acc util.Uint160) (int64, uint32) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint32)
GetHeaderHash(int) util.Uint256 GetHeaderHash(int) util.Uint256
GetHeader(hash util.Uint256) (*block.Header, error) GetHeader(hash util.Uint256) (*block.Header, error)
CurrentHeaderHash() util.Uint256 CurrentHeaderHash() util.Uint256

View file

@ -5,6 +5,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big"
"os" "os"
"testing" "testing"
"time" "time"
@ -181,7 +182,7 @@ func TestCreateBasicChain(t *testing.T) {
priv0 := testchain.PrivateKeyByID(0) priv0 := testchain.PrivateKeyByID(0)
priv0ScriptHash := priv0.GetScriptHash() priv0ScriptHash := priv0.GetScriptHash()
require.Equal(t, int64(0), bc.GetUtilityTokenBalance(priv0ScriptHash)) require.Equal(t, big.NewInt(0), bc.GetUtilityTokenBalance(priv0ScriptHash))
// Move some NEO to one simple account. // Move some NEO to one simple account.
txMoveNeo := newNEP5Transfer(neoHash, neoOwner, priv0ScriptHash, neoAmount) txMoveNeo := newNEP5Transfer(neoHash, neoOwner, priv0ScriptHash, neoAmount)
txMoveNeo.ValidUntilBlock = validUntilBlock txMoveNeo.ValidUntilBlock = validUntilBlock
@ -211,7 +212,7 @@ func TestCreateBasicChain(t *testing.T) {
t.Logf("txMoveNeo: %s", txMoveNeo.Hash().StringLE()) t.Logf("txMoveNeo: %s", txMoveNeo.Hash().StringLE())
t.Logf("txMoveGas: %s", txMoveGas.Hash().StringLE()) t.Logf("txMoveGas: %s", txMoveGas.Hash().StringLE())
require.True(t, bc.GetUtilityTokenBalance(priv0ScriptHash) >= 1000*native.GASFactor) require.True(t, bc.GetUtilityTokenBalance(priv0ScriptHash).Cmp(big.NewInt(1000*native.GASFactor)) >= 0)
// info for getblockheader rpc tests // info for getblockheader rpc tests
t.Logf("header hash: %s", b.Hash().StringLE()) t.Logf("header hash: %s", b.Hash().StringLE())
buf := io.NewBufBinWriter() buf := io.NewBufBinWriter()

View file

@ -1,11 +1,13 @@
package mempool package mempool
import ( import (
"math/big"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
) )
// Feer is an interface that abstract the implementation of the fee calculation. // Feer is an interface that abstract the implementation of the fee calculation.
type Feer interface { type Feer interface {
FeePerByte() int64 FeePerByte() int64
GetUtilityTokenBalance(util.Uint160) int64 GetUtilityTokenBalance(util.Uint160) *big.Int
} }

View file

@ -2,6 +2,7 @@ package mempool
import ( import (
"errors" "errors"
"math/big"
"sort" "sort"
"sync" "sync"
"time" "time"
@ -35,7 +36,7 @@ type items []*item
// utilityBalanceAndFees stores sender's balance and overall fees of // utilityBalanceAndFees stores sender's balance and overall fees of
// sender's transactions which are currently in mempool // sender's transactions which are currently in mempool
type utilityBalanceAndFees struct { type utilityBalanceAndFees struct {
balance int64 balance *big.Int
feeSum int64 feeSum int64
} }
@ -124,7 +125,7 @@ func (mp *Pool) checkBalanceAndUpdate(tx *transaction.Transaction, feer Feer) bo
mp.fees[tx.Sender] = senderFee mp.fees[tx.Sender] = senderFee
} }
needFee := senderFee.feeSum + tx.SystemFee + tx.NetworkFee needFee := senderFee.feeSum + tx.SystemFee + tx.NetworkFee
if senderFee.balance < needFee { if senderFee.balance.Cmp(big.NewInt(needFee)) < 0 {
return false return false
} }
return true return true

View file

@ -1,6 +1,7 @@
package mempool package mempool
import ( import (
"math/big"
"sort" "sort"
"testing" "testing"
@ -16,13 +17,13 @@ type FeerStub struct {
feePerByte int64 feePerByte int64
} }
const balance = 10000000 var balance = big.NewInt(10000000)
func (fs *FeerStub) FeePerByte() int64 { func (fs *FeerStub) FeePerByte() int64 {
return fs.feePerByte return fs.feePerByte
} }
func (fs *FeerStub) GetUtilityTokenBalance(uint160 util.Uint160) int64 { func (fs *FeerStub) GetUtilityTokenBalance(uint160 util.Uint160) *big.Int {
return balance return balance
} }
@ -184,7 +185,7 @@ func TestMemPoolFees(t *testing.T) {
mp := NewMemPool(10) mp := NewMemPool(10)
sender0 := util.Uint160{1, 2, 3} sender0 := util.Uint160{1, 2, 3}
tx0 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0) tx0 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
tx0.NetworkFee = balance + 1 tx0.NetworkFee = balance.Int64() + 1
tx0.Sender = sender0 tx0.Sender = sender0
// insufficient funds to add transaction, but balance should be stored // insufficient funds to add transaction, but balance should be stored
require.Equal(t, false, mp.Verify(tx0, &FeerStub{})) require.Equal(t, false, mp.Verify(tx0, &FeerStub{}))
@ -195,9 +196,10 @@ func TestMemPoolFees(t *testing.T) {
feeSum: 0, feeSum: 0,
}, mp.fees[sender0]) }, mp.fees[sender0])
balancePart := new(big.Int).Div(balance, big.NewInt(4))
// no problems with adding another transaction with lower fee // no problems with adding another transaction with lower fee
tx1 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0) tx1 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
tx1.NetworkFee = balance * 0.7 tx1.NetworkFee = balancePart.Int64()
tx1.Sender = sender0 tx1.Sender = sender0
require.NoError(t, mp.Add(tx1, &FeerStub{})) require.NoError(t, mp.Add(tx1, &FeerStub{}))
require.Equal(t, 1, len(mp.fees)) require.Equal(t, 1, len(mp.fees))
@ -208,14 +210,14 @@ func TestMemPoolFees(t *testing.T) {
// balance shouldn't change after adding one more transaction // balance shouldn't change after adding one more transaction
tx2 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0) tx2 := transaction.New(netmode.UnitTestNet, []byte{byte(opcode.PUSH1)}, 0)
tx2.NetworkFee = balance * 0.3 tx2.NetworkFee = new(big.Int).Sub(balance, balancePart).Int64()
tx2.Sender = sender0 tx2.Sender = sender0
require.NoError(t, mp.Add(tx2, &FeerStub{})) require.NoError(t, mp.Add(tx2, &FeerStub{}))
require.Equal(t, 2, len(mp.verifiedTxes)) require.Equal(t, 2, len(mp.verifiedTxes))
require.Equal(t, 1, len(mp.fees)) require.Equal(t, 1, len(mp.fees))
require.Equal(t, utilityBalanceAndFees{ require.Equal(t, utilityBalanceAndFees{
balance: balance, balance: balance,
feeSum: balance, feeSum: balance.Int64(),
}, mp.fees[sender0]) }, mp.fees[sender0])
// can't add more transactions as we don't have enough GAS // can't add more transactions as we don't have enough GAS
@ -227,7 +229,7 @@ func TestMemPoolFees(t *testing.T) {
require.Equal(t, 1, len(mp.fees)) require.Equal(t, 1, len(mp.fees))
require.Equal(t, utilityBalanceAndFees{ require.Equal(t, utilityBalanceAndFees{
balance: balance, balance: balance,
feeSum: balance, feeSum: balance.Int64(),
}, mp.fees[sender0]) }, mp.fees[sender0])
// check whether sender's fee updates correctly // check whether sender's fee updates correctly

View file

@ -197,9 +197,9 @@ func (n *NEO) distributeGas(ic *interop.Context, h util.Uint160, acc *state.NEOB
if ic.Block == nil || ic.Block.Index == 0 { if ic.Block == nil || ic.Block.Index == 0 {
return nil return nil
} }
gen := ic.Chain.CalculateClaimable(acc.Balance.Int64(), acc.BalanceHeight, ic.Block.Index) gen := ic.Chain.CalculateClaimable(&acc.Balance, acc.BalanceHeight, ic.Block.Index)
acc.BalanceHeight = ic.Block.Index acc.BalanceHeight = ic.Block.Index
n.GAS.mint(ic, h, big.NewInt(gen)) n.GAS.mint(ic, h, gen)
return nil return nil
} }
@ -212,8 +212,8 @@ func (n *NEO) unclaimedGas(ic *interop.Context, args []stackitem.Item) stackitem
} }
tr := bs.Trackers[n.Hash] tr := bs.Trackers[n.Hash]
gen := ic.Chain.CalculateClaimable(tr.Balance, tr.LastUpdatedBlock, end) gen := ic.Chain.CalculateClaimable(&tr.Balance, tr.LastUpdatedBlock, end)
return stackitem.NewBigInteger(big.NewInt(gen)) return stackitem.NewBigInteger(gen)
} }
func (n *NEO) registerValidator(ic *interop.Context, args []stackitem.Item) stackitem.Item { func (n *NEO) registerValidator(ic *interop.Context, args []stackitem.Item) stackitem.Item {

View file

@ -214,7 +214,7 @@ func (c *nep5TokenNative) balanceOf(ic *interop.Context, args []stackitem.Item)
panic(err) panic(err)
} }
balance := bs.Trackers[c.Hash].Balance balance := bs.Trackers[c.Hash].Balance
return stackitem.NewBigInteger(big.NewInt(balance)) return stackitem.NewBigInteger(&balance)
} }
func (c *nep5TokenNative) mint(ic *interop.Context, h util.Uint160, amount *big.Int) { func (c *nep5TokenNative) mint(ic *interop.Context, h util.Uint160, amount *big.Int) {

View file

@ -1,6 +1,9 @@
package state package state
import ( import (
"math/big"
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/util"
) )
@ -8,7 +11,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
@ -17,11 +20,10 @@ type NEP5Tracker struct {
// NEP5TransferLog is a log of NEP5 token transfers for the specific command. // NEP5TransferLog is a log of NEP5 token transfers for the specific command.
type NEP5TransferLog struct { type NEP5TransferLog struct {
Raw []byte Raw []byte
// size is the number of NEP5Transfers written into Raw
size int
} }
// NEP5TransferSize is a size of a marshaled NEP5Transfer struct in bytes.
const NEP5TransferSize = util.Uint160Size*3 + 8 + 4 + 8 + util.Uint256Size
// NEP5Transfer represents a single NEP5 Transfer event. // NEP5Transfer represents a single NEP5 Transfer event.
type NEP5Transfer struct { type NEP5Transfer struct {
// Asset is a NEP5 contract hash. // Asset is a NEP5 contract hash.
@ -32,7 +34,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.
@ -89,6 +91,7 @@ func (lg *NEP5TransferLog) Append(tr *NEP5Transfer) error {
return w.Err return w.Err
} }
lg.Raw = append(lg.Raw, w.Bytes()...) lg.Raw = append(lg.Raw, w.Bytes()...)
lg.size++
return nil return nil
} }
@ -98,9 +101,10 @@ func (lg *NEP5TransferLog) ForEach(f func(*NEP5Transfer) error) error {
return nil return nil
} }
tr := new(NEP5Transfer) tr := new(NEP5Transfer)
for i := 0; i < len(lg.Raw); i += NEP5TransferSize { var bytesRead int
r := io.NewBinReaderFromBuf(lg.Raw[i : i+NEP5TransferSize]) for i := 0; i < len(lg.Raw); i += bytesRead {
tr.DecodeBinary(r) r := io.NewBinReaderFromBuf(lg.Raw[i:])
bytesRead = tr.DecodeBinaryReturnCount(r)
if r.Err != nil { if r.Err != nil {
return r.Err return r.Err
} else if err := f(tr); err != nil { } else if err := f(tr); err != nil {
@ -112,23 +116,22 @@ func (lg *NEP5TransferLog) ForEach(f func(*NEP5Transfer) error) error {
// Size returns an amount of transfer written in log. // Size returns an amount of transfer written in log.
func (lg *NEP5TransferLog) Size() int { func (lg *NEP5TransferLog) Size() int {
return len(lg.Raw) / NEP5TransferSize return lg.size
} }
// 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(bigint.ToBytes(&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 = *bigint.FromBytes(r.ReadVarBytes())
t.LastUpdatedBlock = r.ReadU32LE() t.LastUpdatedBlock = r.ReadU32LE()
} }
// EncodeBinary implements io.Serializable interface. // EncodeBinary implements io.Serializable interface.
// Note: change NEP5TransferSize constant when changing this function.
func (t *NEP5Transfer) EncodeBinary(w *io.BinWriter) { func (t *NEP5Transfer) EncodeBinary(w *io.BinWriter) {
w.WriteBytes(t.Asset[:]) w.WriteBytes(t.Asset[:])
w.WriteBytes(t.Tx[:]) w.WriteBytes(t.Tx[:])
@ -136,16 +139,27 @@ func (t *NEP5Transfer) EncodeBinary(w *io.BinWriter) {
w.WriteBytes(t.To[:]) w.WriteBytes(t.To[:])
w.WriteU32LE(t.Block) w.WriteU32LE(t.Block)
w.WriteU64LE(t.Timestamp) w.WriteU64LE(t.Timestamp)
w.WriteU64LE(uint64(t.Amount)) amountBytes := bigint.ToBytes(&t.Amount)
w.WriteU64LE(uint64(len(amountBytes)))
w.WriteBytes(amountBytes)
} }
// DecodeBinary implements io.Serializable interface. // DecodeBinary implements io.Serializable interface.
func (t *NEP5Transfer) DecodeBinary(r *io.BinReader) { func (t *NEP5Transfer) DecodeBinary(r *io.BinReader) {
_ = t.DecodeBinaryReturnCount(r)
}
// DecodeBinaryReturnCount decodes NEP5Transfer and returns the number of bytes read.
func (t *NEP5Transfer) DecodeBinaryReturnCount(r *io.BinReader) int {
r.ReadBytes(t.Asset[:]) r.ReadBytes(t.Asset[:])
r.ReadBytes(t.Tx[:]) r.ReadBytes(t.Tx[:])
r.ReadBytes(t.From[:]) r.ReadBytes(t.From[:])
r.ReadBytes(t.To[:]) r.ReadBytes(t.To[:])
t.Block = r.ReadU32LE() t.Block = r.ReadU32LE()
t.Timestamp = r.ReadU64LE() t.Timestamp = r.ReadU64LE()
t.Amount = int64(r.ReadU64LE()) amountLen := r.ReadU64LE()
amountBytes := make([]byte, amountLen)
r.ReadBytes(amountBytes)
t.Amount = *bigint.FromBytes(amountBytes)
return util.Uint160Size*3 + 8 + 4 + (8 + len(amountBytes)) + +util.Uint256Size
} }

View file

@ -1,6 +1,7 @@
package state package state
import ( import (
"math/big"
"math/rand" "math/rand"
"testing" "testing"
"time" "time"
@ -40,7 +41,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(),
} }
@ -52,7 +53,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},
@ -64,12 +65,18 @@ func TestNEP5Transfer_DecodeBinary(t *testing.T) {
func TestNEP5TransferSize(t *testing.T) { func TestNEP5TransferSize(t *testing.T) {
tr := randomTransfer(rand.New(rand.NewSource(0))) tr := randomTransfer(rand.New(rand.NewSource(0)))
size := io.GetVarSize(tr) size := io.GetVarSize(tr)
require.EqualValues(t, NEP5TransferSize, size) w := io.NewBufBinWriter()
tr.EncodeBinary(w.BinWriter)
require.NoError(t, w.Err)
r := io.NewBinReaderFromBuf(w.Bytes())
actualTr := &NEP5Transfer{}
actual := actualTr.DecodeBinaryReturnCount(r)
require.EqualValues(t, actual, size)
} }
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

@ -1,6 +1,7 @@
package network package network
import ( import (
"math/big"
"math/rand" "math/rand"
"net" "net"
"strconv" "strconv"
@ -31,7 +32,7 @@ func (chain testChain) ApplyPolicyToTxSet([]*transaction.Transaction) []*transac
func (chain testChain) GetConfig() config.ProtocolConfiguration { func (chain testChain) GetConfig() config.ProtocolConfiguration {
panic("TODO") panic("TODO")
} }
func (chain testChain) CalculateClaimable(int64, uint32, uint32) int64 { func (chain testChain) CalculateClaimable(*big.Int, uint32, uint32) *big.Int {
panic("TODO") panic("TODO")
} }
@ -123,11 +124,11 @@ func (chain testChain) GetMemPool() *mempool.Pool {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetGoverningTokenBalance(acc util.Uint160) (int64, uint32) { func (chain testChain) GetGoverningTokenBalance(acc util.Uint160) (*big.Int, uint32) {
panic("TODO") panic("TODO")
} }
func (chain testChain) GetUtilityTokenBalance(uint160 util.Uint160) int64 { func (chain testChain) GetUtilityTokenBalance(uint160 util.Uint160) *big.Int {
panic("TODO") panic("TODO")
} }

View file

@ -6,6 +6,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"math" "math"
"math/big"
"net" "net"
"net/http" "net/http"
"strconv" "strconv"
@ -512,7 +513,7 @@ func (s *Server) getNEP5Balances(ps request.Params) (interface{}, *response.Erro
if err != nil { if err != nil {
continue continue
} }
amount := amountToString(bal.Balance, dec) amount := amountToString(&bal.Balance, dec)
bs.Balances = append(bs.Balances, result.NEP5Balance{ bs.Balances = append(bs.Balances, result.NEP5Balance{
Asset: h, Asset: h,
Amount: amount, Amount: amount,
@ -547,8 +548,8 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
if err != nil { if err != nil {
return nil return nil
} }
if tr.Amount > 0 { // token was received if tr.Amount.Sign() > 0 { // token was received
transfer.Amount = amountToString(tr.Amount, d) transfer.Amount = amountToString(&tr.Amount, d)
if !tr.From.Equals(util.Uint160{}) { if !tr.From.Equals(util.Uint160{}) {
transfer.Address = address.Uint160ToString(tr.From) transfer.Address = address.Uint160ToString(tr.From)
} }
@ -556,7 +557,7 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
return nil return nil
} }
transfer.Amount = amountToString(-tr.Amount, d) transfer.Amount = amountToString(new(big.Int).Neg(&tr.Amount), d)
if !tr.To.Equals(util.Uint160{}) { if !tr.To.Equals(util.Uint160{}) {
transfer.Address = address.Uint160ToString(tr.To) transfer.Address = address.Uint160ToString(tr.To)
} }
@ -569,15 +570,14 @@ func (s *Server) getNEP5Transfers(ps request.Params) (interface{}, *response.Err
return bs, nil return bs, nil
} }
func amountToString(amount int64, decimals int64) string { func amountToString(amount *big.Int, decimals int64) string {
if decimals == 0 { if decimals == 0 {
return strconv.FormatInt(amount, 10) return amount.String()
} }
pow := int64(math.Pow10(int(decimals))) pow := int64(math.Pow10(int(decimals)))
q := amount / pow q, r := new(big.Int).DivMod(amount, big.NewInt(pow), new(big.Int))
r := amount % pow if r.Sign() == 0 {
if r == 0 { return q.String()
return strconv.FormatInt(q, 10)
} }
fs := fmt.Sprintf("%%d.%%0%dd", decimals) fs := fmt.Sprintf("%%d.%%0%dd", decimals)
return fmt.Sprintf(fs, q, r) return fmt.Sprintf(fs, q, r)
@ -783,11 +783,11 @@ func (s *Server) getUnclaimedGas(ps request.Params) (interface{}, *response.Erro
} }
neo, neoHeight := s.chain.GetGoverningTokenBalance(u) neo, neoHeight := s.chain.GetGoverningTokenBalance(u)
if neo == 0 { if neo.Sign() == 0 {
return "0", nil return "0", nil
} }
gas := s.chain.CalculateClaimable(neo, neoHeight, s.chain.BlockHeight()+1) // +1 as in C#, for the next block. gas := s.chain.CalculateClaimable(neo, neoHeight, s.chain.BlockHeight()+1) // +1 as in C#, for the next block.
return strconv.FormatInt(gas, 10), nil return gas.String(), nil
} }
// getValidators returns the current NEO consensus nodes information and voting status. // getValidators returns the current NEO consensus nodes information and voting status.

View file

@ -1,6 +1,7 @@
package server package server
import ( import (
"math/big"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"os" "os"
@ -90,6 +91,6 @@ func (fs *FeerStub) FeePerByte() int64 {
return 0 return 0
} }
func (fs *FeerStub) GetUtilityTokenBalance(acc util.Uint160) int64 { func (fs *FeerStub) GetUtilityTokenBalance(acc util.Uint160) *big.Int {
return 1000000 * native.GASFactor return big.NewInt(1000000 * native.GASFactor)
} }

View file

@ -6,6 +6,7 @@ import (
"encoding/json" "encoding/json"
"fmt" "fmt"
"io/ioutil" "io/ioutil"
"math/big"
"net/http" "net/http"
"net/http/httptest" "net/http/httptest"
"reflect" "reflect"
@ -270,7 +271,7 @@ var rpcTestCases = map[string][]rpcTestCase{
Timestamp: b.Timestamp, Timestamp: b.Timestamp,
Asset: e.chain.UtilityTokenHash(), Asset: e.chain.UtilityTokenHash(),
Address: "", // burn has empty receiver Address: "", // burn has empty receiver
Amount: amountToString(int64(amount), 8), Amount: amountToString(big.NewInt(amount), 8),
Index: b.Index, Index: b.Index,
TxHash: b.Hash(), TxHash: b.Hash(),
}) })
@ -282,7 +283,7 @@ var rpcTestCases = map[string][]rpcTestCase{
Timestamp: b.Timestamp, Timestamp: b.Timestamp,
Asset: e.chain.UtilityTokenHash(), Asset: e.chain.UtilityTokenHash(),
Address: "", // minted from network fees. Address: "", // minted from network fees.
Amount: amountToString(int64(netFee), 8), Amount: amountToString(big.NewInt(netFee), 8),
Index: b.Index, Index: b.Index,
TxHash: b.Hash(), TxHash: b.Hash(),
}) })