core: fix NEO balance state handler

We need to store NEO balance's LastUpdateHeight before GAS mint,
because mint can call onNEP17Payment and onNEP17Payment can call NEO
transfer which also calls GAS mint. Storing balance height allows to
avoid recursion.
This commit is contained in:
Anna Shaleva 2021-09-16 17:09:42 +03:00
parent 68af14100c
commit c113d682bd
7 changed files with 82 additions and 4 deletions

View file

@ -28,6 +28,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/storage" "github.com/nspcc-dev/neo-go/pkg/core/storage"
"github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/crypto/keys"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/encoding/fixedn" "github.com/nspcc-dev/neo-go/pkg/encoding/fixedn"
"github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract"
@ -395,6 +396,7 @@ func initBasicChain(t *testing.T, bc *Blockchain) {
}, },
} }
require.NoError(t, addNetworkFee(bc, transferTx, acc0)) require.NoError(t, addNetworkFee(bc, transferTx, acc0))
transferTx.SystemFee += 1000000
require.NoError(t, acc0.SignTx(testchain.Network(), transferTx)) require.NoError(t, acc0.SignTx(testchain.Network(), transferTx))
b = bc.newBlock(initTx, transferTx) b = bc.newBlock(initTx, transferTx)
@ -415,6 +417,7 @@ func initBasicChain(t *testing.T, bc *Blockchain) {
}, },
} }
require.NoError(t, addNetworkFee(bc, transferTx, acc0)) require.NoError(t, addNetworkFee(bc, transferTx, acc0))
transferTx.SystemFee += 1000000
require.NoError(t, acc0.SignTx(testchain.Network(), transferTx)) require.NoError(t, acc0.SignTx(testchain.Network(), transferTx))
b = bc.newBlock(transferTx) b = bc.newBlock(transferTx)
@ -603,14 +606,20 @@ func prepareContractMethodInvokeGeneric(chain *Blockchain, sysfee int64,
func signTxWithAccounts(chain *Blockchain, tx *transaction.Transaction, accs ...*wallet.Account) { func signTxWithAccounts(chain *Blockchain, tx *transaction.Transaction, accs ...*wallet.Account) {
scope := transaction.CalledByEntry scope := transaction.CalledByEntry
for _, acc := range accs { for _, acc := range accs {
accH, _ := address.StringToUint160(acc.Address)
tx.Signers = append(tx.Signers, transaction.Signer{ tx.Signers = append(tx.Signers, transaction.Signer{
Account: acc.PrivateKey().GetScriptHash(), Account: accH,
Scopes: scope, Scopes: scope,
}) })
scope = transaction.Global scope = transaction.Global
} }
size := io.GetVarSize(tx) size := io.GetVarSize(tx)
for _, acc := range accs { for _, acc := range accs {
if acc.Contract.Deployed {
// don't need precise calculation for tests
tx.NetworkFee += 1000_0000
continue
}
netFee, sizeDelta := fee.Calculate(chain.GetBaseExecFee(), acc.Contract.Script) netFee, sizeDelta := fee.Calculate(chain.GetBaseExecFee(), acc.Contract.Script)
size += sizeDelta size += sizeDelta
tx.NetworkFee += netFee tx.NetworkFee += netFee

View file

@ -442,6 +442,15 @@ func (n *NEO) distributeGas(ic *interop.Context, h util.Uint160, acc *state.NEOB
return err return err
} }
acc.BalanceHeight = ic.Block.Index acc.BalanceHeight = ic.Block.Index
// Must store acc before GAS distribution to fix acc's BalanceHeight value in the storage for
// further acc's queries from `onNEP17Payment` if so, see https://github.com/nspcc-dev/neo-go/pull/2181.
key := makeAccountKey(h)
err = ic.DAO.PutStorageItem(n.ID, key, acc.Bytes())
if err != nil {
return fmt.Errorf("failed to store acc before gas distribution: %w", err)
}
n.GAS.mint(ic, h, gen, true) n.GAS.mint(ic, h, gen, true)
return nil return nil
} }

View file

@ -195,6 +195,39 @@ func TestNEO_Vote(t *testing.T) {
} }
} }
// TestNEO_RecursiveDistribution is a test for https://github.com/nspcc-dev/neo-go/pull/2181.
func TestNEO_RecursiveGASMint(t *testing.T) {
bc := newTestChain(t)
initBasicChain(t, bc)
contractHash, err := bc.GetContractScriptHash(1) // deployed rpc/server/testdata/test_contract.go contract
require.NoError(t, err)
tx := transferTokenFromMultisigAccount(t, bc, contractHash, bc.contracts.GAS.Hash, 2_0000_0000)
checkTxHalt(t, bc, tx.Hash())
// Transfer 10 NEO to test contract, the contract should earn some GAS by owning this NEO.
tx = transferTokenFromMultisigAccount(t, bc, contractHash, bc.contracts.NEO.Hash, 10)
res, err := bc.GetAppExecResults(tx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, vm.HaltState, res[0].VMState)
// Add blocks to be able to trigger NEO transfer from contract address to owner
// address inside onNEP17Payment (the contract starts NEO transfers from chain height = 100).
for i := bc.BlockHeight(); i < 100; i++ {
require.NoError(t, bc.AddBlock(bc.newBlock()))
}
// Transfer 1 more NEO to the contract. Transfer will trigger onNEP17Payment. OnNEP17Payment will
// trigger transfer of 11 NEO to the contract owner (based on the contract code). 11 NEO Transfer will
// trigger GAS distribution. GAS transfer will trigger OnNEP17Payment one more time. The recursion
// shouldn't occur here, because contract's balance LastUpdated height has already been updated in
// this block.
tx = transferTokenFromMultisigAccount(t, bc, contractHash, bc.contracts.NEO.Hash, 1)
res, err = bc.GetAppExecResults(tx.Hash(), trigger.Application)
require.NoError(t, err)
require.Equal(t, vm.HaltState, res[0].VMState, res[0].FaultException)
}
func TestNEO_SetGasPerBlock(t *testing.T) { func TestNEO_SetGasPerBlock(t *testing.T) {
bc := newTestChain(t) bc := newTestChain(t)

View file

@ -54,8 +54,8 @@ type rpcTestCase struct {
check func(t *testing.T, e *executor, result interface{}) check func(t *testing.T, e *executor, result interface{})
} }
const testContractHash = "bb6a679438ce0fc6cb0ed1aa85ce83cf96cd3aeb" const testContractHash = "5c9e40a12055c6b9e3f72271c9779958c842135d"
const deploymentTxHash = "4c631654b04f6a3b25af45082d260b555de4d0eeba6b7697e3a0f18b3f96434f" const deploymentTxHash = "fefc10d2f7e323282cb50838174b68979b1794c1e5131f2b4737acbc5dde5932"
const genesisBlockHash = "0f8fb4e17d2ab9f3097af75ca7fd16064160fb8043db94909e00dd4e257b9dc4" const genesisBlockHash = "0f8fb4e17d2ab9f3097af75ca7fd16064160fb8043db94909e00dd4e257b9dc4"
const verifyContractHash = "f68822e4ecd93de334bdf1f7c409eda3431bcbd0" const verifyContractHash = "f68822e4ecd93de334bdf1f7c409eda3431bcbd0"
@ -1650,7 +1650,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) {
}, },
{ {
Asset: e.chain.UtilityTokenHash(), Asset: e.chain.UtilityTokenHash(),
Amount: "57900879260", Amount: "57898138260",
LastUpdated: 15, LastUpdated: 15,
}}, }},
Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(),

