From c113d682bd65033be3c7d31c6714191d2e3f7774 Mon Sep 17 00:00:00 2001 From: Anna Shaleva Date: Thu, 16 Sep 2021 17:09:42 +0300 Subject: [PATCH] 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. --- pkg/core/helper_test.go | 11 +++++++- pkg/core/native/native_neo.go | 9 ++++++ pkg/core/native_neo_test.go | 33 ++++++++++++++++++++++ pkg/rpc/server/server_test.go | 6 ++-- pkg/rpc/server/testdata/test_contract.go | 25 ++++++++++++++++ pkg/rpc/server/testdata/test_contract.yml | 2 ++ pkg/rpc/server/testdata/testblocks.acc | Bin 23193 -> 23934 bytes 7 files changed, 82 insertions(+), 4 deletions(-) diff --git a/pkg/core/helper_test.go b/pkg/core/helper_test.go index 4f0f2f566..e91c28db3 100644 --- a/pkg/core/helper_test.go +++ b/pkg/core/helper_test.go @@ -28,6 +28,7 @@ import ( "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/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/io" "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)) + transferTx.SystemFee += 1000000 require.NoError(t, acc0.SignTx(testchain.Network(), transferTx)) b = bc.newBlock(initTx, transferTx) @@ -415,6 +417,7 @@ func initBasicChain(t *testing.T, bc *Blockchain) { }, } require.NoError(t, addNetworkFee(bc, transferTx, acc0)) + transferTx.SystemFee += 1000000 require.NoError(t, acc0.SignTx(testchain.Network(), 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) { scope := transaction.CalledByEntry for _, acc := range accs { + accH, _ := address.StringToUint160(acc.Address) tx.Signers = append(tx.Signers, transaction.Signer{ - Account: acc.PrivateKey().GetScriptHash(), + Account: accH, Scopes: scope, }) scope = transaction.Global } size := io.GetVarSize(tx) 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) size += sizeDelta tx.NetworkFee += netFee diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 391525f1f..58c3a6517 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -442,6 +442,15 @@ func (n *NEO) distributeGas(ic *interop.Context, h util.Uint160, acc *state.NEOB return err } 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) return nil } diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index 089543113..4d2fbf2f1 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -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) { bc := newTestChain(t) diff --git a/pkg/rpc/server/server_test.go b/pkg/rpc/server/server_test.go index 30c58984f..76ca0e202 100644 --- a/pkg/rpc/server/server_test.go +++ b/pkg/rpc/server/server_test.go @@ -54,8 +54,8 @@ type rpcTestCase struct { check func(t *testing.T, e *executor, result interface{}) } -const testContractHash = "bb6a679438ce0fc6cb0ed1aa85ce83cf96cd3aeb" -const deploymentTxHash = "4c631654b04f6a3b25af45082d260b555de4d0eeba6b7697e3a0f18b3f96434f" +const testContractHash = "5c9e40a12055c6b9e3f72271c9779958c842135d" +const deploymentTxHash = "fefc10d2f7e323282cb50838174b68979b1794c1e5131f2b4737acbc5dde5932" const genesisBlockHash = "0f8fb4e17d2ab9f3097af75ca7fd16064160fb8043db94909e00dd4e257b9dc4" const verifyContractHash = "f68822e4ecd93de334bdf1f7c409eda3431bcbd0" @@ -1650,7 +1650,7 @@ func checkNep17Balances(t *testing.T, e *executor, acc interface{}) { }, { Asset: e.chain.UtilityTokenHash(), - Amount: "57900879260", + Amount: "57898138260", LastUpdated: 15, }}, Address: testchain.PrivateKeyByID(0).GetScriptHash().StringLE(), diff --git a/pkg/rpc/server/testdata/test_contract.go b/pkg/rpc/server/testdata/test_contract.go index 01e6a63f4..11f9f9a46 100644 --- a/pkg/rpc/server/testdata/test_contract.go +++ b/pkg/rpc/server/testdata/test_contract.go @@ -3,9 +3,12 @@ package testdata import ( "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/native/ledger" "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/storage" + "github.com/nspcc-dev/neo-go/pkg/interop/util" ) const ( @@ -13,6 +16,8 @@ const ( decimals = 2 ) +var owner = util.FromAddress("NbrUYaZgyhSkNoRo9ugRyEMdUZxrhkNaWB") + func Init() bool { ctx := storage.GetContext() h := runtime.GetExecutingScriptHash() @@ -22,6 +27,26 @@ func Init() bool { 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 { ctx := storage.GetContext() if len(from) != 20 { diff --git a/pkg/rpc/server/testdata/test_contract.yml b/pkg/rpc/server/testdata/test_contract.yml index 2761f0c7e..e0a5ce927 100644 --- a/pkg/rpc/server/testdata/test_contract.yml +++ b/pkg/rpc/server/testdata/test_contract.yml @@ -11,4 +11,6 @@ events: - name: amount type: Integer permissions: + - hash: ef4073a0f2b305a38ec4050e4d3d28bc40ea63f5 + methods: ["transfer"] - methods: ["onNEP17Payment"] \ No newline at end of file diff --git a/pkg/rpc/server/testdata/testblocks.acc b/pkg/rpc/server/testdata/testblocks.acc index 155622bacf2762b6cc0afb04d147b861069ed32d..0f48ffd7870602f42558a1405234f506559d94f7 100644 GIT binary patch delta 6567 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 delta 6204 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