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 155622bac..0f48ffd78 100644 Binary files a/pkg/rpc/server/testdata/testblocks.acc and b/pkg/rpc/server/testdata/testblocks.acc differ