View file

@ -3,9 +3,12 @@ package testdata
import ( import (
"github.com/nspcc-dev/neo-go/pkg/interop" "github.com/nspcc-dev/neo-go/pkg/interop"
"github.com/nspcc-dev/neo-go/pkg/interop/contract" "github.com/nspcc-dev/neo-go/pkg/interop/contract"
"github.com/nspcc-dev/neo-go/pkg/interop/native/ledger"
"github.com/nspcc-dev/neo-go/pkg/interop/native/management" "github.com/nspcc-dev/neo-go/pkg/interop/native/management"
"github.com/nspcc-dev/neo-go/pkg/interop/native/neo"
"github.com/nspcc-dev/neo-go/pkg/interop/runtime" "github.com/nspcc-dev/neo-go/pkg/interop/runtime"
"github.com/nspcc-dev/neo-go/pkg/interop/storage" "github.com/nspcc-dev/neo-go/pkg/interop/storage"
"github.com/nspcc-dev/neo-go/pkg/interop/util"
) )
const ( const (
@ -13,6 +16,8 @@ const (
decimals = 2 decimals = 2
) )
var owner = util.FromAddress("NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB")
func Init() bool { func Init() bool {
ctx := storage.GetContext() ctx := storage.GetContext()
h := runtime.GetExecutingScriptHash() h := runtime.GetExecutingScriptHash()
@ -22,6 +27,26 @@ func Init() bool {
return true return true
} }
func OnNEP17Payment(from interop.Hash160, amount int, data interface{}) {
curr := runtime.GetExecutingScriptHash()
balance := neo.BalanceOf(curr)
if ledger.CurrentIndex() >= 100 {
ok := neo.Transfer(curr, owner, balance, nil)
if !ok {
panic("owner transfer failed")
}
ok = neo.Transfer(curr, owner, 0, nil)
if !ok {
panic("owner transfer failed")
}
}
}
// Verify always returns true and is aimed to serve the TestNEO_RecursiveGASMint.
func Verify() bool {
return true
}
func Transfer(from, to interop.Hash160, amount int, data interface{}) bool { func Transfer(from, to interop.Hash160, amount int, data interface{}) bool {
ctx := storage.GetContext() ctx := storage.GetContext()
if len(from) != 20 { if len(from) != 20 {

View file

@ -11,4 +11,6 @@ events:
- name: amount - name: amount
type: Integer type: Integer
permissions: permissions:
- hash: ef4073a0f2b305a38ec4050e4d3d28bc40ea63f5
methods: ["transfer"]
- methods: ["onNEP17Payment"] - methods: ["onNEP17Payment"]

Binary file not shown.