From 256cd092282473e055d88ab67c3de263cebbb8d5 Mon Sep 17 00:00:00 2001 From: Evgeniy Stratonikov Date: Fri, 5 Mar 2021 14:38:16 +0300 Subject: [PATCH] native: allow to set oracle request price --- pkg/compiler/native_test.go | 2 + pkg/core/native/oracle.go | 71 ++++++++++++++++++++++++++--- pkg/core/native_oracle_test.go | 7 +++ pkg/interop/native/oracle/oracle.go | 10 ++++ 4 files changed, 84 insertions(+), 6 deletions(-) diff --git a/pkg/compiler/native_test.go b/pkg/compiler/native_test.go index 81f74712f..af49507c9 100644 --- a/pkg/compiler/native_test.go +++ b/pkg/compiler/native_test.go @@ -120,7 +120,9 @@ func TestNativeHelpersCompile(t *testing.T) { }, 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{}"}}, diff --git a/pkg/core/native/oracle.go b/pkg/core/native/oracle.go index dd680e9bc..5533eaf66 100644 --- a/pkg/core/native/oracle.go +++ b/pkg/core/native/oracle.go @@ -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. @@ -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 { diff --git a/pkg/core/native_oracle_test.go b/pkg/core/native_oracle_test.go index 2f692f705..12591014c 100644 --- a/pkg/core/native_oracle_test.go +++ b/pkg/core/native_oracle_test.go @@ -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) +} diff --git a/pkg/interop/native/oracle/oracle.go b/pkg/interop/native/oracle/oracle.go index 06a8f438c..edb6534de 100644 --- a/pkg/interop/native/oracle/oracle.go +++ b/pkg/interop/native/oracle/oracle.go @@ -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) +}