diff --git a/cli/flags/fixed8.go b/cli/flags/fixed8.go new file mode 100644 index 000000000..5c199c16b --- /dev/null +++ b/cli/flags/fixed8.go @@ -0,0 +1,75 @@ +package flags + +import ( + "flag" + "strings" + + "github.com/nspcc-dev/neo-go/pkg/util" + "github.com/urfave/cli" +) + +// Fixed8 is a wrapper for Uint160 with flag.Value methods. +type Fixed8 struct { + Value util.Fixed8 +} + +// Fixed8Flag is a flag with type string. +type Fixed8Flag struct { + Name string + Usage string + Value Fixed8 +} + +var ( + _ flag.Value = (*Fixed8)(nil) + _ cli.Flag = Fixed8Flag{} +) + +// String implements fmt.Stringer interface. +func (a Fixed8) String() string { + return a.Value.String() +} + +// Set implements flag.Value interface. +func (a *Fixed8) Set(s string) error { + f, err := util.Fixed8FromString(s) + if err != nil { + return cli.NewExitError(err, 1) + } + a.Value = f + return nil +} + +// Fixed8 casts address to util.Fixed8. +func (a *Fixed8) Fixed8() util.Fixed8 { + return a.Value +} + +// String returns a readable representation of this value +// (for usage defaults). +func (f Fixed8Flag) String() string { + var names []string + eachName(f.Name, func(name string) { + names = append(names, getNameHelp(name)) + }) + + return strings.Join(names, ", ") + "\t" + f.Usage +} + +// GetName returns the name of the flag. +func (f Fixed8Flag) GetName() string { + return f.Name +} + +// Apply populates the flag given the flag set and environment +// Ignores errors. +func (f Fixed8Flag) Apply(set *flag.FlagSet) { + eachName(f.Name, func(name string) { + set.Var(&f.Value, name, f.Usage) + }) +} + +// Fixed8FromContext returns parsed util.Fixed8 value provided flag name. +func Fixed8FromContext(ctx *cli.Context, name string) util.Fixed8 { + return ctx.Generic(name).(*Fixed8Flag).Value.Fixed8() +} diff --git a/cli/smartcontract/smart_contract.go b/cli/smartcontract/smart_contract.go index 54efe463b..5de59e69e 100644 --- a/cli/smartcontract/smart_contract.go +++ b/cli/smartcontract/smart_contract.go @@ -51,7 +51,7 @@ var ( Name: "address, a", Usage: "address to use as transaction signee (and gas source)", } - gasFlag = cli.Float64Flag{ + gasFlag = flags.Fixed8Flag{ Name: "gas, g", Usage: "gas to add to the transaction", } @@ -411,7 +411,7 @@ func invokeInternal(ctx *cli.Context, withMethod bool, signAndPush bool) error { } if signAndPush { - gas = util.Fixed8FromFloat(ctx.Float64("gas")) + gas = flags.Fixed8FromContext(ctx, "gas") acc, err = getAccFromContext(ctx) if err != nil { return err @@ -593,7 +593,7 @@ func contractDeploy(ctx *cli.Context) error { if len(endpoint) == 0 { return cli.NewExitError(errNoEndpoint, 1) } - gas := util.Fixed8FromFloat(ctx.Float64("gas")) + gas := flags.Fixed8FromContext(ctx, "gas") acc, err := getAccFromContext(ctx) if err != nil { diff --git a/cli/wallet/nep5.go b/cli/wallet/nep5.go index dc738325b..5961db31e 100644 --- a/cli/wallet/nep5.go +++ b/cli/wallet/nep5.go @@ -85,7 +85,7 @@ func newNEP5Commands() []cli.Command { Name: "amount", Usage: "Amount of asset to send", }, - cli.StringFlag{ + flags.Fixed8Flag{ Name: "gas", Usage: "Amount of GAS to attach to a tx", }, @@ -309,19 +309,9 @@ func transferNEP5(ctx *cli.Context) error { emit.AppCall(w.BinWriter, token.Hash, false) emit.Opcode(w.BinWriter, opcode.THROWIFNOT) - var gas util.Fixed8 - if gasString := ctx.String("gas"); gasString != "" { - gas, err = util.Fixed8FromString(gasString) - if err != nil { - return cli.NewExitError(fmt.Errorf("invalid GAS amount: %v", err), 1) - } - } - + gas := flags.Fixed8FromContext(ctx, "gas") tx := transaction.NewInvocationTX(w.Bytes(), gas) - tx.Attributes = append(tx.Attributes, transaction.Attribute{ - Usage: transaction.Script, - Data: from.BytesBE(), - }) + tx.AddVerificationHash(from) if err := request.AddInputsAndUnspentsToTx(tx, fromFlag.String(), core.UtilityTokenID(), gas, c); err != nil { return cli.NewExitError(fmt.Errorf("can't add GAS to a tx: %v", err), 1) diff --git a/pkg/core/dao.go b/pkg/core/dao.go index 6a32b0f3e..7ad2a65d8 100644 --- a/pkg/core/dao.go +++ b/pkg/core/dao.go @@ -559,37 +559,26 @@ func (dao *dao) StoreAsTransaction(tx *transaction.Transaction, index uint32) er // IsDoubleSpend verifies that the input transactions are not double spent. func (dao *dao) IsDoubleSpend(tx *transaction.Transaction) bool { - if len(tx.Inputs) == 0 { - return false - } - for _, inputs := range transaction.GroupInputsByPrevHash(tx.Inputs) { - prevHash := inputs[0].PrevHash - unspent, err := dao.GetUnspentCoinState(prevHash) - if err != nil { - return false - } - for _, input := range inputs { - if int(input.PrevIndex) >= len(unspent.States) || (unspent.States[input.PrevIndex].State&state.CoinSpent) != 0 { - return true - } - } - } - return false + return dao.checkUsedInputs(tx.Inputs, state.CoinSpent) } // IsDoubleClaim verifies that given claim inputs are not already claimed by another tx. func (dao *dao) IsDoubleClaim(claim *transaction.ClaimTX) bool { - if len(claim.Claims) == 0 { + return dao.checkUsedInputs(claim.Claims, state.CoinClaimed) +} + +func (dao *dao) checkUsedInputs(inputs []transaction.Input, coin state.Coin) bool { + if len(inputs) == 0 { return false } - for _, inputs := range transaction.GroupInputsByPrevHash(claim.Claims) { + for _, inputs := range transaction.GroupInputsByPrevHash(inputs) { prevHash := inputs[0].PrevHash unspent, err := dao.GetUnspentCoinState(prevHash) if err != nil { return true } for _, input := range inputs { - if int(input.PrevIndex) >= len(unspent.States) || (unspent.States[input.PrevIndex].State&state.CoinClaimed) != 0 { + if int(input.PrevIndex) >= len(unspent.States) || (unspent.States[input.PrevIndex].State&coin) != 0 { return true } } diff --git a/pkg/core/transaction/transaction.go b/pkg/core/transaction/transaction.go index da6704e8e..2bf7108ee 100644 --- a/pkg/core/transaction/transaction.go +++ b/pkg/core/transaction/transaction.go @@ -91,6 +91,14 @@ func (t *Transaction) AddInput(in *Input) { t.Inputs = append(t.Inputs, *in) } +// AddVerificationHash adds a script attribute for transaction verification. +func (t *Transaction) AddVerificationHash(addr util.Uint160) { + t.Attributes = append(t.Attributes, Attribute{ + Usage: Script, + Data: addr.BytesBE(), + }) +} + // DecodeBinary implements Serializable interface. func (t *Transaction) DecodeBinary(br *io.BinReader) { t.Type = TXType(br.ReadB()) diff --git a/pkg/rpc/client/rpc.go b/pkg/rpc/client/rpc.go index 893a2dddc..5da25f9af 100644 --- a/pkg/rpc/client/rpc.go +++ b/pkg/rpc/client/rpc.go @@ -515,11 +515,7 @@ func (c *Client) SignAndPushInvocationTx(script []byte, acc *wallet.Account, sys 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(), - }) + tx.AddVerificationHash(addr) } if err = acc.SignTx(tx); err != nil { diff --git a/pkg/rpc/request/txBuilder.go b/pkg/rpc/request/txBuilder.go index f841a71b3..cd7cd75fc 100644 --- a/pkg/rpc/request/txBuilder.go +++ b/pkg/rpc/request/txBuilder.go @@ -38,11 +38,7 @@ func CreateRawContractTransaction(params ContractTxParams) (*transaction.Transac if toAddressHash, err = address.StringToUint160(toAddress); err != nil { return nil, errs.Wrapf(err, "Failed to take script hash from address: %v", toAddress) } - tx.Attributes = append(tx.Attributes, - transaction.Attribute{ - Usage: transaction.Script, - Data: fromAddressHash.BytesBE(), - }) + tx.AddVerificationHash(fromAddressHash) if err = AddInputsAndUnspentsToTx(tx, fromAddress, assetID, amount, balancer); err != nil { return nil, errs.Wrap(err, "failed to add inputs and unspents to transaction")