diff --git a/cli/smartcontract/generate_test.go b/cli/smartcontract/generate_test.go index ef753fc60..b6a1bf253 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,37 @@ 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")) + checkBinding(filepath.Join("testdata", "verifyrpc", "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 new file mode 100644 index 000000000..d3c938c29 --- /dev/null +++ b/cli/smartcontract/testdata/gas/gas.go @@ -0,0 +1,47 @@ +// 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 62c59082b..5c849b8e7 100644 --- a/cli/smartcontract/testdata/nameservice/nns.go +++ b/cli/smartcontract/testdata/nameservice/nns.go @@ -2,9 +2,10 @@ package nameservice import ( - "github.com/nspcc-dev/neo-go/pkg/neorpc/result" + "github.com/nspcc-dev/neo-go/pkg/core/transaction" "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" @@ -16,7 +17,20 @@ 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) + 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. @@ -25,11 +39,24 @@ type ContractReader struct { 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 +87,217 @@ 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) +} + +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) { + 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) { + 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. +// 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 := 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(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..2ad6941db 100644 --- a/cli/smartcontract/testdata/nex/nex.go +++ b/cli/smartcontract/testdata/nex/nex.go @@ -2,8 +2,8 @@ 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" @@ -16,7 +16,20 @@ 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. +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. @@ -25,11 +38,24 @@ type ContractReader struct { 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/cli/smartcontract/testdata/verifyrpc/verify.go b/cli/smartcontract/testdata/verifyrpc/verify.go new file mode 100644 index 000000000..8b8f07961 --- /dev/null +++ b/cli/smartcontract/testdata/verifyrpc/verify.go @@ -0,0 +1,70 @@ +// 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" +) + +// 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) +} 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/docs/compiler.md b/docs/compiler.md index 1b4f5833d..ab1358b7e 100644 --- a/docs/compiler.md +++ b/docs/compiler.md @@ -436,12 +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. 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. 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/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 diff --git a/pkg/smartcontract/rpcbinding/binding.go b/pkg/smartcontract/rpcbinding/binding.go index 0c8b86c30..d3e9bb507 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,60 @@ func (c *ContractReader) {{.Name}}({{range $index, $arg := .Arguments -}} {{- end}} } {{- end -}} +{{- define "METHOD" -}} +{{- 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) { + {{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}} +// 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) { + {{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}} +// 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) { + {{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. package {{.PackageName}} @@ -36,16 +90,37 @@ 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 - {{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 -}} } -// ContractReader implements safe contract methods. +{{end -}} +{{if .HasWriter}}// Actor is used by Contract to call state-changing methods. +type Actor interface { +{{- if .HasReader}} + Invoker +{{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) + SendCall(contract util.Uint160, method string, params ...interface{}) (util.Uint256, uint32, error) + SendRun(script []byte) (util.Uint256, uint32, error) +{{end -}} +} + +{{end -}} +{{if .HasReader}}// ContractReader implements safe contract methods. type ContractReader struct { {{if .IsNep11D}}nep11.DivisibleReader {{end -}} @@ -56,7 +131,22 @@ type ContractReader struct { invoker Invoker } -// NewReader creates an instance of ContractReader using Hash and the given Invoker. +{{end -}} +{{if .HasWriter}}// Contract implements all contract methods. +type Contract struct { + {{if .HasReader}}ContractReader + {{end -}} + {{if .IsNep11D}}nep11.DivisibleWriter + {{end -}} + {{if .IsNep11ND}}nep11.BaseWriter + {{end -}} + {{if .IsNep17}}nep17.TokenWriter + {{end -}} + actor Actor +} + +{{end -}} +{{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}} @@ -65,7 +155,32 @@ func NewReader(invoker Invoker) *ContractReader { invoker} } -{{range $m := .Methods}} +{{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) + {{end -}} + {{if .IsNep11ND}}var nep11ndt = nep11.NewNonDivisible(actor, Hash) + {{end -}} + {{if .IsNep17}}var nep17t = nep17.New(actor, Hash) + {{end -}} + return &Contract{ + {{- if .HasReader}}ContractReader{ + {{- if .IsNep11D}}nep11dt.DivisibleReader, {{end -}} + {{- if .IsNep11ND}}nep11ndt.NonDivisibleReader, {{end -}} + {{- if .IsNep17}}nep17t.TokenReader, {{end -}} + actor}, {{end -}} + {{- 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 +189,15 @@ var srcTemplate = template.Must(template.New("generate").Parse(srcTmpl)) type ( ContractTmpl struct { binding.ContractTmpl + + SafeMethods []binding.MethodTmpl + IsNep11D bool IsNep11ND bool IsNep17 bool + + HasReader bool + HasWriter bool } ) @@ -87,19 +208,58 @@ 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. + } + } + + // 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 } - 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-- } @@ -108,7 +268,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) @@ -152,71 +312,70 @@ 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 { + 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) + 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. - 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{}{} + 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{}{} + } + 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)