forked from TrueCloudLab/neoneo-go
Merge pull request #747 from nspcc-dev/fix-contract-deploy-gas-parameter
Use wallet in smartcontract deploy/invoke commands and calculate GAS properly
This commit is contained in:
commit
734778c1f9
5 changed files with 120 additions and 59 deletions
|
@ -10,26 +10,31 @@ import (
|
|||
"io/ioutil"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"strings"
|
||||
"syscall"
|
||||
|
||||
"github.com/go-yaml/yaml"
|
||||
"github.com/nspcc-dev/neo-go/cli/flags"
|
||||
"github.com/nspcc-dev/neo-go/pkg/compiler"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/hash"
|
||||
"github.com/nspcc-dev/neo-go/pkg/crypto/keys"
|
||||
"github.com/nspcc-dev/neo-go/pkg/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/client"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/request"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
||||
"github.com/nspcc-dev/neo-go/pkg/smartcontract"
|
||||
"github.com/nspcc-dev/neo-go/pkg/util"
|
||||
"github.com/nspcc-dev/neo-go/pkg/vm"
|
||||
"github.com/nspcc-dev/neo-go/pkg/wallet"
|
||||
"github.com/pkg/errors"
|
||||
"github.com/urfave/cli"
|
||||
"golang.org/x/crypto/ssh/terminal"
|
||||
)
|
||||
|
||||
var (
|
||||
errNoEndpoint = errors.New("no RPC endpoint specified, use option '--endpoint' or '-e'")
|
||||
errNoInput = errors.New("no input file was found, specify an input file with the '--in or -i' flag")
|
||||
errNoConfFile = errors.New("no config file was found, specify a config file with the '--config' or '-c' flag")
|
||||
errNoWIF = errors.New("no WIF parameter found, specify it with the '--wif or -w' flag")
|
||||
errNoWallet = errors.New("no wallet parameter found, specify it with the '--wallet or -w' flag")
|
||||
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")
|
||||
|
@ -38,13 +43,17 @@ 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)",
|
||||
walletFlag = cli.StringFlag{
|
||||
Name: "wallet, w",
|
||||
Usage: "wallet to use to get the key for transaction signing",
|
||||
}
|
||||
addressFlag = flags.AddressFlag{
|
||||
Name: "address, a",
|
||||
Usage: "address to use as transaction signee (and gas source)",
|
||||
}
|
||||
gasFlag = cli.Float64Flag{
|
||||
Name: "gas, g",
|
||||
Usage: "gas to pay for transaction",
|
||||
Usage: "gas to add to the transaction",
|
||||
}
|
||||
)
|
||||
|
||||
|
@ -88,6 +97,12 @@ func NewCommands() []cli.Command {
|
|||
{
|
||||
Name: "deploy",
|
||||
Usage: "deploy a smart contract (.avm with description)",
|
||||
Description: `Deploys given contract into the chain. The gas parameter is for additional
|
||||
gas to be added as a network fee to prioritize the transaction. It may also
|
||||
be required to add that to satisfy chain's policy regarding transaction size
|
||||
and the minimum size fee (so if transaction send fails, try adding 0.001 GAS
|
||||
to it).
|
||||
`,
|
||||
Action: contractDeploy,
|
||||
Flags: []cli.Flag{
|
||||
cli.StringFlag{
|
||||
|
@ -99,14 +114,15 @@ func NewCommands() []cli.Command {
|
|||
Usage: "configuration input file (*.yml)",
|
||||
},
|
||||
endpointFlag,
|
||||
wifFlag,
|
||||
walletFlag,
|
||||
addressFlag,
|
||||
gasFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "invoke",
|
||||
Usage: "invoke deployed contract on the blockchain",
|
||||
UsageText: "neo-go contract invoke -e endpoint -w wif [-g gas] scripthash [arguments...]",
|
||||
UsageText: "neo-go contract invoke -e endpoint -w wallet [-a address] [-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
|
||||
|
@ -115,14 +131,15 @@ func NewCommands() []cli.Command {
|
|||
Action: invoke,
|
||||
Flags: []cli.Flag{
|
||||
endpointFlag,
|
||||
wifFlag,
|
||||
walletFlag,
|
||||
addressFlag,
|
||||
gasFlag,
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "invokefunction",
|
||||
Usage: "invoke deployed contract on the blockchain",
|
||||
UsageText: "neo-go contract invokefunction -e endpoint -w wif [-g gas] scripthash [method] [arguments...]",
|
||||
UsageText: "neo-go contract invokefunction -e endpoint -w wallet [-a address] [-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
|
||||
|
@ -131,7 +148,8 @@ func NewCommands() []cli.Command {
|
|||
Action: invokeFunction,
|
||||
Flags: []cli.Flag{
|
||||
endpointFlag,
|
||||
wifFlag,
|
||||
walletFlag,
|
||||
addressFlag,
|
||||
gasFlag,
|
||||
},
|
||||
},
|
||||
|
@ -365,7 +383,7 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error {
|
|||
params = make([]smartcontract.Parameter, 0)
|
||||
paramsStart = 1
|
||||
resp *result.Invoke
|
||||
wif *keys.WIF
|
||||
acc *wallet.Account
|
||||
)
|
||||
|
||||
endpoint := ctx.String("endpoint")
|
||||
|
@ -394,8 +412,7 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error {
|
|||
|
||||
if signAndPush {
|
||||
gas = util.Fixed8FromFloat(ctx.Float64("gas"))
|
||||
|
||||
wif, err = getWifFromContext(ctx)
|
||||
acc, err = getAccFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -421,7 +438,7 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error {
|
|||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("bad script returned from the RPC node: %v", err), 1)
|
||||
}
|
||||
txHash, err := c.SignAndPushInvocationTx(script, wif, gas)
|
||||
txHash, err := c.SignAndPushInvocationTx(script, acc, 0, gas)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1)
|
||||
}
|
||||
|
@ -525,17 +542,41 @@ 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)
|
||||
func getAccFromContext(ctx *cli.Context) (*wallet.Account, error) {
|
||||
var addr util.Uint160
|
||||
|
||||
wPath := ctx.String("wallet")
|
||||
if len(wPath) == 0 {
|
||||
return nil, cli.NewExitError(errNoWallet, 1)
|
||||
}
|
||||
|
||||
wif, err := keys.WIFDecode(wifStr, 0)
|
||||
wall, err := wallet.NewWalletFromFile(wPath)
|
||||
if err != nil {
|
||||
return nil, cli.NewExitError(fmt.Errorf("bad wif: %v", err), 1)
|
||||
return nil, cli.NewExitError(err, 1)
|
||||
}
|
||||
return wif, nil
|
||||
addrFlag := ctx.Generic("address").(*flags.Address)
|
||||
if addrFlag.IsSet {
|
||||
addr = addrFlag.Uint160()
|
||||
} else {
|
||||
addr = wall.GetChangeAddress()
|
||||
}
|
||||
acc := wall.GetAccount(addr)
|
||||
if acc == nil {
|
||||
return nil, cli.NewExitError(fmt.Errorf("wallet contains no account for '%s'", address.Uint160ToString(addr)), 1)
|
||||
}
|
||||
|
||||
fmt.Printf("Enter account %s password > ", address.Uint160ToString(addr))
|
||||
rawPass, err := terminal.ReadPassword(syscall.Stdin)
|
||||
fmt.Println()
|
||||
if err != nil {
|
||||
return nil, cli.NewExitError(err, 1)
|
||||
}
|
||||
pass := strings.TrimRight(string(rawPass), "\n")
|
||||
err = acc.Decrypt(pass)
|
||||
if err != nil {
|
||||
return nil, cli.NewExitError(err, 1)
|
||||
}
|
||||
return acc, nil
|
||||
}
|
||||
|
||||
// contractDeploy deploys contract.
|
||||
|
@ -554,7 +595,7 @@ func contractDeploy(ctx *cli.Context) error {
|
|||
}
|
||||
gas := util.Fixed8FromFloat(ctx.Float64("gas"))
|
||||
|
||||
wif, err := getWifFromContext(ctx)
|
||||
acc, err := getAccFromContext(ctx)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
@ -583,7 +624,9 @@ func contractDeploy(ctx *cli.Context) error {
|
|||
return cli.NewExitError(fmt.Errorf("failed to create deployment script: %v", err), 1)
|
||||
}
|
||||
|
||||
txHash, err := c.SignAndPushInvocationTx(txScript, wif, gas)
|
||||
sysfee := smartcontract.GetDeploymentPrice(request.DetailsToSCProperties(&conf.Contract))
|
||||
|
||||
txHash, err := c.SignAndPushInvocationTx(txScript, acc, sysfee, gas)
|
||||
if err != nil {
|
||||
return cli.NewExitError(fmt.Errorf("failed to push invocation tx: %v", err), 1)
|
||||
}
|
||||
|
|
|
@ -93,18 +93,7 @@ func getSyscallPrice(v *vm.VM, id uint32) util.Fixed8 {
|
|||
arg := estack.Peek(1).BigInt().Int64()
|
||||
return util.Fixed8FromInt64(arg * 5000)
|
||||
case neoContractCreate, neoContractMigrate, antSharesContractCreate, antSharesContractMigrate:
|
||||
fee := int64(100)
|
||||
props := smartcontract.PropertyState(estack.Peek(3).BigInt().Int64())
|
||||
|
||||
if props&smartcontract.HasStorage != 0 {
|
||||
fee += 400
|
||||
}
|
||||
|
||||
if props&smartcontract.HasDynamicInvoke != 0 {
|
||||
fee += 500
|
||||
}
|
||||
|
||||
return util.Fixed8FromInt64(fee)
|
||||
return smartcontract.GetDeploymentPrice(smartcontract.PropertyState(estack.Peek(3).BigInt().Int64()))
|
||||
case systemStoragePut, systemStoragePutEx, neoStoragePut, antSharesStoragePut:
|
||||
// price for storage PUT is 1 GAS per 1 KiB
|
||||
keySize := len(estack.Peek(1).Bytes())
|
||||
|
|
|
@ -6,7 +6,7 @@ import (
|
|||
"github.com/nspcc-dev/neo-go/pkg/core"
|
||||
"github.com/nspcc-dev/neo-go/pkg/core/block"
|
||||
"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/encoding/address"
|
||||
"github.com/nspcc-dev/neo-go/pkg/io"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/request"
|
||||
"github.com/nspcc-dev/neo-go/pkg/rpc/response/result"
|
||||
|
@ -499,24 +499,30 @@ func (c *Client) TransferAsset(asset util.Uint256, address string, amount util.F
|
|||
// 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) {
|
||||
func (c *Client) SignAndPushInvocationTx(script []byte, acc *wallet.Account, sysfee util.Fixed8, netfee util.Fixed8) (util.Uint256, error) {
|
||||
var txHash util.Uint256
|
||||
var err error
|
||||
|
||||
tx := transaction.NewInvocationTX(script, gas)
|
||||
|
||||
fromAddress := wif.PrivateKey.Address()
|
||||
tx := transaction.NewInvocationTX(script, sysfee)
|
||||
gas := sysfee + netfee
|
||||
|
||||
if gas > 0 {
|
||||
if err = request.AddInputsAndUnspentsToTx(tx, fromAddress, core.UtilityTokenID(), gas, c); err != nil {
|
||||
if err = request.AddInputsAndUnspentsToTx(tx, acc.Address, core.UtilityTokenID(), gas, c); err != nil {
|
||||
return txHash, errors.Wrap(err, "failed to add inputs and unspents to transaction")
|
||||
}
|
||||
} else {
|
||||
addr, err := address.StringToUint160(acc.Address)
|
||||
if err != nil {
|
||||
return txHash, errors.Wrap(err, "failed to get address")
|
||||
}
|
||||
tx.Attributes = append(tx.Attributes,
|
||||
transaction.Attribute{
|
||||
Usage: transaction.Script,
|
||||
Data: addr.BytesBE(),
|
||||
})
|
||||
}
|
||||
|
||||
acc, err := wallet.NewAccountFromWIF(wif.S)
|
||||
if err != nil {
|
||||
return txHash, err
|
||||
} else if err = acc.SignTx(tx); err != nil {
|
||||
if err = acc.SignTx(tx); err != nil {
|
||||
return txHash, errors.Wrap(err, "failed to sign tx")
|
||||
}
|
||||
txHash = tx.Hash()
|
||||
|
|
|
@ -80,17 +80,10 @@ func AddInputsAndUnspentsToTx(tx *transaction.Transaction, addr string, assetID
|
|||
return nil
|
||||
}
|
||||
|
||||
// CreateDeploymentScript returns a script that deploys given smart contract
|
||||
// with its metadata.
|
||||
func CreateDeploymentScript(avm []byte, contract *ContractDetails) ([]byte, error) {
|
||||
// DetailsToSCProperties extract the fields needed from ContractDetails
|
||||
// and converts them to smartcontract.PropertyState.
|
||||
func DetailsToSCProperties(contract *ContractDetails) smartcontract.PropertyState {
|
||||
var props smartcontract.PropertyState
|
||||
|
||||
script := io.NewBufBinWriter()
|
||||
emit.Bytes(script.BinWriter, []byte(contract.Description))
|
||||
emit.Bytes(script.BinWriter, []byte(contract.Email))
|
||||
emit.Bytes(script.BinWriter, []byte(contract.Author))
|
||||
emit.Bytes(script.BinWriter, []byte(contract.Version))
|
||||
emit.Bytes(script.BinWriter, []byte(contract.ProjectName))
|
||||
if contract.HasStorage {
|
||||
props |= smartcontract.HasStorage
|
||||
}
|
||||
|
@ -100,7 +93,19 @@ func CreateDeploymentScript(avm []byte, contract *ContractDetails) ([]byte, erro
|
|||
if contract.IsPayable {
|
||||
props |= smartcontract.IsPayable
|
||||
}
|
||||
emit.Int(script.BinWriter, int64(props))
|
||||
return props
|
||||
}
|
||||
|
||||
// CreateDeploymentScript returns a script that deploys given smart contract
|
||||
// with its metadata.
|
||||
func CreateDeploymentScript(avm []byte, contract *ContractDetails) ([]byte, error) {
|
||||
script := io.NewBufBinWriter()
|
||||
emit.Bytes(script.BinWriter, []byte(contract.Description))
|
||||
emit.Bytes(script.BinWriter, []byte(contract.Email))
|
||||
emit.Bytes(script.BinWriter, []byte(contract.Author))
|
||||
emit.Bytes(script.BinWriter, []byte(contract.Version))
|
||||
emit.Bytes(script.BinWriter, []byte(contract.ProjectName))
|
||||
emit.Int(script.BinWriter, int64(DetailsToSCProperties(contract)))
|
||||
emit.Int(script.BinWriter, int64(contract.ReturnType))
|
||||
params := make([]byte, len(contract.Parameters))
|
||||
for k := range contract.Parameters {
|
||||
|
|
18
pkg/smartcontract/deployment_price.go
Normal file
18
pkg/smartcontract/deployment_price.go
Normal file
|
@ -0,0 +1,18 @@
|
|||
package smartcontract
|
||||
|
||||
import "github.com/nspcc-dev/neo-go/pkg/util"
|
||||
|
||||
// GetDeploymentPrice returns contract deployment price based on its properties.
|
||||
func GetDeploymentPrice(props PropertyState) util.Fixed8 {
|
||||
fee := int64(100)
|
||||
|
||||
if props&HasStorage != 0 {
|
||||
fee += 400
|
||||
}
|
||||
|
||||
if props&HasDynamicInvoke != 0 {
|
||||
fee += 500
|
||||
}
|
||||
|
||||
return util.Fixed8FromInt64(fee)
|
||||
}
|
Loading…
Reference in a new issue