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:
parent
68af14100c
commit
c113d682bd
7 changed files with 82 additions and 4 deletions
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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(),
|
||||
|
|
25
pkg/rpc/server/testdata/test_contract.go
vendored
25
pkg/rpc/server/testdata/test_contract.go
vendored
|
@ -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 {
|
||||
|
|
2
pkg/rpc/server/testdata/test_contract.yml
vendored
2
pkg/rpc/server/testdata/test_contract.yml
vendored
|
@ -11,4 +11,6 @@ events:
|
|||
- name: amount
|
||||
type: Integer
|
||||
permissions:
|
||||
- hash: ef4073a0f2b305a38ec4050e4d3d28bc40ea63f5
|
||||
methods: ["transfer"]
|
||||
- methods: ["onNEP17Payment"]
|
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
BIN
pkg/rpc/server/testdata/testblocks.acc
vendored
Binary file not shown.
Loading…
Reference in a new issue