core: maintain a set of NEP17-compliant contracts

This commit is contained in:
Anna Shaleva 2021-07-23 15:52:30 +03:00
parent ad35db66b5
commit c0a2c74e0c
5 changed files with 112 additions and 0 deletions

View file

@ -33,6 +33,8 @@ type Management struct {
mtx sync.RWMutex
contracts map[util.Uint160]*state.Contract
// nep17 is a map of NEP17-compliant contracts which is updated with every PostPersist.
nep17 map[util.Uint160]struct{}
}
const (
@ -63,6 +65,7 @@ func newManagement() *Management {
var m = &Management{
ContractMD: *interop.NewContractMD(nativenames.Management, managementContractID),
contracts: make(map[util.Uint160]*state.Contract),
nep17: make(map[util.Uint160]struct{}),
}
defer m.UpdateHash()
@ -471,6 +474,9 @@ func (m *Management) OnPersist(ic *interop.Context) error {
}
m.mtx.Lock()
m.contracts[md.Hash] = cs
if md.Manifest.IsStandardSupported(manifest.NEP17StandardName) {
m.nep17[md.Hash] = struct{}{}
}
m.mtx.Unlock()
}
@ -492,6 +498,9 @@ func (m *Management) InitializeCache(d dao.DAO) error {
return
}
m.contracts[cs.Hash] = cs
if cs.Manifest.IsStandardSupported(manifest.NEP17StandardName) {
m.nep17[cs.Hash] = struct{}{}
}
})
return initErr
}
@ -507,14 +516,33 @@ func (m *Management) PostPersist(ic *interop.Context) error {
if err != nil {
// Contract was destroyed.
delete(m.contracts, h)
delete(m.nep17, h)
continue
}
m.contracts[h] = newCs
if newCs.Manifest.IsStandardSupported(manifest.NEP17StandardName) {
m.nep17[h] = struct{}{}
} else {
delete(m.nep17, h)
}
}
m.mtx.Unlock()
return nil
}
// GetNEP17Contracts returns hashes of all deployed contracts that support NEP17 standard. The list
// is updated every PostPersist, so until PostPersist is called, the result for the previous block
// is returned.
func (m *Management) GetNEP17Contracts() []util.Uint160 {
m.mtx.RLock()
result := make([]util.Uint160, 0, len(m.nep17))
for h := range m.nep17 {
result = append(result, h)
}
m.mtx.RUnlock()
return result
}
// Initialize implements Contract interface.
func (m *Management) Initialize(ic *interop.Context) error {
if err := setIntWithKey(m.ID, ic.DAO, keyMinimumDeploymentFee, defaultMinimumDeploymentFee); err != nil {

View file

@ -83,3 +83,50 @@ func TestManagement_Initialize(t *testing.T) {
require.Error(t, mgmt.InitializeCache(d))
})
}
func TestManagement_GetNEP17Contracts(t *testing.T) {
mgmt := newManagement()
d := dao.NewCached(dao.NewSimple(storage.NewMemoryStore(), false))
err := mgmt.Initialize(&interop.Context{DAO: d})
require.NoError(t, err)
require.Empty(t, mgmt.GetNEP17Contracts())
// Deploy NEP17 contract
script := []byte{byte(opcode.RET)}
sender := util.Uint160{1, 2, 3}
ne, err := nef.NewFile(script)
require.NoError(t, err)
manif := manifest.NewManifest("Test")
manif.ABI.Methods = append(manif.ABI.Methods, manifest.Method{
Name: "dummy",
ReturnType: smartcontract.VoidType,
Parameters: []manifest.Parameter{},
})
manif.SupportedStandards = []string{manifest.NEP17StandardName}
c1, err := mgmt.Deploy(d, sender, ne, manif)
require.NoError(t, err)
// PostPersist is not yet called, thus no NEP17 contracts are expected
require.Empty(t, mgmt.GetNEP17Contracts())
// Call PostPersist, check c1 contract hash is returned
require.NoError(t, mgmt.PostPersist(&interop.Context{DAO: d}))
require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts())
// Update contract
manif.ABI.Methods = append(manif.ABI.Methods, manifest.Method{
Name: "dummy2",
ReturnType: smartcontract.VoidType,
Parameters: []manifest.Parameter{},
})
c2, err := mgmt.Update(d, c1.Hash, ne, manif)
require.NoError(t, err)
// No changes expected before PostPersist call.
require.Equal(t, []util.Uint160{c1.Hash}, mgmt.GetNEP17Contracts())
// Call PostPersist, check c2 contract hash is returned
require.NoError(t, mgmt.PostPersist(&interop.Context{DAO: d}))
require.Equal(t, []util.Uint160{c2.Hash}, mgmt.GetNEP17Contracts())
}

View file

@ -607,3 +607,18 @@ func TestMinimumDeploymentFee(t *testing.T) {
testGetSet(t, chain, chain.contracts.Management.Hash, "MinimumDeploymentFee", 10_00000000, 0, 0)
}
func TestManagement_GetNEP17Contracts(t *testing.T) {
t.Run("empty chain", func(t *testing.T) {
chain := newTestChain(t)
require.ElementsMatch(t, []util.Uint160{chain.contracts.NEO.Hash, chain.contracts.GAS.Hash}, chain.contracts.Management.GetNEP17Contracts())
})
t.Run("test chain", func(t *testing.T) {
chain := newTestChain(t)
initBasicChain(t, chain)
rublesHash, err := chain.GetContractScriptHash(1)
require.NoError(t, err)
require.ElementsMatch(t, []util.Uint160{chain.contracts.NEO.Hash, chain.contracts.GAS.Hash, rublesHash}, chain.contracts.Management.GetNEP17Contracts())
})
}

View file

@ -119,6 +119,16 @@ func (m *Manifest) IsValid(hash util.Uint160) error {
return Permissions(m.Permissions).AreValid()
}
// IsStandardSupported denotes whether the specified standard supported by the contract.
func (m *Manifest) IsStandardSupported(standard string) bool {
for _, st := range m.SupportedStandards {
if st == standard {
return true
}
}
return false
}
// ToStackItem converts Manifest to stackitem.Item.
func (m *Manifest) ToStackItem() (stackitem.Item, error) {
groups := make([]stackitem.Item, len(m.Groups))

View file

@ -429,3 +429,15 @@ func TestExtraToStackItem(t *testing.T) {
require.Equal(t, tc.expected, string(actual))
}
}
func TestManifest_IsStandardSupported(t *testing.T) {
m := &Manifest{
SupportedStandards: []string{NEP17StandardName, NEP17Payable, NEP11Payable},
}
for _, st := range m.SupportedStandards {
require.True(t, m.IsStandardSupported(st))
}
require.False(t, m.IsStandardSupported(NEP11StandardName))
require.False(t, m.IsStandardSupported(""))
require.False(t, m.IsStandardSupported("unknown standard"))
}