From 27fc28bd69dce5a5ef6e459c6063d625eb648829 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 5 Mar 2021 14:17:58 +0300 Subject: [PATCH] native: allow to set candidate register price --- pkg/compiler/native_test.go | 2 ++ pkg/core/native/native_neo.go | 60 ++++++++++++++++++++++++++++++++++ pkg/core/native_neo_test.go | 7 ++++ pkg/core/native_policy_test.go | 6 +++- pkg/interop/native/neo/neo.go | 10 ++++++ 5 files changed, 84 insertions(+), 1 deletion(-) diff --git a/pkg/compiler/native_test.go b/pkg/compiler/native_test.go index ae99a46ea..81f74712f 100644 --- a/pkg/compiler/native_test.go +++ b/pkg/compiler/native_test.go @@ -110,8 +110,10 @@ func TestNativeHelpersCompile(t *testing.T) { {"getCommittee", nil}, {"getGasPerBlock", nil}, {"getNextBlockValidators", nil}, + {"getRegisterPrice", nil}, {"registerCandidate", []string{pub}}, {"setGasPerBlock", []string{"1"}}, + {"setRegisterPrice", []string{"10"}}, {"vote", []string{u160, pub}}, {"unclaimedGas", []string{u160, "123"}}, {"unregisterCandidate", []string{pub}}, diff --git a/pkg/core/native/native_neo.go b/pkg/core/native/native_neo.go index 54ef0871f..c4c9c51cf 100644 --- a/pkg/core/native/native_neo.go +++ b/pkg/core/native/native_neo.go @@ -37,6 +37,9 @@ type NEO struct { gasPerBlock atomic.Value gasPerBlockChanged atomic.Value + registerPrice atomic.Value + registerPriceChanged atomic.Value + votesChanged atomic.Value nextValidators atomic.Value validators atomic.Value @@ -53,6 +56,8 @@ const ( neoContractID = -5 // NEOTotalSupply is the total amount of NEO in the system. NEOTotalSupply = 100000000 + // DefaultRegisterPrice is default price for candidate register. + DefaultRegisterPrice = 1000 * GASFactor // prefixCandidate is a prefix used to store validator's data. prefixCandidate = 33 // prefixVotersCount is a prefix for storing total amount of NEO of voters. @@ -64,6 +69,8 @@ const ( voterRewardFactor = 100_000_000 // prefixGASPerBlock is a prefix for storing amount of GAS generated per block. prefixGASPerBlock = 29 + // prefixRegisterPrice is a prefix for storing candidate register price. + prefixRegisterPrice = 13 // effectiveVoterTurnout represents minimal ratio of total supply to total amount voted value // which is require to use non-standby validators. effectiveVoterTurnout = 5 @@ -107,6 +114,7 @@ func newNEO() *NEO { n.validators.Store(keys.PublicKeys(nil)) n.committee.Store(keysWithVotes(nil)) n.committeeHash.Store(util.Uint160{}) + n.registerPriceChanged.Store(true) desc := newDescriptor("unclaimedGas", smartcontract.IntegerType, manifest.NewParameter("account", smartcontract.Hash160Type), @@ -151,6 +159,15 @@ func newNEO() *NEO { md = newMethodAndPrice(n.setGASPerBlock, 1<<15, callflag.States) n.AddMethod(md, desc) + desc = newDescriptor("getRegisterPrice", smartcontract.IntegerType) + md = newMethodAndPrice(n.getRegisterPrice, 1<<15, callflag.ReadStates) + n.AddMethod(md, desc) + + desc = newDescriptor("setRegisterPrice", smartcontract.VoidType, + manifest.NewParameter("registerPrice", smartcontract.IntegerType)) + md = newMethodAndPrice(n.setRegisterPrice, 1<<15, callflag.States) + n.AddMethod(md, desc) + return n } @@ -196,6 +213,12 @@ func (n *NEO) Initialize(ic *interop.Context) error { return err } + err = setIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice}, DefaultRegisterPrice) + if err != nil { + return err + } + n.registerPrice.Store(int64(DefaultRegisterPrice)) + n.registerPriceChanged.Store(false) return nil } @@ -323,6 +346,12 @@ func (n *NEO) PostPersist(ic *interop.Context) error { n.gasPerBlock.Store(gr) n.gasPerBlockChanged.Store(false) } + + if n.registerPriceChanged.Load().(bool) { + p := getIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice}) + n.registerPrice.Store(p) + n.registerPriceChanged.Store(false) + } return nil } @@ -482,6 +511,34 @@ func (n *NEO) SetGASPerBlock(ic *interop.Context, index uint32, gas *big.Int) er return n.putGASRecord(ic.DAO, index, gas) } +func (n *NEO) getRegisterPrice(ic *interop.Context, _ []stackitem.Item) stackitem.Item { + return stackitem.NewBigInteger(big.NewInt(n.getRegisterPriceInternal(ic.DAO))) +} + +func (n *NEO) getRegisterPriceInternal(d dao.DAO) int64 { + if !n.registerPriceChanged.Load().(bool) { + return n.registerPrice.Load().(int64) + } + return getIntWithKey(n.ID, d, []byte{prefixRegisterPrice}) +} + +func (n *NEO) setRegisterPrice(ic *interop.Context, args []stackitem.Item) stackitem.Item { + price := toBigInt(args[0]) + if price.Sign() <= 0 || !price.IsInt64() { + panic("invalid register price") + } + if !n.checkCommittee(ic) { + panic("invalid committee signature") + } + + err := setIntWithKey(n.ID, ic.DAO, []byte{prefixRegisterPrice}, price.Int64()) + if err != nil { + panic(err) + } + n.registerPriceChanged.Store(true) + return stackitem.Null{} +} + func (n *NEO) dropCandidateIfZero(d dao.DAO, pub *keys.PublicKey, c *candidate) (bool, error) { if c.Registered || c.Votes.Sign() != 0 { return false, nil @@ -592,6 +649,9 @@ func (n *NEO) registerCandidate(ic *interop.Context, args []stackitem.Item) stac } else if !ok { return stackitem.NewBool(false) } + if !ic.VM.AddGas(n.getRegisterPriceInternal(ic.DAO)) { + panic("insufficient gas") + } err = n.RegisterCandidateInternal(ic, pub) return stackitem.NewBool(err == nil) } diff --git a/pkg/core/native_neo_test.go b/pkg/core/native_neo_test.go index 02508d471..d86cd9e47 100644 --- a/pkg/core/native_neo_test.go +++ b/pkg/core/native_neo_test.go @@ -1,6 +1,7 @@ package core import ( + "math" "math/big" "sort" "testing" @@ -290,3 +291,9 @@ func TestNEO_TransferOnPayment(t *testing.T) { require.Equal(t, neoOwner.BytesBE(), arr[1].Value()) require.Equal(t, big.NewInt(amount), arr[2].Value()) } + +func TestRegisterPrice(t *testing.T) { + bc := newTestChain(t) + testGetSet(t, bc, bc.contracts.NEO.Hash, "RegisterPrice", + native.DefaultRegisterPrice, 1, math.MaxInt64) +} diff --git a/pkg/core/native_policy_test.go b/pkg/core/native_policy_test.go index 3ca724006..3b315b28c 100644 --- a/pkg/core/native_policy_test.go +++ b/pkg/core/native_policy_test.go @@ -1,6 +1,7 @@ package core import ( + "math/big" "testing" "github.com/nspcc-dev/neo-go/internal/random" @@ -46,7 +47,10 @@ func testGetSet(t *testing.T, chain *Blockchain, hash util.Uint160, name string, if maxValue != 0 { t.Run("set, too large value", func(t *testing.T) { - res, err := invokeContractMethodGeneric(chain, 100000000, hash, setName, true, maxValue+1) + // use big.Int because max can be `math.MaxInt64` + max := big.NewInt(maxValue) + max.Add(max, big.NewInt(1)) + res, err := invokeContractMethodGeneric(chain, 100000000, hash, setName, true, max) require.NoError(t, err) checkFAULTState(t, res) }) diff --git a/pkg/interop/native/neo/neo.go b/pkg/interop/native/neo/neo.go index 98c94a223..8144986f3 100644 --- a/pkg/interop/native/neo/neo.go +++ b/pkg/interop/native/neo/neo.go @@ -59,6 +59,16 @@ func SetGASPerBlock(amount int) { contract.Call(interop.Hash160(Hash), "setGasPerBlock", contract.States, amount) } +// GetRegisterPrice represents `getRegisterPrice` method of NEO native contract. +func GetRegisterPrice() int { + return contract.Call(interop.Hash160(Hash), "getRegisterPrice", contract.ReadStates).(int) +} + +// SetRegisterPrice represents `setRegisterPrice` method of NEO native contract. +func SetRegisterPrice(amount int) { + contract.Call(interop.Hash160(Hash), "setRegisterPrice", contract.States, amount) +} + // RegisterCandidate represents `registerCandidate` method of NEO native contract. func RegisterCandidate(pub interop.PublicKey) bool { return contract.Call(interop.Hash160(Hash), "registerCandidate", contract.States, pub).(bool)