From bb47d971dcf3bb036674958fbc4c0145cd99b722 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 27 Oct 2022 16:18:00 +0300 Subject: [PATCH 1/6] rpcclient/nep11: make DivisibleWriter part reusable Same rationale as for BaseWriter or nep17.TokenWriter, otherwise it's hard to build on top of plain Divisible structure. --- pkg/rpcclient/nep11/divisible.go | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/pkg/rpcclient/nep11/divisible.go b/pkg/rpcclient/nep11/divisible.go index 29a3b4055..d558ab05b 100644 --- a/pkg/rpcclient/nep11/divisible.go +++ b/pkg/rpcclient/nep11/divisible.go @@ -16,10 +16,17 @@ type DivisibleReader struct { BaseReader } -// Divisible is a state-changing interface for divisible NEP-11 contract. +// DivisibleWriter is a state-changing interface for divisible NEP-11 contract. +// It's mostly useful not directly, but as a reusable layer for higher-level +// structures. +type DivisibleWriter struct { + BaseWriter +} + +// Divisible is a full reader interface for divisible NEP-11 contract. type Divisible struct { DivisibleReader - BaseWriter + DivisibleWriter } // OwnerIterator is used for iterating over OwnerOf (for divisible NFTs) results. @@ -38,7 +45,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{*NewDivisibleReader(actor, hash), BaseWriter{hash, actor}} + return &Divisible{*NewDivisibleReader(actor, hash), DivisibleWriter{BaseWriter{hash, actor}}} } // OwnerOf returns returns an iterator that allows to walk through all owners of @@ -72,7 +79,7 @@ func (t *DivisibleReader) BalanceOfD(owner util.Uint160, token []byte) (*big.Int // 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 *Divisible) TransferD(from util.Uint160, to util.Uint160, amount *big.Int, id []byte, data interface{}) (util.Uint256, uint32, error) { +func (t *DivisibleWriter) TransferD(from util.Uint160, to util.Uint160, amount *big.Int, id []byte, data interface{}) (util.Uint256, uint32, error) { script, err := t.transferScript(from, to, amount, id, data) if err != nil { return util.Uint256{}, 0, err @@ -85,7 +92,7 @@ func (t *Divisible) TransferD(from util.Uint160, to util.Uint160, amount *big.In // `transfer` method 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 *Divisible) TransferDTransaction(from util.Uint160, to util.Uint160, amount *big.Int, id []byte, data interface{}) (*transaction.Transaction, error) { +func (t *DivisibleWriter) TransferDTransaction(from util.Uint160, to util.Uint160, amount *big.Int, id []byte, data interface{}) (*transaction.Transaction, error) { script, err := t.transferScript(from, to, amount, id, data) if err != nil { return nil, err @@ -98,7 +105,7 @@ func (t *Divisible) TransferDTransaction(from util.Uint160, to util.Uint160, amo // `transfer` method 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 *Divisible) TransferDUnsigned(from util.Uint160, to util.Uint160, amount *big.Int, id []byte, data interface{}) (*transaction.Transaction, error) { +func (t *DivisibleWriter) TransferDUnsigned(from util.Uint160, to util.Uint160, amount *big.Int, id []byte, data interface{}) (*transaction.Transaction, error) { script, err := t.transferScript(from, to, amount, id, data) if err != nil { return nil, err From 2a4a5ab479f87f54794bfde08bf3466a32b0d62c Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Thu, 27 Oct 2022 16:20:35 +0300 Subject: [PATCH 2/6] rpcbinding: support simple wrappers for writer methods Fixes #2769. --- cli/smartcontract/testdata/nameservice/nns.go | 224 ++++++++++++++++++ cli/smartcontract/testdata/nex/nex.go | 180 ++++++++++++++ docs/compiler.md | 11 +- pkg/smartcontract/rpcbinding/binding.go | 139 +++++++++-- 4 files changed, 530 insertions(+), 24 deletions(-) diff --git a/cli/smartcontract/testdata/nameservice/nns.go b/cli/smartcontract/testdata/nameservice/nns.go index 62c59082b..511acaedf 100644 --- a/cli/smartcontract/testdata/nameservice/nns.go +++ b/cli/smartcontract/testdata/nameservice/nns.go @@ -2,6 +2,7 @@ package nameservice import ( + "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/nep11" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" @@ -19,17 +20,42 @@ type Invoker interface { Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) } +// Actor is used by Contract to call state-changing methods. +type Actor interface { + Invoker + nep11.Actor + MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error) + MakeRun(script []byte) (*transaction.Transaction, error) + MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...interface{}) (*transaction.Transaction, error) + MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) + SendCall(contract util.Uint160, method string, params ...interface{}) (util.Uint256, uint32, error) + SendRun(script []byte) (util.Uint256, uint32, error) +} + // ContractReader implements safe contract methods. type ContractReader struct { nep11.NonDivisibleReader invoker Invoker } +// Contract implements all contract methods. +type Contract struct { + ContractReader + nep11.BaseWriter + actor Actor +} + // NewReader creates an instance of ContractReader using Hash and the given Invoker. func NewReader(invoker Invoker) *ContractReader { return &ContractReader{*nep11.NewNonDivisibleReader(invoker, Hash), invoker} } +// New creates an instance of Contract using Hash and the given Actor. +func New(actor Actor) *Contract { + var nep11ndt = nep11.NewNonDivisible(actor, Hash) + return &Contract{ContractReader{nep11ndt.NonDivisibleReader, actor},nep11ndt.BaseWriter, actor} +} + // Roots invokes `roots` method of contract. func (c *ContractReader) Roots() (stackitem.Item, error) { @@ -60,3 +86,201 @@ func (c *ContractReader) GetAllRecords(name string) (stackitem.Item, error) { func (c *ContractReader) Resolve(name string, typev *big.Int) (string, error) { return unwrap.UTF8String(c.invoker.Call(Hash, "resolve", name, typev)) } + +// 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(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(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(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(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(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(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 []interface{}) (util.Uint256, uint32, error) { + return c.actor.SendCall(Hash, "setPrice", priceList) +} + +// 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 []interface{}) (*transaction.Transaction, error) { + return c.actor.MakeCall(Hash, "setPrice", priceList) +} + +// 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 []interface{}) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(Hash, "setPrice", nil, priceList) +} + +// 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) { + return c.actor.SendCall(Hash, "register", name, owner) +} + +// 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) { + return c.actor.MakeCall(Hash, "register", name, owner) +} + +// 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) { + return c.actor.MakeUnsignedCall(Hash, "register", nil, name, owner) +} + +// 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(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(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(Hash, "renew", nil, name) +} + +// Renew_2 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_2(name string, years *big.Int) (util.Uint256, uint32, error) { + return c.actor.SendCall(Hash, "renew", name, years) +} + +// Renew_2Transaction 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) Renew_2Transaction(name string, years *big.Int) (*transaction.Transaction, error) { + return c.actor.MakeCall(Hash, "renew", name, years) +} + +// Renew_2Unsigned 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) Renew_2Unsigned(name string, years *big.Int) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(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(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(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(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 *big.Int, data string) (util.Uint256, uint32, error) { + return c.actor.SendCall(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 *big.Int, data string) (*transaction.Transaction, error) { + return c.actor.MakeCall(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 *big.Int, data string) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(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 *big.Int) (util.Uint256, uint32, error) { + return c.actor.SendCall(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 *big.Int) (*transaction.Transaction, error) { + return c.actor.MakeCall(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 *big.Int) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(Hash, "deleteRecord", nil, name, typev) +} diff --git a/cli/smartcontract/testdata/nex/nex.go b/cli/smartcontract/testdata/nex/nex.go index 90a9b5645..3ca127356 100644 --- a/cli/smartcontract/testdata/nex/nex.go +++ b/cli/smartcontract/testdata/nex/nex.go @@ -2,6 +2,7 @@ package nextoken import ( + "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17" @@ -19,17 +20,42 @@ type Invoker interface { Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) } +// Actor is used by Contract to call state-changing methods. +type Actor interface { + Invoker + nep17.Actor + MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error) + MakeRun(script []byte) (*transaction.Transaction, error) + MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...interface{}) (*transaction.Transaction, error) + MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) + SendCall(contract util.Uint160, method string, params ...interface{}) (util.Uint256, uint32, error) + SendRun(script []byte) (util.Uint256, uint32, error) +} + // ContractReader implements safe contract methods. type ContractReader struct { nep17.TokenReader invoker Invoker } +// Contract implements all contract methods. +type Contract struct { + ContractReader + nep17.TokenWriter + actor Actor +} + // NewReader creates an instance of ContractReader using Hash and the given Invoker. func NewReader(invoker Invoker) *ContractReader { return &ContractReader{*nep17.NewReader(invoker, Hash), invoker} } +// New creates an instance of Contract using Hash and the given Actor. +func New(actor Actor) *Contract { + var nep17t = nep17.New(actor, Hash) + return &Contract{ContractReader{nep17t.TokenReader, actor},nep17t.TokenWriter, actor} +} + // Cap invokes `cap` method of contract. func (c *ContractReader) Cap() (*big.Int, error) { @@ -50,3 +76,157 @@ func (c *ContractReader) GetOwner() (util.Uint160, error) { func (c *ContractReader) TotalMinted() (*big.Int, error) { return unwrap.BigInt(c.invoker.Call(Hash, "totalMinted")) } + +// ChangeMinter creates a transaction invoking `changeMinter` 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) ChangeMinter(newMinter *keys.PublicKey) (util.Uint256, uint32, error) { + return c.actor.SendCall(Hash, "changeMinter", newMinter) +} + +// ChangeMinterTransaction creates a transaction invoking `changeMinter` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) ChangeMinterTransaction(newMinter *keys.PublicKey) (*transaction.Transaction, error) { + return c.actor.MakeCall(Hash, "changeMinter", newMinter) +} + +// ChangeMinterUnsigned creates a transaction invoking `changeMinter` 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) ChangeMinterUnsigned(newMinter *keys.PublicKey) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(Hash, "changeMinter", nil, newMinter) +} + +// ChangeOwner creates a transaction invoking `changeOwner` 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) ChangeOwner(newOwner util.Uint160) (util.Uint256, uint32, error) { + return c.actor.SendCall(Hash, "changeOwner", newOwner) +} + +// ChangeOwnerTransaction creates a transaction invoking `changeOwner` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) ChangeOwnerTransaction(newOwner util.Uint160) (*transaction.Transaction, error) { + return c.actor.MakeCall(Hash, "changeOwner", newOwner) +} + +// ChangeOwnerUnsigned creates a transaction invoking `changeOwner` 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) ChangeOwnerUnsigned(newOwner util.Uint160) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(Hash, "changeOwner", nil, newOwner) +} + +// Destroy creates a transaction invoking `destroy` 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) Destroy() (util.Uint256, uint32, error) { + return c.actor.SendCall(Hash, "destroy") +} + +// DestroyTransaction creates a transaction invoking `destroy` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) DestroyTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(Hash, "destroy") +} + +// DestroyUnsigned creates a transaction invoking `destroy` 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) DestroyUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(Hash, "destroy", nil) +} + +// MaxSupply creates a transaction invoking `maxSupply` 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) MaxSupply() (util.Uint256, uint32, error) { + return c.actor.SendCall(Hash, "maxSupply") +} + +// MaxSupplyTransaction creates a transaction invoking `maxSupply` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) MaxSupplyTransaction() (*transaction.Transaction, error) { + return c.actor.MakeCall(Hash, "maxSupply") +} + +// MaxSupplyUnsigned creates a transaction invoking `maxSupply` 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) MaxSupplyUnsigned() (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(Hash, "maxSupply", nil) +} + +// Mint creates a transaction invoking `mint` 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) Mint(from util.Uint160, to util.Uint160, amount *big.Int, swapId *big.Int, signature []byte, data interface{}) (util.Uint256, uint32, error) { + return c.actor.SendCall(Hash, "mint", from, to, amount, swapId, signature, data) +} + +// MintTransaction creates a transaction invoking `mint` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) MintTransaction(from util.Uint160, to util.Uint160, amount *big.Int, swapId *big.Int, signature []byte, data interface{}) (*transaction.Transaction, error) { + return c.actor.MakeCall(Hash, "mint", from, to, amount, swapId, signature, data) +} + +// MintUnsigned creates a transaction invoking `mint` 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) MintUnsigned(from util.Uint160, to util.Uint160, amount *big.Int, swapId *big.Int, signature []byte, data interface{}) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(Hash, "mint", nil, from, to, amount, swapId, signature, data) +} + +// 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 []byte) (util.Uint256, uint32, error) { + return c.actor.SendCall(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 []byte) (*transaction.Transaction, error) { + return c.actor.MakeCall(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 []byte) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(Hash, "update", nil, nef, manifest) +} + +// UpdateCap creates a transaction invoking `updateCap` 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) UpdateCap(newCap *big.Int) (util.Uint256, uint32, error) { + return c.actor.SendCall(Hash, "updateCap", newCap) +} + +// UpdateCapTransaction creates a transaction invoking `updateCap` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) UpdateCapTransaction(newCap *big.Int) (*transaction.Transaction, error) { + return c.actor.MakeCall(Hash, "updateCap", newCap) +} + +// UpdateCapUnsigned creates a transaction invoking `updateCap` 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) UpdateCapUnsigned(newCap *big.Int) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(Hash, "updateCap", nil, newCap) +} diff --git a/docs/compiler.md b/docs/compiler.md index 1b4f5833d..7b909fc6c 100644 --- a/docs/compiler.md +++ b/docs/compiler.md @@ -436,12 +436,11 @@ $ ./bin/neo-go contract generate-wrapper --manifest manifest.json --config contr ### Generating RPC contract bindings To simplify interacting with the contract via RPC you can generate -contract-specific RPC bindings with the "generate-rpcwrapper" command. At the -moment it only works for safe (read-only) methods. If your contract is NEP-11 -or NEP-17 that's autodetected and an appropriate package is included as -well. Notice that the type data available in the manifest is limited, so in -some cases the interface generated may use generic stackitem types. Iterators -are not supported yet. +contract-specific RPC bindings with the "generate-rpcwrapper" command. If your +contract is NEP-11 or NEP-17 that's autodetected and an appropriate package is +included as well. Notice that the type data available in the manifest is +limited, so in some cases the interface generated may use generic stackitem +types. Iterators are not supported yet. ``` $ ./bin/neo-go contract generate-rpcwrapper --manifest manifest.json --out rpcwrapper.go --hash 0x1b4357bff5a01bdf2a6581247cf9ed1e24629176 diff --git a/pkg/smartcontract/rpcbinding/binding.go b/pkg/smartcontract/rpcbinding/binding.go index 0c8b86c30..578e0e048 100644 --- a/pkg/smartcontract/rpcbinding/binding.go +++ b/pkg/smartcontract/rpcbinding/binding.go @@ -12,7 +12,7 @@ import ( ) const srcTmpl = ` -{{- define "METHOD" -}} +{{- define "SAFEMETHOD" -}} // {{.Name}} {{.Comment}} func (c *ContractReader) {{.Name}}({{range $index, $arg := .Arguments -}} {{- if ne $index 0}}, {{end}} @@ -26,6 +26,41 @@ func (c *ContractReader) {{.Name}}({{range $index, $arg := .Arguments -}} {{- end}} } {{- end -}} +{{- define "METHOD" -}} +// {{.Name}} {{.Comment}} +// 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) {{.Name}}({{range $index, $arg := .Arguments -}} + {{- if ne $index 0}}, {{end}} + {{- .Name}} {{.Type}} + {{- end}}) (util.Uint256, uint32, error) { + return c.actor.SendCall(Hash, "{{ .NameABI }}" + {{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}) +} + +// {{.Name}}Transaction {{.Comment}} +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) {{.Name}}Transaction({{range $index, $arg := .Arguments -}} + {{- if ne $index 0}}, {{end}} + {{- .Name}} {{.Type}} + {{- end}}) (*transaction.Transaction, error) { + return c.actor.MakeCall(Hash, "{{ .NameABI }}" + {{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}) +} + +// {{.Name}}Unsigned {{.Comment}} +// 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) {{.Name}}Unsigned({{range $index, $arg := .Arguments -}} + {{- if ne $index 0}}, {{end}} + {{- .Name}} {{.Type}} + {{- end}}) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(Hash, "{{ .NameABI }}", nil + {{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}) +} +{{- end -}} // Package {{.PackageName}} contains RPC wrappers for {{.ContractName}} contract. package {{.PackageName}} @@ -45,6 +80,22 @@ type Invoker interface { Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) } +{{if .HasWriter}}// Actor is used by Contract to call state-changing methods. +type Actor interface { + Invoker + {{if or .IsNep11D .IsNep11ND}}nep11.Actor{{end -}} + {{if .IsNep17}}nep17.Actor{{end -}} +{{if len .Methods}} + MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error) + MakeRun(script []byte) (*transaction.Transaction, error) + MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...interface{}) (*transaction.Transaction, error) + MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) + SendCall(contract util.Uint160, method string, params ...interface{}) (util.Uint256, uint32, error) + SendRun(script []byte) (util.Uint256, uint32, error) +{{end -}} +} + +{{end -}} // ContractReader implements safe contract methods. type ContractReader struct { {{if .IsNep11D}}nep11.DivisibleReader @@ -56,6 +107,19 @@ type ContractReader struct { invoker Invoker } +{{if .HasWriter}}// Contract implements all contract methods. +type Contract struct { + ContractReader + {{if .IsNep11D}}nep11.DivisibleWriter + {{end -}} + {{if .IsNep11ND}}nep11.BaseWriter + {{end -}} + {{if .IsNep17}}nep17.TokenWriter + {{end -}} + actor Actor +} + +{{end -}} // NewReader creates an instance of ContractReader using Hash and the given Invoker. func NewReader(invoker Invoker) *ContractReader { return &ContractReader{ @@ -65,7 +129,30 @@ func NewReader(invoker Invoker) *ContractReader { invoker} } -{{range $m := .Methods}} +{{if .HasWriter}}// New creates an instance of Contract using Hash and the given Actor. +func New(actor Actor) *Contract { + {{if .IsNep11D}}var nep11dt = nep11.NewDivisible(actor, Hash) + {{end -}} + {{if .IsNep11ND}}var nep11ndt = nep11.NewNonDivisible(actor, Hash) + {{end -}} + {{if .IsNep17}}var nep17t = nep17.New(actor, Hash) + {{end -}} + return &Contract{ContractReader{ + {{- if .IsNep11D}}nep11dt.DivisibleReader, {{end -}} + {{- if .IsNep11ND}}nep11ndt.NonDivisibleReader, {{end -}} + {{- if .IsNep17}}nep17t.TokenReader, {{end -}} + actor}, + {{- if .IsNep11D}}nep11dt.DivisibleWriter, {{end -}} + {{- if .IsNep11ND}}nep11ndt.BaseWriter, {{end -}} + {{- if .IsNep17}}nep17t.TokenWriter, {{end -}} + actor} +} + +{{end -}} +{{range $m := .SafeMethods}} +{{template "SAFEMETHOD" $m }} +{{end}} +{{- range $m := .Methods}} {{template "METHOD" $m }} {{end}}` @@ -74,9 +161,14 @@ var srcTemplate = template.Must(template.New("generate").Parse(srcTmpl)) type ( ContractTmpl struct { binding.ContractTmpl + + SafeMethods []binding.MethodTmpl + IsNep11D bool IsNep11ND bool IsNep17 bool + + HasWriter bool } ) @@ -180,43 +272,54 @@ func scTemplateToRPC(cfg binding.Config, bctr binding.ContractTmpl) ContractTmpl } for i := 0; i < len(ctr.Methods); i++ { abim := cfg.Manifest.ABI.GetMethod(ctr.Methods[i].NameABI, len(ctr.Methods[i].Arguments)) - if !abim.Safe { + if abim.Safe { + ctr.SafeMethods = append(ctr.SafeMethods, ctr.Methods[i]) ctr.Methods = append(ctr.Methods[:i], ctr.Methods[i+1:]...) i-- + } else { + ctr.Methods[i].Comment = fmt.Sprintf("creates a transaction invoking `%s` method of the contract.", ctr.Methods[i].NameABI) } } // We're misusing CallFlag field for function name here. - for i := range ctr.Methods { - switch ctr.Methods[i].ReturnType { + for i := range ctr.SafeMethods { + switch ctr.SafeMethods[i].ReturnType { case "interface{}": imports["github.com/nspcc-dev/neo-go/pkg/vm/stackitem"] = struct{}{} - ctr.Methods[i].ReturnType = "stackitem.Item" - ctr.Methods[i].CallFlag = "Item" + ctr.SafeMethods[i].ReturnType = "stackitem.Item" + ctr.SafeMethods[i].CallFlag = "Item" case "bool": - ctr.Methods[i].CallFlag = "Bool" + ctr.SafeMethods[i].CallFlag = "Bool" case "*big.Int": - ctr.Methods[i].CallFlag = "BigInt" + ctr.SafeMethods[i].CallFlag = "BigInt" case "string": - ctr.Methods[i].CallFlag = "UTF8String" + ctr.SafeMethods[i].CallFlag = "UTF8String" case "util.Uint160": - ctr.Methods[i].CallFlag = "Uint160" + ctr.SafeMethods[i].CallFlag = "Uint160" case "util.Uint256": - ctr.Methods[i].CallFlag = "Uint256" + ctr.SafeMethods[i].CallFlag = "Uint256" case "*keys.PublicKey": - ctr.Methods[i].CallFlag = "PublicKey" + ctr.SafeMethods[i].CallFlag = "PublicKey" case "[]byte": - ctr.Methods[i].CallFlag = "Bytes" + ctr.SafeMethods[i].CallFlag = "Bytes" case "[]interface{}": imports["github.com/nspcc-dev/neo-go/pkg/vm/stackitem"] = struct{}{} - ctr.Methods[i].ReturnType = "[]stackitem.Item" - ctr.Methods[i].CallFlag = "Array" + ctr.SafeMethods[i].ReturnType = "[]stackitem.Item" + ctr.SafeMethods[i].CallFlag = "Array" case "*stackitem.Map": - ctr.Methods[i].CallFlag = "Map" + ctr.SafeMethods[i].CallFlag = "Map" } } - imports["github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"] = struct{}{} imports["github.com/nspcc-dev/neo-go/pkg/neorpc/result"] = struct{}{} + if len(ctr.SafeMethods) > 0 { + imports["github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"] = struct{}{} + } + if len(ctr.Methods) > 0 { + imports["github.com/nspcc-dev/neo-go/pkg/core/transaction"] = struct{}{} + } + if len(ctr.Methods) > 0 || ctr.IsNep17 || ctr.IsNep11D || ctr.IsNep11ND { + ctr.HasWriter = true + } ctr.Imports = ctr.Imports[:0] for imp := range imports { ctr.Imports = append(ctr.Imports, imp) From aeb61fb61dfd2c22555ebb4d493dba303d302e77 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Mon, 7 Nov 2022 22:13:47 +0300 Subject: [PATCH 3/6] rpcbinding: generate ASSERT for bool-returning methods It's a common pattern. --- cli/smartcontract/testdata/nameservice/nns.go | 23 ++++++++++-- docs/compiler.md | 19 +++++++--- pkg/smartcontract/rpcbinding/binding.go | 36 +++++++++++++++---- 3 files changed, 63 insertions(+), 15 deletions(-) diff --git a/cli/smartcontract/testdata/nameservice/nns.go b/cli/smartcontract/testdata/nameservice/nns.go index 511acaedf..436d7fbcb 100644 --- a/cli/smartcontract/testdata/nameservice/nns.go +++ b/cli/smartcontract/testdata/nameservice/nns.go @@ -6,6 +6,7 @@ import ( "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/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" "github.com/nspcc-dev/neo-go/pkg/vm/stackitem" "math/big" @@ -153,18 +154,30 @@ func (c *Contract) SetPriceUnsigned(priceList []interface{}) (*transaction.Trans return c.actor.MakeUnsignedCall(Hash, "setPrice", nil, priceList) } +func scriptForRegister(name string, owner util.Uint160) ([]byte, error) { + return smartcontract.CreateCallWithAssertScript(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) { - return c.actor.SendCall(Hash, "register", name, owner) + script, err := 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) { - return c.actor.MakeCall(Hash, "register", name, owner) + script, err := scriptForRegister(name, owner) + if err != nil { + return nil, err + } + return c.actor.MakeRun(script) } // RegisterUnsigned creates a transaction invoking `register` method of the contract. @@ -172,7 +185,11 @@ func (c *Contract) RegisterTransaction(name string, owner util.Uint160) (*transa // 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) { - return c.actor.MakeUnsignedCall(Hash, "register", nil, name, owner) + script, err := 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. diff --git a/docs/compiler.md b/docs/compiler.md index 7b909fc6c..ab1358b7e 100644 --- a/docs/compiler.md +++ b/docs/compiler.md @@ -436,11 +436,20 @@ $ ./bin/neo-go contract generate-wrapper --manifest manifest.json --config contr ### Generating RPC contract bindings To simplify interacting with the contract via RPC you can generate -contract-specific RPC bindings with the "generate-rpcwrapper" command. If your -contract is NEP-11 or NEP-17 that's autodetected and an appropriate package is -included as well. Notice that the type data available in the manifest is -limited, so in some cases the interface generated may use generic stackitem -types. Iterators are not supported yet. +contract-specific RPC bindings with the "generate-rpcwrapper" command. It +generates ContractReader structure for safe methods that accept appropriate +data for input and return things returned by the contract. State-changing +methods are contained in Contract structure with each contract method +represented by three wrapper methods that create/send transaction with a +script performing appropriate action. This script invokes contract method and +does not do anything else unless the method's returned value is of a boolean +type, in this case an ASSERT is added to script making it fail when the method +returns false. + +If your contract is NEP-11 or NEP-17 that's autodetected and an appropriate +package is included as well. Notice that the type data available in the +manifest is limited, so in some cases the interface generated may use generic +stackitem types. Iterators are not supported yet. ``` $ ./bin/neo-go contract generate-rpcwrapper --manifest manifest.json --out rpcwrapper.go --hash 0x1b4357bff5a01bdf2a6581247cf9ed1e24629176 diff --git a/pkg/smartcontract/rpcbinding/binding.go b/pkg/smartcontract/rpcbinding/binding.go index 578e0e048..a5e7c5cab 100644 --- a/pkg/smartcontract/rpcbinding/binding.go +++ b/pkg/smartcontract/rpcbinding/binding.go @@ -27,15 +27,26 @@ func (c *ContractReader) {{.Name}}({{range $index, $arg := .Arguments -}} } {{- end -}} {{- define "METHOD" -}} -// {{.Name}} {{.Comment}} +{{- if eq .ReturnType "bool"}}func scriptFor{{.Name}}({{range $index, $arg := .Arguments -}} + {{- if ne $index 0}}, {{end}} + {{- .Name}} {{.Type}} + {{- end}}) ([]byte, error) { + return smartcontract.CreateCallWithAssertScript(Hash, "{{ .NameABI }}"{{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}) +} + +{{end}}// {{.Name}} {{.Comment}} // 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) {{.Name}}({{range $index, $arg := .Arguments -}} {{- if ne $index 0}}, {{end}} {{- .Name}} {{.Type}} {{- end}}) (util.Uint256, uint32, error) { - return c.actor.SendCall(Hash, "{{ .NameABI }}" - {{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}) + {{if ne .ReturnType "bool"}}return c.actor.SendCall(Hash, "{{ .NameABI }}" + {{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}){{else}}script, err := scriptFor{{.Name}}({{- range $index, $arg := .Arguments -}}{{- if ne $index 0}}, {{end}}{{.Name}}{{end}}) + if err != nil { + return util.Uint256{}, 0, err + } + return c.actor.SendRun(script){{end}} } // {{.Name}}Transaction {{.Comment}} @@ -45,8 +56,12 @@ func (c *Contract) {{.Name}}Transaction({{range $index, $arg := .Arguments -}} {{- if ne $index 0}}, {{end}} {{- .Name}} {{.Type}} {{- end}}) (*transaction.Transaction, error) { - return c.actor.MakeCall(Hash, "{{ .NameABI }}" - {{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}) + {{if ne .ReturnType "bool"}}return c.actor.MakeCall(Hash, "{{ .NameABI }}" + {{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}){{else}}script, err := scriptFor{{.Name}}({{- range $index, $arg := .Arguments -}}{{- if ne $index 0}}, {{end}}{{.Name}}{{end}}) + if err != nil { + return nil, err + } + return c.actor.MakeRun(script){{end}} } // {{.Name}}Unsigned {{.Comment}} @@ -57,8 +72,12 @@ func (c *Contract) {{.Name}}Unsigned({{range $index, $arg := .Arguments -}} {{- if ne $index 0}}, {{end}} {{- .Name}} {{.Type}} {{- end}}) (*transaction.Transaction, error) { - return c.actor.MakeUnsignedCall(Hash, "{{ .NameABI }}", nil - {{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}) + {{if ne .ReturnType "bool"}}return c.actor.MakeUnsignedCall(Hash, "{{ .NameABI }}", nil + {{- range $index, $arg := .Arguments -}}, {{.Name}}{{end}}){{else}}script, err := scriptFor{{.Name}}({{- range $index, $arg := .Arguments -}}{{- if ne $index 0}}, {{end}}{{.Name}}{{end}}) + if err != nil { + return nil, err + } + return c.actor.MakeUnsignedRun(script, nil){{end}} } {{- end -}} // Package {{.PackageName}} contains RPC wrappers for {{.ContractName}} contract. @@ -278,6 +297,9 @@ func scTemplateToRPC(cfg binding.Config, bctr binding.ContractTmpl) ContractTmpl i-- } else { ctr.Methods[i].Comment = fmt.Sprintf("creates a transaction invoking `%s` method of the contract.", ctr.Methods[i].NameABI) + if ctr.Methods[i].ReturnType == "bool" { + imports["github.com/nspcc-dev/neo-go/pkg/smartcontract"] = struct{}{} + } } } // We're misusing CallFlag field for function name here. From df29008a50c3941710cbaa35d1a5d3f4d7790544 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 8 Nov 2022 15:43:32 +0300 Subject: [PATCH 4/6] rpcbinding: add GAS testcase, fix methodless wrappers * strip NEP-XX methods before going into generator to avoid unused imports * nepXX.Invoker types already include Call * always import util, it's used for Hash --- cli/smartcontract/generate_test.go | 53 ++++++------ cli/smartcontract/testdata/gas/gas.go | 46 ++++++++++ .../testdata/gas/gas.manifest.json | 1 + cli/smartcontract/testdata/nameservice/nns.go | 3 +- cli/smartcontract/testdata/nex/nex.go | 2 - pkg/smartcontract/rpcbinding/binding.go | 83 +++++++++++-------- 6 files changed, 122 insertions(+), 66 deletions(-) create mode 100644 cli/smartcontract/testdata/gas/gas.go create mode 100644 cli/smartcontract/testdata/gas/gas.manifest.json diff --git a/cli/smartcontract/generate_test.go b/cli/smartcontract/generate_test.go index ef753fc60..e3e649a48 100644 --- a/cli/smartcontract/generate_test.go +++ b/cli/smartcontract/generate_test.go @@ -294,6 +294,7 @@ package myspacecontract import ( "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" + "github.com/nspcc-dev/neo-go/pkg/util" "math/big" ) @@ -328,34 +329,34 @@ func TestGenerateRPCBindings(t *testing.T) { app := cli.NewApp() app.Commands = []cli.Command{generateWrapperCmd, generateRPCWrapperCmd} - outFile := filepath.Join(tmpDir, "out.go") - require.NoError(t, app.Run([]string{"", "generate-rpcwrapper", - "--manifest", filepath.Join("testdata", "nex", "nex.manifest.json"), - "--out", outFile, - "--hash", "0xa2a67f09e8cf22c6bfd5cea24adc0f4bf0a11aa8", - })) + var checkBinding = func(manifest string, hash string, good string) { + t.Run(manifest, func(t *testing.T) { + outFile := filepath.Join(tmpDir, "out.go") + require.NoError(t, app.Run([]string{"", "generate-rpcwrapper", + "--manifest", manifest, + "--out", outFile, + "--hash", hash, + })) - data, err := os.ReadFile(outFile) - require.NoError(t, err) - data = bytes.ReplaceAll(data, []byte("\r"), []byte{}) // Windows. - expected, err := os.ReadFile(filepath.Join("testdata", "nex", "nex.go")) - require.NoError(t, err) - expected = bytes.ReplaceAll(expected, []byte("\r"), []byte{}) // Windows. - require.Equal(t, string(expected), string(data)) + data, err := os.ReadFile(outFile) + require.NoError(t, err) + data = bytes.ReplaceAll(data, []byte("\r"), []byte{}) // Windows. + expected, err := os.ReadFile(good) + require.NoError(t, err) + expected = bytes.ReplaceAll(expected, []byte("\r"), []byte{}) // Windows. + require.Equal(t, string(expected), string(data)) + }) + } - require.NoError(t, app.Run([]string{"", "generate-rpcwrapper", - "--manifest", filepath.Join("testdata", "nameservice", "nns.manifest.json"), - "--out", outFile, - "--hash", "0x50ac1c37690cc2cfc594472833cf57505d5f46de", - })) - - data, err = os.ReadFile(outFile) - require.NoError(t, err) - data = bytes.ReplaceAll(data, []byte("\r"), []byte{}) // Windows. - expected, err = os.ReadFile(filepath.Join("testdata", "nameservice", "nns.go")) - require.NoError(t, err) - expected = bytes.ReplaceAll(expected, []byte("\r"), []byte{}) // Windows. - require.Equal(t, string(expected), string(data)) + checkBinding(filepath.Join("testdata", "nex", "nex.manifest.json"), + "0xa2a67f09e8cf22c6bfd5cea24adc0f4bf0a11aa8", + filepath.Join("testdata", "nex", "nex.go")) + checkBinding(filepath.Join("testdata", "nameservice", "nns.manifest.json"), + "0x50ac1c37690cc2cfc594472833cf57505d5f46de", + filepath.Join("testdata", "nameservice", "nns.go")) + checkBinding(filepath.Join("testdata", "gas", "gas.manifest.json"), + "0xd2a4cff31913016155e38e474a2c06d08be276cf", + filepath.Join("testdata", "gas", "gas.go")) } func TestGenerate_Errors(t *testing.T) { diff --git a/cli/smartcontract/testdata/gas/gas.go b/cli/smartcontract/testdata/gas/gas.go new file mode 100644 index 000000000..e81155f40 --- /dev/null +++ b/cli/smartcontract/testdata/gas/gas.go @@ -0,0 +1,46 @@ +// Package gastoken contains RPC wrappers for GasToken contract. +package gastoken + +import ( + "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17" + "github.com/nspcc-dev/neo-go/pkg/util" +) + +// Hash contains contract hash. +var Hash = util.Uint160{0xcf, 0x76, 0xe2, 0x8b, 0xd0, 0x6, 0x2c, 0x4a, 0x47, 0x8e, 0xe3, 0x55, 0x61, 0x1, 0x13, 0x19, 0xf3, 0xcf, 0xa4, 0xd2} + +// Invoker is used by ContractReader to call various safe methods. +type Invoker interface { + nep17.Invoker +} + +// Actor is used by Contract to call state-changing methods. +type Actor interface { + Invoker + nep17.Actor +} + +// ContractReader implements safe contract methods. +type ContractReader struct { + nep17.TokenReader + invoker Invoker +} + +// Contract implements all contract methods. +type Contract struct { + ContractReader + nep17.TokenWriter + actor Actor +} + +// NewReader creates an instance of ContractReader using Hash and the given Invoker. +func NewReader(invoker Invoker) *ContractReader { + return &ContractReader{*nep17.NewReader(invoker, Hash), invoker} +} + +// New creates an instance of Contract using Hash and the given Actor. +func New(actor Actor) *Contract { + var nep17t = nep17.New(actor, Hash) + return &Contract{ContractReader{nep17t.TokenReader, actor},nep17t.TokenWriter, actor} +} + diff --git a/cli/smartcontract/testdata/gas/gas.manifest.json b/cli/smartcontract/testdata/gas/gas.manifest.json new file mode 100644 index 000000000..b2421f4c1 --- /dev/null +++ b/cli/smartcontract/testdata/gas/gas.manifest.json @@ -0,0 +1 @@ +{"name":"GasToken","groups":[],"features":{},"supportedstandards":["NEP-17"],"abi":{"methods":[{"name":"balanceOf","parameters":[{"name":"account","type":"Hash160"}],"returntype":"Integer","offset":0,"safe":true},{"name":"decimals","parameters":[],"returntype":"Integer","offset":7,"safe":true},{"name":"symbol","parameters":[],"returntype":"String","offset":14,"safe":true},{"name":"totalSupply","parameters":[],"returntype":"Integer","offset":21,"safe":true},{"name":"transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"},{"name":"data","type":"Any"}],"returntype":"Boolean","offset":28,"safe":false}],"events":[{"name":"Transfer","parameters":[{"name":"from","type":"Hash160"},{"name":"to","type":"Hash160"},{"name":"amount","type":"Integer"}]}]},"permissions":[{"contract":"*","methods":"*"}],"trusts":[],"extra":null} diff --git a/cli/smartcontract/testdata/nameservice/nns.go b/cli/smartcontract/testdata/nameservice/nns.go index 436d7fbcb..2e72c192c 100644 --- a/cli/smartcontract/testdata/nameservice/nns.go +++ b/cli/smartcontract/testdata/nameservice/nns.go @@ -3,7 +3,6 @@ package nameservice import ( "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/nep11" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "github.com/nspcc-dev/neo-go/pkg/smartcontract" @@ -18,13 +17,13 @@ var Hash = util.Uint160{0xde, 0x46, 0x5f, 0x5d, 0x50, 0x57, 0xcf, 0x33, 0x28, 0x // Invoker is used by ContractReader to call various safe methods. type Invoker interface { nep11.Invoker - Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) } // Actor is used by Contract to call state-changing methods. type Actor interface { Invoker nep11.Actor + MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error) MakeRun(script []byte) (*transaction.Transaction, error) MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...interface{}) (*transaction.Transaction, error) diff --git a/cli/smartcontract/testdata/nex/nex.go b/cli/smartcontract/testdata/nex/nex.go index 3ca127356..8c67fe731 100644 --- a/cli/smartcontract/testdata/nex/nex.go +++ b/cli/smartcontract/testdata/nex/nex.go @@ -4,7 +4,6 @@ package nextoken import ( "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/crypto/keys" - "github.com/nspcc-dev/neo-go/pkg/neorpc/result" "github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17" "github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap" "github.com/nspcc-dev/neo-go/pkg/util" @@ -17,7 +16,6 @@ var Hash = util.Uint160{0xa8, 0x1a, 0xa1, 0xf0, 0x4b, 0xf, 0xdc, 0x4a, 0xa2, 0xc // Invoker is used by ContractReader to call various safe methods. type Invoker interface { nep17.Invoker - Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) } // Actor is used by Contract to call state-changing methods. diff --git a/pkg/smartcontract/rpcbinding/binding.go b/pkg/smartcontract/rpcbinding/binding.go index a5e7c5cab..749e6a252 100644 --- a/pkg/smartcontract/rpcbinding/binding.go +++ b/pkg/smartcontract/rpcbinding/binding.go @@ -92,20 +92,18 @@ var Hash = {{ .Hash }} // Invoker is used by ContractReader to call various safe methods. type Invoker interface { - {{if or .IsNep11D .IsNep11ND}}nep11.Invoker - {{end -}} - {{if .IsNep17}}nep17.Invoker - {{end -}} - Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) +{{if or .IsNep11D .IsNep11ND}} nep11.Invoker +{{else if .IsNep17}} nep17.Invoker +{{else if len .SafeMethods}} Call(contract util.Uint160, operation string, params ...interface{}) (*result.Invoke, error) +{{end -}} } {{if .HasWriter}}// Actor is used by Contract to call state-changing methods. type Actor interface { Invoker - {{if or .IsNep11D .IsNep11ND}}nep11.Actor{{end -}} - {{if .IsNep17}}nep17.Actor{{end -}} -{{if len .Methods}} - MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error) +{{if or .IsNep11D .IsNep11ND}} nep11.Actor +{{else if .IsNep17}} nep17.Actor{{end}} +{{if len .Methods}} MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error) MakeRun(script []byte) (*transaction.Transaction, error) MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...interface{}) (*transaction.Transaction, error) MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) @@ -198,19 +196,50 @@ func NewConfig() binding.Config { // Generate writes Go file containing smartcontract bindings to the `cfg.Output`. func Generate(cfg binding.Config) error { + // Avoid changing *cfg.Manifest. + mfst := *cfg.Manifest + mfst.ABI.Methods = make([]manifest.Method, len(mfst.ABI.Methods)) + copy(mfst.ABI.Methods, cfg.Manifest.ABI.Methods) + cfg.Manifest = &mfst + + var imports = make(map[string]struct{}) + var ctr ContractTmpl + + // Strip standard methods from NEP-XX packages. + for _, std := range cfg.Manifest.SupportedStandards { + if std == manifest.NEP11StandardName { + imports["github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11"] = struct{}{} + if standard.ComplyABI(cfg.Manifest, standard.Nep11Divisible) == nil { + mfst.ABI.Methods = dropStdMethods(mfst.ABI.Methods, standard.Nep11Divisible) + ctr.IsNep11D = true + } else if standard.ComplyABI(cfg.Manifest, standard.Nep11NonDivisible) == nil { + mfst.ABI.Methods = dropStdMethods(mfst.ABI.Methods, standard.Nep11NonDivisible) + ctr.IsNep11ND = true + } + break // Can't be NEP-17 at the same time. + } + if std == manifest.NEP17StandardName && standard.ComplyABI(cfg.Manifest, standard.Nep17) == nil { + mfst.ABI.Methods = dropStdMethods(mfst.ABI.Methods, standard.Nep17) + imports["github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"] = struct{}{} + ctr.IsNep17 = true + break // Can't be NEP-11 at the same time. + } + } + bctr, err := binding.TemplateFromManifest(cfg, scTypeToGo) if err != nil { return err } - ctr := scTemplateToRPC(cfg, bctr) + ctr.ContractTmpl = bctr + ctr = scTemplateToRPC(cfg, ctr, imports) return srcTemplate.Execute(cfg.Output, ctr) } -func dropManifestMethods(meths []binding.MethodTmpl, manifested []manifest.Method) []binding.MethodTmpl { +func dropManifestMethods(meths []manifest.Method, manifested []manifest.Method) []manifest.Method { for _, m := range manifested { for i := 0; i < len(meths); i++ { - if meths[i].NameABI == m.Name && len(meths[i].Arguments) == len(m.Parameters) { + if meths[i].Name == m.Name && len(meths[i].Parameters) == len(m.Parameters) { meths = append(meths[:i], meths[i+1:]...) i-- } @@ -219,7 +248,7 @@ func dropManifestMethods(meths []binding.MethodTmpl, manifested []manifest.Metho return meths } -func dropStdMethods(meths []binding.MethodTmpl, std *standard.Standard) []binding.MethodTmpl { +func dropStdMethods(meths []manifest.Method, std *standard.Standard) []manifest.Method { meths = dropManifestMethods(meths, std.Manifest.ABI.Methods) if std.Optional != nil { meths = dropManifestMethods(meths, std.Optional) @@ -263,32 +292,11 @@ func scTypeToGo(name string, typ smartcontract.ParamType, overrides map[string]b } } -func scTemplateToRPC(cfg binding.Config, bctr binding.ContractTmpl) ContractTmpl { - var imports = make(map[string]struct{}) - var ctr = ContractTmpl{ContractTmpl: bctr} +func scTemplateToRPC(cfg binding.Config, ctr ContractTmpl, imports map[string]struct{}) ContractTmpl { for i := range ctr.Imports { imports[ctr.Imports[i]] = struct{}{} } ctr.Hash = fmt.Sprintf("%#v", cfg.Hash) - for _, std := range cfg.Manifest.SupportedStandards { - if std == manifest.NEP11StandardName { - imports["github.com/nspcc-dev/neo-go/pkg/rpcclient/nep11"] = struct{}{} - if standard.ComplyABI(cfg.Manifest, standard.Nep11Divisible) == nil { - ctr.Methods = dropStdMethods(ctr.Methods, standard.Nep11Divisible) - ctr.IsNep11D = true - } else if standard.ComplyABI(cfg.Manifest, standard.Nep11NonDivisible) == nil { - ctr.Methods = dropStdMethods(ctr.Methods, standard.Nep11NonDivisible) - ctr.IsNep11ND = true - } - break // Can't be NEP-17 at the same time. - } - if std == manifest.NEP17StandardName && standard.ComplyABI(cfg.Manifest, standard.Nep17) == nil { - ctr.Methods = dropStdMethods(ctr.Methods, standard.Nep17) - imports["github.com/nspcc-dev/neo-go/pkg/rpcclient/nep17"] = struct{}{} - ctr.IsNep17 = true - break // Can't be NEP-11 at the same time. - } - } for i := 0; i < len(ctr.Methods); i++ { abim := cfg.Manifest.ABI.GetMethod(ctr.Methods[i].NameABI, len(ctr.Methods[i].Arguments)) if abim.Safe { @@ -332,9 +340,12 @@ func scTemplateToRPC(cfg binding.Config, bctr binding.ContractTmpl) ContractTmpl } } - imports["github.com/nspcc-dev/neo-go/pkg/neorpc/result"] = struct{}{} + imports["github.com/nspcc-dev/neo-go/pkg/util"] = struct{}{} if len(ctr.SafeMethods) > 0 { imports["github.com/nspcc-dev/neo-go/pkg/rpcclient/unwrap"] = struct{}{} + if !(ctr.IsNep17 || ctr.IsNep11D || ctr.IsNep11ND) { + imports["github.com/nspcc-dev/neo-go/pkg/neorpc/result"] = struct{}{} + } } if len(ctr.Methods) > 0 { imports["github.com/nspcc-dev/neo-go/pkg/core/transaction"] = struct{}{} From 130608ac67f95dfc07fb1d7f373ab44fd67630f6 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 8 Nov 2022 16:36:38 +0300 Subject: [PATCH 5/6] rpcbinding: support writer-only wrappers "verify" contract doesn't have any safe methods. --- cli/smartcontract/generate_test.go | 3 + cli/smartcontract/testdata/gas/gas.go | 3 +- cli/smartcontract/testdata/nameservice/nns.go | 3 +- cli/smartcontract/testdata/nex/nex.go | 4 +- .../testdata/verifyrpc/verify.go | 93 +++++++++++++++++++ pkg/smartcontract/rpcbinding/binding.go | 33 +++++-- 6 files changed, 127 insertions(+), 12 deletions(-) create mode 100644 cli/smartcontract/testdata/verifyrpc/verify.go diff --git a/cli/smartcontract/generate_test.go b/cli/smartcontract/generate_test.go index e3e649a48..df595f83f 100644 --- a/cli/smartcontract/generate_test.go +++ b/cli/smartcontract/generate_test.go @@ -357,6 +357,9 @@ func TestGenerateRPCBindings(t *testing.T) { checkBinding(filepath.Join("testdata", "gas", "gas.manifest.json"), "0xd2a4cff31913016155e38e474a2c06d08be276cf", filepath.Join("testdata", "gas", "gas.go")) + checkBinding(filepath.Join("testdata", "verify.manifest.json"), + "0x00112233445566778899aabbccddeeff00112233", + filepath.Join("testdata", "verifyrpc", "verify.go")) } func TestGenerate_Errors(t *testing.T) { diff --git a/cli/smartcontract/testdata/gas/gas.go b/cli/smartcontract/testdata/gas/gas.go index e81155f40..d3c938c29 100644 --- a/cli/smartcontract/testdata/gas/gas.go +++ b/cli/smartcontract/testdata/gas/gas.go @@ -17,6 +17,7 @@ type Invoker interface { // Actor is used by Contract to call state-changing methods. type Actor interface { Invoker + nep17.Actor } @@ -41,6 +42,6 @@ func NewReader(invoker Invoker) *ContractReader { // New creates an instance of Contract using Hash and the given Actor. func New(actor Actor) *Contract { var nep17t = nep17.New(actor, Hash) - return &Contract{ContractReader{nep17t.TokenReader, actor},nep17t.TokenWriter, actor} + return &Contract{ContractReader{nep17t.TokenReader, actor}, nep17t.TokenWriter, actor} } diff --git a/cli/smartcontract/testdata/nameservice/nns.go b/cli/smartcontract/testdata/nameservice/nns.go index 2e72c192c..5c849b8e7 100644 --- a/cli/smartcontract/testdata/nameservice/nns.go +++ b/cli/smartcontract/testdata/nameservice/nns.go @@ -22,6 +22,7 @@ type Invoker interface { // Actor is used by Contract to call state-changing methods. type Actor interface { Invoker + nep11.Actor MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error) @@ -53,7 +54,7 @@ func NewReader(invoker Invoker) *ContractReader { // New creates an instance of Contract using Hash and the given Actor. func New(actor Actor) *Contract { var nep11ndt = nep11.NewNonDivisible(actor, Hash) - return &Contract{ContractReader{nep11ndt.NonDivisibleReader, actor},nep11ndt.BaseWriter, actor} + return &Contract{ContractReader{nep11ndt.NonDivisibleReader, actor}, nep11ndt.BaseWriter, actor} } diff --git a/cli/smartcontract/testdata/nex/nex.go b/cli/smartcontract/testdata/nex/nex.go index 8c67fe731..2ad6941db 100644 --- a/cli/smartcontract/testdata/nex/nex.go +++ b/cli/smartcontract/testdata/nex/nex.go @@ -21,7 +21,9 @@ type Invoker interface { // Actor is used by Contract to call state-changing methods. type Actor interface { Invoker + nep17.Actor + MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error) MakeRun(script []byte) (*transaction.Transaction, error) MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...interface{}) (*transaction.Transaction, error) @@ -51,7 +53,7 @@ func NewReader(invoker Invoker) *ContractReader { // New creates an instance of Contract using Hash and the given Actor. func New(actor Actor) *Contract { var nep17t = nep17.New(actor, Hash) - return &Contract{ContractReader{nep17t.TokenReader, actor},nep17t.TokenWriter, actor} + return &Contract{ContractReader{nep17t.TokenReader, actor}, nep17t.TokenWriter, actor} } diff --git a/cli/smartcontract/testdata/verifyrpc/verify.go b/cli/smartcontract/testdata/verifyrpc/verify.go new file mode 100644 index 000000000..0725c3836 --- /dev/null +++ b/cli/smartcontract/testdata/verifyrpc/verify.go @@ -0,0 +1,93 @@ +// Package verify contains RPC wrappers for verify contract. +package verify + +import ( + "github.com/nspcc-dev/neo-go/pkg/core/transaction" + "github.com/nspcc-dev/neo-go/pkg/smartcontract" + "github.com/nspcc-dev/neo-go/pkg/util" + "math/big" +) + +// Hash contains contract hash. +var Hash = util.Uint160{0x33, 0x22, 0x11, 0x0, 0xff, 0xee, 0xdd, 0xcc, 0xbb, 0xaa, 0x99, 0x88, 0x77, 0x66, 0x55, 0x44, 0x33, 0x22, 0x11, 0x0} + +// Actor is used by Contract to call state-changing methods. +type Actor interface { + MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error) + MakeRun(script []byte) (*transaction.Transaction, error) + MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...interface{}) (*transaction.Transaction, error) + MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) + SendCall(contract util.Uint160, method string, params ...interface{}) (util.Uint256, uint32, error) + SendRun(script []byte) (util.Uint256, uint32, error) +} + +// Contract implements all contract methods. +type Contract struct { + actor Actor +} + +// New creates an instance of Contract using Hash and the given Actor. +func New(actor Actor) *Contract { + return &Contract{actor} +} + + +func scriptForVerify() ([]byte, error) { + return smartcontract.CreateCallWithAssertScript(Hash, "verify") +} + +// Verify creates a transaction invoking `verify` 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) Verify() (util.Uint256, uint32, error) { + script, err := scriptForVerify() + if err != nil { + return util.Uint256{}, 0, err + } + return c.actor.SendRun(script) +} + +// VerifyTransaction creates a transaction invoking `verify` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) VerifyTransaction() (*transaction.Transaction, error) { + script, err := scriptForVerify() + if err != nil { + return nil, err + } + return c.actor.MakeRun(script) +} + +// VerifyUnsigned creates a transaction invoking `verify` 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) VerifyUnsigned() (*transaction.Transaction, error) { + script, err := scriptForVerify() + if err != nil { + return nil, err + } + return c.actor.MakeUnsignedRun(script, nil) +} + +// OnNEP17Payment creates a transaction invoking `onNEP17Payment` 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) OnNEP17Payment(from []byte, amount *big.Int, data interface{}) (util.Uint256, uint32, error) { + return c.actor.SendCall(Hash, "onNEP17Payment", from, amount, data) +} + +// OnNEP17PaymentTransaction creates a transaction invoking `onNEP17Payment` method of the contract. +// This transaction is signed, but not sent to the network, instead it's +// returned to the caller. +func (c *Contract) OnNEP17PaymentTransaction(from []byte, amount *big.Int, data interface{}) (*transaction.Transaction, error) { + return c.actor.MakeCall(Hash, "onNEP17Payment", from, amount, data) +} + +// OnNEP17PaymentUnsigned creates a transaction invoking `onNEP17Payment` 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) OnNEP17PaymentUnsigned(from []byte, amount *big.Int, data interface{}) (*transaction.Transaction, error) { + return c.actor.MakeUnsignedCall(Hash, "onNEP17Payment", nil, from, amount, data) +} diff --git a/pkg/smartcontract/rpcbinding/binding.go b/pkg/smartcontract/rpcbinding/binding.go index 749e6a252..93b6266b9 100644 --- a/pkg/smartcontract/rpcbinding/binding.go +++ b/pkg/smartcontract/rpcbinding/binding.go @@ -90,7 +90,7 @@ import ( // Hash contains contract hash. var Hash = {{ .Hash }} -// Invoker is used by ContractReader to call various safe methods. +{{if .HasReader}}// Invoker is used by ContractReader to call various safe methods. type Invoker interface { {{if or .IsNep11D .IsNep11ND}} nep11.Invoker {{else if .IsNep17}} nep17.Invoker @@ -98,12 +98,19 @@ type Invoker interface { {{end -}} } +{{end -}} {{if .HasWriter}}// Actor is used by Contract to call state-changing methods. type Actor interface { +{{- if .HasReader}} Invoker -{{if or .IsNep11D .IsNep11ND}} nep11.Actor -{{else if .IsNep17}} nep17.Actor{{end}} -{{if len .Methods}} MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error) +{{end}} +{{- if or .IsNep11D .IsNep11ND}} + nep11.Actor +{{else if .IsNep17}} + nep17.Actor +{{end}} +{{- if len .Methods}} + MakeCall(contract util.Uint160, method string, params ...interface{}) (*transaction.Transaction, error) MakeRun(script []byte) (*transaction.Transaction, error) MakeUnsignedCall(contract util.Uint160, method string, attrs []transaction.Attribute, params ...interface{}) (*transaction.Transaction, error) MakeUnsignedRun(script []byte, attrs []transaction.Attribute) (*transaction.Transaction, error) @@ -113,7 +120,7 @@ type Actor interface { } {{end -}} -// ContractReader implements safe contract methods. +{{if .HasReader}}// ContractReader implements safe contract methods. type ContractReader struct { {{if .IsNep11D}}nep11.DivisibleReader {{end -}} @@ -124,9 +131,11 @@ type ContractReader struct { invoker Invoker } +{{end -}} {{if .HasWriter}}// Contract implements all contract methods. type Contract struct { - ContractReader + {{if .HasReader}}ContractReader + {{end -}} {{if .IsNep11D}}nep11.DivisibleWriter {{end -}} {{if .IsNep11ND}}nep11.BaseWriter @@ -137,7 +146,7 @@ type Contract struct { } {{end -}} -// NewReader creates an instance of ContractReader using Hash and the given Invoker. +{{if .HasReader}}// NewReader creates an instance of ContractReader using Hash and the given Invoker. func NewReader(invoker Invoker) *ContractReader { return &ContractReader{ {{- if .IsNep11D}}*nep11.NewDivisibleReader(invoker, Hash), {{end}} @@ -146,6 +155,7 @@ func NewReader(invoker Invoker) *ContractReader { invoker} } +{{end -}} {{if .HasWriter}}// New creates an instance of Contract using Hash and the given Actor. func New(actor Actor) *Contract { {{if .IsNep11D}}var nep11dt = nep11.NewDivisible(actor, Hash) @@ -154,11 +164,12 @@ func New(actor Actor) *Contract { {{end -}} {{if .IsNep17}}var nep17t = nep17.New(actor, Hash) {{end -}} - return &Contract{ContractReader{ + return &Contract{ + {{- if .HasReader}}ContractReader{ {{- if .IsNep11D}}nep11dt.DivisibleReader, {{end -}} {{- if .IsNep11ND}}nep11ndt.NonDivisibleReader, {{end -}} {{- if .IsNep17}}nep17t.TokenReader, {{end -}} - actor}, + actor}, {{end -}} {{- if .IsNep11D}}nep11dt.DivisibleWriter, {{end -}} {{- if .IsNep11ND}}nep11ndt.BaseWriter, {{end -}} {{- if .IsNep17}}nep17t.TokenWriter, {{end -}} @@ -185,6 +196,7 @@ type ( IsNep11ND bool IsNep17 bool + HasReader bool HasWriter bool } ) @@ -353,6 +365,9 @@ func scTemplateToRPC(cfg binding.Config, ctr ContractTmpl, imports map[string]st if len(ctr.Methods) > 0 || ctr.IsNep17 || ctr.IsNep11D || ctr.IsNep11ND { ctr.HasWriter = true } + if len(ctr.SafeMethods) > 0 || ctr.IsNep17 || ctr.IsNep11D || ctr.IsNep11ND { + ctr.HasReader = true + } ctr.Imports = ctr.Imports[:0] for imp := range imports { ctr.Imports = append(ctr.Imports, imp) From 69d8905ad9121f21cc0c0f0d71d18945f3983749 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Tue, 8 Nov 2022 17:00:26 +0300 Subject: [PATCH 6/6] rpcbinding: exclude onNEPXXPayment methods from wrappers They make no sense there. --- cli/smartcontract/generate_test.go | 2 +- .../testdata/verifyrpc/verify.go | 23 ------ .../testdata/verifyrpc/verify.manifest.json | 79 +++++++++++++++++++ pkg/smartcontract/rpcbinding/binding.go | 8 ++ 4 files changed, 88 insertions(+), 24 deletions(-) create mode 100755 cli/smartcontract/testdata/verifyrpc/verify.manifest.json diff --git a/cli/smartcontract/generate_test.go b/cli/smartcontract/generate_test.go index df595f83f..b6a1bf253 100644 --- a/cli/smartcontract/generate_test.go +++ b/cli/smartcontract/generate_test.go @@ -357,7 +357,7 @@ func TestGenerateRPCBindings(t *testing.T) { checkBinding(filepath.Join("testdata", "gas", "gas.manifest.json"), "0xd2a4cff31913016155e38e474a2c06d08be276cf", filepath.Join("testdata", "gas", "gas.go")) - checkBinding(filepath.Join("testdata", "verify.manifest.json"), + checkBinding(filepath.Join("testdata", "verifyrpc", "verify.manifest.json"), "0x00112233445566778899aabbccddeeff00112233", filepath.Join("testdata", "verifyrpc", "verify.go")) } diff --git a/cli/smartcontract/testdata/verifyrpc/verify.go b/cli/smartcontract/testdata/verifyrpc/verify.go index 0725c3836..8b8f07961 100644 --- a/cli/smartcontract/testdata/verifyrpc/verify.go +++ b/cli/smartcontract/testdata/verifyrpc/verify.go @@ -5,7 +5,6 @@ import ( "github.com/nspcc-dev/neo-go/pkg/core/transaction" "github.com/nspcc-dev/neo-go/pkg/smartcontract" "github.com/nspcc-dev/neo-go/pkg/util" - "math/big" ) // Hash contains contract hash. @@ -69,25 +68,3 @@ func (c *Contract) VerifyUnsigned() (*transaction.Transaction, error) { } return c.actor.MakeUnsignedRun(script, nil) } - -// OnNEP17Payment creates a transaction invoking `onNEP17Payment` 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) OnNEP17Payment(from []byte, amount *big.Int, data interface{}) (util.Uint256, uint32, error) { - return c.actor.SendCall(Hash, "onNEP17Payment", from, amount, data) -} - -// OnNEP17PaymentTransaction creates a transaction invoking `onNEP17Payment` method of the contract. -// This transaction is signed, but not sent to the network, instead it's -// returned to the caller. -func (c *Contract) OnNEP17PaymentTransaction(from []byte, amount *big.Int, data interface{}) (*transaction.Transaction, error) { - return c.actor.MakeCall(Hash, "onNEP17Payment", from, amount, data) -} - -// OnNEP17PaymentUnsigned creates a transaction invoking `onNEP17Payment` 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) OnNEP17PaymentUnsigned(from []byte, amount *big.Int, data interface{}) (*transaction.Transaction, error) { - return c.actor.MakeUnsignedCall(Hash, "onNEP17Payment", nil, from, amount, data) -} diff --git a/cli/smartcontract/testdata/verifyrpc/verify.manifest.json b/cli/smartcontract/testdata/verifyrpc/verify.manifest.json new file mode 100755 index 000000000..8b70f752b --- /dev/null +++ b/cli/smartcontract/testdata/verifyrpc/verify.manifest.json @@ -0,0 +1,79 @@ +{ + "groups" : [], + "extra" : null, + "supportedstandards" : [], + "name" : "verify", + "trusts" : [], + "permissions" : [ + { + "methods" : "*", + "contract" : "*" + } + ], + "abi" : { + "methods" : [ + { + "safe" : false, + "offset" : 0, + "parameters" : [], + "name" : "verify", + "returntype" : "Boolean" + }, + { + "returntype" : "Void", + "safe" : false, + "offset" : 5, + "parameters" : [ + { + "type" : "Hash160", + "name" : "from" + }, + { + "type" : "Integer", + "name" : "amount" + }, + { + "type" : "Any", + "name" : "data" + } + ], + "name" : "onNEP17Payment" + }, + { + "returntype" : "Void", + "safe" : false, + "offset" : 5, + "parameters" : [ + { + "type" : "Hash160", + "name" : "from" + }, + { + "type" : "Integer", + "name" : "amount" + }, + { + "type" : "ByteArray", + "name" : "tokenid" + }, + { + "type" : "Any", + "name" : "data" + } + ], + "name" : "onNEP11Payment" + } + ], + "events" : [ + { + "parameters" : [ + { + "type" : "Array", + "name" : "args" + } + ], + "name" : "Hello world!" + } + ] + } +} diff --git a/pkg/smartcontract/rpcbinding/binding.go b/pkg/smartcontract/rpcbinding/binding.go index 93b6266b9..d3e9bb507 100644 --- a/pkg/smartcontract/rpcbinding/binding.go +++ b/pkg/smartcontract/rpcbinding/binding.go @@ -238,6 +238,14 @@ func Generate(cfg binding.Config) error { } } + // OnNepXXPayment handlers normally can't be called directly. + if standard.ComplyABI(cfg.Manifest, standard.Nep11Payable) == nil { + mfst.ABI.Methods = dropStdMethods(mfst.ABI.Methods, standard.Nep11Payable) + } + if standard.ComplyABI(cfg.Manifest, standard.Nep17Payable) == nil { + mfst.ABI.Methods = dropStdMethods(mfst.ABI.Methods, standard.Nep17Payable) + } + bctr, err := binding.TemplateFromManifest(cfg, scTypeToGo) if err != nil { return err