diff --git a/pkg/compiler/analysis.go b/pkg/compiler/analysis.go index 019f4a7c9..97a841464 100644 --- a/pkg/compiler/analysis.go +++ b/pkg/compiler/analysis.go @@ -316,7 +316,7 @@ func canConvert(s string) bool { s = s[len(interopPrefix):] return s != "/iterator.Iterator" && s != "/storage.Context" && s != "/native/ledger.Block" && s != "/native/ledger.Transaction" && - s != "/native/management.Contract" + s != "/native/management.Contract" && s != "/native/neo.AccountState" } return true } diff --git a/pkg/compiler/native_test.go b/pkg/compiler/native_test.go index b26f10813..bf73a1baf 100644 --- a/pkg/compiler/native_test.go +++ b/pkg/compiler/native_test.go @@ -116,6 +116,7 @@ func TestNativeHelpersCompile(t *testing.T) { {"vote", []string{u160, pub}}, {"unclaimedGas", []string{u160, "123"}}, {"unregisterCandidate", []string{pub}}, + {"getAccountState", []string{u160}}, }, nep17TestCases...)) runNativeTestCases(t, cs.GAS.ContractMD, "gas", append([]nativeTestCase{ {"refuel", []string{u160, "123"}}, diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 1a6af1a16..aad0bc9a7 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -20,6 +20,7 @@ import ( "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" + "github.com/nspcc-dev/neo-go/pkg/io" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/smartcontract/callflag" "github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest" @@ -143,6 +144,11 @@ func newNEO() *NEO { md = newMethodAndPrice(n.getCandidatesCall, 1<<22, callflag.ReadStates) n.AddMethod(md, desc) + desc = newDescriptor("getAccountState", smartcontract.ArrayType, + manifest.NewParameter("account", smartcontract.Hash160Type)) + md = newMethodAndPrice(n.getAccountState, 1<<15, callflag.ReadStates) + n.AddMethod(md, desc) + desc = newDescriptor("getCommittee", smartcontract.ArrayType) md = newMethodAndPrice(n.getCommittee, 1<<16, callflag.ReadStates) n.AddMethod(md, desc) @@ -863,6 +869,21 @@ func (n *NEO) getCandidatesCall(ic *interop.Context, _ []stackitem.Item) stackit return stackitem.NewArray(arr) } +func (n *NEO) getAccountState(ic *interop.Context, args []stackitem.Item) stackitem.Item { + key := makeAccountKey(toUint160(args[0])) + si := ic.DAO.GetStorageItem(n.ID, key) + if len(si) == 0 { + return stackitem.Null{} + } + + r := io.NewBinReaderFromBuf(si) + item := stackitem.DecodeBinaryStackItem(r) + if r.Err != nil { + panic(r.Err) // no errors are expected but we better be sure + } + return item +} + // ComputeNextBlockValidators returns an actual list of current validators. func (n *NEO) ComputeNextBlockValidators(bc blockchainer.Blockchainer, d dao.DAO) (keys.PublicKeys, error) { if vals := n.validators.Load().(keys.PublicKeys); vals != nil { diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index 5050681da..f5766d565 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -229,6 +229,34 @@ func TestNEO_CalculateBonus(t *testing.T) { }) } +func TestNEO_GetAccountState(t *testing.T) { + bc := newTestChain(t) + + acc, err := wallet.NewAccount() + require.NoError(t, err) + + h := acc.Contract.ScriptHash() + t.Run("empty", func(t *testing.T) { + res, err := invokeContractMethod(bc, 1_0000000, bc.contracts.NEO.Hash, "getAccountState", h) + require.NoError(t, err) + checkResult(t, res, stackitem.Null{}) + }) + + const amount = 123 + transferTokenFromMultisigAccountCheckOK(t, bc, h, bc.GoverningTokenHash(), int64(amount)) + + t.Run("with funds", func(t *testing.T) { + bs := stackitem.NewStruct([]stackitem.Item{ + stackitem.Make(123), + stackitem.Make(bc.BlockHeight()), + stackitem.Null{}, + }) + res, err := invokeContractMethod(bc, 1_0000000, bc.contracts.NEO.Hash, "getAccountState", h) + require.NoError(t, err) + checkResult(t, res, bs) + }) +} + func TestNEO_CommitteeBountyOnPersist(t *testing.T) { bc := newTestChain(t) diff --git a/pkg/interop/native/neo/neo.go b/pkg/interop/native/neo/neo.go index b72a16404..44003bbfe 100644 --- a/pkg/interop/native/neo/neo.go +++ b/pkg/interop/native/neo/neo.go @@ -11,6 +11,13 @@ import ( "github.com/nspcc-dev/neo-go/pkg/interop/contract" ) +// AccountState contains info about NEO holder. +type AccountState struct { + Balance int + Height int + VoteTo interop.PublicKey +} + // Hash represents NEO contract hash. const Hash = "\xf5\x63\xea\x40\xbc\x28\x3d\x4d\x0e\x05\xc4\x8e\xa3\x05\xb3\xf2\xa0\x73\x40\xef" @@ -94,3 +101,8 @@ func Vote(addr interop.Hash160, pub interop.PublicKey) bool { func UnclaimedGAS(addr interop.Hash160, end int) int { return contract.Call(interop.Hash160(Hash), "unclaimedGas", contract.ReadStates, addr, end).(int) } + +// GetAccountState represents `getAccountState` method of NEO native contract. +func GetAccountState(addr interop.Hash160) *AccountState { + return contract.Call(interop.Hash160(Hash), "getAccountState", contract.ReadStates, addr).(*AccountState) +}