Merge pull request #1818 from nspcc-dev/fix/fee

Split native method price into Cpu and Storage fees
This commit is contained in:
Roman Khimov 2021-03-11 11:29:51 +03:00 committed by GitHub
commit 75ec20b8bd
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
24 changed files with 280 additions and 90 deletions

View file

@ -110,15 +110,19 @@ 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}},
}, nep17TestCases...))
runNativeTestCases(t, cs.GAS.ContractMD, "gas", nep17TestCases)
runNativeTestCases(t, cs.Oracle.ContractMD, "oracle", []nativeTestCase{
{"getPrice", nil},
{"request", []string{`"url"`, "nil", `"callback"`, "nil", "123"}},
{"setPrice", []string{"10"}},
})
runNativeTestCases(t, cs.Designate.ContractMD, "roles", []nativeTestCase{
{"designateAsRole", []string{"1", "[]interop.PublicKey{}"}},

View file

@ -1029,7 +1029,7 @@ func TestVerifyTx(t *testing.T) {
fee.Opcode(bc.GetBaseExecFee(), // Notary verification script
opcode.PUSHDATA1, opcode.RET, // invocation script
opcode.PUSH0, opcode.SYSCALL, opcode.RET) + // Neo.Native.Call
native.NotaryVerificationPrice // Notary witness verification price
native.NotaryVerificationPrice*bc.GetBaseExecFee() // Notary witness verification price
tx.Scripts = []transaction.Witness{
{
InvocationScript: append([]byte{byte(opcode.PUSHDATA1), 64}, make([]byte, 64, 64)...),

View file

@ -90,7 +90,8 @@ type Method = func(ic *Context, args []stackitem.Item) stackitem.Item
type MethodAndPrice struct {
Func Method
MD *manifest.Method
Price int64
CPUFee int64
StorageFee int64
SyscallOffset int
RequiredFlags callflag.CallFlag
}

View file

@ -92,13 +92,13 @@ func newDesignate(p2pSigExtensionsEnabled bool) *Designate {
desc := newDescriptor("getDesignatedByRole", smartcontract.ArrayType,
manifest.NewParameter("role", smartcontract.IntegerType),
manifest.NewParameter("index", smartcontract.IntegerType))
md := newMethodAndPrice(s.getDesignatedByRole, 1000000, callflag.ReadStates)
md := newMethodAndPrice(s.getDesignatedByRole, 1<<15, callflag.ReadStates)
s.AddMethod(md, desc)
desc = newDescriptor("designateAsRole", smartcontract.VoidType,
manifest.NewParameter("role", smartcontract.IntegerType),
manifest.NewParameter("nodes", smartcontract.ArrayType))
md = newMethodAndPrice(s.designateAsRole, 0, callflag.States)
md = newMethodAndPrice(s.designateAsRole, 1<<15, callflag.States)
s.AddMethod(md, desc)
return s

View file

@ -34,8 +34,9 @@ func Call(ic *interop.Context) error {
return fmt.Errorf("missing call flags for native %d `%s` operation call: %05b vs %05b",
version, m.MD.Name, ic.VM.Context().GetCallFlags(), m.RequiredFlags)
}
// Native contract prices are not multiplied by `BaseExecFee`.
if !ic.VM.AddGas(m.Price) {
invokeFee := m.CPUFee*ic.Chain.GetPolicer().GetBaseExecFee() +
m.StorageFee*ic.Chain.GetPolicer().GetStoragePrice()
if !ic.VM.AddGas(invokeFee) {
return errors.New("gas limit exceeded")
}
ctx := ic.VM.Context()

View file

@ -42,32 +42,32 @@ func newLedger() *Ledger {
defer l.UpdateHash()
desc := newDescriptor("currentHash", smartcontract.Hash256Type)
md := newMethodAndPrice(l.currentHash, 1000000, callflag.ReadStates)
md := newMethodAndPrice(l.currentHash, 1<<15, callflag.ReadStates)
l.AddMethod(md, desc)
desc = newDescriptor("currentIndex", smartcontract.IntegerType)
md = newMethodAndPrice(l.currentIndex, 1000000, callflag.ReadStates)
md = newMethodAndPrice(l.currentIndex, 1<<15, callflag.ReadStates)
l.AddMethod(md, desc)
desc = newDescriptor("getBlock", smartcontract.ArrayType,
manifest.NewParameter("indexOrHash", smartcontract.ByteArrayType))
md = newMethodAndPrice(l.getBlock, 1000000, callflag.ReadStates)
md = newMethodAndPrice(l.getBlock, 1<<15, callflag.ReadStates)
l.AddMethod(md, desc)
desc = newDescriptor("getTransaction", smartcontract.ArrayType,
manifest.NewParameter("hash", smartcontract.Hash256Type))
md = newMethodAndPrice(l.getTransaction, 1000000, callflag.ReadStates)
md = newMethodAndPrice(l.getTransaction, 1<<15, callflag.ReadStates)
l.AddMethod(md, desc)
desc = newDescriptor("getTransactionHeight", smartcontract.IntegerType,
manifest.NewParameter("hash", smartcontract.Hash256Type))
md = newMethodAndPrice(l.getTransactionHeight, 1000000, callflag.ReadStates)
md = newMethodAndPrice(l.getTransactionHeight, 1<<15, callflag.ReadStates)
l.AddMethod(md, desc)
desc = newDescriptor("getTransactionFromBlock", smartcontract.ArrayType,
manifest.NewParameter("blockIndexOrHash", smartcontract.ByteArrayType),
manifest.NewParameter("txIndex", smartcontract.IntegerType))
md = newMethodAndPrice(l.getTransactionFromBlock, 2000000, callflag.ReadStates)
md = newMethodAndPrice(l.getTransactionFromBlock, 1<<16, callflag.ReadStates)
l.AddMethod(md, desc)
return l

View file

@ -69,7 +69,7 @@ func newManagement() *Management {
desc := newDescriptor("getContract", smartcontract.ArrayType,
manifest.NewParameter("hash", smartcontract.Hash160Type))
md := newMethodAndPrice(m.getContract, 1000000, callflag.ReadStates)
md := newMethodAndPrice(m.getContract, 1<<15, callflag.ReadStates)
m.AddMethod(md, desc)
desc = newDescriptor("deploy", smartcontract.ArrayType,
@ -99,16 +99,16 @@ func newManagement() *Management {
m.AddMethod(md, desc)
desc = newDescriptor("destroy", smartcontract.VoidType)
md = newMethodAndPrice(m.destroy, 1000000, callflag.States|callflag.AllowNotify)
md = newMethodAndPrice(m.destroy, 1<<15, callflag.States|callflag.AllowNotify)
m.AddMethod(md, desc)
desc = newDescriptor("getMinimumDeploymentFee", smartcontract.IntegerType)
md = newMethodAndPrice(m.getMinimumDeploymentFee, 100_0000, callflag.ReadStates)
md = newMethodAndPrice(m.getMinimumDeploymentFee, 1<<15, callflag.ReadStates)
m.AddMethod(md, desc)
desc = newDescriptor("setMinimumDeploymentFee", smartcontract.VoidType,
manifest.NewParameter("value", smartcontract.IntegerType))
md = newMethodAndPrice(m.setMinimumDeploymentFee, 300_0000, callflag.States)
md = newMethodAndPrice(m.setMinimumDeploymentFee, 1<<15, callflag.States)
m.AddMethod(md, desc)
hashParam := manifest.NewParameter("Hash", smartcontract.Hash160Type)

View file

@ -104,27 +104,27 @@ func newNameService() *NameService {
desc := newDescriptor("addRoot", smartcontract.VoidType,
manifest.NewParameter("root", smartcontract.StringType))
md := newMethodAndPrice(n.addRoot, 3000000, callflag.States)
md := newMethodAndPrice(n.addRoot, 1<<15, callflag.States)
n.AddMethod(md, desc)
desc = newDescriptor("setPrice", smartcontract.VoidType,
manifest.NewParameter("price", smartcontract.IntegerType))
md = newMethodAndPrice(n.setPrice, 3000000, callflag.States)
md = newMethodAndPrice(n.setPrice, 1<<15, callflag.States)
n.AddMethod(md, desc)
desc = newDescriptor("getPrice", smartcontract.IntegerType)
md = newMethodAndPrice(n.getPrice, 1000000, callflag.ReadStates)
md = newMethodAndPrice(n.getPrice, 1<<15, callflag.ReadStates)
n.AddMethod(md, desc)
desc = newDescriptor("isAvailable", smartcontract.BoolType,
manifest.NewParameter("name", smartcontract.StringType))
md = newMethodAndPrice(n.isAvailable, 1000000, callflag.ReadStates)
md = newMethodAndPrice(n.isAvailable, 1<<15, callflag.ReadStates)
n.AddMethod(md, desc)
desc = newDescriptor("register", smartcontract.BoolType,
manifest.NewParameter("name", smartcontract.StringType),
manifest.NewParameter("owner", smartcontract.Hash160Type))
md = newMethodAndPrice(n.register, 1000000, callflag.States)
md = newMethodAndPrice(n.register, 1<<15, callflag.States)
n.AddMethod(md, desc)
desc = newDescriptor("renew", smartcontract.IntegerType,
@ -135,32 +135,34 @@ func newNameService() *NameService {
desc = newDescriptor("setAdmin", smartcontract.VoidType,
manifest.NewParameter("name", smartcontract.StringType),
manifest.NewParameter("admin", smartcontract.Hash160Type))
md = newMethodAndPrice(n.setAdmin, 3000000, callflag.States)
md = newMethodAndPrice(n.setAdmin, 1<<15, callflag.States)
md.StorageFee = 20
n.AddMethod(md, desc)
desc = newDescriptor("setRecord", smartcontract.VoidType,
manifest.NewParameter("name", smartcontract.StringType),
manifest.NewParameter("type", smartcontract.IntegerType),
manifest.NewParameter("data", smartcontract.StringType))
md = newMethodAndPrice(n.setRecord, 30000000, callflag.States)
md = newMethodAndPrice(n.setRecord, 1<<15, callflag.States)
md.StorageFee = 200
n.AddMethod(md, desc)
desc = newDescriptor("getRecord", smartcontract.StringType,
manifest.NewParameter("name", smartcontract.StringType),
manifest.NewParameter("type", smartcontract.IntegerType))
md = newMethodAndPrice(n.getRecord, 1000000, callflag.ReadStates)
md = newMethodAndPrice(n.getRecord, 1<<15, callflag.ReadStates)
n.AddMethod(md, desc)
desc = newDescriptor("deleteRecord", smartcontract.VoidType,
manifest.NewParameter("name", smartcontract.StringType),
manifest.NewParameter("type", smartcontract.IntegerType))
md = newMethodAndPrice(n.deleteRecord, 1000000, callflag.States)
md = newMethodAndPrice(n.deleteRecord, 1<<15, callflag.States)
n.AddMethod(md, desc)
desc = newDescriptor("resolve", smartcontract.StringType,
manifest.NewParameter("name", smartcontract.StringType),
manifest.NewParameter("type", smartcontract.IntegerType))
md = newMethodAndPrice(n.resolve, 3000000, callflag.ReadStates)
md = newMethodAndPrice(n.resolve, 1<<17, callflag.ReadStates)
n.AddMethod(md, desc)
return n

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.
@ -62,8 +67,10 @@ const (
// voterRewardFactor is a factor by which voter reward per committee is multiplied
// to make calculations more precise.
voterRewardFactor = 100_000_000
// prefixGasPerBlock is a prefix for storing amount of GAS generated per block.
// 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,48 +114,58 @@ 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),
manifest.NewParameter("end", smartcontract.IntegerType))
md := newMethodAndPrice(n.unclaimedGas, 3000000, callflag.ReadStates)
md := newMethodAndPrice(n.unclaimedGas, 1<<17, callflag.ReadStates)
n.AddMethod(md, desc)
desc = newDescriptor("registerCandidate", smartcontract.BoolType,
manifest.NewParameter("pubkey", smartcontract.ByteArrayType))
md = newMethodAndPrice(n.registerCandidate, 1000_00000000, callflag.States)
md = newMethodAndPrice(n.registerCandidate, 0, callflag.States)
n.AddMethod(md, desc)
desc = newDescriptor("unregisterCandidate", smartcontract.BoolType,
manifest.NewParameter("pubkey", smartcontract.ByteArrayType))
md = newMethodAndPrice(n.unregisterCandidate, 5000000, callflag.States)
md = newMethodAndPrice(n.unregisterCandidate, 1<<16, callflag.States)
n.AddMethod(md, desc)
desc = newDescriptor("vote", smartcontract.BoolType,
manifest.NewParameter("account", smartcontract.Hash160Type),
manifest.NewParameter("voteTo", smartcontract.ByteArrayType))
md = newMethodAndPrice(n.vote, 5000000, callflag.States)
md = newMethodAndPrice(n.vote, 1<<16, callflag.States)
n.AddMethod(md, desc)
desc = newDescriptor("getCandidates", smartcontract.ArrayType)
md = newMethodAndPrice(n.getCandidatesCall, 100000000, callflag.ReadStates)
md = newMethodAndPrice(n.getCandidatesCall, 1<<22, callflag.ReadStates)
n.AddMethod(md, desc)
desc = newDescriptor("getCommittee", smartcontract.ArrayType)
md = newMethodAndPrice(n.getCommittee, 100000000, callflag.ReadStates)
md = newMethodAndPrice(n.getCommittee, 1<<22, callflag.ReadStates)
n.AddMethod(md, desc)
desc = newDescriptor("getNextBlockValidators", smartcontract.ArrayType)
md = newMethodAndPrice(n.getNextBlockValidators, 100000000, callflag.ReadStates)
md = newMethodAndPrice(n.getNextBlockValidators, 1<<22, callflag.ReadStates)
n.AddMethod(md, desc)
desc = newDescriptor("getGasPerBlock", smartcontract.IntegerType)
md = newMethodAndPrice(n.getGASPerBlock, 100_0000, callflag.ReadStates)
md = newMethodAndPrice(n.getGASPerBlock, 1<<15, callflag.ReadStates)
n.AddMethod(md, desc)
desc = newDescriptor("setGasPerBlock", smartcontract.VoidType,
manifest.NewParameter("gasPerBlock", smartcontract.IntegerType))
md = newMethodAndPrice(n.setGASPerBlock, 500_0000, callflag.States)
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

@ -55,12 +55,12 @@ func newNEP17Native(name string, id int32) *nep17TokenNative {
n.AddMethod(md, desc)
desc = newDescriptor("totalSupply", smartcontract.IntegerType)
md = newMethodAndPrice(n.TotalSupply, 1000000, callflag.ReadStates)
md = newMethodAndPrice(n.TotalSupply, 1<<15, callflag.ReadStates)
n.AddMethod(md, desc)
desc = newDescriptor("balanceOf", smartcontract.IntegerType,
manifest.NewParameter("account", smartcontract.Hash160Type))
md = newMethodAndPrice(n.balanceOf, 1000000, callflag.ReadStates)
md = newMethodAndPrice(n.balanceOf, 1<<15, callflag.ReadStates)
n.AddMethod(md, desc)
transferParams := []manifest.Parameter{
@ -71,7 +71,8 @@ func newNEP17Native(name string, id int32) *nep17TokenNative {
desc = newDescriptor("transfer", smartcontract.BoolType,
append(transferParams, manifest.NewParameter("data", smartcontract.AnyType))...,
)
md = newMethodAndPrice(n.Transfer, 9000000, callflag.States|callflag.AllowCall|callflag.AllowNotify)
md = newMethodAndPrice(n.Transfer, 1<<17, callflag.States|callflag.AllowCall|callflag.AllowNotify)
md.StorageFee = 50
n.AddMethod(md, desc)
n.AddEvent("Transfer", transferParams...)
@ -283,10 +284,10 @@ func newDescriptor(name string, ret smartcontract.ParamType, ps ...manifest.Para
}
}
func newMethodAndPrice(f interop.Method, price int64, flags callflag.CallFlag) *interop.MethodAndPrice {
func newMethodAndPrice(f interop.Method, cpuFee int64, flags callflag.CallFlag) *interop.MethodAndPrice {
return &interop.MethodAndPrice{
Func: f,
Price: price,
CPUFee: cpuFee,
RequiredFlags: flags,
}
}

View file

@ -77,37 +77,38 @@ func newNonFungible(name string, id int32, symbol string, decimals byte) *nonfun
n.AddMethod(md, desc)
desc = newDescriptor("totalSupply", smartcontract.IntegerType)
md = newMethodAndPrice(n.totalSupply, 1000000, callflag.ReadStates)
md = newMethodAndPrice(n.totalSupply, 1<<15, callflag.ReadStates)
n.AddMethod(md, desc)
desc = newDescriptor("ownerOf", smartcontract.Hash160Type,
manifest.NewParameter("tokenId", smartcontract.ByteArrayType))
md = newMethodAndPrice(n.OwnerOf, 1000000, callflag.ReadStates)
md = newMethodAndPrice(n.OwnerOf, 1<<15, callflag.ReadStates)
n.AddMethod(md, desc)
desc = newDescriptor("balanceOf", smartcontract.IntegerType,
manifest.NewParameter("owner", smartcontract.Hash160Type))
md = newMethodAndPrice(n.BalanceOf, 1000000, callflag.ReadStates)
md = newMethodAndPrice(n.BalanceOf, 1<<15, callflag.ReadStates)
n.AddMethod(md, desc)
desc = newDescriptor("properties", smartcontract.MapType,
manifest.NewParameter("tokenId", smartcontract.ByteArrayType))
md = newMethodAndPrice(n.Properties, 1000000, callflag.ReadStates)
md = newMethodAndPrice(n.Properties, 1<<15, callflag.ReadStates)
n.AddMethod(md, desc)
desc = newDescriptor("tokens", smartcontract.AnyType)
md = newMethodAndPrice(n.tokens, 1000000, callflag.ReadStates)
md = newMethodAndPrice(n.tokens, 1<<15, callflag.ReadStates)
n.AddMethod(md, desc)
desc = newDescriptor("tokensOf", smartcontract.AnyType,
manifest.NewParameter("owner", smartcontract.Hash160Type))
md = newMethodAndPrice(n.tokensOf, 1000000, callflag.ReadStates)
md = newMethodAndPrice(n.tokensOf, 1<<15, callflag.ReadStates)
n.AddMethod(md, desc)
desc = newDescriptor("transfer", smartcontract.BoolType,
manifest.NewParameter("to", smartcontract.Hash160Type),
manifest.NewParameter("tokenId", smartcontract.ByteArrayType))
md = newMethodAndPrice(n.transfer, 9000000, callflag.States|callflag.AllowNotify)
md = newMethodAndPrice(n.transfer, 1<<17, callflag.States|callflag.AllowNotify)
md.StorageFee = 50
n.AddMethod(md, desc)
n.AddEvent("Transfer",

View file

@ -41,7 +41,7 @@ type Notary struct {
const (
notaryContractID = reservedContractID - 1
// NotaryVerificationPrice is the price of `verify` Notary method.
NotaryVerificationPrice = 100_0000
NotaryVerificationPrice = 1 << 15
// prefixDeposit is a prefix for storing Notary deposits.
prefixDeposit = 1
@ -60,29 +60,29 @@ func newNotary() *Notary {
manifest.NewParameter("from", smartcontract.Hash160Type),
manifest.NewParameter("amount", smartcontract.IntegerType),
manifest.NewParameter("data", smartcontract.AnyType))
md := newMethodAndPrice(n.onPayment, 100_0000, callflag.States)
md := newMethodAndPrice(n.onPayment, 1<<15, callflag.States)
n.AddMethod(md, desc)
desc = newDescriptor("lockDepositUntil", smartcontract.BoolType,
manifest.NewParameter("address", smartcontract.Hash160Type),
manifest.NewParameter("till", smartcontract.IntegerType))
md = newMethodAndPrice(n.lockDepositUntil, 100_0000, callflag.States)
md = newMethodAndPrice(n.lockDepositUntil, 1<<15, callflag.States)
n.AddMethod(md, desc)
desc = newDescriptor("withdraw", smartcontract.BoolType,
manifest.NewParameter("from", smartcontract.Hash160Type),
manifest.NewParameter("to", smartcontract.Hash160Type))
md = newMethodAndPrice(n.withdraw, 100_0000, callflag.States)
md = newMethodAndPrice(n.withdraw, 1<<15, callflag.States)
n.AddMethod(md, desc)
desc = newDescriptor("balanceOf", smartcontract.IntegerType,
manifest.NewParameter("addr", smartcontract.Hash160Type))
md = newMethodAndPrice(n.balanceOf, 100_0000, callflag.ReadStates)
md = newMethodAndPrice(n.balanceOf, 1<<15, callflag.ReadStates)
n.AddMethod(md, desc)
desc = newDescriptor("expirationOf", smartcontract.IntegerType,
manifest.NewParameter("addr", smartcontract.Hash160Type))
md = newMethodAndPrice(n.expirationOf, 100_0000, callflag.ReadStates)
md = newMethodAndPrice(n.expirationOf, 1<<15, callflag.ReadStates)
n.AddMethod(md, desc)
desc = newDescriptor("verify", smartcontract.BoolType,
@ -91,12 +91,12 @@ func newNotary() *Notary {
n.AddMethod(md, desc)
desc = newDescriptor("getMaxNotValidBeforeDelta", smartcontract.IntegerType)
md = newMethodAndPrice(n.getMaxNotValidBeforeDelta, 100_0000, callflag.ReadStates)
md = newMethodAndPrice(n.getMaxNotValidBeforeDelta, 1<<15, callflag.ReadStates)
n.AddMethod(md, desc)
desc = newDescriptor("setMaxNotValidBeforeDelta", smartcontract.VoidType,
manifest.NewParameter("value", smartcontract.IntegerType))
md = newMethodAndPrice(n.setMaxNotValidBeforeDelta, 300_0000, callflag.States)
md = newMethodAndPrice(n.setMaxNotValidBeforeDelta, 1<<15, callflag.States)
n.AddMethod(md, desc)
return n

View file

@ -40,6 +40,9 @@ type Oracle struct {
Desig *Designate
oracleScript []byte
requestPrice atomic.Value
requestPriceChanged atomic.Value
// Module is an oracle module capable of talking with the external world.
Module atomic.Value
// newRequests contains new requests created during current block.
@ -55,13 +58,15 @@ const (
// maxRequestsCount is the maximum number of requests per URL
maxRequestsCount = 256
oracleRequestPrice = 5000_0000
// DefaultOracleRequestPrice is default amount GAS needed for oracle request.
DefaultOracleRequestPrice = 5000_0000
)
var (
prefixIDList = []byte{6}
prefixRequest = []byte{7}
prefixRequestID = []byte{9}
prefixRequestPrice = []byte{5}
prefixIDList = []byte{6}
prefixRequest = []byte{7}
prefixRequestID = []byte{9}
)
// Various validation errors.
@ -91,7 +96,7 @@ func newOracle() *Oracle {
manifest.NewParameter("callback", smartcontract.StringType),
manifest.NewParameter("userData", smartcontract.AnyType),
manifest.NewParameter("gasForResponse", smartcontract.IntegerType))
md := newMethodAndPrice(o.request, oracleRequestPrice, callflag.States|callflag.AllowNotify)
md := newMethodAndPrice(o.request, 0, callflag.States|callflag.AllowNotify)
o.AddMethod(md, desc)
desc = newDescriptor("finish", smartcontract.VoidType)
@ -99,7 +104,7 @@ func newOracle() *Oracle {
o.AddMethod(md, desc)
desc = newDescriptor("verify", smartcontract.BoolType)
md = newMethodAndPrice(o.verify, 100_0000, callflag.NoneFlag)
md = newMethodAndPrice(o.verify, 1<<15, callflag.NoneFlag)
o.AddMethod(md, desc)
o.AddEvent("OracleRequest", manifest.NewParameter("Id", smartcontract.IntegerType),
@ -109,6 +114,17 @@ func newOracle() *Oracle {
o.AddEvent("OracleResponse", manifest.NewParameter("Id", smartcontract.IntegerType),
manifest.NewParameter("OriginalTx", smartcontract.Hash256Type))
desc = newDescriptor("getPrice", smartcontract.IntegerType)
md = newMethodAndPrice(o.getPrice, 1<<15, callflag.ReadStates)
o.AddMethod(md, desc)
desc = newDescriptor("setPrice", smartcontract.VoidType,
manifest.NewParameter("price", smartcontract.IntegerType))
md = newMethodAndPrice(o.setPrice, 1<<15, callflag.States)
o.AddMethod(md, desc)
o.requestPriceChanged.Store(true)
return o
}
@ -130,9 +146,15 @@ func (o *Oracle) OnPersist(ic *interop.Context) error {
// PostPersist represents `postPersist` method.
func (o *Oracle) PostPersist(ic *interop.Context) error {
p := o.getPriceInternal(ic.DAO)
if o.requestPriceChanged.Load().(bool) {
o.requestPrice.Store(p)
o.requestPriceChanged.Store(false)
}
var nodes keys.PublicKeys
var reward []big.Int
single := new(big.Int).SetUint64(oracleRequestPrice)
single := big.NewInt(p)
var removedIDs []uint64
orc, _ := o.Module.Load().(services.Oracle)
@ -202,7 +224,15 @@ func (o *Oracle) Metadata() *interop.ContractMD {
// Initialize initializes Oracle contract.
func (o *Oracle) Initialize(ic *interop.Context) error {
return setIntWithKey(o.ID, ic.DAO, prefixRequestID, 0)
if err := setIntWithKey(o.ID, ic.DAO, prefixRequestID, 0); err != nil {
return err
}
if err := setIntWithKey(o.ID, ic.DAO, prefixRequestPrice, DefaultOracleRequestPrice); err != nil {
return err
}
o.requestPrice.Store(int64(DefaultOracleRequestPrice))
o.requestPriceChanged.Store(false)
return nil
}
func getResponse(tx *transaction.Transaction) *transaction.OracleResponse {
@ -281,6 +311,9 @@ func (o *Oracle) request(ic *interop.Context, args []stackitem.Item) stackitem.I
if err != nil {
panic(err)
}
if !ic.VM.AddGas(o.getPriceInternal(ic.DAO)) {
panic("insufficient gas")
}
if err := o.RequestInternal(ic, url, filter, cb, userData, gas); err != nil {
panic(err)
}
@ -404,6 +437,32 @@ func (o *Oracle) verify(ic *interop.Context, _ []stackitem.Item) stackitem.Item
return stackitem.NewBool(ic.Tx.HasAttribute(transaction.OracleResponseT))
}
func (o *Oracle) getPrice(ic *interop.Context, _ []stackitem.Item) stackitem.Item {
return stackitem.NewBigInteger(big.NewInt(o.getPriceInternal(ic.DAO)))
}
func (o *Oracle) getPriceInternal(d dao.DAO) int64 {
if !o.requestPriceChanged.Load().(bool) {
return o.requestPrice.Load().(int64)
}
return getIntWithKey(o.ID, d, prefixRequestPrice)
}
func (o *Oracle) setPrice(ic *interop.Context, args []stackitem.Item) stackitem.Item {
price := toBigInt(args[0])
if price.Sign() <= 0 || !price.IsInt64() {
panic("invalid register price")
}
if !o.NEO.checkCommittee(ic) {
panic("invalid committee signature")
}
if err := setIntWithKey(o.ID, ic.DAO, prefixRequestPrice, price.Int64()); err != nil {
panic(err)
}
o.requestPriceChanged.Store(true)
return stackitem.Null{}
}
func (o *Oracle) getOriginalTxID(d dao.DAO, tx *transaction.Transaction) util.Uint256 {
for i := range tx.Attributes {
if tx.Attributes[i].Type == transaction.OracleResponseT {

View file

@ -72,45 +72,45 @@ func newPolicy() *Policy {
defer p.UpdateHash()
desc := newDescriptor("getFeePerByte", smartcontract.IntegerType)
md := newMethodAndPrice(p.getFeePerByte, 1000000, callflag.ReadStates)
md := newMethodAndPrice(p.getFeePerByte, 1<<15, callflag.ReadStates)
p.AddMethod(md, desc)
desc = newDescriptor("isBlocked", smartcontract.BoolType,
manifest.NewParameter("account", smartcontract.Hash160Type))
md = newMethodAndPrice(p.isBlocked, 1000000, callflag.ReadStates)
md = newMethodAndPrice(p.isBlocked, 1<<15, callflag.ReadStates)
p.AddMethod(md, desc)
desc = newDescriptor("getExecFeeFactor", smartcontract.IntegerType)
md = newMethodAndPrice(p.getExecFeeFactor, 1000000, callflag.ReadStates)
md = newMethodAndPrice(p.getExecFeeFactor, 1<<15, callflag.ReadStates)
p.AddMethod(md, desc)
desc = newDescriptor("setExecFeeFactor", smartcontract.VoidType,
manifest.NewParameter("value", smartcontract.IntegerType))
md = newMethodAndPrice(p.setExecFeeFactor, 3000000, callflag.States)
md = newMethodAndPrice(p.setExecFeeFactor, 1<<15, callflag.States)
p.AddMethod(md, desc)
desc = newDescriptor("getStoragePrice", smartcontract.IntegerType)
md = newMethodAndPrice(p.getStoragePrice, 1000000, callflag.ReadStates)
md = newMethodAndPrice(p.getStoragePrice, 1<<15, callflag.ReadStates)
p.AddMethod(md, desc)
desc = newDescriptor("setStoragePrice", smartcontract.VoidType,
manifest.NewParameter("value", smartcontract.IntegerType))
md = newMethodAndPrice(p.setStoragePrice, 3000000, callflag.States)
md = newMethodAndPrice(p.setStoragePrice, 1<<15, callflag.States)
p.AddMethod(md, desc)
desc = newDescriptor("setFeePerByte", smartcontract.VoidType,
manifest.NewParameter("value", smartcontract.IntegerType))
md = newMethodAndPrice(p.setFeePerByte, 3000000, callflag.States)
md = newMethodAndPrice(p.setFeePerByte, 1<<15, callflag.States)
p.AddMethod(md, desc)
desc = newDescriptor("blockAccount", smartcontract.BoolType,
manifest.NewParameter("account", smartcontract.Hash160Type))
md = newMethodAndPrice(p.blockAccount, 3000000, callflag.States)
md = newMethodAndPrice(p.blockAccount, 1<<15, callflag.States)
p.AddMethod(md, desc)
desc = newDescriptor("unblockAccount", smartcontract.BoolType,
manifest.NewParameter("account", smartcontract.Hash160Type))
md = newMethodAndPrice(p.unblockAccount, 3000000, callflag.States)
md = newMethodAndPrice(p.unblockAccount, 1<<15, callflag.States)
p.AddMethod(md, desc)
return p

View file

@ -56,7 +56,10 @@ func (bc *Blockchain) registerNative(c interop.Contract) {
bc.contracts.Contracts = append(bc.contracts.Contracts, c)
}
const testSumPrice = 1 << 15 * interop.DefaultBaseExecFee // same as contract.Call
const (
testSumCPUFee = 1 << 15 // same as contract.Call
testSumStorageFee = 200
)
func newTestNative() *testNative {
tn := &testNative{
@ -76,7 +79,8 @@ func newTestNative() *testNative {
}
md := &interop.MethodAndPrice{
Func: tn.sum,
Price: testSumPrice,
CPUFee: testSumCPUFee,
StorageFee: testSumStorageFee,
RequiredFlags: callflag.NoneFlag,
}
tn.meta.AddMethod(md, desc)
@ -93,7 +97,7 @@ func newTestNative() *testNative {
}
md = &interop.MethodAndPrice{
Func: tn.callOtherContractNoReturn,
Price: testSumPrice,
CPUFee: testSumCPUFee,
RequiredFlags: callflag.NoneFlag}
tn.meta.AddMethod(md, desc)
@ -108,7 +112,7 @@ func newTestNative() *testNative {
}
md = &interop.MethodAndPrice{
Func: tn.callOtherContractWithReturn,
Price: testSumPrice,
CPUFee: testSumCPUFee,
RequiredFlags: callflag.NoneFlag}
tn.meta.AddMethod(md, desc)
@ -182,7 +186,8 @@ func TestNativeContract_Invoke(t *testing.T) {
require.NoError(t, err)
// System.Contract.Call + "sum" itself + opcodes for pushing arguments.
price := int64(testSumPrice * 2)
price := int64(testSumCPUFee * chain.GetBaseExecFee() * 2)
price += testSumStorageFee * chain.GetStoragePrice()
price += 3 * fee.Opcode(chain.GetBaseExecFee(), opcode.PUSHINT8)
price += 2 * fee.Opcode(chain.GetBaseExecFee(), opcode.SYSCALL, opcode.PUSHDATA1, opcode.PUSHINT8)
price += fee.Opcode(chain.GetBaseExecFee(), opcode.PACK)
@ -281,15 +286,16 @@ func TestNativeContract_InvokeOtherContract(t *testing.T) {
cs, _ := getTestContractState(chain)
require.NoError(t, chain.contracts.Management.PutContractState(chain.dao, cs))
baseFee := chain.GetBaseExecFee()
t.Run("non-native, no return", func(t *testing.T) {
res, err := invokeContractMethod(chain, testSumPrice*4+10000, tn.Metadata().Hash, "callOtherContractNoReturn", cs.Hash, "justReturn", []interface{}{})
res, err := invokeContractMethod(chain, testSumCPUFee*baseFee*4+10000, tn.Metadata().Hash, "callOtherContractNoReturn", cs.Hash, "justReturn", []interface{}{})
require.NoError(t, err)
drainTN(t)
require.Equal(t, vm.HaltState, res.VMState, res.FaultException)
checkResult(t, res, stackitem.Null{}) // simple call is done with EnsureNotEmpty
})
t.Run("non-native, with return", func(t *testing.T) {
res, err := invokeContractMethod(chain, testSumPrice*4+10000, tn.Metadata().Hash,
res, err := invokeContractMethod(chain, testSumCPUFee*baseFee*4+10000, tn.Metadata().Hash,
"callOtherContractWithReturn", cs.Hash, "ret7", []interface{}{})
require.NoError(t, err)
drainTN(t)

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

@ -2,6 +2,7 @@ package core
import (
"errors"
"math"
"math/big"
"testing"
@ -248,3 +249,9 @@ func TestOracle_Request(t *testing.T) {
})
})
}
func TestGetSetPrice(t *testing.T) {
bc := newTestChain(t)
testGetSet(t, bc, bc.contracts.Oracle.Hash, "Price",
native.DefaultOracleRequestPrice, 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

@ -98,8 +98,8 @@ func TestCreateResponseTx(t *testing.T) {
tx, err := orc.CreateResponseTx(int64(req.GasForResponse), 1, resp)
require.NoError(t, err)
assert.Equal(t, 166, tx.Size())
assert.Equal(t, int64(2215610), tx.NetworkFee)
assert.Equal(t, int64(97784390), tx.SystemFee)
assert.Equal(t, int64(2198650), tx.NetworkFee)
assert.Equal(t, int64(97801350), tx.SystemFee)
}
func TestOracle_InvalidWallet(t *testing.T) {

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)

View file

@ -14,3 +14,13 @@ func Request(url string, filter []byte, cb string, userData interface{}, gasForR
contract.States|contract.AllowNotify,
url, filter, cb, userData, gasForResponse)
}
// GetPrice represents `getPrice` method of Oracle native contract.
func GetPrice() int {
return contract.Call(interop.Hash160(Hash), "getPrice", contract.ReadStates).(int)
}
// SetPrice represents `setPrice` method of Oracle native contract.
func SetPrice(amount int) {
contract.Call(interop.Hash160(Hash), "setPrice", contract.States, amount)
}

View file

@ -694,7 +694,7 @@ func (c *Client) CalculateNotaryFee(nKeys uint8) (int64, error) {
fee.Opcode(baseExecFee, // Notary node witness
opcode.PUSHDATA1, opcode.RET, // invocation script
opcode.PUSH0, opcode.SYSCALL, opcode.RET) + // System.Contract.CallNative
native.NotaryVerificationPrice + // Notary witness verification price
native.NotaryVerificationPrice*baseExecFee + // Notary witness verification price
feePerByte*int64(io.GetVarSize(make([]byte, 66))) + // invocation script per-byte fee
feePerByte*int64(io.GetVarSize([]byte{})), // verification script per-byte fee
nil

View file

@ -59,14 +59,21 @@ func Int(w *io.BinWriter, i int64) {
val := opcode.Opcode(int(opcode.PUSH1) - 1 + int(i))
Opcodes(w, val)
default:
buf := bigint.ToPreallocatedBytes(big.NewInt(i), make([]byte, 0, 32))
// l != 0 becase of switch
padSize := byte(8 - bits.LeadingZeros8(byte(len(buf)-1)))
Opcodes(w, opcode.PUSHINT8+opcode.Opcode(padSize))
w.WriteBytes(padRight(1<<padSize, buf))
bigInt(w, big.NewInt(i))
}
}
func bigInt(w *io.BinWriter, n *big.Int) {
buf := bigint.ToPreallocatedBytes(n, make([]byte, 0, 32))
if len(buf) == 0 {
Opcodes(w, opcode.PUSH0)
return
}
padSize := byte(8 - bits.LeadingZeros8(byte(len(buf)-1)))
Opcodes(w, opcode.PUSHINT8+opcode.Opcode(padSize))
w.WriteBytes(padRight(1<<padSize, buf))
}
// Array emits array of elements to the given buffer.
func Array(w *io.BinWriter, es ...interface{}) {
for i := len(es) - 1; i >= 0; i-- {
@ -75,6 +82,8 @@ func Array(w *io.BinWriter, es ...interface{}) {
Array(w, e...)
case int64:
Int(w, e)
case *big.Int:
bigInt(w, e)
case string:
String(w, e)
case util.Uint160:

View file

@ -3,6 +3,8 @@ package emit
import (
"encoding/binary"
"errors"
"math"
"math/big"
"testing"
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
@ -144,7 +146,10 @@ func TestBytes(t *testing.T) {
func TestEmitArray(t *testing.T) {
t.Run("good", func(t *testing.T) {
buf := io.NewBufBinWriter()
Array(buf.BinWriter, []interface{}{int64(1), int64(2)}, nil, int64(1), "str", true, []byte{0xCA, 0xFE})
veryBig := new(big.Int).SetUint64(math.MaxUint64)
veryBig.Add(veryBig, big.NewInt(1))
Array(buf.BinWriter, big.NewInt(0), veryBig,
[]interface{}{int64(1), int64(2)}, nil, int64(1), "str", true, []byte{0xCA, 0xFE})
require.NoError(t, buf.Err)
res := buf.Bytes()
@ -163,6 +168,9 @@ func TestEmitArray(t *testing.T) {
assert.EqualValues(t, opcode.PUSH1, res[15])
assert.EqualValues(t, opcode.PUSH2, res[16])
assert.EqualValues(t, opcode.PACK, res[17])
assert.EqualValues(t, opcode.PUSHINT128, res[18])
assert.EqualValues(t, veryBig, bigint.FromBytes(res[19:35]))
assert.EqualValues(t, opcode.PUSH0, res[35])
})
t.Run("empty", func(t *testing.T) {