Merge pull request #3291 from nspcc-dev/nns_wrapper
This commit is contained in:
commit
9d95a2691b
3 changed files with 1153 additions and 65 deletions
|
@ -1,29 +1,59 @@
|
||||||
/*
|
// Package nns provide RPC wrappers for the non-native NNS contract.
|
||||||
Package nns provide some RPC wrappers for the non-native NNS contract.
|
// This is Neo N3 NNS contract wrapper, the source code of the contract can be found here:
|
||||||
|
// https://github.com/neo-project/non-native-contracts/blob/8d72b92e5e5705d763232bcc24784ced0fb8fc87/src/NameService/NameService.cs
|
||||||
It's not yet a complete interface because there are different NNS versions
|
|
||||||
available, yet it provides the most widely used ones that were available from
|
|
||||||
the old RPC client API.
|
|
||||||
*/
|
|
||||||
package nns
|
package nns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/big"
|
||||||
|
"unicode/utf8"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"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/neorpc/result"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11"
|
"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/rpcclient/unwrap"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
)
|
)
|
||||||
|
|
||||||
// Invoker is used by ContractReader to call various methods.
|
// MaxNameLength is the max length of domain name.
|
||||||
|
const MaxNameLength = 255
|
||||||
|
|
||||||
|
// SetAdminEvent represents "SetAdmin" event emitted by the contract.
|
||||||
|
type SetAdminEvent struct {
|
||||||
|
Name string
|
||||||
|
OldAdmin util.Uint160
|
||||||
|
NewAdmin util.Uint160
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewEvent represents "Renew" event emitted by the contract.
|
||||||
|
type RenewEvent struct {
|
||||||
|
Name string
|
||||||
|
OldExpiration *big.Int
|
||||||
|
NewExpiration *big.Int
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invoker is used by ContractReader to call various safe methods.
|
||||||
type Invoker interface {
|
type Invoker interface {
|
||||||
nep11.Invoker
|
nep11.Invoker
|
||||||
}
|
}
|
||||||
|
|
||||||
// ContractReader provides an interface to call read-only NNS contract methods.
|
// Actor is used by Contract to call state-changing methods.
|
||||||
|
type Actor interface {
|
||||||
|
Invoker
|
||||||
|
nep11.Actor
|
||||||
|
|
||||||
|
MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error)
|
||||||
|
MakeRun(script []byte) (*transaction.Transaction, error)
|
||||||
|
MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error)
|
||||||
|
MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error)
|
||||||
|
SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error)
|
||||||
|
SendRun(script []byte) (util.Uint256, uint32, error)
|
||||||
|
}
|
||||||
|
|
||||||
|
// ContractReader implements safe contract methods.
|
||||||
type ContractReader struct {
|
type ContractReader struct {
|
||||||
nep11.NonDivisibleReader
|
nep11.NonDivisibleReader
|
||||||
|
|
||||||
|
@ -31,38 +61,70 @@ type ContractReader struct {
|
||||||
hash util.Uint160
|
hash util.Uint160
|
||||||
}
|
}
|
||||||
|
|
||||||
// RecordIterator is used for iterating over GetAllRecords results.
|
// Contract provides full NeoNameService interface, both safe and state-changing methods.
|
||||||
type RecordIterator struct {
|
type Contract struct {
|
||||||
client Invoker
|
ContractReader
|
||||||
session uuid.UUID
|
nep11.BaseWriter
|
||||||
iterator result.Iterator
|
|
||||||
|
actor Actor
|
||||||
|
hash util.Uint160
|
||||||
}
|
}
|
||||||
|
|
||||||
// NewReader creates an instance of ContractReader that can be used to read
|
// NewReader creates an instance of ContractReader using provided contract hash and the given Invoker.
|
||||||
// data from the contract.
|
|
||||||
func NewReader(invoker Invoker, hash util.Uint160) *ContractReader {
|
func NewReader(invoker Invoker, hash util.Uint160) *ContractReader {
|
||||||
return &ContractReader{*nep11.NewNonDivisibleReader(invoker, hash), invoker, hash}
|
return &ContractReader{*nep11.NewNonDivisibleReader(invoker, hash), invoker, hash}
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetPrice returns current domain registration price in GAS.
|
// New creates an instance of Contract using provided contract hash and the given Actor.
|
||||||
func (c *ContractReader) GetPrice() (int64, error) {
|
func New(actor Actor, hash util.Uint160) *Contract {
|
||||||
return unwrap.Int64(c.invoker.Call(c.hash, "getPrice"))
|
var nep11ndt = nep11.NewNonDivisible(actor, hash)
|
||||||
|
return &Contract{ContractReader{nep11ndt.NonDivisibleReader, actor, hash}, nep11ndt.BaseWriter, actor, hash}
|
||||||
}
|
}
|
||||||
|
|
||||||
// IsAvailable checks whether the domain given is available for registration.
|
// Roots invokes `roots` method of contract.
|
||||||
|
func (c *ContractReader) Roots() (*RootIterator, error) {
|
||||||
|
sess, iter, err := unwrap.SessionIterator(c.invoker.Call(c.hash, "roots"))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return &RootIterator{
|
||||||
|
client: c.invoker,
|
||||||
|
iterator: iter,
|
||||||
|
session: sess,
|
||||||
|
}, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RootsExpanded is similar to Roots (uses the same contract
|
||||||
|
// method), but can be useful if the server used doesn't support sessions and
|
||||||
|
// doesn't expand iterators. It creates a script that will get the specified
|
||||||
|
// number of result items from the iterator right in the VM and return them to
|
||||||
|
// you. It's only limited by VM stack and GAS available for RPC invocations.
|
||||||
|
func (c *ContractReader) RootsExpanded(_numOfIteratorItems int) ([]string, error) {
|
||||||
|
arr, err := unwrap.Array(c.invoker.CallAndExpandIterator(c.hash, "roots", _numOfIteratorItems))
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
|
||||||
|
return itemsToRoots(arr)
|
||||||
|
}
|
||||||
|
|
||||||
|
// GetPrice invokes `getPrice` method of contract.
|
||||||
|
func (c *ContractReader) GetPrice(length uint8) (*big.Int, error) {
|
||||||
|
return unwrap.BigInt(c.invoker.Call(c.hash, "getPrice", length))
|
||||||
|
}
|
||||||
|
|
||||||
|
// IsAvailable invokes `isAvailable` method of contract.
|
||||||
func (c *ContractReader) IsAvailable(name string) (bool, error) {
|
func (c *ContractReader) IsAvailable(name string) (bool, error) {
|
||||||
return unwrap.Bool(c.invoker.Call(c.hash, "isAvailable", name))
|
return unwrap.Bool(c.invoker.Call(c.hash, "isAvailable", name))
|
||||||
}
|
}
|
||||||
|
|
||||||
// Resolve resolves the given record type for the given domain (with no more
|
// GetRecord invokes `getRecord` method of contract.
|
||||||
// than three redirects).
|
func (c *ContractReader) GetRecord(name string, typev RecordType) (string, error) {
|
||||||
func (c *ContractReader) Resolve(name string, typ RecordType) (string, error) {
|
return unwrap.UTF8String(c.invoker.Call(c.hash, "getRecord", name, typev))
|
||||||
return unwrap.UTF8String(c.invoker.Call(c.hash, "resolve", name, int64(typ)))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// GetAllRecords returns an iterator that allows to retrieve all RecordState
|
// GetAllRecords invokes `getAllRecords` method of contract.
|
||||||
// items for the given domain name. It depends on the server to provide proper
|
|
||||||
// session-based iterator, but can also work with expanded one.
|
|
||||||
func (c *ContractReader) GetAllRecords(name string) (*RecordIterator, error) {
|
func (c *ContractReader) GetAllRecords(name string) (*RecordIterator, error) {
|
||||||
sess, iter, err := unwrap.SessionIterator(c.invoker.Call(c.hash, "getAllRecords", name))
|
sess, iter, err := unwrap.SessionIterator(c.invoker.Call(c.hash, "getAllRecords", name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
|
@ -76,47 +138,412 @@ func (c *ContractReader) GetAllRecords(name string) (*RecordIterator, error) {
|
||||||
}, nil
|
}, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Next returns the next set of elements from the iterator (up to num of them).
|
// GetAllRecordsExpanded is similar to GetAllRecords (uses the same contract
|
||||||
// It can return less than num elements in case iterator doesn't have that many
|
|
||||||
// or zero elements if the iterator has no more elements or the session is
|
|
||||||
// expired.
|
|
||||||
func (r *RecordIterator) Next(num int) ([]RecordState, error) {
|
|
||||||
items, err := r.client.TraverseIterator(r.session, &r.iterator, num)
|
|
||||||
if err != nil {
|
|
||||||
return nil, err
|
|
||||||
}
|
|
||||||
return itemsToRecords(items)
|
|
||||||
}
|
|
||||||
|
|
||||||
// Terminate closes the iterator session used by RecordIterator (if it's
|
|
||||||
// session-based).
|
|
||||||
func (r *RecordIterator) Terminate() error {
|
|
||||||
if r.iterator.ID == nil {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
return r.client.TerminateSession(r.session)
|
|
||||||
}
|
|
||||||
|
|
||||||
// GetAllRecordsExpanded is similar to GetAllRecords (uses the same NNS
|
|
||||||
// method), but can be useful if the server used doesn't support sessions and
|
// method), but can be useful if the server used doesn't support sessions and
|
||||||
// doesn't expand iterators. It creates a script that will get num of result
|
// doesn't expand iterators. It creates a script that will get the specified
|
||||||
// items from the iterator right in the VM and return them to you. It's only
|
// number of result items from the iterator right in the VM and return them to
|
||||||
// limited by VM stack and GAS available for RPC invocations.
|
// you. It's only limited by VM stack and GAS available for RPC invocations.
|
||||||
func (c *ContractReader) GetAllRecordsExpanded(name string, num int) ([]RecordState, error) {
|
func (c *ContractReader) GetAllRecordsExpanded(name string, _numOfIteratorItems int) ([]RecordState, error) {
|
||||||
arr, err := unwrap.Array(c.invoker.CallAndExpandIterator(c.hash, "getAllRecords", num, name))
|
arr, err := unwrap.Array(c.invoker.CallAndExpandIterator(c.hash, "getAllRecords", _numOfIteratorItems, name))
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
return itemsToRecords(arr)
|
return itemsToRecords(arr)
|
||||||
}
|
}
|
||||||
|
|
||||||
func itemsToRecords(arr []stackitem.Item) ([]RecordState, error) {
|
// Resolve invokes `resolve` method of contract.
|
||||||
res := make([]RecordState, len(arr))
|
func (c *ContractReader) Resolve(name string, typev RecordType) (string, error) {
|
||||||
for i := range arr {
|
return unwrap.UTF8String(c.invoker.Call(c.hash, "resolve", name, int64(typev)))
|
||||||
err := res[i].FromStackItem(arr[i])
|
}
|
||||||
if err != nil {
|
|
||||||
return nil, fmt.Errorf("item #%d: %w", i, err)
|
// Update creates a transaction invoking `update` method of the contract.
|
||||||
|
// This transaction is signed and immediately sent to the network.
|
||||||
|
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||||
|
func (c *Contract) Update(nef []byte, manifest string) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "update", nef, manifest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateTransaction creates a transaction invoking `update` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) UpdateTransaction(nef []byte, manifest string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "update", nef, manifest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// UpdateUnsigned creates a transaction invoking `update` method of the contract.
|
||||||
|
// This transaction is not signed, it's simply returned to the caller.
|
||||||
|
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||||
|
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||||
|
func (c *Contract) UpdateUnsigned(nef []byte, manifest string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "update", nil, nef, manifest)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRoot creates a transaction invoking `addRoot` method of the contract.
|
||||||
|
// This transaction is signed and immediately sent to the network.
|
||||||
|
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||||
|
func (c *Contract) AddRoot(root string) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "addRoot", root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRootTransaction creates a transaction invoking `addRoot` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) AddRootTransaction(root string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "addRoot", root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// AddRootUnsigned creates a transaction invoking `addRoot` method of the contract.
|
||||||
|
// This transaction is not signed, it's simply returned to the caller.
|
||||||
|
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||||
|
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||||
|
func (c *Contract) AddRootUnsigned(root string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "addRoot", nil, root)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPrice creates a transaction invoking `setPrice` method of the contract.
|
||||||
|
// This transaction is signed and immediately sent to the network.
|
||||||
|
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||||
|
func (c *Contract) SetPrice(priceList []int64) (util.Uint256, uint32, error) {
|
||||||
|
anyPriceList := make([]any, len(priceList))
|
||||||
|
for i, price := range priceList {
|
||||||
|
anyPriceList[i] = price
|
||||||
|
}
|
||||||
|
return c.actor.SendCall(c.hash, "setPrice", anyPriceList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPriceTransaction creates a transaction invoking `setPrice` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) SetPriceTransaction(priceList []int64) (*transaction.Transaction, error) {
|
||||||
|
anyPriceList := make([]any, len(priceList))
|
||||||
|
for i, price := range priceList {
|
||||||
|
anyPriceList[i] = price
|
||||||
|
}
|
||||||
|
return c.actor.MakeCall(c.hash, "setPrice", anyPriceList)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetPriceUnsigned creates a transaction invoking `setPrice` method of the contract.
|
||||||
|
// This transaction is not signed, it's simply returned to the caller.
|
||||||
|
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||||
|
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||||
|
func (c *Contract) SetPriceUnsigned(priceList []int64) (*transaction.Transaction, error) {
|
||||||
|
anyPriceList := make([]any, len(priceList))
|
||||||
|
for i, price := range priceList {
|
||||||
|
anyPriceList[i] = price
|
||||||
|
}
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "setPrice", nil, anyPriceList)
|
||||||
|
}
|
||||||
|
|
||||||
|
func (c *Contract) scriptForRegister(name string, owner util.Uint160) ([]byte, error) {
|
||||||
|
return smartcontract.CreateCallWithAssertScript(c.hash, "register", name, owner)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Register creates a transaction invoking `register` method of the contract.
|
||||||
|
// This transaction is signed and immediately sent to the network.
|
||||||
|
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||||
|
func (c *Contract) Register(name string, owner util.Uint160) (util.Uint256, uint32, error) {
|
||||||
|
script, err := c.scriptForRegister(name, owner)
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint256{}, 0, err
|
||||||
|
}
|
||||||
|
return c.actor.SendRun(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterTransaction creates a transaction invoking `register` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) RegisterTransaction(name string, owner util.Uint160) (*transaction.Transaction, error) {
|
||||||
|
script, err := c.scriptForRegister(name, owner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.actor.MakeRun(script)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RegisterUnsigned creates a transaction invoking `register` method of the contract.
|
||||||
|
// This transaction is not signed, it's simply returned to the caller.
|
||||||
|
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||||
|
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||||
|
func (c *Contract) RegisterUnsigned(name string, owner util.Uint160) (*transaction.Transaction, error) {
|
||||||
|
script, err := c.scriptForRegister(name, owner)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return c.actor.MakeUnsignedRun(script, nil)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renew creates a transaction invoking `renew` method of the contract.
|
||||||
|
// This transaction is signed and immediately sent to the network.
|
||||||
|
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||||
|
func (c *Contract) Renew(name string) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "renew", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewTransaction creates a transaction invoking `renew` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) RenewTransaction(name string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "renew", name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewUnsigned creates a transaction invoking `renew` method of the contract.
|
||||||
|
// This transaction is not signed, it's simply returned to the caller.
|
||||||
|
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||||
|
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||||
|
func (c *Contract) RenewUnsigned(name string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "renew", nil, name)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renew2 creates a transaction invoking `renew` method of the contract.
|
||||||
|
// This transaction is signed and immediately sent to the network.
|
||||||
|
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||||
|
func (c *Contract) Renew2(name string, years int64) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "renew", name, years)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renew2Transaction creates a transaction invoking `renew` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) Renew2Transaction(name string, years int64) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "renew", name, years)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Renew2Unsigned creates a transaction invoking `renew` method of the contract.
|
||||||
|
// This transaction is not signed, it's simply returned to the caller.
|
||||||
|
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||||
|
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||||
|
func (c *Contract) Renew2Unsigned(name string, years int64) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "renew", nil, name, years)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAdmin creates a transaction invoking `setAdmin` method of the contract.
|
||||||
|
// This transaction is signed and immediately sent to the network.
|
||||||
|
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||||
|
func (c *Contract) SetAdmin(name string, admin util.Uint160) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "setAdmin", name, admin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAdminTransaction creates a transaction invoking `setAdmin` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) SetAdminTransaction(name string, admin util.Uint160) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "setAdmin", name, admin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAdminUnsigned creates a transaction invoking `setAdmin` method of the contract.
|
||||||
|
// This transaction is not signed, it's simply returned to the caller.
|
||||||
|
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||||
|
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||||
|
func (c *Contract) SetAdminUnsigned(name string, admin util.Uint160) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "setAdmin", nil, name, admin)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRecord creates a transaction invoking `setRecord` method of the contract.
|
||||||
|
// This transaction is signed and immediately sent to the network.
|
||||||
|
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||||
|
func (c *Contract) SetRecord(name string, typev RecordType, data string) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "setRecord", name, typev, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRecordTransaction creates a transaction invoking `setRecord` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) SetRecordTransaction(name string, typev RecordType, data string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "setRecord", name, typev, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetRecordUnsigned creates a transaction invoking `setRecord` method of the contract.
|
||||||
|
// This transaction is not signed, it's simply returned to the caller.
|
||||||
|
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||||
|
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||||
|
func (c *Contract) SetRecordUnsigned(name string, typev RecordType, data string) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "setRecord", nil, name, typev, data)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRecord creates a transaction invoking `deleteRecord` method of the contract.
|
||||||
|
// This transaction is signed and immediately sent to the network.
|
||||||
|
// The values returned are its hash, ValidUntilBlock value and error if any.
|
||||||
|
func (c *Contract) DeleteRecord(name string, typev RecordType) (util.Uint256, uint32, error) {
|
||||||
|
return c.actor.SendCall(c.hash, "deleteRecord", name, typev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRecordTransaction creates a transaction invoking `deleteRecord` method of the contract.
|
||||||
|
// This transaction is signed, but not sent to the network, instead it's
|
||||||
|
// returned to the caller.
|
||||||
|
func (c *Contract) DeleteRecordTransaction(name string, typev RecordType) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeCall(c.hash, "deleteRecord", name, typev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// DeleteRecordUnsigned creates a transaction invoking `deleteRecord` method of the contract.
|
||||||
|
// This transaction is not signed, it's simply returned to the caller.
|
||||||
|
// Any fields of it that do not affect fees can be changed (ValidUntilBlock,
|
||||||
|
// Nonce), fee values (NetworkFee, SystemFee) can be increased as well.
|
||||||
|
func (c *Contract) DeleteRecordUnsigned(name string, typev RecordType) (*transaction.Transaction, error) {
|
||||||
|
return c.actor.MakeUnsignedCall(c.hash, "deleteRecord", nil, name, typev)
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetAdminEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "SetAdmin" name from the provided [result.ApplicationLog].
|
||||||
|
func SetAdminEventsFromApplicationLog(log *result.ApplicationLog) ([]*SetAdminEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*SetAdminEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "SetAdmin" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(SetAdminEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize SetAdminEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return res, nil
|
return res, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to SetAdminEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *SetAdminEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 3 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.Name, err = func(item stackitem.Item) (string, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
return "", errors.New("not a UTF-8 string")
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}(arr[index])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field Name: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
index++
|
||||||
|
e.OldAdmin, err = func(item stackitem.Item) (util.Uint160, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint160{}, err
|
||||||
|
}
|
||||||
|
u, err := util.Uint160DecodeBytesBE(b)
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint160{}, err
|
||||||
|
}
|
||||||
|
return u, nil
|
||||||
|
}(arr[index])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field OldAdmin: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
index++
|
||||||
|
e.NewAdmin, err = func(item stackitem.Item) (util.Uint160, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint160{}, err
|
||||||
|
}
|
||||||
|
u, err := util.Uint160DecodeBytesBE(b)
|
||||||
|
if err != nil {
|
||||||
|
return util.Uint160{}, err
|
||||||
|
}
|
||||||
|
return u, nil
|
||||||
|
}(arr[index])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field NewAdmin: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// RenewEventsFromApplicationLog retrieves a set of all emitted events
|
||||||
|
// with "Renew" name from the provided [result.ApplicationLog].
|
||||||
|
func RenewEventsFromApplicationLog(log *result.ApplicationLog) ([]*RenewEvent, error) {
|
||||||
|
if log == nil {
|
||||||
|
return nil, errors.New("nil application log")
|
||||||
|
}
|
||||||
|
|
||||||
|
var res []*RenewEvent
|
||||||
|
for i, ex := range log.Executions {
|
||||||
|
for j, e := range ex.Events {
|
||||||
|
if e.Name != "Renew" {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
event := new(RenewEvent)
|
||||||
|
err := event.FromStackItem(e.Item)
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("failed to deserialize RenewEvent from stackitem (execution #%d, event #%d): %w", i, j, err)
|
||||||
|
}
|
||||||
|
res = append(res, event)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// FromStackItem converts provided [stackitem.Array] to RenewEvent or
|
||||||
|
// returns an error if it's not possible to do to so.
|
||||||
|
func (e *RenewEvent) FromStackItem(item *stackitem.Array) error {
|
||||||
|
if item == nil {
|
||||||
|
return errors.New("nil item")
|
||||||
|
}
|
||||||
|
arr, ok := item.Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return errors.New("not an array")
|
||||||
|
}
|
||||||
|
if len(arr) != 3 {
|
||||||
|
return errors.New("wrong number of structure elements")
|
||||||
|
}
|
||||||
|
|
||||||
|
var (
|
||||||
|
index = -1
|
||||||
|
err error
|
||||||
|
)
|
||||||
|
index++
|
||||||
|
e.Name, err = func(item stackitem.Item) (string, error) {
|
||||||
|
b, err := item.TryBytes()
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
if !utf8.Valid(b) {
|
||||||
|
return "", errors.New("not a UTF-8 string")
|
||||||
|
}
|
||||||
|
return string(b), nil
|
||||||
|
}(arr[index])
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field Name: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
index++
|
||||||
|
e.OldExpiration, err = arr[index].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field OldExpiration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
index++
|
||||||
|
e.NewExpiration, err = arr[index].TryInteger()
|
||||||
|
if err != nil {
|
||||||
|
return fmt.Errorf("field NewExpiration: %w", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
|
|
@ -2,10 +2,13 @@ package nns
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"math/big"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
"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/neorpc/result"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||||
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
"github.com/stretchr/testify/require"
|
"github.com/stretchr/testify/require"
|
||||||
|
@ -14,6 +17,9 @@ import (
|
||||||
type testAct struct {
|
type testAct struct {
|
||||||
err error
|
err error
|
||||||
res *result.Invoke
|
res *result.Invoke
|
||||||
|
tx *transaction.Transaction
|
||||||
|
txh util.Uint256
|
||||||
|
vub uint32
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *testAct) Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) {
|
func (t *testAct) Call(contract util.Uint160, operation string, params ...any) (*result.Invoke, error) {
|
||||||
|
@ -29,17 +35,42 @@ func (t *testAct) TraverseIterator(sessionID uuid.UUID, iterator *result.Iterato
|
||||||
return t.res.Stack, t.err
|
return t.res.Stack, t.err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func (t *testAct) MakeRun(script []byte) (*transaction.Transaction, error) {
|
||||||
|
return t.tx, t.err
|
||||||
|
}
|
||||||
|
func (t *testAct) MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) {
|
||||||
|
return t.tx, t.err
|
||||||
|
}
|
||||||
|
func (t *testAct) SendRun(script []byte) (util.Uint256, uint32, error) {
|
||||||
|
return t.txh, t.vub, t.err
|
||||||
|
}
|
||||||
|
func (t *testAct) MakeCall(contract util.Uint160, method string, params ...any) (*transaction.Transaction, error) {
|
||||||
|
return t.tx, t.err
|
||||||
|
}
|
||||||
|
func (t *testAct) MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...any) (*transaction.Transaction, error) {
|
||||||
|
return t.tx, t.err
|
||||||
|
}
|
||||||
|
func (t *testAct) SendCall(contract util.Uint160, method string, params ...any) (util.Uint256, uint32, error) {
|
||||||
|
return t.txh, t.vub, t.err
|
||||||
|
}
|
||||||
|
|
||||||
|
func (t *testAct) SignAndSend(tx *transaction.Transaction) (util.Uint256, uint32, error) {
|
||||||
|
return t.txh, t.vub, t.err
|
||||||
|
}
|
||||||
|
|
||||||
func TestSimpleGetters(t *testing.T) {
|
func TestSimpleGetters(t *testing.T) {
|
||||||
ta := &testAct{}
|
ta := &testAct{}
|
||||||
nns := NewReader(ta, util.Uint160{1, 2, 3})
|
nns := NewReader(ta, util.Uint160{1, 2, 3})
|
||||||
|
|
||||||
ta.err = errors.New("")
|
ta.err = errors.New("")
|
||||||
_, err := nns.GetPrice()
|
_, err := nns.GetPrice(uint8(A))
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
_, err = nns.IsAvailable("nspcc.neo")
|
_, err = nns.IsAvailable("nspcc.neo")
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
_, err = nns.Resolve("nspcc.neo", A)
|
_, err = nns.Resolve("nspcc.neo", A)
|
||||||
require.Error(t, err)
|
require.Error(t, err)
|
||||||
|
_, err = nns.GetRecord("nspcc.neo", A)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
ta.err = nil
|
ta.err = nil
|
||||||
ta.res = &result.Invoke{
|
ta.res = &result.Invoke{
|
||||||
|
@ -48,9 +79,9 @@ func TestSimpleGetters(t *testing.T) {
|
||||||
stackitem.Make(100500),
|
stackitem.Make(100500),
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
price, err := nns.GetPrice()
|
price, err := nns.GetPrice(uint8(A))
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, int64(100500), price)
|
require.Equal(t, new(big.Int).SetInt64(100500), price)
|
||||||
|
|
||||||
ta.res = &result.Invoke{
|
ta.res = &result.Invoke{
|
||||||
State: "HALT",
|
State: "HALT",
|
||||||
|
@ -71,6 +102,10 @@ func TestSimpleGetters(t *testing.T) {
|
||||||
txt, err := nns.Resolve("nspcc.neo", TXT)
|
txt, err := nns.Resolve("nspcc.neo", TXT)
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
require.Equal(t, "some text", txt)
|
require.Equal(t, "some text", txt)
|
||||||
|
|
||||||
|
rec, err := nns.GetRecord("nspcc.neo", TXT)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, "some text", rec)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetAllRecords(t *testing.T) {
|
func TestGetAllRecords(t *testing.T) {
|
||||||
|
@ -108,7 +143,6 @@ func TestGetAllRecords(t *testing.T) {
|
||||||
iter, err := nns.GetAllRecords("nspcc.neo")
|
iter, err := nns.GetAllRecords("nspcc.neo")
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
require.NoError(t, err)
|
|
||||||
ta.res = &result.Invoke{
|
ta.res = &result.Invoke{
|
||||||
Stack: []stackitem.Item{
|
Stack: []stackitem.Item{
|
||||||
stackitem.Make([]stackitem.Item{
|
stackitem.Make([]stackitem.Item{
|
||||||
|
@ -154,6 +188,26 @@ func TestGetAllRecords(t *testing.T) {
|
||||||
ta.err = errors.New("")
|
ta.err = errors.New("")
|
||||||
err = iter.Terminate()
|
err = iter.Terminate()
|
||||||
require.NoError(t, err)
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ta.err = nil
|
||||||
|
ta.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.NewInterop(result.Iterator{
|
||||||
|
Values: []stackitem.Item{
|
||||||
|
stackitem.Make("valid data"),
|
||||||
|
stackitem.Make(-1),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
iter, err = nns.GetAllRecords("nspcc.neo")
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = iter.Next(10)
|
||||||
|
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Contains(t, err.Error(), "item #0: ")
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestGetAllRecordsExpanded(t *testing.T) {
|
func TestGetAllRecordsExpanded(t *testing.T) {
|
||||||
|
@ -195,3 +249,520 @@ func TestGetAllRecordsExpanded(t *testing.T) {
|
||||||
Data: "cool",
|
Data: "cool",
|
||||||
}, vals[0])
|
}, vals[0])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestRoots(t *testing.T) {
|
||||||
|
ta := &testAct{}
|
||||||
|
nns := NewReader(ta, util.Uint160{1, 2, 3})
|
||||||
|
ta.err = errors.New("")
|
||||||
|
_, err := nns.Roots()
|
||||||
|
require.Error(t, err)
|
||||||
|
iid := uuid.New()
|
||||||
|
|
||||||
|
// Session-based iterator.
|
||||||
|
sid := uuid.New()
|
||||||
|
ta.res = &result.Invoke{
|
||||||
|
Session: sid,
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.NewInterop(result.Iterator{
|
||||||
|
ID: &iid,
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ta.err = nil
|
||||||
|
iter, err := nns.Roots()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ta.res = &result.Invoke{
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.Make([]stackitem.Item{
|
||||||
|
stackitem.Make("n3"),
|
||||||
|
stackitem.Make("aaaaaa"),
|
||||||
|
stackitem.Make("cool"),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
vals, err := iter.Next(10)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, 1, len(vals))
|
||||||
|
require.Equal(t, "n3", vals[0])
|
||||||
|
|
||||||
|
ta.err = errors.New("")
|
||||||
|
_, err = iter.Next(1)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
err = iter.Terminate()
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
// Value-based iterator.
|
||||||
|
ta.err = nil
|
||||||
|
ta.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.NewInterop(result.Iterator{
|
||||||
|
Values: []stackitem.Item{
|
||||||
|
stackitem.Make("n3"),
|
||||||
|
stackitem.Make("aaaaaa"),
|
||||||
|
stackitem.Make("cool"),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
iter, err = nns.Roots()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
ta.err = errors.New("")
|
||||||
|
err = iter.Terminate()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
sid = uuid.New()
|
||||||
|
iid = uuid.New()
|
||||||
|
ta.res = &result.Invoke{
|
||||||
|
Session: sid,
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.NewInterop(result.Iterator{
|
||||||
|
ID: &iid,
|
||||||
|
Values: []stackitem.Item{
|
||||||
|
stackitem.Make("incorrect format"),
|
||||||
|
},
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
ta.err = nil
|
||||||
|
iter, err = nns.Roots()
|
||||||
|
require.NoError(t, err)
|
||||||
|
|
||||||
|
_, err = iter.Next(10)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, "wrong number of elements", err.Error())
|
||||||
|
|
||||||
|
ta.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.Make([]stackitem.Item{
|
||||||
|
stackitem.Make([]stackitem.Item{
|
||||||
|
stackitem.Make("root1"),
|
||||||
|
}),
|
||||||
|
stackitem.Make([]stackitem.Item{
|
||||||
|
stackitem.Make("root2"),
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
roots, err := nns.RootsExpanded(10)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, []string{"root1", "root2"}, roots)
|
||||||
|
|
||||||
|
ta.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.Make("incorrect format"), // Not a slice of stackitem.Item
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
_, err = nns.RootsExpanded(10)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, "not an array", err.Error())
|
||||||
|
|
||||||
|
ta.err = errors.New("call and expand iterator error")
|
||||||
|
_, err = nns.RootsExpanded(10)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, "call and expand iterator error", err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestUpdate(t *testing.T) {
|
||||||
|
ta := &testAct{}
|
||||||
|
nns := New(ta, util.Uint160{1, 2, 3})
|
||||||
|
|
||||||
|
nef := []byte{0x01, 0x02, 0x03}
|
||||||
|
manifest := "manifest data"
|
||||||
|
|
||||||
|
ta.err = errors.New("test error")
|
||||||
|
_, _, err := nns.Update(nef, manifest)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
// Test successful update
|
||||||
|
ta.err = nil
|
||||||
|
ta.txh = util.Uint256{0x04, 0x05, 0x06}
|
||||||
|
txh, vub, err := nns.Update(nef, manifest)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ta.txh, txh)
|
||||||
|
require.Equal(t, ta.vub, vub)
|
||||||
|
|
||||||
|
for _, fun := range []func(nef []byte, manifest string) (*transaction.Transaction, error){
|
||||||
|
nns.UpdateTransaction,
|
||||||
|
nns.UpdateUnsigned,
|
||||||
|
} {
|
||||||
|
ta.err = errors.New("")
|
||||||
|
_, err := fun(nil, "")
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
ta.err = nil
|
||||||
|
ta.tx = &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
||||||
|
tx, err := fun(nil, "")
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ta.tx, tx)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestAddRoot(t *testing.T) {
|
||||||
|
ta := &testAct{}
|
||||||
|
nns := New(ta, util.Uint160{1, 2, 3})
|
||||||
|
|
||||||
|
root := "example.root"
|
||||||
|
params, err := smartcontract.NewParameterFromValue(root)
|
||||||
|
require.NoError(t, err)
|
||||||
|
ta.err = errors.New("test error")
|
||||||
|
_, _, err = nns.AddRoot(params.Value.(string))
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
// Test success case
|
||||||
|
ta.err = nil
|
||||||
|
ta.txh = util.Uint256{0x07, 0x08, 0x09}
|
||||||
|
txh, vub, err := nns.AddRoot(root)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ta.txh, txh)
|
||||||
|
require.Equal(t, ta.vub, vub)
|
||||||
|
|
||||||
|
ta.tx = &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
||||||
|
tx, err := nns.AddRootTransaction(root)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ta.tx, tx)
|
||||||
|
tx, err = nns.AddRootUnsigned(root)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ta.tx, tx)
|
||||||
|
|
||||||
|
ta.err = errors.New("")
|
||||||
|
_, err = nns.AddRootTransaction(root)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
ta.err = errors.New("")
|
||||||
|
_, err = nns.AddRootUnsigned(root)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetPrice(t *testing.T) {
|
||||||
|
ta := &testAct{}
|
||||||
|
nns := New(ta, util.Uint160{1, 2, 3})
|
||||||
|
|
||||||
|
priceList := []int64{100, 200}
|
||||||
|
ta.err = errors.New("test error")
|
||||||
|
_, _, err := nns.SetPrice(priceList)
|
||||||
|
require.Error(t, err)
|
||||||
|
_, err = nns.SetPriceTransaction(priceList)
|
||||||
|
require.Error(t, err)
|
||||||
|
_, err = nns.SetPriceUnsigned(priceList)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
// Test success case
|
||||||
|
ta.err = nil
|
||||||
|
ta.txh = util.Uint256{0x0A, 0x0B, 0x0C}
|
||||||
|
ta.vub = 42
|
||||||
|
|
||||||
|
ta.res = &result.Invoke{
|
||||||
|
State: "HALT",
|
||||||
|
Stack: []stackitem.Item{
|
||||||
|
stackitem.Make(42),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
txh, vub, err := nns.SetPrice(priceList)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ta.txh, txh)
|
||||||
|
require.Equal(t, ta.vub, vub)
|
||||||
|
|
||||||
|
ta.tx = &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
||||||
|
tx, err := nns.SetPriceTransaction(priceList)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ta.tx, tx)
|
||||||
|
tx, err = nns.SetPriceUnsigned(priceList)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ta.tx, tx)
|
||||||
|
|
||||||
|
ta.err = errors.New("")
|
||||||
|
_, err = nns.SetPriceTransaction(priceList)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
ta.err = errors.New("")
|
||||||
|
_, err = nns.SetPriceUnsigned(priceList)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRegister(t *testing.T) {
|
||||||
|
ta := &testAct{}
|
||||||
|
nns := New(ta, util.Uint160{1, 2, 3})
|
||||||
|
|
||||||
|
name := "example.neo"
|
||||||
|
owner := util.Uint160{0x0D, 0x0E, 0x0F}
|
||||||
|
|
||||||
|
ta.err = errors.New("test error")
|
||||||
|
txh, vub, err := nns.Register(name, owner)
|
||||||
|
require.Error(t, err)
|
||||||
|
require.Equal(t, util.Uint256{}, txh) // Check if returned Uint256 is zero-initialized
|
||||||
|
require.Equal(t, uint32(0), vub)
|
||||||
|
|
||||||
|
// Test success case
|
||||||
|
ta.err = nil
|
||||||
|
ta.txh = util.Uint256{0x10, 0x11, 0x12}
|
||||||
|
txh, vub, err = nns.Register(name, owner)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ta.txh, txh)
|
||||||
|
require.Equal(t, ta.vub, vub)
|
||||||
|
|
||||||
|
ta.tx = &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
||||||
|
tx, err := nns.RegisterTransaction(name, owner)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ta.tx, tx)
|
||||||
|
|
||||||
|
tx, err = nns.RegisterUnsigned(name, owner)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ta.tx, tx)
|
||||||
|
|
||||||
|
ta.err = errors.New("")
|
||||||
|
_, err = nns.RegisterTransaction(name, owner)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
ta.err = errors.New("")
|
||||||
|
_, err = nns.RegisterUnsigned(name, owner)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestRenew(t *testing.T) {
|
||||||
|
ta := &testAct{}
|
||||||
|
nns := New(ta, util.Uint160{1, 2, 3})
|
||||||
|
|
||||||
|
name := "example.neo"
|
||||||
|
|
||||||
|
ta.err = errors.New("test error")
|
||||||
|
_, _, err := nns.Renew(name)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
// Test success case
|
||||||
|
ta.err = nil
|
||||||
|
ta.txh = util.Uint256{0x13, 0x14, 0x15}
|
||||||
|
txh, vub, err := nns.Renew(name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ta.txh, txh)
|
||||||
|
require.Equal(t, ta.vub, vub)
|
||||||
|
|
||||||
|
txh, vub, err = nns.Renew2(name, 1)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ta.txh, txh)
|
||||||
|
require.Equal(t, ta.vub, vub)
|
||||||
|
|
||||||
|
ta.tx = &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
||||||
|
tx, err := nns.RenewTransaction(name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ta.tx, tx)
|
||||||
|
|
||||||
|
tx, err = nns.RenewUnsigned(name)
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, ta.tx, tx)
|
||||||
|
|
||||||
|
ta.err = errors.New("")
|
||||||
|
_, err = nns.RenewTransaction(name)
|
||||||
|
require.Error(t, err)
|
||||||
|
|
||||||
|
ta.err = errors.New("")
|
||||||
|
_, err = nns.RenewUnsigned(name)
|
||||||
|
require.Error(t, err)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetAdmin(t *testing.T) {
|
||||||
|
ta := &testAct{}
|
||||||
|
c := New(ta, util.Uint160{1, 2, 3})
|
||||||
|
|
||||||
|
name := "example.neo"
|
||||||
|
admin := util.Uint160{4, 5, 6}
|
||||||
|
txMock := &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
||||||
|
txhMock := util.Uint256{0x13, 0x14, 0x15}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
setup func()
|
||||||
|
testFunc func() (interface{}, error)
|
||||||
|
want interface{}
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "SetAdmin - Error",
|
||||||
|
setup: func() {
|
||||||
|
ta.err = errors.New("test error")
|
||||||
|
},
|
||||||
|
testFunc: func() (interface{}, error) {
|
||||||
|
txh, vub, err := c.SetAdmin(name, admin)
|
||||||
|
return []interface{}{txh, vub}, err
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SetAdmin - Success",
|
||||||
|
setup: func() {
|
||||||
|
ta.err = nil
|
||||||
|
ta.txh = txhMock
|
||||||
|
ta.vub = 42
|
||||||
|
},
|
||||||
|
testFunc: func() (interface{}, error) {
|
||||||
|
txh, vub, err := c.SetAdmin(name, admin)
|
||||||
|
return []interface{}{txh, vub}, err
|
||||||
|
},
|
||||||
|
want: []interface{}{txhMock, uint32(42)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SetAdminTransaction - Success",
|
||||||
|
setup: func() {
|
||||||
|
ta.err = nil
|
||||||
|
ta.tx = txMock
|
||||||
|
},
|
||||||
|
testFunc: func() (interface{}, error) {
|
||||||
|
return c.SetAdminTransaction(name, admin)
|
||||||
|
},
|
||||||
|
want: txMock,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SetAdminTransaction - Error",
|
||||||
|
setup: func() {
|
||||||
|
ta.err = errors.New("test error")
|
||||||
|
},
|
||||||
|
testFunc: func() (interface{}, error) {
|
||||||
|
return c.SetAdminTransaction(name, admin)
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SetAdminUnsigned - Success",
|
||||||
|
setup: func() {
|
||||||
|
ta.err = nil
|
||||||
|
ta.tx = txMock
|
||||||
|
},
|
||||||
|
testFunc: func() (interface{}, error) {
|
||||||
|
return c.SetAdminUnsigned(name, admin)
|
||||||
|
},
|
||||||
|
want: txMock,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SetAdminUnsigned - Error",
|
||||||
|
setup: func() {
|
||||||
|
ta.err = errors.New("test error")
|
||||||
|
},
|
||||||
|
testFunc: func() (interface{}, error) {
|
||||||
|
return c.SetAdminUnsigned(name, admin)
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
tc.setup()
|
||||||
|
got, err := tc.testFunc()
|
||||||
|
if tc.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tc.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestSetRecord(t *testing.T) {
|
||||||
|
ta := &testAct{}
|
||||||
|
c := New(ta, util.Uint160{1, 2, 3})
|
||||||
|
|
||||||
|
name := "example.neo"
|
||||||
|
typev := A
|
||||||
|
data := "record data"
|
||||||
|
txMock := &transaction.Transaction{Nonce: 100500, ValidUntilBlock: 42}
|
||||||
|
txhMock := util.Uint256{0x13, 0x14, 0x15}
|
||||||
|
|
||||||
|
testCases := []struct {
|
||||||
|
name string
|
||||||
|
setup func()
|
||||||
|
testFunc func() (interface{}, error)
|
||||||
|
want interface{}
|
||||||
|
wantErr bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "SetRecord - Error",
|
||||||
|
setup: func() {
|
||||||
|
ta.err = errors.New("test error")
|
||||||
|
},
|
||||||
|
testFunc: func() (interface{}, error) {
|
||||||
|
txh, vub, err := c.SetRecord(name, typev, data)
|
||||||
|
return []interface{}{txh, vub}, err
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SetRecord - Success",
|
||||||
|
setup: func() {
|
||||||
|
ta.err = nil
|
||||||
|
ta.txh = txhMock
|
||||||
|
ta.vub = 42
|
||||||
|
},
|
||||||
|
testFunc: func() (interface{}, error) {
|
||||||
|
txh, vub, err := c.SetRecord(name, typev, data)
|
||||||
|
return []interface{}{txh, vub}, err
|
||||||
|
},
|
||||||
|
want: []interface{}{txhMock, uint32(42)},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SetRecordTransaction - Success",
|
||||||
|
setup: func() {
|
||||||
|
ta.err = nil
|
||||||
|
ta.tx = txMock
|
||||||
|
},
|
||||||
|
testFunc: func() (interface{}, error) {
|
||||||
|
return c.SetRecordTransaction(name, typev, data)
|
||||||
|
},
|
||||||
|
want: txMock,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SetRecordTransaction - Error",
|
||||||
|
setup: func() {
|
||||||
|
ta.err = errors.New("test error")
|
||||||
|
},
|
||||||
|
testFunc: func() (interface{}, error) {
|
||||||
|
return c.SetRecordTransaction(name, typev, data)
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SetRecordUnsigned - Success",
|
||||||
|
setup: func() {
|
||||||
|
ta.err = nil
|
||||||
|
ta.tx = txMock
|
||||||
|
},
|
||||||
|
testFunc: func() (interface{}, error) {
|
||||||
|
return c.SetRecordUnsigned(name, typev, data)
|
||||||
|
},
|
||||||
|
want: txMock,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "SetRecordUnsigned - Error",
|
||||||
|
setup: func() {
|
||||||
|
ta.err = errors.New("test error")
|
||||||
|
},
|
||||||
|
testFunc: func() (interface{}, error) {
|
||||||
|
return c.SetRecordUnsigned(name, typev, data)
|
||||||
|
},
|
||||||
|
wantErr: true,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
|
||||||
|
for _, tc := range testCases {
|
||||||
|
t.Run(tc.name, func(t *testing.T) {
|
||||||
|
tc.setup()
|
||||||
|
got, err := tc.testFunc()
|
||||||
|
if tc.wantErr {
|
||||||
|
require.Error(t, err)
|
||||||
|
} else {
|
||||||
|
require.NoError(t, err)
|
||||||
|
require.Equal(t, tc.want, got)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
90
pkg/rpcclient/nns/iterators.go
Normal file
90
pkg/rpcclient/nns/iterators.go
Normal file
|
@ -0,0 +1,90 @@
|
||||||
|
package nns
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
|
||||||
|
"github.com/google/uuid"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/neorpc/result"
|
||||||
|
"github.com/nspcc-dev/neo-go/pkg/vm/stackitem"
|
||||||
|
)
|
||||||
|
|
||||||
|
// RecordIterator is used for iterating over GetAllRecords results.
|
||||||
|
type RecordIterator struct {
|
||||||
|
client Invoker
|
||||||
|
session uuid.UUID
|
||||||
|
iterator result.Iterator
|
||||||
|
}
|
||||||
|
|
||||||
|
// RootIterator is used for iterating over Roots results.
|
||||||
|
type RootIterator struct {
|
||||||
|
client Invoker
|
||||||
|
session uuid.UUID
|
||||||
|
iterator result.Iterator
|
||||||
|
}
|
||||||
|
|
||||||
|
func itemsToRecords(arr []stackitem.Item) ([]RecordState, error) {
|
||||||
|
res := make([]RecordState, len(arr))
|
||||||
|
for i := range arr {
|
||||||
|
err := res[i].FromStackItem(arr[i])
|
||||||
|
if err != nil {
|
||||||
|
return nil, fmt.Errorf("item #%d: %w", i, err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
func itemsToRoots(arr []stackitem.Item) ([]string, error) {
|
||||||
|
res := make([]string, len(arr))
|
||||||
|
for i := range arr {
|
||||||
|
rs, ok := arr[i].Value().([]stackitem.Item)
|
||||||
|
if !ok {
|
||||||
|
return nil, errors.New("wrong number of elements")
|
||||||
|
}
|
||||||
|
myval, _ := rs[0].TryBytes()
|
||||||
|
res[i] = string(myval)
|
||||||
|
}
|
||||||
|
return res, nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next set of elements from the iterator (up to num of them).
|
||||||
|
// It can return less than num elements in case iterator doesn't have that many
|
||||||
|
// or zero elements if the iterator has no more elements or the session is
|
||||||
|
// expired.
|
||||||
|
func (r *RecordIterator) Next(num int) ([]RecordState, error) {
|
||||||
|
items, err := r.client.TraverseIterator(r.session, &r.iterator, num)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return itemsToRecords(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Next returns the next set of elements from the iterator (up to num of them).
|
||||||
|
// It can return less than num elements in case iterator doesn't have that many
|
||||||
|
// or zero elements if the iterator has no more elements or the session is
|
||||||
|
// expired.
|
||||||
|
func (r *RootIterator) Next(num int) ([]string, error) {
|
||||||
|
items, err := r.client.TraverseIterator(r.session, &r.iterator, num)
|
||||||
|
if err != nil {
|
||||||
|
return nil, err
|
||||||
|
}
|
||||||
|
return itemsToRoots(items)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminate closes the iterator session used by RecordIterator (if it's
|
||||||
|
// session-based).
|
||||||
|
func (r *RecordIterator) Terminate() error {
|
||||||
|
if r.iterator.ID == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return r.client.TerminateSession(r.session)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Terminate closes the iterator session used by RootIterator (if it's
|
||||||
|
// session-based).
|
||||||
|
func (r *RootIterator) Terminate() error {
|
||||||
|
if r.iterator.ID == nil {
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
return r.client.TerminateSession(r.session)
|
||||||
|
}
|
Loading…
Reference in a new issue