native: allow to set candidate register price

This commit is contained in:
Evgeniy Stratonikov 2021-03-05 14:17:58 +03:00
parent b780a64b4d
commit 27fc28bd69
5 changed files with 84 additions and 1 deletions

View file

@ -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}},

View file

@ -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)
}

View file

@ -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)
}

View file

@ -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)
})

View file

@ -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)