diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index ee81daeb9..c7530b541 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -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 } diff --git a/pkg/core/state/contract.go b/pkg/core/state/contract.go index 0b89c4b5b..26bd9bf06 100644 --- a/pkg/core/state/contract.go +++ b/pkg/core/state/contract.go @@ -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") diff --git a/pkg/core/state/contract_test.go b/pkg/core/state/contract_test.go index 3f872974a..9abdd0146 100644 --- a/pkg/core/state/contract_test.go +++ b/pkg/core/state/contract_test.go @@ -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})}, diff --git a/pkg/rpcclient/management/management.go b/pkg/rpcclient/management/management.go new file mode 100644 index 000000000..2cecb5d74 --- /dev/null +++ b/pkg/rpcclient/management/management.go @@ -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) +} diff --git a/pkg/rpcclient/management/management_test.go b/pkg/rpcclient/management/management_test.go new file mode 100644 index 000000000..1f2730032 --- /dev/null +++ b/pkg/rpcclient/management/management_test.go @@ -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). +} diff --git a/pkg/rpcclient/native.go b/pkg/rpcclient/native.go index 827f23d62..203bc04a0 100644 --- a/pkg/rpcclient/native.go +++ b/pkg/rpcclient/native.go @@ -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 { diff --git a/pkg/rpcclient/oracle/oracle.go b/pkg/rpcclient/oracle/oracle.go new file mode 100644 index 000000000..fe3a79964 --- /dev/null +++ b/pkg/rpcclient/oracle/oracle.go @@ -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) +} diff --git a/pkg/rpcclient/oracle/oracle_test.go b/pkg/rpcclient/oracle/oracle_test.go new file mode 100644 index 000000000..25e960d67 --- /dev/null +++ b/pkg/rpcclient/oracle/oracle_test.go @@ -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) +} diff --git a/pkg/services/rpcsrv/client_test.go b/pkg/services/rpcsrv/client_test.go index 2882b5083..6d20036cf 100644 --- a/pkg/services/rpcsrv/client_test.go +++ b/pkg/services/rpcsrv/client_test.go @@ -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) {