mirror of
https://github.com/kaspanet/kaspad.git
synced 2025-11-24 06:25:55 +00:00
Deal with SendAmount as strings all the way
This commit is contained in:
parent
471b7b2f16
commit
1a6e883ece
@ -57,7 +57,7 @@ type sendConfig struct {
|
||||
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"`
|
||||
ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"`
|
||||
FromAddresses []string `long:"from-address" short:"a" description:"Specific public address to send Kaspa from. Use multiple times to accept several addresses" required:"false"`
|
||||
SendAmount float64 `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)"`
|
||||
SendAmount string `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)"`
|
||||
IsSendAll bool `long:"send-all" description:"Send all the Kaspa in the wallet (mutually exclusive with --send-amount)"`
|
||||
UseExistingChangeAddress bool `long:"use-existing-change-address" short:"u" description:"Will use an existing change address (in case no change address was ever used, it will use a new one)"`
|
||||
Verbose bool `long:"show-serialized" short:"s" description:"Show a list of hex encoded sent transactions"`
|
||||
@ -74,7 +74,7 @@ type createUnsignedTransactionConfig struct {
|
||||
DaemonAddress string `long:"daemonaddress" short:"d" description:"Wallet daemon server to connect to"`
|
||||
ToAddress string `long:"to-address" short:"t" description:"The public address to send Kaspa to" required:"true"`
|
||||
FromAddresses []string `long:"from-address" short:"a" description:"Specific public address to send Kaspa from. Use multiple times to accept several addresses" required:"false"`
|
||||
SendAmount float64 `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)"`
|
||||
SendAmount string `long:"send-amount" short:"v" description:"An amount to send in Kaspa (e.g. 1234.12345678)"`
|
||||
IsSendAll bool `long:"send-all" description:"Send all the Kaspa in the wallet (mutually exclusive with --send-amount)"`
|
||||
UseExistingChangeAddress bool `long:"use-existing-change-address" short:"u" description:"Will use an existing change address (in case no change address was ever used, it will use a new one)"`
|
||||
config.NetworkFlags
|
||||
@ -296,8 +296,8 @@ func parseCommandLine() (subCommand string, config interface{}) {
|
||||
}
|
||||
|
||||
func validateCreateUnsignedTransactionConf(conf *createUnsignedTransactionConfig) error {
|
||||
if (!conf.IsSendAll && conf.SendAmount == 0) ||
|
||||
(conf.IsSendAll && conf.SendAmount > 0) {
|
||||
if (!conf.IsSendAll && conf.SendAmount == "") ||
|
||||
(conf.IsSendAll && conf.SendAmount > "") {
|
||||
|
||||
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
|
||||
}
|
||||
@ -305,8 +305,8 @@ func validateCreateUnsignedTransactionConf(conf *createUnsignedTransactionConfig
|
||||
}
|
||||
|
||||
func validateSendConfig(conf *sendConfig) error {
|
||||
if (!conf.IsSendAll && conf.SendAmount == 0) ||
|
||||
(conf.IsSendAll && conf.SendAmount > 0) {
|
||||
if (!conf.IsSendAll && conf.SendAmount == "") ||
|
||||
(conf.IsSendAll && conf.SendAmount != "") {
|
||||
|
||||
return errors.New("exactly one of '--send-amount' or '--all' must be specified")
|
||||
}
|
||||
|
||||
@ -7,7 +7,7 @@ import (
|
||||
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/utils"
|
||||
)
|
||||
|
||||
func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
|
||||
@ -20,7 +20,12 @@ func createUnsignedTransaction(conf *createUnsignedTransactionConfig) error {
|
||||
ctx, cancel := context.WithTimeout(context.Background(), daemonTimeout)
|
||||
defer cancel()
|
||||
|
||||
sendAmountSompi := uint64(conf.SendAmount * constants.SompiPerKaspa)
|
||||
sendAmountSompi, err := utils.KasToSompi(conf.SendAmount)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
response, err := daemonClient.CreateUnsignedTransactions(ctx, &pb.CreateUnsignedTransactionsRequest{
|
||||
From: conf.FromAddresses,
|
||||
Address: conf.ToAddress,
|
||||
|
||||
@ -4,13 +4,13 @@ import (
|
||||
"context"
|
||||
"fmt"
|
||||
"os"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/client"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/daemon/pb"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/keys"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/libkaspawallet"
|
||||
"github.com/kaspanet/kaspad/cmd/kaspawallet/utils"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
@ -35,7 +35,13 @@ func send(conf *sendConfig) error {
|
||||
|
||||
var sendAmountSompi uint64
|
||||
if !conf.IsSendAll {
|
||||
sendAmountSompi = kasToSompi(conf.SendAmount)
|
||||
parsedAmountSompi, err := utils.KasToSompi(conf.SendAmount)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
sendAmountSompi = parsedAmountSompi
|
||||
}
|
||||
|
||||
createUnsignedTransactionsResponse, err :=
|
||||
@ -99,15 +105,3 @@ func send(conf *sendConfig) error {
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
/**
|
||||
*/
|
||||
func kasToSompi(amount float64) uint64 {
|
||||
amountInStr := fmt.Sprintf("%.8f", amount)
|
||||
|
||||
parts := strings.Split(amountInStr, ".")
|
||||
|
||||
convertedAmount, _ := strconv.ParseUint(strings.Join(parts, ""), 10, 64)
|
||||
|
||||
return convertedAmount
|
||||
}
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
package main
|
||||
|
||||
import "testing"
|
||||
|
||||
func TestKasToSompi(t *testing.T) {
|
||||
type testVector struct {
|
||||
originalAmount float64
|
||||
convertedAmount uint64
|
||||
}
|
||||
|
||||
testVectors := []testVector{
|
||||
{originalAmount: 0, convertedAmount: 0},
|
||||
{originalAmount: 1, convertedAmount: 100000000},
|
||||
{originalAmount: 33184.1489732, convertedAmount: 3318414897320},
|
||||
{originalAmount: 21.35808032, convertedAmount: 2135808032},
|
||||
{originalAmount: 184467440737.09551615, convertedAmount: 18446744073709551615},
|
||||
}
|
||||
|
||||
for _, currentTestVector := range testVectors {
|
||||
if kasToSompi(currentTestVector.originalAmount) != currentTestVector.convertedAmount {
|
||||
t.Fail()
|
||||
t.Logf("Expected %.8f, to convert to %d. Got: %d", currentTestVector.originalAmount, currentTestVector.convertedAmount, kasToSompi(currentTestVector.originalAmount))
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -2,6 +2,8 @@ package utils
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"github.com/kaspanet/kaspad/domain/consensus/utils/constants"
|
||||
)
|
||||
@ -14,3 +16,28 @@ func FormatKas(amount uint64) string {
|
||||
}
|
||||
return res
|
||||
}
|
||||
|
||||
// Takes in a string representation of the Kas value to convert to Sompi
|
||||
func KasToSompi(amount string) (uint64, error) {
|
||||
err := ValidateAmountFormat(amount)
|
||||
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// after validation, amount can only be either an int OR
|
||||
// a float with an int component and decimal places
|
||||
parts := strings.Split(amount, ".")
|
||||
amountStr := ""
|
||||
|
||||
if len(parts) == 2 {
|
||||
amountStr = fmt.Sprintf("%s%-*s", parts[0], 8, parts[1]) // Padded with spaces at the end to fill for missing decimals: Sample "0.01234 "
|
||||
amountStr = strings.ReplaceAll(amountStr, " ", "0") // Make the spaces be 0s. Sample "0.012340000"
|
||||
} else {
|
||||
amountStr = fmt.Sprintf("%s00000000", parts[0])
|
||||
}
|
||||
|
||||
convertedAmount, err := strconv.ParseUint(amountStr, 10, 64)
|
||||
|
||||
return convertedAmount, err
|
||||
}
|
||||
|
||||
44
cmd/kaspawallet/utils/format_kas_test.go
Normal file
44
cmd/kaspawallet/utils/format_kas_test.go
Normal file
@ -0,0 +1,44 @@
|
||||
package utils
|
||||
|
||||
import "testing"
|
||||
|
||||
// Takes in a string representation of the Kas value to convert to Sompi
|
||||
func TestKasToSompi(t *testing.T) {
|
||||
type testVector struct {
|
||||
originalAmount string
|
||||
convertedAmount uint64
|
||||
}
|
||||
|
||||
validCases := []testVector{
|
||||
{originalAmount: "0", convertedAmount: 0},
|
||||
{originalAmount: "1", convertedAmount: 100000000},
|
||||
{originalAmount: "33184.1489732", convertedAmount: 3318414897320},
|
||||
{originalAmount: "21.35808032", convertedAmount: 2135808032},
|
||||
{originalAmount: "184467440737.09551615", convertedAmount: 18446744073709551615},
|
||||
}
|
||||
|
||||
for _, currentTestVector := range validCases {
|
||||
convertedAmount, err := KasToSompi(currentTestVector.originalAmount)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
} else if convertedAmount != currentTestVector.convertedAmount {
|
||||
t.Errorf("Expected %s, to convert to %d. Got: %d", currentTestVector.originalAmount, currentTestVector.convertedAmount, convertedAmount)
|
||||
}
|
||||
}
|
||||
|
||||
invalidCases := []string{
|
||||
"184467440737.09551616", // Bigger than max uint64
|
||||
"-1",
|
||||
"a",
|
||||
"",
|
||||
}
|
||||
|
||||
for _, currentTestVector := range invalidCases {
|
||||
_, err := KasToSompi(currentTestVector)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error but succeeded validation for test case %s", currentTestVector)
|
||||
}
|
||||
}
|
||||
}
|
||||
34
cmd/kaspawallet/utils/validate_amount.go
Normal file
34
cmd/kaspawallet/utils/validate_amount.go
Normal file
@ -0,0 +1,34 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
/**
|
||||
* 1. May be an integer (no decimal components)
|
||||
* 2. May be float with up to 8 decimal places
|
||||
*/
|
||||
func ValidateAmountFormat(amount string) error {
|
||||
// Check whether it's an integer, or a float with max 8 digits
|
||||
match, err := regexp.MatchString("^\\d{1,19}(.\\d{0,8})?$", amount)
|
||||
|
||||
if !match {
|
||||
return errors.Errorf("Invalid send amount")
|
||||
}
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// If it parses properly, then this is valid
|
||||
_, err = strconv.ParseFloat(amount, 64)
|
||||
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
39
cmd/kaspawallet/utils/validate_amount_test.go
Normal file
39
cmd/kaspawallet/utils/validate_amount_test.go
Normal file
@ -0,0 +1,39 @@
|
||||
package utils
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
func TestValidateAmountFormat(t *testing.T) {
|
||||
validCases := []string{
|
||||
"0",
|
||||
"1",
|
||||
"1.0",
|
||||
"0.1",
|
||||
"0.12345678",
|
||||
}
|
||||
|
||||
for _, testCase := range validCases {
|
||||
err := ValidateAmountFormat(testCase)
|
||||
|
||||
if err != nil {
|
||||
t.Error(err)
|
||||
}
|
||||
}
|
||||
|
||||
invalidCases := []string{
|
||||
"",
|
||||
"a",
|
||||
"-1",
|
||||
"0.123456789", // 9 decimal digits
|
||||
".1", // decimal but no integer component
|
||||
}
|
||||
|
||||
for _, testCase := range invalidCases {
|
||||
err := ValidateAmountFormat(testCase)
|
||||
|
||||
if err == nil {
|
||||
t.Errorf("Expected an error but succeeded validation for test case %s", testCase)
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user