mirror of
https://github.com/nspcc-dev/neo-go.git
synced 2024-12-23 13:41:37 +00:00
Merge pull request #2681 from nspcc-dev/rpcclient-comments
RPC client examples, comments and small fixes
This commit is contained in:
commit
8dc5b38568
31 changed files with 938 additions and 114 deletions
|
@ -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 {
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
122
pkg/rpcclient/actor/doc_test.go
Normal file
122
pkg/rpcclient/actor/doc_test.go
Normal 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.
|
||||
}
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
99
pkg/rpcclient/invoker/doc_test.go
Normal file
99
pkg/rpcclient/invoker/doc_test.go
Normal 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
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
91
pkg/rpcclient/neo/doc_test.go
Normal file
91
pkg/rpcclient/neo/doc_test.go
Normal 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
|
||||
}
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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...)
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
167
pkg/rpcclient/nep11/doc_test.go
Normal file
167
pkg/rpcclient/nep11/doc_test.go
Normal 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
|
||||
}
|
||||
}
|
||||
}
|
|
@ -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)
|
||||
}
|
||||
|
|
67
pkg/rpcclient/nep17/doc_test.go
Normal file
67
pkg/rpcclient/nep17/doc_test.go
Normal 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
|
||||
}
|
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
89
pkg/rpcclient/notary/doc_test.go
Normal file
89
pkg/rpcclient/notary/doc_test.go
Normal 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
|
||||
}
|
|
@ -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 {
|
||||
|
|
|
@ -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]
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -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:
|
||||
|
|
|
@ -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) {
|
||||
|
|
Loading…
Reference in a new issue