mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-22 19:19:09 +00:00
native: add candidate registration via onNEP17Payment
Solves two problems: * inability to estimate GAS needed for registerCandidate in a regular way because of its very high fee (more than what normal RPC servers allow) * inability to have MaxBlockSystemFee lower than the registration price which is very high on its own (more than practically possible to execute) See https://github.com/neo-project/neo/issues/3552. Signed-off-by: Roman Khimov <roman@nspcc.ru>
This commit is contained in:
parent
df5946d9cb
commit
d4d1ff284b
2 changed files with 113 additions and 0 deletions
|
@ -200,6 +200,13 @@ func newNEO(cfg config.ProtocolConfiguration) *NEO {
|
||||||
md = newMethodAndPrice(n.unregisterCandidate, 1<<16, callflag.States)
|
md = newMethodAndPrice(n.unregisterCandidate, 1<<16, callflag.States)
|
||||||
n.AddMethod(md, desc)
|
n.AddMethod(md, desc)
|
||||||
|
|
||||||
|
desc = newDescriptor("onNEP17Payment", smartcontract.VoidType,
|
||||||
|
manifest.NewParameter("from", smartcontract.Hash160Type),
|
||||||
|
manifest.NewParameter("amount", smartcontract.IntegerType),
|
||||||
|
manifest.NewParameter("data", smartcontract.AnyType))
|
||||||
|
md = newMethodAndPrice(n.onNEP17Payment, 1<<15, callflag.States|callflag.AllowCall|callflag.AllowNotify, config.HFEchidna)
|
||||||
|
n.AddMethod(md, desc)
|
||||||
|
|
||||||
desc = newDescriptor("vote", smartcontract.BoolType,
|
desc = newDescriptor("vote", smartcontract.BoolType,
|
||||||
manifest.NewParameter("account", smartcontract.Hash160Type),
|
manifest.NewParameter("account", smartcontract.Hash160Type),
|
||||||
manifest.NewParameter("voteTo", smartcontract.PublicKeyType))
|
manifest.NewParameter("voteTo", smartcontract.PublicKeyType))
|
||||||
|
@ -828,6 +835,34 @@ func (n *NEO) registerCandidate(ic *interop.Context, args []stackitem.Item) stac
|
||||||
return stackitem.NewBool(err == nil)
|
return stackitem.NewBool(err == nil)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (n *NEO) onNEP17Payment(ic *interop.Context, args []stackitem.Item) stackitem.Item {
|
||||||
|
var (
|
||||||
|
caller = ic.VM.GetCallingScriptHash()
|
||||||
|
_ = toUint160(args[0])
|
||||||
|
amount = toBigInt(args[1])
|
||||||
|
pub = toPublicKey(args[2])
|
||||||
|
)
|
||||||
|
|
||||||
|
if caller != n.GAS.Hash {
|
||||||
|
panic("only GAS is accepted")
|
||||||
|
}
|
||||||
|
if !amount.IsInt64() || amount.Int64() != n.getRegisterPriceInternal(ic.DAO) {
|
||||||
|
panic(fmt.Errorf("incorrect GAS amount for registration (expected %d)", n.getRegisterPriceInternal(ic.DAO)))
|
||||||
|
}
|
||||||
|
ok, err := runtime.CheckKeyedWitness(ic, pub)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
} else if !ok {
|
||||||
|
panic("not witnessed by the key owner")
|
||||||
|
}
|
||||||
|
n.GAS.burn(ic, n.Hash, amount)
|
||||||
|
err = n.RegisterCandidateInternal(ic, pub)
|
||||||
|
if err != nil {
|
||||||
|
panic(err)
|
||||||
|
}
|
||||||
|
return stackitem.Null{}
|
||||||
|
}
|
||||||
|
|
||||||
// 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
|
var emitEvent = true
|
||||||
|
|
|
@ -889,3 +889,81 @@ func TestNEO_GetCandidates(t *testing.T) {
|
||||||
neoCommitteeInvoker.Invoke(t, expected, "getCandidates")
|
neoCommitteeInvoker.Invoke(t, expected, "getCandidates")
|
||||||
checkGetAllCandidates(t, expected)
|
checkGetAllCandidates(t, expected)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestNEO_RegisterViaNEP27(t *testing.T) {
|
||||||
|
neoCommitteeInvoker := newNeoCommitteeClient(t, 100_0000_0000)
|
||||||
|
neoValidatorsInvoker := neoCommitteeInvoker.WithSigners(neoCommitteeInvoker.Validator)
|
||||||
|
e := neoCommitteeInvoker.Executor
|
||||||
|
neoHash := e.NativeHash(t, nativenames.Neo)
|
||||||
|
|
||||||
|
cfg := e.Chain.GetConfig()
|
||||||
|
candidatesCount := cfg.GetCommitteeSize(0) - 1
|
||||||
|
|
||||||
|
// Register a set of candidates and vote for them.
|
||||||
|
voters := make([]neotest.Signer, candidatesCount)
|
||||||
|
candidates := make([]neotest.Signer, candidatesCount)
|
||||||
|
for i := range candidatesCount {
|
||||||
|
voters[i] = e.NewAccount(t, 2000_0000_0000) // enough for one registration
|
||||||
|
candidates[i] = e.NewAccount(t, 2000_0000_0000)
|
||||||
|
}
|
||||||
|
|
||||||
|
stack, err := neoCommitteeInvoker.TestInvoke(t, "getRegisterPrice")
|
||||||
|
require.NoError(t, err)
|
||||||
|
registrationPrice, err := stack.Pop().Item().TryInteger()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
gasValidatorsInvoker := e.CommitteeInvoker(e.NativeHash(t, nativenames.Gas))
|
||||||
|
txes := make([]*transaction.Transaction, 0, candidatesCount*3)
|
||||||
|
for i := range candidatesCount {
|
||||||
|
transferTx := neoValidatorsInvoker.PrepareInvoke(t, "transfer", e.Validator.ScriptHash(), voters[i].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), int64(candidatesCount+1-i)*1000000, nil)
|
||||||
|
txes = append(txes, transferTx)
|
||||||
|
registerTx := gasValidatorsInvoker.WithSigners(candidates[i]).PrepareInvoke(t, "transfer", candidates[i].(neotest.SingleSigner).Account().ScriptHash(), neoHash, registrationPrice, candidates[i].(neotest.SingleSigner).Account().PublicKey().Bytes())
|
||||||
|
txes = append(txes, registerTx)
|
||||||
|
voteTx := neoValidatorsInvoker.WithSigners(voters[i]).PrepareInvoke(t, "vote", voters[i].(neotest.SingleSigner).Account().PrivateKey().GetScriptHash(), candidates[i].(neotest.SingleSigner).Account().PublicKey().Bytes())
|
||||||
|
txes = append(txes, voteTx)
|
||||||
|
}
|
||||||
|
|
||||||
|
neoValidatorsInvoker.AddNewBlock(t, txes...)
|
||||||
|
for _, tx := range txes {
|
||||||
|
e.CheckHalt(t, tx.Hash(), stackitem.Make(true)) // luckily, both `transfer` and `vote` return boolean values
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ensure NEO holds no GAS.
|
||||||
|
stack, err = gasValidatorsInvoker.TestInvoke(t, "balanceOf", neoHash)
|
||||||
|
require.NoError(t, err)
|
||||||
|
balance, err := stack.Pop().Item().TryInteger()
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 0, balance.Sign())
|
||||||
|
|
||||||
|
var expected = make([]stackitem.Item, candidatesCount)
|
||||||
|
for i := range expected {
|
||||||
|
pub := candidates[i].(neotest.SingleSigner).Account().PublicKey().Bytes()
|
||||||
|
v := stackitem.NewBigInteger(big.NewInt(int64(candidatesCount-i+1) * 1000000))
|
||||||
|
expected[i] = stackitem.NewStruct([]stackitem.Item{
|
||||||
|
stackitem.NewByteArray(pub),
|
||||||
|
v,
|
||||||
|
})
|
||||||
|
neoCommitteeInvoker.Invoke(t, v, "getCandidateVote", pub)
|
||||||
|
}
|
||||||
|
|
||||||
|
slices.SortFunc(expected, func(a, b stackitem.Item) int {
|
||||||
|
return bytes.Compare(a.Value().([]stackitem.Item)[0].Value().([]byte), b.Value().([]stackitem.Item)[0].Value().([]byte))
|
||||||
|
})
|
||||||
|
|
||||||
|
neoCommitteeInvoker.Invoke(t, stackitem.NewArray(expected), "getCandidates")
|
||||||
|
|
||||||
|
// Invalid cases.
|
||||||
|
var newCand = voters[0]
|
||||||
|
|
||||||
|
// Missing data.
|
||||||
|
gasValidatorsInvoker.WithSigners(newCand).InvokeFail(t, "invalid conversion", "transfer", newCand.(neotest.SingleSigner).Account().ScriptHash(), neoHash, registrationPrice, nil)
|
||||||
|
// Invalid data.
|
||||||
|
gasValidatorsInvoker.WithSigners(newCand).InvokeFail(t, "unexpected EOF", "transfer", newCand.(neotest.SingleSigner).Account().ScriptHash(), neoHash, registrationPrice, []byte{2, 2, 2})
|
||||||
|
// NEO transfer.
|
||||||
|
neoValidatorsInvoker.WithSigners(newCand).InvokeFail(t, "only GAS is accepted", "transfer", newCand.(neotest.SingleSigner).Account().ScriptHash(), neoHash, 1, newCand.(neotest.SingleSigner).Account().PublicKey().Bytes())
|
||||||
|
// Incorrect amount.
|
||||||
|
gasValidatorsInvoker.WithSigners(newCand).InvokeFail(t, "incorrect GAS amount", "transfer", newCand.(neotest.SingleSigner).Account().ScriptHash(), neoHash, 1, newCand.(neotest.SingleSigner).Account().PublicKey().Bytes())
|
||||||
|
// Incorrect witness.
|
||||||
|
var anotherAcc = e.NewAccount(t, 2000_0000_0000)
|
||||||
|
gasValidatorsInvoker.WithSigners(newCand).InvokeFail(t, "not witnessed by the key owner", "transfer", newCand.(neotest.SingleSigner).Account().ScriptHash(), neoHash, registrationPrice, anotherAcc.(neotest.SingleSigner).Account().PublicKey().Bytes())
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in a new issue