From 8a5ac12df85da4041cc77003ed24d08ab6c968b6 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 29 Nov 2019 17:59:07 +0300 Subject: [PATCH 1/6] rpc: make generic SignAndPushInvocationTx out of DeployContract SignAndPushInvocationTx() is gonna be used for more than just deploying contracts. --- cli/smartcontract/smart_contract.go | 9 +++++++-- pkg/rpc/rpc.go | 15 ++++++--------- 2 files changed, 13 insertions(+), 11 deletions(-) diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index e3f8cd2b2..4b7c2877c 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -503,9 +503,14 @@ func contractDeploy(ctx *cli.Context) error { return cli.NewExitError(err, 1) } - txHash, err := client.DeployContract(avm, &conf.Contract, wif, gas) + txScript, err := rpc.CreateDeploymentScript(avm, &conf.Contract) if err != nil { - return cli.NewExitError(fmt.Errorf("failed to deploy: %v", err), 1) + return cli.NewExitError(fmt.Errorf("failed to create deployment script: %v", err), 1) + } + + txHash, err := client.SignAndPushInvocationTx(txScript, wif, gas) + if err != nil { + return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1) } fmt.Printf("Sent deployment transaction %s for contract %s\n", txHash.ReverseString(), hash.Hash160(avm).ReverseString()) return nil diff --git a/pkg/rpc/rpc.go b/pkg/rpc/rpc.go index 6995c74d7..be9143085 100644 --- a/pkg/rpc/rpc.go +++ b/pkg/rpc/rpc.go @@ -153,20 +153,17 @@ func (c *Client) SendToAddress(asset util.Uint256, address string, amount util.F return response, nil } -// DeployContract deploys given contract to the blockchain using given wif to -// sign the transaction and spending the amount of gas specified. It returns -// a hash of the deployment transaction and an error. -func (c *Client) DeployContract(avm []byte, contract *ContractDetails, wif *keys.WIF, gas util.Fixed8) (util.Uint256, error) { +// SignAndPushInvocationTx signs and pushes given script as an invocation +// transaction using given wif to sign it and spending the amount of gas +// specified. It returns a hash of the invocation transaction and an error. +func (c *Client) SignAndPushInvocationTx(script []byte, wif *keys.WIF, gas util.Fixed8) (util.Uint256, error) { var txHash util.Uint256 + var err error gasIDB, _ := hex.DecodeString("602c79718b16e442de58778e148d0b1084e3b2dffd5de6b7b16cee7969282de7") gasID, _ := util.Uint256DecodeReverseBytes(gasIDB) - txScript, err := CreateDeploymentScript(avm, contract) - if err != nil { - return txHash, errors.Wrap(err, "failed creating deployment script") - } - tx := transaction.NewInvocationTX(txScript, gas) + tx := transaction.NewInvocationTX(script, gas) fromAddress := wif.PrivateKey.Address() From dccf440dca07ef5327128e61cfdb2aa449f5d6f3 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 29 Nov 2019 18:00:08 +0300 Subject: [PATCH 2/6] rpc: skip input/outputs mangling if no gas is attached to invocation --- pkg/rpc/rpc.go | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/pkg/rpc/rpc.go b/pkg/rpc/rpc.go index be9143085..d079142ef 100644 --- a/pkg/rpc/rpc.go +++ b/pkg/rpc/rpc.go @@ -167,8 +167,10 @@ func (c *Client) SignAndPushInvocationTx(script []byte, wif *keys.WIF, gas util. fromAddress := wif.PrivateKey.Address() - if err = AddInputsAndUnspentsToTx(tx, fromAddress, gasID, gas, c); err != nil { - return txHash, errors.Wrap(err, "failed to add inputs and unspents to transaction") + if gas > 0 { + if err = AddInputsAndUnspentsToTx(tx, fromAddress, gasID, gas, c); err != nil { + return txHash, errors.Wrap(err, "failed to add inputs and unspents to transaction") + } } if err = SignTx(tx, wif); err != nil { From d8ef9ba0ec222de5f5215fc2b19968ab367d62bc Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 29 Nov 2019 18:11:49 +0300 Subject: [PATCH 3/6] cli/smartcontract: make smartContractTmpl a constant As it can be a constant and should be a constant. --- cli/smartcontract/smart_contract.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 4b7c2877c..488a62a33 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -33,7 +33,7 @@ var ( errFileExist = errors.New("A file with given smart-contract name already exists") ) -var ( +const ( // smartContractTmpl is written to a file when used with `init` command. // %s is parsed to be the smartContractName smartContractTmpl = `package %s From fd93986503638200910b70f9dce4a690d6eb6ba5 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 29 Nov 2019 18:16:46 +0300 Subject: [PATCH 4/6] cli/smartcontract: move common endpoint parameter definition Put more emphasys on that it should be trusted and use localhost example (because you usually do trust it). --- cli/smartcontract/smart_contract.go | 25 +++++++++---------------- 1 file changed, 9 insertions(+), 16 deletions(-) diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 488a62a33..567576944 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -31,6 +31,11 @@ var ( errNoScriptHash = errors.New("no smart contract hash was provided, specify one as the first argument") errNoSmartContractName = errors.New("no name was provided, specify the '--name or -n' flag") errFileExist = errors.New("A file with given smart-contract name already exists") + + endpointFlag = cli.StringFlag{ + Name: "endpoint, e", + Usage: "trusted RPC endpoint address (like 'http://localhost:20331')", + } ) const ( @@ -83,10 +88,7 @@ func NewCommands() []cli.Command { Name: "config, c", Usage: "configuration input file (*.yml)", }, - cli.StringFlag{ - Name: "endpoint, e", - Usage: "RPC endpoint address (like 'http://seed4.ngd.network:20332')", - }, + endpointFlag, cli.StringFlag{ Name: "wif, w", Usage: "key to sign deployed transaction (in wif format)", @@ -114,10 +116,7 @@ func NewCommands() []cli.Command { `, Action: testInvoke, Flags: []cli.Flag{ - cli.StringFlag{ - Name: "endpoint, e", - Usage: "RPC endpoint address (like 'http://seed4.ngd.network:20332')", - }, + endpointFlag, }, }, { @@ -188,10 +187,7 @@ func NewCommands() []cli.Command { `, Action: testInvokeFunction, Flags: []cli.Flag{ - cli.StringFlag{ - Name: "endpoint, e", - Usage: "RPC endpoint address (like 'http://seed4.ngd.network:20332')", - }, + endpointFlag, }, }, { @@ -199,10 +195,7 @@ func NewCommands() []cli.Command { Usage: "Invoke compiled AVM code on the blockchain (test mode, not creating a transaction for it)", Action: testInvokeScript, Flags: []cli.Flag{ - cli.StringFlag{ - Name: "endpoint, e", - Usage: "RPC endpoint address (like 'http://seed4.ngd.network:20332')", - }, + endpointFlag, cli.StringFlag{ Name: "in, i", Usage: "Input location of the avm file that needs to be invoked", From 82c658613e589e3b9ba07fa1268bbf5f592737a0 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 29 Nov 2019 18:28:03 +0300 Subject: [PATCH 5/6] cli/smartcontract: allow float gas specification It can be useful for non-deployment transactions. --- cli/smartcontract/smart_contract.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 567576944..d788d2f2d 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -93,7 +93,7 @@ func NewCommands() []cli.Command { Name: "wif, w", Usage: "key to sign deployed transaction (in wif format)", }, - cli.IntFlag{ + cli.Float64Flag{ Name: "gas, g", Usage: "gas to pay for contract deployment", }, @@ -470,7 +470,7 @@ func contractDeploy(ctx *cli.Context) error { if len(wifStr) == 0 { return cli.NewExitError(errNoWIF, 1) } - gas := util.Fixed8FromInt64(int64(ctx.Int("gas"))) + gas := util.Fixed8FromFloat(ctx.Float64("gas")) wif, err := keys.WIFDecode(wifStr, 0) if err != nil { From 53e7807d2d7fe6556ba37b50a7b0418904d117d3 Mon Sep 17 00:00:00 2001 From: Roman Khimov Date: Fri, 29 Nov 2019 18:59:55 +0300 Subject: [PATCH 6/6] smartcontract: implement non-test invokers Do something real, yeah! --- cli/smartcontract/smart_contract.go | 130 ++++++++++++++++++++++------ 1 file changed, 104 insertions(+), 26 deletions(-) diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index d788d2f2d..f9bcbb8e8 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -36,6 +36,14 @@ var ( Name: "endpoint, e", Usage: "trusted RPC endpoint address (like 'http://localhost:20331')", } + wifFlag = cli.StringFlag{ + Name: "wif, w", + Usage: "key to sign deployed transaction (in wif format)", + } + gasFlag = cli.Float64Flag{ + Name: "gas, g", + Usage: "gas to pay for transaction", + } ) const ( @@ -89,14 +97,40 @@ func NewCommands() []cli.Command { Usage: "configuration input file (*.yml)", }, endpointFlag, - cli.StringFlag{ - Name: "wif, w", - Usage: "key to sign deployed transaction (in wif format)", - }, - cli.Float64Flag{ - Name: "gas, g", - Usage: "gas to pay for contract deployment", - }, + wifFlag, + gasFlag, + }, + }, + { + Name: "invoke", + Usage: "invoke deployed contract on the blockchain", + UsageText: "neo-go contract invoke -e endpoint -w wif [-g gas] scripthash [arguments...]", + Description: `Executes given (as a script hash) deployed script with the given arguments. + See testinvoke documentation for the details about parameters. It differs + from testinvoke in that this command sends an invocation transaction to + the network. +`, + Action: invoke, + Flags: []cli.Flag{ + endpointFlag, + wifFlag, + gasFlag, + }, + }, + { + Name: "invokefunction", + Usage: "invoke deployed contract on the blockchain", + UsageText: "neo-go contract invokefunction -e endpoint -w wif [-g gas] scripthash [method] [arguments...]", + Description: `Executes given (as a script hash) deployed script with the given method and + and arguments. See testinvokefunction documentation for the details about + parameters. It differs from testinvokefunction in that this command sends an + invocation transaction to the network. +`, + Action: invokeFunction, + Flags: []cli.Flag{ + endpointFlag, + wifFlag, + gasFlag, }, }, { @@ -304,18 +338,31 @@ func contractCompile(ctx *cli.Context) error { } func testInvoke(ctx *cli.Context) error { - return testInvokeInternal(ctx, false) + return invokeInternal(ctx, false, false) } func testInvokeFunction(ctx *cli.Context) error { - return testInvokeInternal(ctx, true) + return invokeInternal(ctx, true, false) } -func testInvokeInternal(ctx *cli.Context, withMethod bool) error { - var resp *rpc.InvokeScriptResponse - var operation string - var paramsStart = 1 - var params = make([]smartcontract.Parameter, 0) +func invoke(ctx *cli.Context) error { + return invokeInternal(ctx, false, true) +} + +func invokeFunction(ctx *cli.Context) error { + return invokeInternal(ctx, true, true) +} + +func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error { + var ( + err error + gas util.Fixed8 + operation string + params = make([]smartcontract.Parameter, 0) + paramsStart = 1 + resp *rpc.InvokeScriptResponse + wif *keys.WIF + ) endpoint := ctx.String("endpoint") if len(endpoint) == 0 { @@ -341,6 +388,14 @@ func testInvokeInternal(ctx *cli.Context, withMethod bool) error { } } + if signAndPush { + gas = util.Fixed8FromFloat(ctx.Float64("gas")) + + wif, err = getWifFromContext(ctx) + if err != nil { + return err + } + } client, err := rpc.NewClient(context.TODO(), endpoint, rpc.ClientOptions{}) if err != nil { return cli.NewExitError(err, 1) @@ -354,14 +409,28 @@ func testInvokeInternal(ctx *cli.Context, withMethod bool) error { if err != nil { return cli.NewExitError(err, 1) } + if signAndPush { + if len(resp.Result.Script) == 0 { + return cli.NewExitError(errors.New("no script returned from the RPC node"), 1) + } + script, err := hex.DecodeString(resp.Result.Script) + if err != nil { + return cli.NewExitError(fmt.Errorf("bad script returned from the RPC node: %v", err), 1) + } + txHash, err := client.SignAndPushInvocationTx(script, wif, gas) + if err != nil { + return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1) + } + fmt.Printf("Sent invocation transaction %s\n", txHash.ReverseString()) + } else { + b, err := json.MarshalIndent(resp.Result, "", " ") + if err != nil { + return cli.NewExitError(err, 1) + } - b, err := json.MarshalIndent(resp.Result, "", " ") - if err != nil { - return cli.NewExitError(err, 1) + fmt.Println(string(b)) } - fmt.Println(string(b)) - return nil } @@ -452,6 +521,19 @@ func inspect(ctx *cli.Context) error { return nil } +func getWifFromContext(ctx *cli.Context) (*keys.WIF, error) { + wifStr := ctx.String("wif") + if len(wifStr) == 0 { + return nil, cli.NewExitError(errNoWIF, 1) + } + + wif, err := keys.WIFDecode(wifStr, 0) + if err != nil { + return nil, cli.NewExitError(fmt.Errorf("bad wif: %v", err), 1) + } + return wif, nil +} + // contractDeploy deploys contract. func contractDeploy(ctx *cli.Context) error { in := ctx.String("in") @@ -466,15 +548,11 @@ func contractDeploy(ctx *cli.Context) error { if len(endpoint) == 0 { return cli.NewExitError(errNoEndpoint, 1) } - wifStr := ctx.String("wif") - if len(wifStr) == 0 { - return cli.NewExitError(errNoWIF, 1) - } gas := util.Fixed8FromFloat(ctx.Float64("gas")) - wif, err := keys.WIFDecode(wifStr, 0) + wif, err := getWifFromContext(ctx) if err != nil { - return cli.NewExitError(fmt.Errorf("bad wif: %v", err), 1) + return err } avm, err := ioutil.ReadFile(in) if err != nil {