native: emit vote/candidate events

Follow neo-project/neo#2754.
This commit is contained in:
Roman Khimov 2022-05-27 15:33:17 +03:00
parent 502cdd68ab
commit 5e013311e6
3 changed files with 109 additions and 5 deletions

View file

@ -240,6 +240,18 @@ func newNEO(cfg config.ProtocolConfiguration) *NEO {
md = newMethodAndPrice(n.setRegisterPrice, 1<<15, callflag.States) md = newMethodAndPrice(n.setRegisterPrice, 1<<15, callflag.States)
n.AddMethod(md, desc) n.AddMethod(md, desc)
n.AddEvent("CandidateStateChanged",
manifest.NewParameter("pubkey", smartcontract.PublicKeyType),
manifest.NewParameter("registered", smartcontract.BoolType),
manifest.NewParameter("votes", smartcontract.IntegerType),
)
n.AddEvent("Vote",
manifest.NewParameter("account", smartcontract.Hash160Type),
manifest.NewParameter("from", smartcontract.PublicKeyType),
manifest.NewParameter("to", smartcontract.PublicKeyType),
manifest.NewParameter("amount", smartcontract.IntegerType),
)
return n return n
} }
@ -733,6 +745,8 @@ func (n *NEO) registerCandidate(ic *interop.Context, args []stackitem.Item) stac
// RegisterCandidateInternal registers pub as a new candidate. // RegisterCandidateInternal registers pub as a new candidate.
func (n *NEO) RegisterCandidateInternal(ic *interop.Context, pub *keys.PublicKey) error { func (n *NEO) RegisterCandidateInternal(ic *interop.Context, pub *keys.PublicKey) error {
var emitEvent = true
key := makeValidatorKey(pub) key := makeValidatorKey(pub)
si := ic.DAO.GetStorageItem(n.ID, key) si := ic.DAO.GetStorageItem(n.ID, key)
var c *candidate var c *candidate
@ -740,9 +754,18 @@ func (n *NEO) RegisterCandidateInternal(ic *interop.Context, pub *keys.PublicKey
c = &candidate{Registered: true} c = &candidate{Registered: true}
} else { } else {
c = new(candidate).FromBytes(si) c = new(candidate).FromBytes(si)
emitEvent = !c.Registered
c.Registered = true c.Registered = true
} }
return putConvertibleToDAO(n.ID, ic.DAO, key, c) err := putConvertibleToDAO(n.ID, ic.DAO, key, c)
if emitEvent {
ic.AddNotification(n.Hash, "CandidateStateChanged", stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray(pub.Bytes()),
stackitem.NewBool(c.Registered),
stackitem.NewBigInteger(&c.Votes),
}))
}
return err
} }
func (n *NEO) unregisterCandidate(ic *interop.Context, args []stackitem.Item) stackitem.Item { func (n *NEO) unregisterCandidate(ic *interop.Context, args []stackitem.Item) stackitem.Item {
@ -759,6 +782,8 @@ func (n *NEO) unregisterCandidate(ic *interop.Context, args []stackitem.Item) st
// UnregisterCandidateInternal unregisters pub as a candidate. // UnregisterCandidateInternal unregisters pub as a candidate.
func (n *NEO) UnregisterCandidateInternal(ic *interop.Context, pub *keys.PublicKey) error { func (n *NEO) UnregisterCandidateInternal(ic *interop.Context, pub *keys.PublicKey) error {
var err error
key := makeValidatorKey(pub) key := makeValidatorKey(pub)
si := ic.DAO.GetStorageItem(n.ID, key) si := ic.DAO.GetStorageItem(n.ID, key)
if si == nil { if si == nil {
@ -767,12 +792,20 @@ func (n *NEO) UnregisterCandidateInternal(ic *interop.Context, pub *keys.PublicK
cache := ic.DAO.GetRWCache(n.ID).(*NeoCache) cache := ic.DAO.GetRWCache(n.ID).(*NeoCache)
cache.validators = nil cache.validators = nil
c := new(candidate).FromBytes(si) c := new(candidate).FromBytes(si)
emitEvent := c.Registered
c.Registered = false c.Registered = false
ok := n.dropCandidateIfZero(ic.DAO, cache, pub, c) ok := n.dropCandidateIfZero(ic.DAO, cache, pub, c)
if ok { if !ok {
return nil err = putConvertibleToDAO(n.ID, ic.DAO, key, c)
} }
return putConvertibleToDAO(n.ID, ic.DAO, key, c) if emitEvent {
ic.AddNotification(n.Hash, "CandidateStateChanged", stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray(pub.Bytes()),
stackitem.NewBool(c.Registered),
stackitem.NewBigInteger(&c.Votes),
}))
}
return err
} }
func (n *NEO) vote(ic *interop.Context, args []stackitem.Item) stackitem.Item { func (n *NEO) vote(ic *interop.Context, args []stackitem.Item) stackitem.Item {
@ -834,17 +867,33 @@ func (n *NEO) VoteInternal(ic *interop.Context, h util.Uint160, pub *keys.Public
if err := n.ModifyAccountVotes(acc, ic.DAO, new(big.Int).Neg(&acc.Balance), false); err != nil { if err := n.ModifyAccountVotes(acc, ic.DAO, new(big.Int).Neg(&acc.Balance), false); err != nil {
return err return err
} }
oldVote := acc.VoteTo
acc.VoteTo = pub acc.VoteTo = pub
if err := n.ModifyAccountVotes(acc, ic.DAO, &acc.Balance, true); err != nil { if err := n.ModifyAccountVotes(acc, ic.DAO, &acc.Balance, true); err != nil {
return err return err
} }
ic.DAO.PutStorageItem(n.ID, key, acc.Bytes()) ic.DAO.PutStorageItem(n.ID, key, acc.Bytes())
ic.AddNotification(n.Hash, "Vote", stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray(h.BytesBE()),
keyToStackItem(oldVote),
keyToStackItem(pub),
stackitem.NewBigInteger(&acc.Balance),
}))
if newGas != nil { // Can be if it was already distributed in the same block. if newGas != nil { // Can be if it was already distributed in the same block.
n.GAS.mint(ic, h, newGas, true) n.GAS.mint(ic, h, newGas, true)
} }
return nil return nil
} }
func keyToStackItem(k *keys.PublicKey) stackitem.Item {
if k == nil {
return stackitem.Null{}
}
return stackitem.NewByteArray(k.Bytes())
}
// ModifyAccountVotes modifies votes of the specified account by value (can be negative). // ModifyAccountVotes modifies votes of the specified account by value (can be negative).
// typ specifies if this modify is occurring during transfer or vote (with old or new validator). // typ specifies if this modify is occurring during transfer or vote (with old or new validator).
func (n *NEO) ModifyAccountVotes(acc *state.NEOBalance, d *dao.Simple, value *big.Int, isNewVote bool) error { func (n *NEO) ModifyAccountVotes(acc *state.NEOBalance, d *dao.Simple, value *big.Int, isNewVote bool) error {

View file

@ -60,6 +60,61 @@ func TestNEO_RegisterPriceCache(t *testing.T) {
testGetSetCache(t, newNeoCommitteeClient(t, 100_0000_0000), "RegisterPrice", native.DefaultRegisterPrice) testGetSetCache(t, newNeoCommitteeClient(t, 100_0000_0000), "RegisterPrice", native.DefaultRegisterPrice)
} }
func TestNEO_CandidateEvents(t *testing.T) {
c := newNativeClient(t, nativenames.Neo)
singleSigner := c.Signers[0].(neotest.MultiSigner).Single(0)
cc := c.WithSigners(c.Signers[0], singleSigner)
e := c.Executor
pkb := singleSigner.Account().PrivateKey().PublicKey().Bytes()
// Register 1 -> event
tx := cc.Invoke(t, true, "registerCandidate", pkb)
e.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{
ScriptHash: c.Hash,
Name: "CandidateStateChanged",
Item: stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray(pkb),
stackitem.NewBool(true),
stackitem.Make(0),
}),
})
// Register 2 -> no event
tx = cc.Invoke(t, true, "registerCandidate", pkb)
aer := e.GetTxExecResult(t, tx)
require.Equal(t, 0, len(aer.Events))
// Vote -> event
tx = c.Invoke(t, true, "vote", c.Signers[0].ScriptHash().BytesBE(), pkb)
e.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{
ScriptHash: c.Hash,
Name: "Vote",
Item: stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray(c.Signers[0].ScriptHash().BytesBE()),
stackitem.Null{},
stackitem.NewByteArray(pkb),
stackitem.Make(100000000),
}),
})
// Unregister 1 -> event
tx = cc.Invoke(t, true, "unregisterCandidate", pkb)
e.CheckTxNotificationEvent(t, tx, 0, state.NotificationEvent{
ScriptHash: c.Hash,
Name: "CandidateStateChanged",
Item: stackitem.NewArray([]stackitem.Item{
stackitem.NewByteArray(pkb),
stackitem.NewBool(false),
stackitem.Make(100000000),
}),
})
// Unregister 2 -> no event
tx = cc.Invoke(t, true, "unregisterCandidate", pkb)
aer = e.GetTxExecResult(t, tx)
require.Equal(t, 0, len(aer.Events))
}
func TestNEO_Vote(t *testing.T) { func TestNEO_Vote(t *testing.T) {
neoCommitteeInvoker := newNeoCommitteeClient(t, 100_0000_0000) neoCommitteeInvoker := newNeoCommitteeClient(t, 100_0000_0000)
neoValidatorsInvoker := neoCommitteeInvoker.WithSigners(neoCommitteeInvoker.Validator) neoValidatorsInvoker := neoCommitteeInvoker.WithSigners(neoCommitteeInvoker.Validator)

View file

@ -75,7 +75,7 @@ const (
nfsoContractHash = "5f9ebd6b001b54c7bc70f96e0412fcf415dfe09f" nfsoContractHash = "5f9ebd6b001b54c7bc70f96e0412fcf415dfe09f"
nfsoToken1ID = "7e244ffd6aa85fb1579d2ed22e9b761ab62e3486" nfsoToken1ID = "7e244ffd6aa85fb1579d2ed22e9b761ab62e3486"
invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA" invokescriptContractAVM = "VwIADBQBDAMOBQYMDQIODw0DDgcJAAAAAErZMCQE2zBwaEH4J+yMqiYEEUAMFA0PAwIJAAIBAwcDBAUCAQAOBgwJStkwJATbMHFpQfgn7IyqJgQSQBNA"
block20StateRootLE = "0fbb19143cb782df2c893d448a89ec959bea8bf467faf747638975812d570f72" block20StateRootLE = "cda0adf452c190700f792bcac29973b85532b7c27192e96172cfa0c8acaa4f9e"
) )
var ( var (