diff --git a/cli/wallet_test.go b/cli/wallet_test.go index bae2449d6..f78871cef 100644 --- a/cli/wallet_test.go +++ b/cli/wallet_test.go @@ -172,21 +172,48 @@ func TestClaimGas(t *testing.T) { e := newExecutor(t, true) defer e.Close(t) - start := e.Chain.BlockHeight() - balanceBefore := e.Chain.GetUtilityTokenBalance(validatorHash) + const walletPath = "testdata/testwallet.json" + w, err := wallet.NewWalletFromFile(walletPath) + require.NoError(t, err) + defer w.Close() + + args := []string{ + "neo-go", "wallet", "nep5", "multitransfer", + "--rpc-endpoint", "http://" + e.RPC.Addr, + "--wallet", validatorWallet, + "--from", validatorAddr, + "neo:" + w.Accounts[0].Address + ":1000", + "gas:" + w.Accounts[0].Address + ":1000", // for tx send + } e.In.WriteString("one\r") + e.Run(t, args...) + e.checkTxPersisted(t) + + h, err := address.StringToUint160(w.Accounts[0].Address) + require.NoError(t, err) + + balanceBefore := e.Chain.GetUtilityTokenBalance(h) + claimHeight := e.Chain.BlockHeight() + 1 + cl, err := e.Chain.CalculateClaimable(h, claimHeight) + require.NoError(t, err) + require.True(t, cl.Sign() > 0) + + e.In.WriteString("testpass\r") e.Run(t, "neo-go", "wallet", "claim", "--rpc-endpoint", "http://"+e.RPC.Addr, - "--wallet", validatorWallet, - "--address", validatorAddr) - tx, end := e.checkTxPersisted(t) - b, _ := e.Chain.GetGoverningTokenBalance(validatorHash) - cl := e.Chain.CalculateClaimable(b, start, end) - require.True(t, cl.Sign() > 0) - cl.Sub(cl, big.NewInt(tx.NetworkFee+tx.SystemFee)) + "--wallet", walletPath, + "--address", w.Accounts[0].Address) + tx, height := e.checkTxPersisted(t) + balanceBefore.Sub(balanceBefore, big.NewInt(tx.NetworkFee+tx.SystemFee)) + balanceBefore.Add(balanceBefore, cl) - balanceAfter := e.Chain.GetUtilityTokenBalance(validatorHash) - require.Equal(t, 0, balanceAfter.Cmp(balanceBefore.Add(balanceBefore, cl))) + balanceAfter := e.Chain.GetUtilityTokenBalance(h) + // height can be bigger than claimHeight especially when tests are executed with -race. + if height == claimHeight { + require.Equal(t, 0, balanceAfter.Cmp(balanceBefore)) + } else { + require.Equal(t, 1, balanceAfter.Cmp(balanceBefore)) + } } func TestImportDeployed(t *testing.T) { diff --git a/pkg/core/blockchain.go b/pkg/core/blockchain.go index 81b705230..f575ed082 100644 --- a/pkg/core/blockchain.go +++ b/pkg/core/blockchain.go @@ -1156,10 +1156,8 @@ func (bc *Blockchain) UnsubscribeFromExecutions(ch chan<- *state.AppExecResult) // CalculateClaimable calculates the amount of GAS generated by owning specified // amount of NEO between specified blocks. -func (bc *Blockchain) CalculateClaimable(value *big.Int, startHeight, endHeight uint32) *big.Int { - ic := bc.newInteropContext(trigger.Application, bc.dao, nil, nil) - res, _ := bc.contracts.NEO.CalculateNEOHolderReward(ic, value, startHeight, endHeight) - return res +func (bc *Blockchain) CalculateClaimable(acc util.Uint160, endHeight uint32) (*big.Int, error) { + return bc.contracts.NEO.CalculateBonus(bc.dao, acc, endHeight) } // FeePerByte returns transaction network fee per byte. diff --git a/pkg/core/blockchain_test.go b/pkg/core/blockchain_test.go index dab65aa4b..d43b35fae 100644 --- a/pkg/core/blockchain_test.go +++ b/pkg/core/blockchain_test.go @@ -854,8 +854,9 @@ func TestGetClaimable(t *testing.T) { require.NoError(t, err) t.Run("first generation period", func(t *testing.T) { - amount := bc.CalculateClaimable(big.NewInt(1), 0, 2) - require.EqualValues(t, big.NewInt(1), amount) + amount, err := bc.CalculateClaimable(neoOwner, 1) + require.NoError(t, err) + require.EqualValues(t, big.NewInt(5*native.GASFactor/10), amount) }) } diff --git a/pkg/core/blockchainer/blockchainer.go b/pkg/core/blockchainer/blockchainer.go index 04e72b569..8158798d8 100644 --- a/pkg/core/blockchainer/blockchainer.go +++ b/pkg/core/blockchainer/blockchainer.go @@ -22,7 +22,7 @@ type Blockchainer interface { AddHeaders(...*block.Header) error AddBlock(*block.Block) error AddStateRoot(r *state.MPTRoot) error - CalculateClaimable(value *big.Int, startHeight, endHeight uint32) *big.Int + CalculateClaimable(h util.Uint160, endHeight uint32) (*big.Int, error) Close() HeaderHeight() uint32 GetBlock(hash util.Uint256) (*block.Block, error) diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 9922d8cd9..e182c2e95 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -15,6 +15,7 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/interop" "github.com/nspcc-dev/neo-go/pkg/core/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/core/state" + "github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/crypto/hash" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/encoding/bigint" @@ -397,7 +398,7 @@ func (n *NEO) distributeGas(ic *interop.Context, h util.Uint160, acc *state.NEOB if ic.Block == nil || ic.Block.Index == 0 { return nil } - gen, err := n.CalculateBonus(ic, acc.VoteTo, &acc.Balance, acc.BalanceHeight, ic.Block.Index) + gen, err := n.calculateBonus(ic.DAO, acc.VoteTo, &acc.Balance, acc.BalanceHeight, ic.Block.Index) if err != nil { return err } @@ -409,13 +410,7 @@ func (n *NEO) distributeGas(ic *interop.Context, h util.Uint160, acc *state.NEOB func (n *NEO) unclaimedGas(ic *interop.Context, args []stackitem.Item) stackitem.Item { u := toUint160(args[0]) end := uint32(toBigInt(args[1]).Int64()) - key := makeAccountKey(u) - si := ic.DAO.GetStorageItem(n.ContractID, key) - st, err := state.NEOBalanceStateFromBytes(si.Value) - if err != nil { - panic(err) - } - gen, err := n.CalculateBonus(ic, st.VoteTo, &st.Balance, st.BalanceHeight, end) + gen, err := n.CalculateBonus(ic.DAO, u, end) if err != nil { panic(err) } @@ -534,14 +529,27 @@ func makeVoterKey(pub []byte, prealloc ...[]byte) []byte { // CalculateBonus calculates amount of gas generated for holding value NEO from start to end block // and having voted for active committee member. -func (n *NEO) CalculateBonus(ic *interop.Context, vote *keys.PublicKey, value *big.Int, start, end uint32) (*big.Int, error) { - r, err := n.CalculateNEOHolderReward(ic, value, start, end) +func (n *NEO) CalculateBonus(d dao.DAO, acc util.Uint160, end uint32) (*big.Int, error) { + key := makeAccountKey(acc) + si := d.GetStorageItem(n.ContractID, key) + if si == nil { + return nil, storage.ErrKeyNotFound + } + st, err := state.NEOBalanceStateFromBytes(si.Value) + if err != nil { + return nil, err + } + return n.calculateBonus(d, st.VoteTo, &st.Balance, st.BalanceHeight, end) +} + +func (n *NEO) calculateBonus(d dao.DAO, vote *keys.PublicKey, value *big.Int, start, end uint32) (*big.Int, error) { + r, err := n.CalculateNEOHolderReward(d, value, start, end) if err != nil || vote == nil { return r, err } var key = makeVoterKey(vote.Bytes()) - var reward = n.getGASPerVote(ic.DAO, key, start, end) + var reward = n.getGASPerVote(d, key, start, end) var tmp = new(big.Int).Sub(&reward[1], &reward[0]) tmp.Mul(tmp, value) tmp.Div(tmp, big.NewInt(voterRewardFactor)) @@ -550,7 +558,7 @@ func (n *NEO) CalculateBonus(ic *interop.Context, vote *keys.PublicKey, value *b } // CalculateNEOHolderReward return GAS reward for holding `value` of NEO from start to end block. -func (n *NEO) CalculateNEOHolderReward(ic *interop.Context, value *big.Int, start, end uint32) (*big.Int, error) { +func (n *NEO) CalculateNEOHolderReward(d dao.DAO, value *big.Int, start, end uint32) (*big.Int, error) { if value.Sign() == 0 || start >= end { return big.NewInt(0), nil } else if value.Sign() < 0 { @@ -563,7 +571,7 @@ func (n *NEO) CalculateNEOHolderReward(ic *interop.Context, value *big.Int, star if !n.gasPerBlockChanged.Load().(bool) { gr = n.gasPerBlock.Load().(gasRecord) } else { - gr, err = n.getSortedGASRecordFromDAO(ic.DAO) + gr, err = n.getSortedGASRecordFromDAO(d) if err != nil { return nil, err } diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index c1e3f41ce..9601262f6 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -162,7 +162,8 @@ func TestNEO_Vote(t *testing.T) { newGAS := bc.GetUtilityTokenBalance(accs[i].Contract.ScriptHash()) newGAS.Sub(newGAS, gasBalance[i]) - gasForHold := bc.CalculateClaimable(neoBalance[i], transferBlock, bc.BlockHeight()) + gasForHold, err := bc.contracts.NEO.CalculateNEOHolderReward(bc.dao, neoBalance[i], transferBlock, bc.BlockHeight()) + require.NoError(t, err) newGAS.Sub(newGAS, gasForHold) require.True(t, newGAS.Sign() > 0) gasBalance[i] = newGAS @@ -258,11 +259,11 @@ func TestNEO_CalculateBonus(t *testing.T) { ic.SpawnVM() ic.VM.LoadScript([]byte{byte(opcode.RET)}) t.Run("Invalid", func(t *testing.T) { - _, err := neo.CalculateNEOHolderReward(ic, new(big.Int).SetInt64(-1), 0, 1) + _, err := neo.CalculateNEOHolderReward(ic.DAO, new(big.Int).SetInt64(-1), 0, 1) require.Error(t, err) }) t.Run("Zero", func(t *testing.T) { - res, err := neo.CalculateNEOHolderReward(ic, big.NewInt(0), 0, 100) + res, err := neo.CalculateNEOHolderReward(ic.DAO, big.NewInt(0), 0, 100) require.NoError(t, err) require.EqualValues(t, 0, res.Int64()) }) @@ -272,7 +273,7 @@ func TestNEO_CalculateBonus(t *testing.T) { require.NoError(t, err) require.True(t, ok) - res, err := neo.CalculateNEOHolderReward(ic, big.NewInt(100), 5, 15) + res, err := neo.CalculateNEOHolderReward(ic.DAO, big.NewInt(100), 5, 15) require.NoError(t, err) require.EqualValues(t, (100*5*5/10)+(100*5*1/10), res.Int64()) diff --git a/pkg/network/helper_test.go b/pkg/network/helper_test.go index b8a619006..c114d5f74 100644 --- a/pkg/network/helper_test.go +++ b/pkg/network/helper_test.go @@ -33,7 +33,7 @@ func (chain testChain) ApplyPolicyToTxSet([]*transaction.Transaction) []*transac func (chain testChain) GetConfig() config.ProtocolConfiguration { panic("TODO") } -func (chain testChain) CalculateClaimable(*big.Int, uint32, uint32) *big.Int { +func (chain testChain) CalculateClaimable(util.Uint160, uint32) (*big.Int, error) { panic("TODO") } diff --git a/pkg/rpc/server/server.go b/pkg/rpc/server/server.go index bf41e54fd..65f2f7b60 100644 --- a/pkg/rpc/server/server.go +++ b/pkg/rpc/server/server.go @@ -951,13 +951,16 @@ func (s *Server) getUnclaimedGas(ps request.Params) (interface{}, *response.Erro return nil, response.ErrInvalidParams } - neo, neoHeight := s.chain.GetGoverningTokenBalance(u) + neo, _ := s.chain.GetGoverningTokenBalance(u) if neo.Sign() == 0 { return result.UnclaimedGas{ Address: u, }, nil } - gas := s.chain.CalculateClaimable(neo, neoHeight, s.chain.BlockHeight()+1) // +1 as in C#, for the next block. + gas, err := s.chain.CalculateClaimable(u, s.chain.BlockHeight()+1) // +1 as in C#, for the next block. + if err != nil { + return nil, response.NewInternalServerError("can't calculate claimable", err) + } return result.UnclaimedGas{ Address: u, Unclaimed: *gas,