Merge pull request #2646 from nspcc-dev/more-native-contract-rpc-wrappers
More native contract RPC wrappers
This commit is contained in:
commit
cdc2a762a1
9 changed files with 704 additions and 18 deletions
|
@ -17,7 +17,6 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/cli/paramcontext"
|
||||
cliwallet "github.com/nspcc-dev/neo-go/cli/wallet"
|
||||
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
|
@ -25,6 +24,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||
|
@ -971,19 +971,6 @@ func contractDeploy(ctx *cli.Context) error {
|
|||
appCallParams = append(appCallParams, data[0])
|
||||
}
|
||||
|
||||
gctx, cancel := options.GetTimeoutContext(ctx)
|
||||
defer cancel()
|
||||
|
||||
c, err := options.GetRPCClient(gctx, ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
mgmtHash, err := c.GetNativeContractHash(nativenames.Management)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to get management contract's hash: %w", err), 1)
|
||||
}
|
||||
|
||||
acc, w, err := getAccFromContext(ctx)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("can't get sender address: %w", err), 1)
|
||||
|
@ -999,7 +986,7 @@ func contractDeploy(ctx *cli.Context) error {
|
|||
}}
|
||||
}
|
||||
|
||||
sender, extErr := invokeWithArgs(ctx, acc, w, mgmtHash, "deploy", appCallParams, cosigners)
|
||||
sender, extErr := invokeWithArgs(ctx, acc, w, management.Hash, "deploy", appCallParams, cosigners)
|
||||
if extErr != nil {
|
||||
return extErr
|
||||
}
|
||||
|
|
|
@ -61,6 +61,9 @@ func (c *Contract) FromStackItem(item stackitem.Item) error {
|
|||
if !ok {
|
||||
return errors.New("not an array")
|
||||
}
|
||||
if len(arr) != 5 {
|
||||
return errors.New("invalid structure")
|
||||
}
|
||||
bi, ok := arr[0].Value().(*big.Int)
|
||||
if !ok {
|
||||
return errors.New("ID is not an integer")
|
||||
|
|
|
@ -98,6 +98,7 @@ func TestContractFromStackItem(t *testing.T) {
|
|||
item stackitem.Item
|
||||
}{
|
||||
{"not an array", stackitem.Make(1)},
|
||||
{"wrong array", stackitem.Make([]stackitem.Item{})},
|
||||
{"id is not a number", stackitem.Make([]stackitem.Item{manifItem, counter, chash, nefItem, manifItem})},
|
||||
{"id is out of range", stackitem.Make([]stackitem.Item{stackitem.Make(math.MaxUint32), counter, chash, nefItem, manifItem})},
|
||||
{"counter is not a number", stackitem.Make([]stackitem.Item{id, manifItem, chash, nefItem, manifItem})},
|
||||
|
|
191
pkg/rpcclient/management/management.go
Normal file
191
pkg/rpcclient/management/management.go
Normal file
|
@ -0,0 +1,191 @@
|
|||
/*
|
||||
Package management provides an RPC wrapper for the native ContractManagement contract.
|
||||
|
||||
Safe methods are encapsulated in the ContractReader structure while Contract provides
|
||||
various methods to perform state-changing calls.
|
||||
*/
|
||||
package management
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"math/big"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// Invoker is used by ContractReader to call various methods.
|
||||
type Invoker interface {
|
||||
Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error)
|
||||
}
|
||||
|
||||
// Actor is used by Contract to create and send transactions.
|
||||
type Actor interface {
|
||||
Invoker
|
||||
|
||||
MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error)
|
||||
MakeRun(script []byte) (*transaction.Transaction, error)
|
||||
MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...interface{}) (*transaction.Transaction, error)
|
||||
MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error)
|
||||
SendCall(contract util.Uint160, method string, params ...interface{}) (util.Uint256, uint32, error)
|
||||
SendRun(script []byte) (util.Uint256, uint32, error)
|
||||
}
|
||||
|
||||
// ContractReader provides an interface to call read-only ContractManagement
|
||||
// contract's methods.
|
||||
type ContractReader struct {
|
||||
invoker Invoker
|
||||
}
|
||||
|
||||
// Contract represents a ContractManagement contract client that can be used to
|
||||
// invoke all of its methods except 'update' and 'destroy' because they can be
|
||||
// called successfully only from the contract itself (that is doing an update
|
||||
// or self-destruction).
|
||||
type Contract struct {
|
||||
ContractReader
|
||||
|
||||
actor Actor
|
||||
}
|
||||
|
||||
// Hash stores the hash of the native ContractManagement contract.
|
||||
var Hash = state.CreateNativeContractHash(nativenames.Management)
|
||||
|
||||
// Event is the event emitted on contract deployment/update/destroy.
|
||||
// Even though these events are different they all have the same field inside.
|
||||
type Event struct {
|
||||
Hash util.Uint160
|
||||
}
|
||||
|
||||
const setMinFeeMethod = "setMinimumDeploymentFee"
|
||||
|
||||
// NewReader creates an instance of ContractReader that can be used to read
|
||||
// data from the contract.
|
||||
func NewReader(invoker Invoker) *ContractReader {
|
||||
return &ContractReader{invoker}
|
||||
}
|
||||
|
||||
// New creates an instance of Contract to perform actions using
|
||||
// the given Actor.
|
||||
func New(actor Actor) *Contract {
|
||||
return &Contract{*NewReader(actor), actor}
|
||||
}
|
||||
|
||||
// GetContract allows to get contract data from its hash. This method is mostly
|
||||
// useful for historic invocations since for current contracts there is a direct
|
||||
// getcontractstate RPC API that has more options and works faster than going
|
||||
// via contract invocation.
|
||||
func (c *ContractReader) GetContract(hash util.Uint160) (*state.Contract, error) {
|
||||
itm, err := unwrap.Item(c.invoker.Call(Hash, "getContract", hash))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
res := new(state.Contract)
|
||||
err = res.FromStackItem(itm)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return res, nil
|
||||
}
|
||||
|
||||
// GetMinimumDeploymentFee returns the minimal amount of GAS needed to deploy a
|
||||
// contract on the network.
|
||||
func (c *ContractReader) GetMinimumDeploymentFee() (*big.Int, error) {
|
||||
return unwrap.BigInt(c.invoker.Call(Hash, "getMinimumDeploymentFee"))
|
||||
}
|
||||
|
||||
// HasMethod checks if the contract specified has a method with the given name
|
||||
// and number of parameters.
|
||||
func (c *ContractReader) HasMethod(hash util.Uint160, method string, pcount int) (bool, error) {
|
||||
return unwrap.Bool(c.invoker.Call(Hash, "hasMethod", hash, method, pcount))
|
||||
}
|
||||
|
||||
// Deploy creates and sends to the network a transaction that deploys the given
|
||||
// contract (with the manifest provided), if data is not nil then it also added
|
||||
// to the invocation and will be used for "_deploy" method invocation done by
|
||||
// the ContractManagement contract. If successful, this method returns deployed
|
||||
// contract state that can be retrieved from the stack after execution.
|
||||
func (c *Contract) Deploy(exe *nef.File, manif *manifest.Manifest, data interface{}) (util.Uint256, uint32, error) {
|
||||
script, err := mkDeployScript(exe, manif, data)
|
||||
if err != nil {
|
||||
return util.Uint256{}, 0, err
|
||||
}
|
||||
return c.actor.SendRun(script)
|
||||
}
|
||||
|
||||
// DeployTransaction creates and returns a transaction that deploys the given
|
||||
// contract (with the manifest provided), if data is not nil then it also added
|
||||
// to the invocation and will be used for "_deploy" method invocation done by
|
||||
// the ContractManagement contract. If successful, this method returns deployed
|
||||
// contract state that can be retrieved from the stack after execution.
|
||||
func (c *Contract) DeployTransaction(exe *nef.File, manif *manifest.Manifest, data interface{}) (*transaction.Transaction, error) {
|
||||
script, err := mkDeployScript(exe, manif, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.actor.MakeRun(script)
|
||||
}
|
||||
|
||||
// DeployUnsigned creates and returns an unsigned transaction that deploys the given
|
||||
// contract (with the manifest provided), if data is not nil then it also added
|
||||
// to the invocation and will be used for "_deploy" method invocation done by
|
||||
// the ContractManagement contract. If successful, this method returns deployed
|
||||
// contract state that can be retrieved from the stack after execution.
|
||||
func (c *Contract) DeployUnsigned(exe *nef.File, manif *manifest.Manifest, data interface{}) (*transaction.Transaction, error) {
|
||||
script, err := mkDeployScript(exe, manif, data)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return c.actor.MakeUnsignedRun(script, nil)
|
||||
}
|
||||
|
||||
func mkDeployScript(exe *nef.File, manif *manifest.Manifest, data interface{}) ([]byte, error) {
|
||||
exeB, err := exe.Bytes()
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad NEF: %w", err)
|
||||
}
|
||||
manifB, err := json.Marshal(manif)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("bad manifest: %w", err)
|
||||
}
|
||||
if data != nil {
|
||||
return smartcontract.CreateCallScript(Hash, "deploy", exeB, manifB, data)
|
||||
}
|
||||
return smartcontract.CreateCallScript(Hash, "deploy", exeB, manifB)
|
||||
}
|
||||
|
||||
// SetMinimumDeploymentFee creates and sends a transaction that changes the
|
||||
// minimum GAS amount required to deploy a contract. This method can be called
|
||||
// successfully only by the network's committee, so make sure you're using an
|
||||
// appropriate Actor. This invocation returns nothing and is successful when
|
||||
// transactions ends up in the HALT state.
|
||||
func (c *Contract) SetMinimumDeploymentFee(value *big.Int) (util.Uint256, uint32, error) {
|
||||
return c.actor.SendCall(Hash, setMinFeeMethod, value)
|
||||
}
|
||||
|
||||
// SetMinimumDeploymentFeeTransaction creates a transaction that changes the
|
||||
// minimum GAS amount required to deploy a contract. This method can be called
|
||||
// successfully only by the network's committee, so make sure you're using an
|
||||
// appropriate Actor. This invocation returns nothing and is successful when
|
||||
// transactions ends up in the HALT state. The transaction returned is signed,
|
||||
// but not sent to the network.
|
||||
func (c *Contract) SetMinimumDeploymentFeeTransaction(value *big.Int) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeCall(Hash, setMinFeeMethod, value)
|
||||
}
|
||||
|
||||
// SetMinimumDeploymentFeeUnsigned creates a transaction that changes the
|
||||
// minimum GAS amount required to deploy a contract. This method can be called
|
||||
// successfully only by the network's committee, so make sure you're using an
|
||||
// appropriate Actor. This invocation returns nothing and is successful when
|
||||
// transactions ends up in the HALT state. The transaction returned is not
|
||||
// signed.
|
||||
func (c *Contract) SetMinimumDeploymentFeeUnsigned(value *big.Int) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeUnsignedCall(Hash, setMinFeeMethod, nil, value)
|
||||
}
|
206
pkg/rpcclient/management/management_test.go
Normal file
206
pkg/rpcclient/management/management_test.go
Normal file
|
@ -0,0 +1,206 @@
|
|||
package management
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/manifest"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract/nef"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testAct struct {
|
||||
err error
|
||||
res *result.Invoke
|
||||
tx *transaction.Transaction
|
||||
txh util.Uint256
|
||||
vub uint32
|
||||
}
|
||||
|
||||
func (t *testAct) Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) {
|
||||
return t.res, t.err
|
||||
}
|
||||
func (t *testAct) MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error) {
|
||||
return t.tx, t.err
|
||||
}
|
||||
func (t *testAct) MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...interface{}) (*transaction.Transaction, error) {
|
||||
return t.tx, t.err
|
||||
}
|
||||
func (t *testAct) SendCall(contract util.Uint160, method string, params ...interface{}) (util.Uint256, uint32, error) {
|
||||
return t.txh, t.vub, t.err
|
||||
}
|
||||
func (t *testAct) MakeRun(script []byte) (*transaction.Transaction, error) {
|
||||
return t.tx, t.err
|
||||
}
|
||||
func (t *testAct) MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) {
|
||||
return t.tx, t.err
|
||||
}
|
||||
func (t *testAct) SendRun(script []byte) (util.Uint256, uint32, error) {
|
||||
return t.txh, t.vub, t.err
|
||||
}
|
||||
|
||||
func TestReader(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
man := NewReader(ta)
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, err := man.GetContract(util.Uint160{1, 2, 3})
|
||||
require.Error(t, err)
|
||||
_, err = man.GetMinimumDeploymentFee()
|
||||
require.Error(t, err)
|
||||
_, err = man.HasMethod(util.Uint160{1, 2, 3}, "method", 0)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make(42),
|
||||
},
|
||||
}
|
||||
_, err = man.GetContract(util.Uint160{1, 2, 3})
|
||||
require.Error(t, err)
|
||||
fee, err := man.GetMinimumDeploymentFee()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, big.NewInt(42), fee)
|
||||
hm, err := man.HasMethod(util.Uint160{1, 2, 3}, "method", 0)
|
||||
require.NoError(t, err)
|
||||
require.True(t, hm)
|
||||
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make(false),
|
||||
},
|
||||
}
|
||||
_, err = man.GetContract(util.Uint160{1, 2, 3})
|
||||
require.Error(t, err)
|
||||
hm, err = man.HasMethod(util.Uint160{1, 2, 3}, "method", 0)
|
||||
require.NoError(t, err)
|
||||
require.False(t, hm)
|
||||
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{}),
|
||||
},
|
||||
}
|
||||
_, err = man.GetContract(util.Uint160{1, 2, 3})
|
||||
require.Error(t, err)
|
||||
|
||||
nefFile, _ := nef.NewFile([]byte{1, 2, 3})
|
||||
nefBytes, _ := nefFile.Bytes()
|
||||
manif := manifest.DefaultManifest("stack item")
|
||||
manifItem, _ := manif.ToStackItem()
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make([]stackitem.Item{
|
||||
stackitem.Make(1),
|
||||
stackitem.Make(0),
|
||||
stackitem.Make(util.Uint160{1, 2, 3}.BytesBE()),
|
||||
stackitem.Make(nefBytes),
|
||||
manifItem,
|
||||
}),
|
||||
},
|
||||
}
|
||||
cs, err := man.GetContract(util.Uint160{1, 2, 3})
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, int32(1), cs.ID)
|
||||
require.Equal(t, uint16(0), cs.UpdateCounter)
|
||||
require.Equal(t, util.Uint160{1, 2, 3}, cs.Hash)
|
||||
}
|
||||
|
||||
func TestSetMinimumDeploymentFee(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
man := New(ta)
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, _, err := man.SetMinimumDeploymentFee(big.NewInt(42))
|
||||
require.Error(t, err)
|
||||
|
||||
for _, m := range []func(*big.Int) (*transaction.Transaction, error){
|
||||
man.SetMinimumDeploymentFeeTransaction,
|
||||
man.SetMinimumDeploymentFeeUnsigned,
|
||||
} {
|
||||
_, err = m(big.NewInt(100))
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
ta.err = nil
|
||||
ta.txh = util.Uint256{1, 2, 3}
|
||||
ta.vub = 42
|
||||
|
||||
h, vub, err := man.SetMinimumDeploymentFee(big.NewInt(42))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.txh, h)
|
||||
require.Equal(t, ta.vub, vub)
|
||||
|
||||
ta.tx = transaction.New([]byte{1, 2, 3}, 100500)
|
||||
for _, m := range []func(*big.Int) (*transaction.Transaction, error){
|
||||
man.SetMinimumDeploymentFeeTransaction,
|
||||
man.SetMinimumDeploymentFeeUnsigned,
|
||||
} {
|
||||
tx, err := m(big.NewInt(100))
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.tx, tx)
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeploy(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
man := New(ta)
|
||||
nefFile, _ := nef.NewFile([]byte{1, 2, 3})
|
||||
manif := manifest.DefaultManifest("stack item")
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, _, err := man.Deploy(nefFile, manif, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
for _, m := range []func(exe *nef.File, manif *manifest.Manifest, data interface{}) (*transaction.Transaction, error){
|
||||
man.DeployTransaction,
|
||||
man.DeployUnsigned,
|
||||
} {
|
||||
_, err = m(nefFile, manif, nil)
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
ta.err = nil
|
||||
ta.txh = util.Uint256{1, 2, 3}
|
||||
ta.vub = 42
|
||||
|
||||
h, vub, err := man.Deploy(nefFile, manif, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.txh, h)
|
||||
require.Equal(t, ta.vub, vub)
|
||||
|
||||
ta.tx = transaction.New([]byte{1, 2, 3}, 100500)
|
||||
for _, m := range []func(exe *nef.File, manif *manifest.Manifest, data interface{}) (*transaction.Transaction, error){
|
||||
man.DeployTransaction,
|
||||
man.DeployUnsigned,
|
||||
} {
|
||||
tx, err := m(nefFile, manif, nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.tx, tx)
|
||||
|
||||
_, err = m(nefFile, manif, map[int]int{})
|
||||
require.Error(t, err)
|
||||
}
|
||||
|
||||
_, _, err = man.Deploy(nefFile, manif, map[int]int{})
|
||||
require.Error(t, err)
|
||||
|
||||
_, _, err = man.Deploy(nefFile, manif, 100500)
|
||||
require.NoError(t, err)
|
||||
|
||||
nefFile.Compiler = "intentionally very long compiler string that will make NEF code explode on encoding"
|
||||
_, _, err = man.Deploy(nefFile, manif, nil)
|
||||
require.Error(t, err)
|
||||
|
||||
// Unfortunately, manifest _always_ marshals successfully (or panics).
|
||||
}
|
|
@ -20,6 +20,8 @@ import (
|
|||
)
|
||||
|
||||
// GetOraclePrice invokes `getPrice` method on a native Oracle contract.
|
||||
//
|
||||
// Deprecated: please use oracle subpackage.
|
||||
func (c *Client) GetOraclePrice() (int64, error) {
|
||||
oracleHash, err := c.GetNativeContractHash(nativenames.Oracle)
|
||||
if err != nil {
|
||||
|
|
113
pkg/rpcclient/oracle/oracle.go
Normal file
113
pkg/rpcclient/oracle/oracle.go
Normal file
|
@ -0,0 +1,113 @@
|
|||
/*
|
||||
Package oracle allows to work with the native OracleContract contract via RPC.
|
||||
|
||||
Safe methods are encapsulated into ContractReader structure while Contract provides
|
||||
various methods to perform state-changing calls.
|
||||
*/
|
||||
package oracle
|
||||
|
||||
import (
|
||||
"math/big"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/native/nativenames"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/state"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
)
|
||||
|
||||
// Invoker is used by ContractReader to call various methods.
|
||||
type Invoker interface {
|
||||
Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error)
|
||||
}
|
||||
|
||||
// Actor is used by Contract to create and send transactions.
|
||||
type Actor interface {
|
||||
Invoker
|
||||
|
||||
MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error)
|
||||
MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...interface{}) (*transaction.Transaction, error)
|
||||
SendCall(contract util.Uint160, method string, params ...interface{}) (util.Uint256, uint32, error)
|
||||
}
|
||||
|
||||
// Hash stores the hash of the native OracleContract contract.
|
||||
var Hash = state.CreateNativeContractHash(nativenames.Oracle)
|
||||
|
||||
const priceSetter = "setPrice"
|
||||
|
||||
// ContractReader provides an interface to call read-only OracleContract
|
||||
// contract's methods. "verify" method is not exposed since it's very specific
|
||||
// and can't be executed successfully outside of the proper oracle response
|
||||
// transaction.
|
||||
type ContractReader struct {
|
||||
invoker Invoker
|
||||
}
|
||||
|
||||
// Contract represents the OracleContract contract client that can be used to
|
||||
// invoke its "setPrice" method. Other methods are useless for direct calls,
|
||||
// "request" requires a callback that entry script can't provide and "finish"
|
||||
// will only work in an oracle transaction. Since "setPrice" can be called
|
||||
// successfully only by the network's committee, an appropriate Actor is needed
|
||||
// for Contract.
|
||||
type Contract struct {
|
||||
ContractReader
|
||||
|
||||
actor Actor
|
||||
}
|
||||
|
||||
// RequestEvent represents an OracleRequest notification event emitted from
|
||||
// the OracleContract contract.
|
||||
type RequestEvent struct {
|
||||
ID int64
|
||||
Contract util.Uint160
|
||||
URL string
|
||||
Filter string
|
||||
}
|
||||
|
||||
// ResponseEvent represents an OracleResponse notification event emitted from
|
||||
// the OracleContract contract.
|
||||
type ResponseEvent struct {
|
||||
ID int64
|
||||
OriginalTx util.Uint256
|
||||
}
|
||||
|
||||
// NewReader creates an instance of ContractReader that can be used to read
|
||||
// data from the contract.
|
||||
func NewReader(invoker Invoker) *ContractReader {
|
||||
return &ContractReader{invoker}
|
||||
}
|
||||
|
||||
// New creates an instance of Contract to perform actions using
|
||||
// the given Actor.
|
||||
func New(actor Actor) *Contract {
|
||||
return &Contract{*NewReader(actor), actor}
|
||||
}
|
||||
|
||||
// GetPrice returns current price of the oracle request call.
|
||||
func (c *ContractReader) GetPrice() (*big.Int, error) {
|
||||
return unwrap.BigInt(c.invoker.Call(Hash, "getPrice"))
|
||||
}
|
||||
|
||||
// SetPrice creates and sends a transaction that sets the new price for the
|
||||
// oracle request call. The action is successful when transaction ends in HALT
|
||||
// state. The returned values are transaction hash, its ValidUntilBlock value and
|
||||
// an error if any.
|
||||
func (c *Contract) SetPrice(value *big.Int) (util.Uint256, uint32, error) {
|
||||
return c.actor.SendCall(Hash, priceSetter, value)
|
||||
}
|
||||
|
||||
// SetPriceTransaction creates a transaction that sets the new price for the
|
||||
// oracle request call. The action is successful when transaction ends in HALT
|
||||
// state. The transaction is signed, but not sent to the network, instead it's
|
||||
// returned to the caller.
|
||||
func (c *Contract) SetPriceTransaction(value *big.Int) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeCall(Hash, priceSetter, value)
|
||||
}
|
||||
|
||||
// SetPriceUnsigned creates a transaction that sets the new price for the
|
||||
// oracle request call. The action is successful when transaction ends in HALT
|
||||
// state. The transaction is not signed and just returned to the caller.
|
||||
func (c *Contract) SetPriceUnsigned(value *big.Int) (*transaction.Transaction, error) {
|
||||
return c.actor.MakeUnsignedCall(Hash, priceSetter, nil, value)
|
||||
}
|
86
pkg/rpcclient/oracle/oracle_test.go
Normal file
86
pkg/rpcclient/oracle/oracle_test.go
Normal file
|
@ -0,0 +1,86 @@
|
|||
package oracle
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"math/big"
|
||||
"testing"
|
||||
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
|
||||
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
type testAct struct {
|
||||
err error
|
||||
res *result.Invoke
|
||||
tx *transaction.Transaction
|
||||
txh util.Uint256
|
||||
vub uint32
|
||||
}
|
||||
|
||||
func (t *testAct) Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) {
|
||||
return t.res, t.err
|
||||
}
|
||||
func (t *testAct) MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error) {
|
||||
return t.tx, t.err
|
||||
}
|
||||
func (t *testAct) MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...interface{}) (*transaction.Transaction, error) {
|
||||
return t.tx, t.err
|
||||
}
|
||||
func (t *testAct) SendCall(contract util.Uint160, method string, params ...interface{}) (util.Uint256, uint32, error) {
|
||||
return t.txh, t.vub, t.err
|
||||
}
|
||||
|
||||
func TestReader(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
ora := NewReader(ta)
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, err := ora.GetPrice()
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.res = &result.Invoke{
|
||||
State: "HALT",
|
||||
Stack: []stackitem.Item{
|
||||
stackitem.Make(42),
|
||||
},
|
||||
}
|
||||
price, err := ora.GetPrice()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, big.NewInt(42), price)
|
||||
}
|
||||
|
||||
func TestPriceSetter(t *testing.T) {
|
||||
ta := new(testAct)
|
||||
ora := New(ta)
|
||||
|
||||
big42 := big.NewInt(42)
|
||||
|
||||
ta.err = errors.New("")
|
||||
_, _, err := ora.SetPrice(big42)
|
||||
require.Error(t, err)
|
||||
_, err = ora.SetPriceTransaction(big42)
|
||||
require.Error(t, err)
|
||||
_, err = ora.SetPriceUnsigned(big42)
|
||||
require.Error(t, err)
|
||||
|
||||
ta.err = nil
|
||||
ta.txh = util.Uint256{1, 2, 3}
|
||||
ta.vub = 42
|
||||
ta.tx = transaction.New([]byte{1, 2, 3}, 100500)
|
||||
|
||||
h, vub, err := ora.SetPrice(big42)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.txh, h)
|
||||
require.Equal(t, ta.vub, vub)
|
||||
|
||||
tx, err := ora.SetPriceTransaction(big42)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.tx, tx)
|
||||
tx, err = ora.SetPriceUnsigned(big42)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, ta.tx, tx)
|
||||
}
|
|
@ -32,8 +32,10 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/gas"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/management"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nns"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/oracle"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/policy"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/rolemgmt"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
|
@ -225,6 +227,70 @@ func TestClientPolicyContract(t *testing.T) {
|
|||
require.True(t, ret)
|
||||
}
|
||||
|
||||
func TestClientManagementContract(t *testing.T) {
|
||||
chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t)
|
||||
defer chain.Close()
|
||||
defer rpcSrv.Shutdown()
|
||||
|
||||
c, err := rpcclient.New(context.Background(), httpSrv.URL, rpcclient.Options{})
|
||||
require.NoError(t, err)
|
||||
require.NoError(t, c.Init())
|
||||
|
||||
manReader := management.NewReader(invoker.New(c, nil))
|
||||
|
||||
fee, err := manReader.GetMinimumDeploymentFee()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, big.NewInt(10*1_0000_0000), fee)
|
||||
|
||||
cs1, err := manReader.GetContract(gas.Hash)
|
||||
require.NoError(t, err)
|
||||
cs2, err := c.GetContractStateByHash(gas.Hash)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, cs2, cs1)
|
||||
|
||||
ret, err := manReader.HasMethod(gas.Hash, "transfer", 4)
|
||||
require.NoError(t, err)
|
||||
require.True(t, ret)
|
||||
|
||||
act, err := actor.New(c, []actor.SignerAccount{{
|
||||
Signer: transaction.Signer{
|
||||
Account: testchain.CommitteeScriptHash(),
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
Account: &wallet.Account{
|
||||
Address: testchain.CommitteeAddress(),
|
||||
Contract: &wallet.Contract{
|
||||
Script: testchain.CommitteeVerificationScript(),
|
||||
},
|
||||
},
|
||||
}})
|
||||
require.NoError(t, err)
|
||||
|
||||
man := management.New(act)
|
||||
|
||||
txfee, err := man.SetMinimumDeploymentFeeUnsigned(big.NewInt(1 * 1_0000_0000))
|
||||
require.NoError(t, err)
|
||||
txdepl, err := man.DeployUnsigned(&cs1.NEF, &cs1.Manifest, nil) // Redeploy from a different account.
|
||||
require.NoError(t, err)
|
||||
|
||||
for _, tx := range []*transaction.Transaction{txfee, txdepl} {
|
||||
tx.Scripts[0].InvocationScript = testchain.SignCommittee(tx)
|
||||
}
|
||||
|
||||
bl := testchain.NewBlock(t, chain, 1, 0, txfee, txdepl)
|
||||
_, err = c.SubmitBlock(*bl)
|
||||
require.NoError(t, err)
|
||||
|
||||
fee, err = manReader.GetMinimumDeploymentFee()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, big.NewInt(1_0000_0000), fee)
|
||||
|
||||
appLog, err := c.GetApplicationLog(txdepl.Hash(), nil)
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, vmstate.Halt, appLog.Executions[0].VMState)
|
||||
require.Equal(t, 1, len(appLog.Executions[0].Events))
|
||||
}
|
||||
|
||||
func TestAddNetworkFeeCalculateNetworkFee(t *testing.T) {
|
||||
chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t)
|
||||
defer chain.Close()
|
||||
|
@ -1388,7 +1454,7 @@ func TestClient_GetNotaryServiceFeePerKey(t *testing.T) {
|
|||
require.Equal(t, defaultNotaryServiceFeePerKey, actual)
|
||||
}
|
||||
|
||||
func TestClient_GetOraclePrice(t *testing.T) {
|
||||
func TestClientOracle(t *testing.T) {
|
||||
chain, rpcSrv, httpSrv := initServerWithInMemoryChain(t)
|
||||
defer chain.Close()
|
||||
defer rpcSrv.Shutdown()
|
||||
|
@ -1397,10 +1463,41 @@ func TestClient_GetOraclePrice(t *testing.T) {
|
|||
require.NoError(t, err)
|
||||
require.NoError(t, c.Init())
|
||||
|
||||
var defaultOracleRequestPrice int64 = 5000_0000
|
||||
actual, err := c.GetOraclePrice()
|
||||
oraRe := oracle.NewReader(invoker.New(c, nil))
|
||||
|
||||
var defaultOracleRequestPrice = big.NewInt(5000_0000)
|
||||
actual, err := oraRe.GetPrice()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, defaultOracleRequestPrice, actual)
|
||||
|
||||
act, err := actor.New(c, []actor.SignerAccount{{
|
||||
Signer: transaction.Signer{
|
||||
Account: testchain.CommitteeScriptHash(),
|
||||
Scopes: transaction.CalledByEntry,
|
||||
},
|
||||
Account: &wallet.Account{
|
||||
Address: testchain.CommitteeAddress(),
|
||||
Contract: &wallet.Contract{
|
||||
Script: testchain.CommitteeVerificationScript(),
|
||||
},
|
||||
},
|
||||
}})
|
||||
require.NoError(t, err)
|
||||
|
||||
ora := oracle.New(act)
|
||||
|
||||
newPrice := big.NewInt(1_0000_0000)
|
||||
tx, err := ora.SetPriceUnsigned(newPrice)
|
||||
require.NoError(t, err)
|
||||
|
||||
tx.Scripts[0].InvocationScript = testchain.SignCommittee(tx)
|
||||
bl := testchain.NewBlock(t, chain, 1, 0, tx)
|
||||
_, err = c.SubmitBlock(*bl)
|
||||
require.NoError(t, err)
|
||||
|
||||
actual, err = ora.GetPrice()
|
||||
require.NoError(t, err)
|
||||
require.Equal(t, newPrice, actual)
|
||||
}
|
||||
|
||||
func TestClient_InvokeAndPackIteratorResults(t *testing.T) {
|
||||
|
|
Loading…
Reference in a new issue