Merge pull request #2681 from nspcc-dev/rpcclient-comments

RPC client examples, comments and small fixes
This commit is contained in:
Roman Khimov 2022-09-08 14:47:31 +03:00 committed by GitHub
commit 8dc5b38568
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
31 changed files with 938 additions and 114 deletions

View file

@ -15,7 +15,7 @@ import (
// as an input to `multisig sign`. If a wallet.Account is given and can sign,
// it's signed as well using it.
func InitAndSave(net netmode.Magic, tx *transaction.Transaction, acc *wallet.Account, filename string) error {
scCtx := context.NewParameterContext("Neo.Network.P2P.Payloads.Transaction", net, tx)
scCtx := context.NewParameterContext(context.TransactionType, net, tx)
if acc != nil && acc.CanSign() {
sign := acc.SignHashable(net, tx)
if err := scCtx.AddSignature(acc.ScriptHash(), acc.Contract, acc.PublicKey(), sign); err != nil {

View file

@ -292,8 +292,10 @@ server via [`submitnotaryrequest` RPC call](./rpc.md#submitnotaryrequest-call).
Note, that all parties must generate the same main transaction while fallbacks
can differ.
To create a notary request, you can use [NeoGo RPC client](./rpc.md#Client). Follow
the steps to create a signature request:
To create a notary request, you can use [NeoGo RPC client](./rpc.md#Client). The
procedure below uses only basic RPC client functions and show all of the notary
request internals. You can use much simpler Actor interface in the notary
subpackage with an example written in Go doc.
1. Prepare a list of signers with scopes for the main transaction (i.e. the
transaction that signatures are being collected for, that will be `Signers`
@ -307,8 +309,7 @@ the steps to create a signature request:
Include Notary native contract in the list of signers with the following
constraints:
* Notary signer hash is the hash of a native Notary contract that can be fetched
from
[func (*Client) GetNativeContractHash](https://pkg.go.dev/github.com/nspcc-dev/neo-go@v0.97.2/pkg/rpcclient#Client.GetNativeContractHash).
from the notary RPC client subpackage (notary.Hash)
* A notary signer must have `None` scope.
* A notary signer shouldn't be placed at the beginning of the signer list
because Notary contract does not pay main transaction fees. Other positions
@ -390,8 +391,7 @@ the steps to create a signature request:
tries to push all associated fallbacks. Use the following rules to define
`fallbackValidFor`:
- `fallbackValidFor` shouldn't be more than `MaxNotValidBeforeDelta` value.
- Use [func (*Client) GetMaxNotValidBeforeDelta](https://pkg.go.dev/github.com/nspcc-dev/neo-go@v0.97.2/pkg/rpcclient#Client.GetMaxNotValidBeforeDelta)
to check `MaxNotValidBefore` value.
- Use notary package's GetMaxNotValidBeforeDelta to check `MaxNotValidBefore` value.
11. Construct a script for the fallback transaction. The script may do something useful,
i.g. invoke method of a contract. However, if you don't need to perform anything
special on fallback invocation, you can use simple `opcode.RET` script.

View file

@ -1,10 +1,11 @@
/*
Package actor provides a way to change chain state via RPC client.
This layer builds on top of the basic RPC client and simplifies creating,
signing and sending transactions to the network (since that's the only way chain
state is changed). It's generic enough to be used for any contract that you may
want to invoke and contract-specific functions can build on top of it.
This layer builds on top of the basic RPC client and [invoker] package, it
simplifies creating, signing and sending transactions to the network (since
that's the only way chain state is changed). It's generic enough to be used for
any contract that you may want to invoke and contract-specific functions can
build on top of it.
*/
package actor
@ -44,6 +45,14 @@ type SignerAccount struct {
// state-changing actions (via transactions that can also be created without
// sending them to the network) on behalf of a set of signers. It also provides
// an Invoker interface to perform test calls with the same set of signers.
//
// Actor-specific APIs follow the naming scheme set by Invoker in method
// suffixes. *Call methods operate with function calls and require a contract
// hash, a method and parameters if any. *Run methods operate with scripts and
// require a NeoVM script that will be used directly. Prefixes denote the
// action to be performed, "Make" prefix is used for methods that create
// transactions in various ways, while "Send" prefix is used by methods that
// directly transmit created transactions to the RPC server.
type Actor struct {
invoker.Invoker

View file

@ -0,0 +1,122 @@
package actor_test
import (
"context"
"encoding/json"
"os"
"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"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/actor"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/policy"
sccontext "github.com/nspcc-dev/neo-go/pkg/smartcontract/context"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/nspcc-dev/neo-go/pkg/wallet"
)
func ExampleActor() {
// No error checking done at all, intentionally.
w, _ := wallet.NewWalletFromFile("somewhere")
defer w.Close()
c, _ := rpcclient.New(context.Background(), "url", rpcclient.Options{})
// Create a simple CalledByEntry-scoped actor (assuming there are accounts
// inside the wallet).
a, _ := actor.NewSimple(c, w.Accounts[0])
customContract := util.Uint160{9, 8, 7}
// Actor has an Invoker inside, so we can perform test invocations, it will
// have a signer with the first wallet account and CalledByEntry scope.
res, _ := a.Call(customContract, "method", 1, 2, 3)
if res.State != vmstate.Halt.String() {
// The call failed.
}
// All of the side-effects in res can be analyzed.
// Now we want to send the same invocation in a transaction, but we already
// have the script and a proper system fee for it, therefore SendUncheckedRun
// can be used.
txid, vub, _ := a.SendUncheckedRun(res.Script, res.GasConsumed, nil, nil)
_ = txid
_ = vub
// You need to wait for it to persist and then check the on-chain result of it.
// Now we want to send some transaction, but give it a priority by increasing
// its network fee, this can be done with Tuned APIs.
txid, vub, _ = a.SendTunedCall(customContract, "method", nil, func(r *result.Invoke, t *transaction.Transaction) error {
// This code is run after the test-invocation done by *Call methods.
// Reuse the default function to check for HALT execution state.
err := actor.DefaultCheckerModifier(r, t)
if err != nil {
return err
}
// Some additional checks can be performed right here, but we only
// want to raise the network fee by ~20%.
t.NetworkFee += (t.NetworkFee / 5)
return nil
}, 1, 2, 3)
_ = txid
_ = vub
// Actor can be used for higher-level wrappers as well, if we want to interact with
// NEO then [neo] package can accept our Actor and allow to easily use NEO methods.
neoContract := neo.New(a)
balance, _ := neoContract.BalanceOf(a.Sender())
_ = balance
// Now suppose the second wallet account is a committee account. We want to
// create and sign transactions for committee, but use the first account as
// a sender (because committee account has no GAS). We at the same time want
// to make all transactions using this actor high-priority ones, because
// committee can use this attribute.
// Get the default options to have CheckerModifier/Modifier set up correctly.
opts := actor.NewDefaultOptions()
// And override attributes.
opts.Attributes = []transaction.Attribute{{Type: transaction.HighPriority}}
// Create an Actor.
a, _ = actor.NewTuned(c, []actor.SignerAccount{{
// Sender, regular account with None scope.
Signer: transaction.Signer{
Account: w.Accounts[0].ScriptHash(),
Scopes: transaction.None,
},
Account: w.Accounts[0],
}, {
// Commmitee.
Signer: transaction.Signer{
Account: w.Accounts[1].ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: w.Accounts[1],
}}, opts)
// Use policy contract wrapper to simplify things. All changes in the
// Policy contract are made by the committee.
policyContract := policy.New(a)
// Create a transaction to set storage price, it'll be high-priority and have two
// signers from above. Committee is a multisignature account, so we can't sign/send
// it right away, w.Accounts[1] has only one public key. Therefore, we need to
// create a partially signed transaction and save it, then collect other signatures
// and send.
tx, _ := policyContract.SetStoragePriceUnsigned(10)
net := a.GetNetwork()
scCtx := sccontext.NewParameterContext(sccontext.TransactionType, net, tx)
sign := w.Accounts[0].SignHashable(net, tx)
_ = scCtx.AddSignature(w.Accounts[0].ScriptHash(), w.Accounts[0].Contract, w.Accounts[0].PublicKey(), sign)
sign = w.Accounts[1].SignHashable(net, tx)
_ = scCtx.AddSignature(w.Accounts[1].ScriptHash(), w.Accounts[1].Contract, w.Accounts[1].PublicKey(), sign)
data, _ := json.Marshal(scCtx)
_ = os.WriteFile("tx.json", data, 0644)
// Signature collection is out of scope, usually it's manual for cases like this.
}

View file

@ -86,7 +86,8 @@ type calculateValidUntilBlockCache struct {
}
// New returns a new Client ready to use. You should call Init method to
// initialize network magic the client is operating on.
// initialize stateroot setting for the network the client is operating on if
// you plan using GetBlock*.
func New(ctx context.Context, endpoint string, opts Options) (*Client, error) {
cl := new(Client)
err := initClient(ctx, cl, endpoint, opts)

View file

@ -1,21 +1,57 @@
/*
Package rpcclient implements NEO-specific JSON-RPC 2.0 client.
This package is currently in beta and is subject to change.
This package itself is designed to be a thin layer on top of the regular JSON-RPC
interface provided by Neo implementations. Therefore the set of methods provided
by clients is exactly the same as available from servers and they use data types
that directly correspond to JSON data exchanged. While this is the most powerful
and direct interface, it at the same time is not very convenient for many
purposes, like the most popular one --- performing test invocations and
creating/sending transactions that will do something to the chain. Please check
subpackages for more convenient APIs.
# Subpackages
The overall structure can be seen as a number of layers built on top of rpcclient
and on top of each other with each package and each layer solving different
problems.
These layers are:
- Basic RPC API, rpcclient package itself.
- Generic invocation/transaction API represented by invoker, unwrap (auxiliary,
but very convenient) and actor packages. These allow to perform test
invocations with plain Go types, use historic states for these invocations,
get the execution results from reader functions and create/send transactions
that change something on-chain.
- Standard-specific wrappers that are implemented in nep11 and nep17 packages
(with common methods in neptoken). They implement the respective NEP-11 and
NEP-17 APIs both for safe (read-only) and state-changing methods. Safe methods
require an Invoker to be called, while Actor is used to create/send
transactions.
- Contract-specific wrappers for native contracts that include management, gas,
neo, oracle, policy and rolemgmt packages for the respective native contracts.
Complete contract functionality is exposed (reusing nep17 package for gas and
neo).
- Notary actor and contract, a bit special since it's a NeoGo protocol
extension, but notary package provides both the notary native contract wrapper
and a notary-specific actor implementation that allows to easily wrap any
transaction into a notary request.
- Non-native contract-specific wrappers, currently partially provided only for
NNS contract (it's still in development), at the moment that's mostly an
example of how contract-specific wrappers can be built for other dApps
(reusing invoker/actor layers it's pretty easy).
# Client
After creating a client instance with or without a ClientConfig
you can interact with the NEO blockchain by its exposed methods.
Some of the methods also allow to pass a verbose bool. This will
return a more pretty printed response from the server instead of
a raw hex string.
TODO:
Allow client to connect using client cert.
More in-depth examples.
Supported methods
calculatenetworkfee

View file

@ -2,7 +2,8 @@
Package gas provides a convenience wrapper for GAS contract to use it via RPC.
GAS itself only has standard NEP-17 methods, so this package only contains its
hash and allows to create NEP-17 structures in an easier way.
hash and allows to create NEP-17 structures in an easier way. Refer to [nep17]
package for more details on NEP-17 interface.
*/
package gas

View file

@ -0,0 +1,99 @@
package invoker_test
import (
"context"
"errors"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"github.com/nspcc-dev/neo-go/pkg/rpcclient"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
)
func ExampleInvoker() {
// No error checking done at all, intentionally.
c, _ := rpcclient.New(context.Background(), "url", rpcclient.Options{})
// A simple invoker with no signers, perfectly fine for reads from safe methods.
inv := invoker.New(c, nil)
// Get the NEO token supply (notice that unwrap is used to get the result).
supply, _ := unwrap.BigInt(inv.Call(neo.Hash, "totalSupply"))
_ = supply
acc, _ := address.StringToUint160("NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq")
// Get the NEO balance for account NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq.
balance, _ := unwrap.BigInt(inv.Call(neo.Hash, "balanceOf", acc))
_ = balance
// Test-invoke transfer call.
res, _ := inv.Call(neo.Hash, "transfer", acc, util.Uint160{1, 2, 3}, 1, nil)
if res.State == vmstate.Halt.String() {
// NEO is broken! inv has no signers and transfer requires a witness to be performed.
} else {
// OK, this actually should fail.
}
// A historic invoker with no signers at block 1000000.
inv = invoker.NewHistoricAtHeight(1000000, c, nil)
// It's the same call as above, but the data is for a state at block 1000000.
balance, _ = unwrap.BigInt(inv.Call(neo.Hash, "balanceOf", acc))
_ = balance
// This invoker has a signer for NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq account with
// CalledByEntry scope, which is sufficient for most operation. It uses current
// state which is exactly what you need if you want to then create a transaction
// with the same action.
inv = invoker.New(c, []transaction.Signer{{Account: acc, Scopes: transaction.CalledByEntry}})
// Now test invocation should be fine (if NVTiAjNgagDkTr5HTzDmQP9kPwPHN5BgVq has 1 NEO of course).
res, _ = inv.Call(neo.Hash, "transfer", acc, util.Uint160{1, 2, 3}, 1, nil)
if res.State == vmstate.Halt.String() {
// transfer actually returns a value, so check it too.
ok, _ := unwrap.Bool(res, nil)
if ok {
// OK, as expected. res.Script contains the corresponding
// script and res.GasConsumed has an appropriate system fee
// required for a transaction.
}
}
// Now let's try working with iterators.
nep11Contract := util.Uint160{1, 2, 3}
var tokens [][]byte
// Try doing it the right way, by traversing the iterator.
sess, iter, err := unwrap.SessionIterator(inv.Call(nep11Contract, "tokensOf", acc))
// The server doesn't support sessions and doesn't perform iterator expansion,
// iterators can't be used.
if err != nil {
if errors.Is(err, unwrap.ErrNoSessionID) {
// But if we expect some low number of elements, CallAndExpandIterator
// can help us in this case. If the account has more than 10 elements,
// some of them will be missing from the response.
tokens, _ = unwrap.ArrayOfBytes(inv.CallAndExpandIterator(nep11Contract, "tokensOf", 10, acc))
} else {
panic("some error")
}
} else {
items, err := inv.TraverseIterator(sess, &iter, 100)
// Keep going until there are no more elements
for err == nil && len(items) != 0 {
for _, itm := range items {
tokenID, _ := itm.TryBytes()
tokens = append(tokens, tokenID)
}
items, err = inv.TraverseIterator(sess, &iter, 100)
}
// Let the server release the session.
_ = inv.TerminateSession(sess)
}
_ = tokens
}

View file

@ -1,3 +1,12 @@
/*
Package invoker provides a convenient wrapper to perform test calls via RPC client.
This layer builds on top of the basic RPC client and simplifies performing
test function invocations and script runs. It also makes historic calls (NeoGo
extension) transparent, allowing to use the same API as for regular calls.
Results of these calls can be interpreted by upper layer packages like actor
(to create transactions) or unwrap (to retrieve data from return values).
*/
package invoker
import (
@ -70,6 +79,9 @@ type historicConverter struct {
}
// New creates an Invoker to test-execute things at the current blockchain height.
// If you only want to read data from the contract using its safe methods normally
// (but contract-specific in general case) it's OK to pass nil for signers (that
// is, use no signers).
func New(client RPCInvoke, signers []transaction.Signer) *Invoker {
return &Invoker{client, signers}
}
@ -187,7 +199,8 @@ func (v *Invoker) Run(script []byte) (*result.Invoke, error) {
}
// TerminateSession closes the given session, returning an error if anything
// goes wrong.
// goes wrong. It's not strictly required to close the session (it'll expire on
// the server anyway), but it helps to release server resources earlier.
func (v *Invoker) TerminateSession(sessionID uuid.UUID) error {
return termSession(v.client, sessionID)
}

View file

@ -0,0 +1,91 @@
package neo_test
import (
"context"
"math/big"
"sort"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"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/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
"github.com/nspcc-dev/neo-go/pkg/wallet"
)
func ExampleContractReader() {
// No error checking done at all, intentionally.
c, _ := rpcclient.New(context.Background(), "url", rpcclient.Options{})
// Safe methods are reachable with just an invoker, no need for an account there.
inv := invoker.New(c, nil)
// Create a reader interface.
neoToken := neo.NewReader(inv)
// Account hash we're interested in.
accHash, _ := address.StringToUint160("NdypBhqkz2CMMnwxBgvoC9X2XjKF5axgKo")
// Get the account balance.
balance, _ := neoToken.BalanceOf(accHash)
_ = balance
// Get the extended NEO-specific balance data.
bNeo, _ := neoToken.GetAccountState(accHash)
// Account can have no associated vote.
if bNeo.VoteTo == nil {
return
}
// Committee keys.
comm, _ := neoToken.GetCommittee()
// Check if the vote is made for a committee member.
var votedForCommitteeMember bool
for i := range comm {
if bNeo.VoteTo.Equal(comm[i]) {
votedForCommitteeMember = true
break
}
}
_ = votedForCommitteeMember
}
func ExampleContract() {
// No error checking done at all, intentionally.
w, _ := wallet.NewWalletFromFile("somewhere")
defer w.Close()
c, _ := rpcclient.New(context.Background(), "url", rpcclient.Options{})
// Create a simple CalledByEntry-scoped actor (assuming there is an account
// inside the wallet).
a, _ := actor.NewSimple(c, w.Accounts[0])
// Create a complete contract representation.
neoToken := neo.New(a)
tgtAcc, _ := address.StringToUint160("NdypBhqkz2CMMnwxBgvoC9X2XjKF5axgKo")
// Send a transaction that transfers one token to another account.
txid, vub, _ := neoToken.Transfer(a.Sender(), tgtAcc, big.NewInt(1), nil)
_ = txid
_ = vub
// Get a list of candidates (it's limited, but should be sufficient in most cases).
cands, _ := neoToken.GetCandidates()
// Sort by votes.
sort.Slice(cands, func(i, j int) bool { return cands[i].Votes < cands[j].Votes })
// Get the extended NEO-specific balance data.
bNeo, _ := neoToken.GetAccountState(a.Sender())
// If not yet voted, or voted for suboptimal candidate (we want the one with the least votes),
// send a new voting transaction
if bNeo.VoteTo == nil || !bNeo.VoteTo.Equal(&cands[0].PublicKey) {
txid, vub, _ = neoToken.Vote(a.Sender(), &cands[0].PublicKey)
_ = txid
_ = vub
}
}

View file

@ -63,7 +63,7 @@ type ContractReader struct {
// Contract provides full NEO interface, both safe and state-changing methods.
type Contract struct {
ContractReader
nep17.Token
nep17.TokenWriter
actor Actor
}
@ -102,7 +102,8 @@ func NewReader(invoker Invoker) *ContractReader {
// New creates an instance of Contract to perform state-changing actions in the
// NEO contract.
func New(actor Actor) *Contract {
return &Contract{*NewReader(actor), *nep17.New(actor, Hash), actor}
nep := nep17.New(actor, Hash)
return &Contract{ContractReader{nep.TokenReader, actor}, nep.TokenWriter, actor}
}
// GetAccountState returns current NEO balance state for the account which

View file

@ -51,12 +51,19 @@ type BaseReader struct {
hash util.Uint160
}
// BaseWriter is a transaction-creating interface for common divisible and
// non-divisible NEP-11 methods. It simplifies reusing this set of methods,
// but a complete Base is expected to be used in other packages.
type BaseWriter struct {
hash util.Uint160
actor Actor
}
// Base is a state-changing interface for common divisible and non-divisible NEP-11
// methods.
type Base struct {
BaseReader
actor Actor
BaseWriter
}
// TransferEvent represents a Transfer event as defined in the NEP-11 standard.
@ -83,7 +90,7 @@ func NewBaseReader(invoker Invoker, hash util.Uint160) *BaseReader {
// NewBase creates an instance of Base for contract with the given
// hash using the given actor.
func NewBase(actor Actor, hash util.Uint160) *Base {
return &Base{*NewBaseReader(actor, hash), actor}
return &Base{*NewBaseReader(actor, hash), BaseWriter{hash, actor}}
}
// Properties returns a set of token's properties such as name or URL. The map
@ -142,7 +149,7 @@ func (t *BaseReader) TokensOfExpanded(account util.Uint160, num int) ([][]byte,
// transaction if it's not true. It works for divisible NFTs only when there is
// one owner for the particular token. The returned values are transaction hash,
// its ValidUntilBlock value and an error if any.
func (t *Base) Transfer(to util.Uint160, id []byte, data interface{}) (util.Uint256, uint32, error) {
func (t *BaseWriter) Transfer(to util.Uint160, id []byte, data interface{}) (util.Uint256, uint32, error) {
script, err := t.transferScript(to, id, data)
if err != nil {
return util.Uint256{}, 0, err
@ -155,7 +162,7 @@ func (t *Base) Transfer(to util.Uint160, id []byte, data interface{}) (util.Uint
// transaction if it's not true. It works for divisible NFTs only when there is
// one owner for the particular token. This transaction is signed, but not sent
// to the network, instead it's returned to the caller.
func (t *Base) TransferTransaction(to util.Uint160, id []byte, data interface{}) (*transaction.Transaction, error) {
func (t *BaseWriter) TransferTransaction(to util.Uint160, id []byte, data interface{}) (*transaction.Transaction, error) {
script, err := t.transferScript(to, id, data)
if err != nil {
return nil, err
@ -168,7 +175,7 @@ func (t *Base) TransferTransaction(to util.Uint160, id []byte, data interface{})
// transaction if it's not true. It works for divisible NFTs only when there is
// one owner for the particular token. This transaction is not signed and just
// returned to the caller.
func (t *Base) TransferUnsigned(to util.Uint160, id []byte, data interface{}) (*transaction.Transaction, error) {
func (t *BaseWriter) TransferUnsigned(to util.Uint160, id []byte, data interface{}) (*transaction.Transaction, error) {
script, err := t.transferScript(to, id, data)
if err != nil {
return nil, err
@ -176,7 +183,7 @@ func (t *Base) TransferUnsigned(to util.Uint160, id []byte, data interface{}) (*
return t.actor.MakeUnsignedRun(script, nil)
}
func (t *Base) transferScript(params ...interface{}) ([]byte, error) {
func (t *BaseWriter) transferScript(params ...interface{}) ([]byte, error) {
return smartcontract.CreateCallWithAssertScript(t.hash, "transfer", params...)
}

View file

@ -18,7 +18,8 @@ type DivisibleReader struct {
// Divisible is a state-changing interface for divisible NEP-11 contract.
type Divisible struct {
Base
DivisibleReader
BaseWriter
}
// OwnerIterator is used for iterating over OwnerOf (for divisible NFTs) results.
@ -37,7 +38,7 @@ func NewDivisibleReader(invoker Invoker, hash util.Uint160) *DivisibleReader {
// NewDivisible creates an instance of Divisible for a contract
// with the given hash using the given actor.
func NewDivisible(actor Actor, hash util.Uint160) *Divisible {
return &Divisible{*NewBase(actor, hash)}
return &Divisible{*NewDivisibleReader(actor, hash), BaseWriter{hash, actor}}
}
// OwnerOf returns returns an iterator that allows to walk through all owners of
@ -66,24 +67,6 @@ func (t *DivisibleReader) BalanceOfD(owner util.Uint160, token []byte) (*big.Int
return unwrap.BigInt(t.invoker.Call(t.hash, "balanceOf", owner, token))
}
// OwnerOf is the same as (*DivisibleReader).OwnerOf.
func (t *Divisible) OwnerOf(token []byte) (*OwnerIterator, error) {
r := DivisibleReader{t.BaseReader}
return r.OwnerOf(token)
}
// OwnerOfExpanded is the same as (*DivisibleReader).OwnerOfExpanded.
func (t *Divisible) OwnerOfExpanded(token []byte, num int) ([]util.Uint160, error) {
r := DivisibleReader{t.BaseReader}
return r.OwnerOfExpanded(token, num)
}
// BalanceOfD is the same as (*DivisibleReader).BalanceOfD.
func (t *Divisible) BalanceOfD(owner util.Uint160, token []byte) (*big.Int, error) {
r := DivisibleReader{t.BaseReader}
return r.BalanceOfD(owner, token)
}
// TransferD is a divisible version of (*Base).Transfer, allowing to transfer a
// part of NFT. It creates and sends a transaction that performs a `transfer`
// method call using the given parameters and checks for this call result,

View file

@ -0,0 +1,167 @@
package nep11_test
import (
"context"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"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/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet"
)
func ExampleNonDivisibleReader() {
// No error checking done at all, intentionally.
c, _ := rpcclient.New(context.Background(), "url", rpcclient.Options{})
// Safe methods are reachable with just an invoker, no need for an account there.
inv := invoker.New(c, nil)
// NEP-11 contract hash.
nep11Hash := util.Uint160{9, 8, 7}
// Most of the time contracts are non-divisible, create a reader for nep11Hash.
n11 := nep11.NewNonDivisibleReader(inv, nep11Hash)
// Get the metadata. Even though these methods are implemented in neptoken package,
// they're available for NEP-11 wrappers.
symbol, _ := n11.Symbol()
supply, _ := n11.TotalSupply()
_ = symbol
_ = supply
// Account hash we're interested in.
accHash, _ := address.StringToUint160("NdypBhqkz2CMMnwxBgvoC9X2XjKF5axgKo")
// Get account balance.
balance, _ := n11.BalanceOf(accHash)
if balance.Sign() > 0 {
// There are some tokens there, let's look at them.
tokIter, _ := n11.TokensOf(accHash)
for toks, err := tokIter.Next(10); err == nil && len(toks) > 0; toks, err = tokIter.Next(10) {
for i := range toks {
// We know the owner of the token, but let's check internal contract consistency.
owner, _ := n11.OwnerOf(toks[i])
if !owner.Equals(accHash) {
panic("NEP-11 contract is broken!")
}
}
}
}
}
func ExampleDivisibleReader() {
// No error checking done at all, intentionally.
c, _ := rpcclient.New(context.Background(), "url", rpcclient.Options{})
// Safe methods are reachable with just an invoker, no need for an account there.
inv := invoker.New(c, nil)
// NEP-11 contract hash.
nep11Hash := util.Uint160{9, 8, 7}
// Divisible contract are more rare, but we can handle them too.
n11 := nep11.NewDivisibleReader(inv, nep11Hash)
// Get the metadata. Even though these methods are implemented in neptoken package,
// they're available for NEP-11 wrappers.
symbol, _ := n11.Symbol()
supply, _ := n11.TotalSupply()
_ = symbol
_ = supply
// Account hash we're interested in.
accHash, _ := address.StringToUint160("NdypBhqkz2CMMnwxBgvoC9X2XjKF5axgKo")
// Get account balance.
balance, _ := n11.BalanceOf(accHash)
if balance.Sign() > 0 && balance.Cmp(big.NewInt(10)) < 0 {
// We know we have a low number of tokens, so we can use a simple API to get them.
toks, _ := n11.TokensOfExpanded(accHash, 10)
// We can build a list of all owners of account's tokens.
var owners = make([]util.Uint160, 0)
for i := range toks {
ownIter, _ := n11.OwnerOf(toks[i])
for ows, err := ownIter.Next(10); err == nil && len(ows) > 0; ows, err = ownIter.Next(10) {
// Notice that it includes accHash too.
owners = append(owners, ows...)
}
}
// The list can be sorted/deduplicated if needed.
_ = owners
}
}
func ExampleNonDivisible() {
// No error checking done at all, intentionally.
w, _ := wallet.NewWalletFromFile("somewhere")
defer w.Close()
c, _ := rpcclient.New(context.Background(), "url", rpcclient.Options{})
// Create a simple CalledByEntry-scoped actor (assuming there is an account
// inside the wallet).
a, _ := actor.NewSimple(c, w.Accounts[0])
// NEP-11 contract hash.
nep11Hash := util.Uint160{9, 8, 7}
// Create a complete non-divisible contract representation.
n11 := nep11.NewNonDivisible(a, nep11Hash)
tgtAcc, _ := address.StringToUint160("NdypBhqkz2CMMnwxBgvoC9X2XjKF5axgKo")
// Let's tranfer all of account's tokens to some other account.
tokIter, _ := n11.TokensOf(a.Sender())
for toks, err := tokIter.Next(10); err == nil && len(toks) > 0; toks, err = tokIter.Next(10) {
for i := range toks {
// This creates a transaction for every token, but you can
// create a script that will move multiple tokens in one
// transaction with Builder from smartcontract package.
txid, vub, _ := n11.Transfer(tgtAcc, toks[i], nil)
_ = txid
_ = vub
}
}
}
func ExampleDivisible() {
// No error checking done at all, intentionally.
w, _ := wallet.NewWalletFromFile("somewhere")
defer w.Close()
c, _ := rpcclient.New(context.Background(), "url", rpcclient.Options{})
// Create a simple CalledByEntry-scoped actor (assuming there is an account
// inside the wallet).
a, _ := actor.NewSimple(c, w.Accounts[0])
// NEP-11 contract hash.
nep11Hash := util.Uint160{9, 8, 7}
// Create a complete divisible contract representation.
n11 := nep11.NewDivisible(a, nep11Hash)
tgtAcc, _ := address.StringToUint160("NdypBhqkz2CMMnwxBgvoC9X2XjKF5axgKo")
// Let's tranfer all of account's tokens to some other account.
tokIter, _ := n11.TokensOf(a.Sender())
for toks, err := tokIter.Next(10); err == nil && len(toks) > 0; toks, err = tokIter.Next(10) {
for i := range toks {
// It's a divisible token, so balance data is required in general case.
balance, _ := n11.BalanceOfD(a.Sender(), toks[i])
// This creates a transaction for every token, but you can
// create a script that will move multiple tokens in one
// transaction with Builder from smartcontract package.
txid, vub, _ := n11.TransferD(a.Sender(), tgtAcc, balance, toks[i], nil)
_ = txid
_ = vub
}
}
}

View file

@ -12,7 +12,8 @@ type NonDivisibleReader struct {
// NonDivisible is a state-changing interface for non-divisble NEP-11 contract.
type NonDivisible struct {
Base
NonDivisibleReader
BaseWriter
}
// NewNonDivisibleReader creates an instance of NonDivisibleReader for a contract
@ -24,16 +25,10 @@ func NewNonDivisibleReader(invoker Invoker, hash util.Uint160) *NonDivisibleRead
// NewNonDivisible creates an instance of NonDivisible for a contract
// with the given hash using the given actor.
func NewNonDivisible(actor Actor, hash util.Uint160) *NonDivisible {
return &NonDivisible{*NewBase(actor, hash)}
return &NonDivisible{*NewNonDivisibleReader(actor, hash), BaseWriter{hash, actor}}
}
// OwnerOf returns the owner of the given NFT.
func (t *NonDivisibleReader) OwnerOf(token []byte) (util.Uint160, error) {
return unwrap.Uint160(t.invoker.Call(t.hash, "ownerOf", token))
}
// OwnerOf is the same as (*NonDivisibleReader).OwnerOf.
func (t *NonDivisible) OwnerOf(token []byte) (util.Uint160, error) {
r := NonDivisibleReader{t.BaseReader}
return r.OwnerOf(token)
}

View file

@ -0,0 +1,67 @@
package nep17_test
import (
"context"
"math/big"
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
"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/invoker"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet"
)
func ExampleTokenReader() {
// No error checking done at all, intentionally.
c, _ := rpcclient.New(context.Background(), "url", rpcclient.Options{})
// Safe methods are reachable with just an invoker, no need for an account there.
inv := invoker.New(c, nil)
// NEP-17 contract hash.
nep17Hash := util.Uint160{9, 8, 7}
// And a reader interface.
n17 := nep17.NewReader(inv, nep17Hash)
// Get the metadata. Even though these methods are implemented in neptoken package,
// they're available for NEP-17 wrappers.
symbol, _ := n17.Symbol()
supply, _ := n17.TotalSupply()
_ = symbol
_ = supply
// Account hash we're interested in.
accHash, _ := address.StringToUint160("NdypBhqkz2CMMnwxBgvoC9X2XjKF5axgKo")
// Get account balance.
balance, _ := n17.BalanceOf(accHash)
_ = balance
}
func ExampleToken() {
// No error checking done at all, intentionally.
w, _ := wallet.NewWalletFromFile("somewhere")
defer w.Close()
c, _ := rpcclient.New(context.Background(), "url", rpcclient.Options{})
// Create a simple CalledByEntry-scoped actor (assuming there is an account
// inside the wallet).
a, _ := actor.NewSimple(c, w.Accounts[0])
// NEP-17 contract hash.
nep17Hash := util.Uint160{9, 8, 7}
// Create a complete NEP-17 contract representation.
n17 := nep17.New(a, nep17Hash)
tgtAcc, _ := address.StringToUint160("NdypBhqkz2CMMnwxBgvoC9X2XjKF5axgKo")
// Send a transaction that transfers one token to another account.
txid, vub, _ := n17.Transfer(a.Sender(), tgtAcc, big.NewInt(1), nil)
_ = txid
_ = vub
}

View file

@ -36,12 +36,19 @@ type TokenReader struct {
neptoken.Base
}
// TokenWriter contains NEP-17 token methods that change state. It's not meant
// to be used directly (Token that includes it is more convenient) and just
// separates one set of methods from another to simplify reusing this package
// for other contracts that extend NEP-17 interface.
type TokenWriter struct {
hash util.Uint160
actor Actor
}
// Token provides full NEP-17 interface, both safe and state-changing methods.
type Token struct {
TokenReader
hash util.Uint160
actor Actor
TokenWriter
}
// TransferEvent represents a Transfer event as defined in the NEP-17 standard.
@ -68,14 +75,14 @@ func NewReader(invoker Invoker, hash util.Uint160) *TokenReader {
// New creates an instance of Token for contract with the given hash
// using the given Actor.
func New(actor Actor, hash util.Uint160) *Token {
return &Token{*NewReader(actor, hash), hash, actor}
return &Token{*NewReader(actor, hash), TokenWriter{hash, actor}}
}
// Transfer creates and sends a transaction that performs a `transfer` method
// call using the given parameters and checks for this call result, failing the
// transaction if it's not true. The returned values are transaction hash, its
// ValidUntilBlock value and an error if any.
func (t *Token) Transfer(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (util.Uint256, uint32, error) {
func (t *TokenWriter) Transfer(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (util.Uint256, uint32, error) {
return t.MultiTransfer([]TransferParameters{{from, to, amount, data}})
}
@ -83,7 +90,7 @@ func (t *Token) Transfer(from util.Uint160, to util.Uint160, amount *big.Int, da
// call using the given parameters and checks for this call result, failing the
// transaction if it's not true. This transaction is signed, but not sent to the
// network, instead it's returned to the caller.
func (t *Token) TransferTransaction(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (*transaction.Transaction, error) {
func (t *TokenWriter) TransferTransaction(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (*transaction.Transaction, error) {
return t.MultiTransferTransaction([]TransferParameters{{from, to, amount, data}})
}
@ -91,11 +98,11 @@ func (t *Token) TransferTransaction(from util.Uint160, to util.Uint160, amount *
// call using the given parameters and checks for this call result, failing the
// transaction if it's not true. This transaction is not signed and just returned
// to the caller.
func (t *Token) TransferUnsigned(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (*transaction.Transaction, error) {
func (t *TokenWriter) TransferUnsigned(from util.Uint160, to util.Uint160, amount *big.Int, data interface{}) (*transaction.Transaction, error) {
return t.MultiTransferUnsigned([]TransferParameters{{from, to, amount, data}})
}
func (t *Token) multiTransferScript(params []TransferParameters) ([]byte, error) {
func (t *TokenWriter) multiTransferScript(params []TransferParameters) ([]byte, error) {
if len(params) == 0 {
return nil, errors.New("at least one transfer parameter required")
}
@ -113,7 +120,7 @@ func (t *Token) multiTransferScript(params []TransferParameters) ([]byte, error)
// many times as needed (with ASSERTs added, so if any of these transfers fail
// whole transaction (with all transfers) fails). The values returned are the
// same as in Transfer.
func (t *Token) MultiTransfer(params []TransferParameters) (util.Uint256, uint32, error) {
func (t *TokenWriter) MultiTransfer(params []TransferParameters) (util.Uint256, uint32, error) {
script, err := t.multiTransferScript(params)
if err != nil {
return util.Uint256{}, 0, err
@ -123,7 +130,7 @@ func (t *Token) MultiTransfer(params []TransferParameters) (util.Uint256, uint32
// MultiTransferTransaction is similar to MultiTransfer, but returns the same values
// as TransferTransaction (signed transaction that is not yet sent).
func (t *Token) MultiTransferTransaction(params []TransferParameters) (*transaction.Transaction, error) {
func (t *TokenWriter) MultiTransferTransaction(params []TransferParameters) (*transaction.Transaction, error) {
script, err := t.multiTransferScript(params)
if err != nil {
return nil, err
@ -133,7 +140,7 @@ func (t *Token) MultiTransferTransaction(params []TransferParameters) (*transact
// MultiTransferUnsigned is similar to MultiTransfer, but returns the same values
// as TransferUnsigned (not yet signed transaction).
func (t *Token) MultiTransferUnsigned(params []TransferParameters) (*transaction.Transaction, error) {
func (t *TokenWriter) MultiTransferUnsigned(params []TransferParameters) (*transaction.Transaction, error) {
script, err := t.multiTransferScript(params)
if err != nil {
return nil, err

View file

@ -12,6 +12,7 @@ import (
"github.com/google/uuid"
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
@ -19,14 +20,13 @@ import (
// Invoker is used by ContractReader to call various methods.
type Invoker interface {
Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error)
CallAndExpandIterator(contract util.Uint160, method string, maxItems int, params ...interface{}) (*result.Invoke, error)
TerminateSession(sessionID uuid.UUID) error
TraverseIterator(sessionID uuid.UUID, iterator *result.Iterator, num int) ([]stackitem.Item, error)
nep11.Invoker
}
// ContractReader provides an interface to call read-only NNS contract methods.
type ContractReader struct {
nep11.NonDivisibleReader
invoker Invoker
hash util.Uint160
}
@ -41,7 +41,7 @@ type RecordIterator struct {
// NewReader creates an instance of ContractReader that can be used to read
// data from the contract.
func NewReader(invoker Invoker, hash util.Uint160) *ContractReader {
return &ContractReader{invoker, hash}
return &ContractReader{*nep11.NewNonDivisibleReader(invoker, hash), invoker, hash}
}
// GetPrice returns current domain registration price in GAS.

View file

@ -59,6 +59,15 @@ type Contract struct {
actor ContractActor
}
// OnNEP17PaymentData is the data set that is accepted by the notary contract
// onNEP17Payment handler. It's mandatory for GAS tranfers to this contract.
type OnNEP17PaymentData struct {
// Account can be nil, in this case transfer sender (from) account is used.
Account *util.Uint160
// Till specifies the deposit lock time (in blocks).
Till uint32
}
// Hash stores the hash of the native Notary contract.
var Hash = state.CreateNativeContractHash(nativenames.Notary)

View file

@ -0,0 +1,89 @@
package notary_test
import (
"context"
"math/big"
"time"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"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/gas"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/notary"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/policy"
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
"github.com/nspcc-dev/neo-go/pkg/wallet"
)
func ExampleActor() {
// No error checking done at all, intentionally.
w, _ := wallet.NewWalletFromFile("somewhere")
defer w.Close()
// We assume there are two accounts in the wallet --- one is a simple signature
// account and another one is committee account. The first one will send notary
// requests, while committee signatures need to be collected.
// Create an RPC client.
c, _ := rpcclient.New(context.Background(), "url", rpcclient.Options{})
// An actor for the first account.
single, _ := actor.NewSimple(c, w.Accounts[0])
// Transfer some GAS to the Notary contract to be able to send notary requests
// from the first account.
gasSingle := gas.New(single)
txid, vub, _ := gasSingle.Transfer(single.Sender(), notary.Hash, big.NewInt(10_0000_0000), notary.OnNEP17PaymentData{Till: 10000000})
var depositOK bool
// Wait for transaction to be persisted, either it gets in and we get
// an application log with some result or it expires.
for height, err := c.GetBlockCount(); err == nil && height <= vub; height, err = c.GetBlockCount() {
appLog, err := c.GetApplicationLog(txid, nil)
// We can't separate "application log missing" from other errors at the moment, see #2248.
if err != nil {
time.Sleep(5 * time.Second)
continue
}
if len(appLog.Executions) == 1 && appLog.Executions[0].VMState == vmstate.Halt {
depositOK = true
} else {
break
}
}
if !depositOK {
panic("deposit failed")
}
var opts notary.ActorOptions
// Add high priority attribute, we gonna be making committee-signed transactions anyway.
opts.MainAttributes = []transaction.Attribute{{Type: transaction.HighPriority}}
// Create an Actor with the simple account used for paying fees and committee
// signature to be collected.
multi, _ := notary.NewTunedActor(c, []actor.SignerAccount{{
// Sender, regular account with None scope.
Signer: transaction.Signer{
Account: w.Accounts[0].ScriptHash(),
Scopes: transaction.None,
},
Account: w.Accounts[0],
}, {
// Commmitee.
Signer: transaction.Signer{
Account: w.Accounts[1].ScriptHash(),
Scopes: transaction.CalledByEntry,
},
Account: w.Accounts[1],
}}, opts)
// Use the Policy contract to perform something requiring committee signature.
policyContract := policy.New(multi)
// Wrap a transaction to set storage price into a notary request. Fallback will
// be create automatically and all appropriate attributes will be added to both
// transactions.
mainTx, fbTx, vub, _ := multi.Notarize(policyContract.SetStoragePriceTransaction(10))
_ = mainTx
_ = fbTx
_ = vub
}

View file

@ -30,6 +30,9 @@ func (c *Client) GetStoragePrice() (int64, error) {
}
// GetMaxNotValidBeforeDelta invokes `getMaxNotValidBeforeDelta` method on a native Notary contract.
//
// Deprecated: please use notary subpackage. This method will be removed
// in future versions.
func (c *Client) GetMaxNotValidBeforeDelta() (int64, error) {
notaryHash, err := c.GetNativeContractHash(nativenames.Notary)
if err != nil {

View file

@ -81,14 +81,14 @@ func (c *Client) GetBlockCount() (uint32, error) {
return resp, nil
}
// GetBlockByIndex returns a block by its height. You should initialize network magic
// with Init before calling GetBlockByIndex.
// GetBlockByIndex returns a block by its height. In-header stateroot option
// must be initialized with Init before calling this method.
func (c *Client) GetBlockByIndex(index uint32) (*block.Block, error) {
return c.getBlock(index)
}
// GetBlockByHash returns a block by its hash. You should initialize network magic
// with Init before calling GetBlockByHash.
// GetBlockByHash returns a block by its hash. In-header stateroot option
// must be initialized with Init before calling this method.
func (c *Client) GetBlockByHash(hash util.Uint256) (*block.Block, error) {
return c.getBlock(hash.StringLE())
}
@ -116,14 +116,16 @@ func (c *Client) getBlock(param interface{}) (*block.Block, error) {
}
// GetBlockByIndexVerbose returns a block wrapper with additional metadata by
// its height. You should initialize network magic with Init before calling GetBlockByIndexVerbose.
// its height. In-header stateroot option must be initialized with Init before
// calling this method.
// NOTE: to get transaction.ID and transaction.Size, use t.Hash() and io.GetVarSize(t) respectively.
func (c *Client) GetBlockByIndexVerbose(index uint32) (*result.Block, error) {
return c.getBlockVerbose(index)
}
// GetBlockByHashVerbose returns a block wrapper with additional metadata by
// its hash. You should initialize network magic with Init before calling GetBlockByHashVerbose.
// its hash. In-header stateroot option must be initialized with Init before
// calling this method.
func (c *Client) GetBlockByHashVerbose(hash util.Uint256) (*result.Block, error) {
return c.getBlockVerbose(hash.StringLE())
}
@ -158,8 +160,8 @@ func (c *Client) GetBlockHash(index uint32) (util.Uint256, error) {
}
// GetBlockHeader returns the corresponding block header information from a serialized hex string
// according to the specified script hash. You should initialize network magic
// with Init before calling GetBlockHeader.
// according to the specified script hash. In-header stateroot option must be
// initialized with Init before calling this method.
func (c *Client) GetBlockHeader(hash util.Uint256) (*block.Header, error) {
var (
params = []interface{}{hash.StringLE()}
@ -193,7 +195,8 @@ func (c *Client) GetBlockHeaderCount() (uint32, error) {
}
// GetBlockHeaderVerbose returns the corresponding block header information from a Json format string
// according to the specified script hash.
// according to the specified script hash. In-header stateroot option must be
// initialized with Init before calling this method.
func (c *Client) GetBlockHeaderVerbose(hash util.Uint256) (*result.Header, error) {
var (
params = []interface{}{hash.StringLE(), 1}
@ -206,6 +209,7 @@ func (c *Client) GetBlockHeaderVerbose(hash util.Uint256) (*result.Header, error
}
// GetBlockSysFee returns the system fees of the block based on the specified index.
// This method is only supported by NeoGo servers.
func (c *Client) GetBlockSysFee(index uint32) (fixedn.Fixed8, error) {
var (
params = []interface{}{index}
@ -242,12 +246,16 @@ func (c *Client) GetContractStateByHash(hash util.Uint160) (*state.Contract, err
return c.getContractState(hash.StringLE())
}
// GetContractStateByAddressOrName queries contract information according to the contract address or name.
// GetContractStateByAddressOrName queries contract information using the contract
// address or name. Notice that name-based queries work only for native contracts,
// non-native ones can't be requested this way.
func (c *Client) GetContractStateByAddressOrName(addressOrName string) (*state.Contract, error) {
return c.getContractState(addressOrName)
}
// GetContractStateByID queries contract information according to the contract ID.
// Notice that this is supported by all servers only for native contracts,
// non-native ones can be requested only from NeoGo servers.
func (c *Client) GetContractStateByID(id int32) (*state.Contract, error) {
return c.getContractState(id)
}
@ -994,6 +1002,7 @@ func (c *Client) SubmitP2PNotaryRequest(req *payload.P2PNotaryRequest) (util.Uin
}
// ValidateAddress verifies that the address is a correct NEO address.
// Consider using [address] package instead to do it locally.
func (c *Client) ValidateAddress(address string) error {
var (
params = []interface{}{address}
@ -1095,7 +1104,11 @@ func (c *Client) AddNetworkFee(tx *transaction.Transaction, extraFee int64, accs
return nil
}
// GetNetwork returns the network magic of the RPC node the client connected to.
// GetNetwork returns the network magic of the RPC node the client connected to. It
// requires Init to be done first, otherwise an error is returned.
//
// Deprecated: please use GetVersion (it has the same data in the Protocol section)
// or actor subpackage. This method will be removed in future versions.
func (c *Client) GetNetwork() (netmode.Magic, error) {
c.cacheLock.RLock()
defer c.cacheLock.RUnlock()
@ -1108,6 +1121,9 @@ func (c *Client) GetNetwork() (netmode.Magic, error) {
// StateRootInHeader returns true if the state root is contained in the block header.
// You should initialize Client cache with Init() before calling StateRootInHeader.
//
// Deprecated: please use GetVersion (it has the same data in the Protocol section).
// This method will be removed in future versions.
func (c *Client) StateRootInHeader() (bool, error) {
c.cacheLock.RLock()
defer c.cacheLock.RUnlock()
@ -1119,6 +1135,11 @@ func (c *Client) StateRootInHeader() (bool, error) {
}
// GetNativeContractHash returns native contract hash by its name.
//
// Deprecated: please use native contract subpackages that have hashes directly
// (gas, management, neo, notary, oracle, policy, rolemgmt) or
// GetContractStateByAddressOrName method that will return hash along with other
// data.
func (c *Client) GetNativeContractHash(name string) (util.Uint160, error) {
c.cacheLock.RLock()
hash, ok := c.cache.nativeHashes[name]

View file

@ -25,6 +25,12 @@ import (
"github.com/nspcc-dev/neo-go/pkg/vm/vmstate"
)
// ErrNoSessionID is returned from the SessionIterator when the server does not
// have sessions enabled and does not perform automatic iterator expansion. It
// means you have no way to get the data from returned iterators using this
// server, other than expanding it in the VM script.
var ErrNoSessionID = errors.New("server returned iterator ID, but no session ID")
// BigInt expects correct execution (HALT state) with a single stack item
// returned. A big.Int is extracted from this item and returned.
func BigInt(r *result.Invoke, err error) (*big.Int, error) {
@ -142,7 +148,10 @@ func Uint256(r *result.Invoke, err error) (util.Uint256, error) {
// SessionIterator expects correct execution (HALT state) with a single stack
// item returned. If this item is an iterator it's returned to the caller along
// with the session ID.
// with the session ID. Notice that this function also returns successfully
// with zero session ID (but an appropriate Iterator holding all the data
// received) when RPC server performs (limited) iterator expansion which is the
// default behavior for NeoGo servers with SessionEnabled set to false.
func SessionIterator(r *result.Invoke, err error) (uuid.UUID, result.Iterator, error) {
itm, err := Item(r, err)
if err != nil {
@ -156,7 +165,7 @@ func SessionIterator(r *result.Invoke, err error) (uuid.UUID, result.Iterator, e
return uuid.UUID{}, result.Iterator{}, errors.New("the item is InteropInterface, but not an Iterator")
}
if (r.Session == uuid.UUID{}) && iter.ID != nil {
return uuid.UUID{}, result.Iterator{}, errors.New("server returned iterator ID, but no session ID")
return uuid.UUID{}, result.Iterator{}, ErrNoSessionID
}
return r.Session, iter, nil
}

View file

@ -18,6 +18,18 @@ import (
// "entry scripts"), so the set of methods it exposes is tailored to this model
// of use and any calls emitted don't limit flags in any way (always use
// callflag.All).
//
// When using this API keep in mind that the resulting script can't be larger than
// 64K (transaction.MaxScriptLength) to be used as a transaction entry script and
// it can't have more than 2048 elements on the stack. Technically, this limits
// the number of calls that can be made to a lesser value because invocations use
// the same stack too (the exact number depends on methods and parameters).
//
// This API is not (and won't be) suitable to create complex scripts that use
// returned values as parameters to other calls or perform loops or do any other
// things that can be done in NeoVM. This hardly can be expressed in an API like
// this, so if you need more than that and if you're ready to work with bare
// NeoVM instructions please refer to [emit] and [opcode] packages.
type Builder struct {
bw *io.BufBinWriter
}

View file

@ -21,6 +21,12 @@ import (
"github.com/nspcc-dev/neo-go/pkg/wallet"
)
// TransactionType is the ParameterContext Type used for transactions.
const TransactionType = "Neo.Network.P2P.Payloads.Transaction"
// compatTransactionType is the old, 2.x type used for transactions.
const compatTransactionType = "Neo.Core.ContractTransaction"
// ParameterContext represents smartcontract parameter's context.
type ParameterContext struct {
// Type is a type of a verifiable item.
@ -213,7 +219,7 @@ func (c *ParameterContext) UnmarshalJSON(data []byte) error {
var verif crypto.VerifiableDecodable
switch pc.Type {
case "Neo.Core.ContractTransaction", "Neo.Network.P2P.Payloads.Transaction":
case compatTransactionType, TransactionType:
tx := new(transaction.Transaction)
verif = tx
default:

View file

@ -102,7 +102,7 @@ func TestParameterContext_AddSignatureMultisig(t *testing.T) {
},
}
tx := getContractTx(ctr.ScriptHash())
c := NewParameterContext("Neo.Network.P2P.Payloads.Transaction", netmode.UnitTestNet, tx)
c := NewParameterContext(TransactionType, netmode.UnitTestNet, tx)
priv, err := keys.NewPrivateKey()
require.NoError(t, err)
sig := priv.SignHashable(uint32(c.Network), tx)

View file

@ -4,9 +4,10 @@ import (
"context"
"encoding/hex"
"github.com/nspcc-dev/neo-go/pkg/core/transaction"
"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/gas"
"github.com/nspcc-dev/neo-go/pkg/rpcclient/neo"
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/wallet"
@ -14,37 +15,48 @@ import (
func ExampleBuilder() {
// No error checking done at all, intentionally.
w, _ := wallet.NewWalletFromFile("somewhere")
defer w.Close()
c, _ := rpcclient.New(context.Background(), "url", rpcclient.Options{})
neoHash, _ := c.GetNativeContractHash("NeoToken")
// Assuming there is one Account inside.
a, _ := actor.NewSimple(c, w.Accounts[0])
pKey, _ := hex.DecodeString("03d9e8b16bd9b22d3345d6d4cde31be1c3e1d161532e3d0ccecb95ece2eb58336e") // Public key.
b := smartcontract.NewBuilder()
// Single NEO "vote" call with a check
b.InvokeWithAssert(neoHash, "vote", pKey)
// Transfer + vote in a single script with each action leaving return value on the stack.
b.InvokeMethod(neo.Hash, "transfer", a.Sender(), util.Uint160{0xff}, 1, nil)
b.InvokeMethod(neo.Hash, "vote", pKey)
script, _ := b.Script()
// The script can then be used to create transaction or to invoke via RPC.
res, _ := c.InvokeScript(script, []transaction.Signer{{Account: util.Uint160{0x01, 0x02, 0x03}, Scopes: transaction.CalledByEntry}})
if res.State != "HALT" {
// The script failed
// Actor has an Invoker inside, so we can perform test invocation using the script.
res, _ := a.Run(script)
if res.State != "HALT" || len(res.Stack) != 2 {
// The script failed completely or didn't return proper number of return values.
}
transferResult, _ := res.Stack[0].TryBool()
voteResult, _ := res.Stack[1].TryBool()
if !transferResult {
// Transfer failed.
}
if !voteResult {
// Vote failed.
}
b.Reset() // Copy the old script above if you need it!
w, _ := wallet.NewWalletFromFile("somewhere")
defer w.Close()
// Assuming there is one Account inside
a, _ := actor.NewSimple(c, w.Accounts[0])
from := w.Accounts[0].Contract.ScriptHash() // Assuming Contract is present.
// Multiple transfers in a single script. If any of them fail whole script fails.
b.InvokeWithAssert(neoHash, "transfer", from, util.Uint160{0x70}, 1, nil)
b.InvokeWithAssert(neoHash, "transfer", from, util.Uint160{0x71}, 10, []byte("data"))
b.InvokeWithAssert(neoHash, "transfer", from, util.Uint160{0x72}, 1, nil)
// Multiple transfers of different tokens in a single script. If any of
// them fails whole script fails.
b.InvokeWithAssert(neo.Hash, "transfer", a.Sender(), util.Uint160{0x70}, 1, nil)
b.InvokeWithAssert(gas.Hash, "transfer", a.Sender(), util.Uint160{0x71}, 100000, []byte("data"))
b.InvokeWithAssert(neo.Hash, "transfer", a.Sender(), util.Uint160{0x72}, 1, nil)
script, _ = b.Script()
// The script can then be used to create transaction or to invoke via RPC.
// Now send a transaction with this script via an RPC node.
txid, vub, _ := a.SendRun(script)
_ = txid
_ = vub

View file

@ -311,6 +311,18 @@ func NewParameterFromValue(value interface{}) (Parameter, error) {
result.Type = Hash160Type
case util.Uint256:
result.Type = Hash256Type
case *util.Uint160:
if v != nil {
return NewParameterFromValue(*v)
}
result.Type = AnyType
result.Value = nil
case *util.Uint256:
if v != nil {
return NewParameterFromValue(*v)
}
result.Type = AnyType
result.Value = nil
case keys.PublicKey:
return NewParameterFromValue(&v)
case *keys.PublicKey:
@ -387,7 +399,7 @@ func ExpandParameterToEmitable(param Parameter) (interface{}, error) {
}
}
return res, nil
case MapType, InteropInterfaceType, UnknownType, AnyType, VoidType:
case MapType, InteropInterfaceType, UnknownType, VoidType:
return nil, fmt.Errorf("unsupported parameter type: %s", t.String())
default:
return param.Value, nil

View file

@ -484,6 +484,10 @@ func TestExpandParameterToEmitable(t *testing.T) {
In: Parameter{Type: SignatureType, Value: []byte{1, 2, 3}},
Expected: []byte{1, 2, 3},
},
{
In: Parameter{Type: AnyType},
Expected: nil,
},
{
In: Parameter{Type: ArrayType, Value: []Parameter{
{
@ -517,7 +521,6 @@ func TestExpandParameterToEmitable(t *testing.T) {
require.NoError(t, bw.Err)
}
errCases := []Parameter{
{Type: AnyType},
{Type: UnknownType},
{Type: MapType},
{Type: InteropInterfaceType},
@ -636,6 +639,24 @@ func TestParameterFromValue(t *testing.T) {
expType: Hash256Type,
expVal: util.Uint256{3, 2, 1},
},
{
value: (*util.Uint160)(nil),
expType: AnyType,
},
{
value: &util.Uint160{1, 2, 3},
expType: Hash160Type,
expVal: util.Uint160{1, 2, 3},
},
{
value: (*util.Uint256)(nil),
expType: AnyType,
},
{
value: &util.Uint256{3, 2, 1},
expType: Hash256Type,
expVal: util.Uint256{3, 2, 1},
},
{
value: pk1.PublicKey(),
expType: PublicKeyType,

View file

@ -133,6 +133,18 @@ func Array(w *io.BinWriter, es ...interface{}) {
Bytes(w, e.BytesBE())
case util.Uint256:
Bytes(w, e.BytesBE())
case *util.Uint160:
if e == nil {
Opcodes(w, opcode.PUSHNULL)
} else {
Bytes(w, e.BytesBE())
}
case *util.Uint256:
if e == nil {
Opcodes(w, opcode.PUSHNULL)
} else {
Bytes(w, e.BytesBE())
}
case []byte:
Bytes(w, e)
case bool:

View file

@ -10,6 +10,7 @@ import (
"github.com/nspcc-dev/neo-go/pkg/core/interop/interopnames"
"github.com/nspcc-dev/neo-go/pkg/encoding/bigint"
"github.com/nspcc-dev/neo-go/pkg/io"
"github.com/nspcc-dev/neo-go/pkg/util"
"github.com/nspcc-dev/neo-go/pkg/vm/opcode"
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
"github.com/stretchr/testify/assert"
@ -216,9 +217,13 @@ func TestBytes(t *testing.T) {
func TestEmitArray(t *testing.T) {
t.Run("good", func(t *testing.T) {
buf := io.NewBufBinWriter()
var p160 *util.Uint160
var p256 *util.Uint256
u160 := util.Uint160{1, 2, 3}
u256 := util.Uint256{1, 2, 3}
veryBig := new(big.Int).SetUint64(math.MaxUint64)
veryBig.Add(veryBig, big.NewInt(1))
Array(buf.BinWriter, big.NewInt(0), veryBig,
Array(buf.BinWriter, p160, p256, &u160, &u256, u160, u256, big.NewInt(0), veryBig,
[]interface{}{int64(1), int64(2)}, nil, int64(1), "str", true, []byte{0xCA, 0xFE})
require.NoError(t, buf.Err)
@ -241,6 +246,20 @@ func TestEmitArray(t *testing.T) {
assert.EqualValues(t, opcode.PUSHINT128, res[18])
assert.EqualValues(t, veryBig, bigint.FromBytes(res[19:35]))
assert.EqualValues(t, opcode.PUSH0, res[35])
assert.EqualValues(t, opcode.PUSHDATA1, res[36])
assert.EqualValues(t, 32, res[37])
assert.EqualValues(t, u256.BytesBE(), res[38:70])
assert.EqualValues(t, opcode.PUSHDATA1, res[70])
assert.EqualValues(t, 20, res[71])
assert.EqualValues(t, u160.BytesBE(), res[72:92])
assert.EqualValues(t, opcode.PUSHDATA1, res[92])
assert.EqualValues(t, 32, res[93])
assert.EqualValues(t, u256.BytesBE(), res[94:126])
assert.EqualValues(t, opcode.PUSHDATA1, res[126])
assert.EqualValues(t, 20, res[127])
assert.EqualValues(t, u160.BytesBE(), res[128:148])
assert.EqualValues(t, opcode.PUSHNULL, res[148])
assert.EqualValues(t, opcode.PUSHNULL, res[149])
})
t.Run("empty", func(t *testing.T) {