From 2a4e2d75bfbaa5a7e86023991b2cd295d48d5d50 Mon Sep 17 00:00:00 2001 From: Angelo De Caro Date: Mon, 19 Sep 2022 14:19:12 +0200 Subject: [PATCH] double check the use of NewQuantityFromUInt64 #218 (#381) Signed-off-by: Angelo De Caro --- integration/token/fungible/views/accept.go | 16 +++++------ integration/token/fungible/views/issue.go | 6 +++- integration/token/fungible/views/transfer.go | 9 ++++-- samples/fungible/views/accept.go | 8 +++--- samples/fungible/views/issue.go | 6 +++- token/core/fabtoken/issuer.go | 7 ++++- token/request.go | 18 +++++++----- token/services/nfttx/qe.go | 2 +- token/token/quantity.go | 29 ++++++++++++++++++-- token/token/quantity_test.go | 2 +- 10 files changed, 73 insertions(+), 30 deletions(-) diff --git a/integration/token/fungible/views/accept.go b/integration/token/fungible/views/accept.go index ea3c386b0..0c00f36dd 100644 --- a/integration/token/fungible/views/accept.go +++ b/integration/token/fungible/views/accept.go @@ -24,13 +24,13 @@ func (a *AcceptCashView) Call(context view.Context) (interface{}, error) { id, err := ttx.RespondRequestRecipientIdentityUsingWallet(context, "") assert.NoError(err, "failed to respond to identity request") - // At some point, the recipient receives the token transaction that in the mean time has been assembled + // At some point, the recipient receives the token transaction that in the meantime has been assembled tx, err := ttx.ReceiveTransaction(context) assert.NoError(err, "failed to receive tokens") // The recipient can perform any check on the transaction as required by the business process // In particular, here, the recipient checks that the transaction contains at least one output, and - // that there is at least one output that names the recipient. (The recipient is receiving something. + // that there is at least one output that names the recipient.(The recipient is receiving something). outputs, err := tx.Outputs() assert.NoError(err, "failed getting outputs") assert.True(outputs.Count() > 0) @@ -39,13 +39,13 @@ func (a *AcceptCashView) Call(context view.Context) (interface{}, error) { // The recipient here is checking that, for each type of token she is receiving, // she does not hold already more than 3000 units of that type. // Just a fancy query to show the capabilities of the services we are using. + precision := tx.TokenService().PublicParametersManager().Precision() for _, output := range outputs.ByRecipient(id).Outputs() { unspentTokens, err := ttx.MyWallet(context).ListUnspentTokens(ttx.WithType(output.Type)) assert.NoError(err, "failed retrieving the unspent tokens for type [%s]", output.Type) - assert.True( - unspentTokens.Sum(tx.TokenService().PublicParametersManager().Precision()).Cmp(token2.NewQuantityFromUInt64(3000)) <= 0, - "cannot have more than 3000 unspent quantity for type [%s]", output.Type, - ) + upperBound, err := token2.UInt64ToQuantity(3000, precision) + assert.NoError(err, "failed to convert to quantity") + assert.True(unspentTokens.Sum(precision).Cmp(upperBound) <= 0, "cannot have more than 3000 unspent quantity for type [%s]", output.Type) } // If everything is fine, the recipient accepts and sends back her signature. @@ -89,13 +89,13 @@ func (a *AcceptPreparedCashView) Call(context view.Context) (interface{}, error) id, err := ttx.RespondRequestRecipientIdentityUsingWallet(context, "") assert.NoError(err, "failed to respond to identity request") - // At some point, the recipient receives the token transaction that in the mean time has been assembled + // At some point, the recipient receives the token transaction that in the meantime has been assembled tx, err := ttx.ReceiveTransaction(context) assert.NoError(err, "failed to receive tokens") // The recipient can perform any check on the transaction as required by the business process // In particular, here, the recipient checks that the transaction contains at least one output, and - // that there is at least one output that names the recipient. (The recipient is receiving something. + // that there is at least one output that names the recipient (The recipient is receiving something). outputs, err := tx.Outputs() assert.NoError(err, "failed getting outputs") assert.True(outputs.Count() > 0) diff --git a/integration/token/fungible/views/issue.go b/integration/token/fungible/views/issue.go index 11821f6d5..e9185ef22 100644 --- a/integration/token/fungible/views/issue.go +++ b/integration/token/fungible/views/issue.go @@ -62,7 +62,11 @@ func (p *IssueCashView) Call(context view.Context) (interface{}, error) { fmt.Printf("History [%s,%s]<[241]?\n", history.Sum(precision).ToBigInt().Text(10), p.TokenType) // Fail if the sum of the issued tokens and the current quest is larger than 241 - assert.True(history.Sum(precision).Add(token2.NewQuantityFromUInt64(p.Quantity)).Cmp(token2.NewQuantityFromUInt64(241)) <= 0) + q, err := token2.UInt64ToQuantity(p.Quantity, precision) + assert.NoError(err, "failed to covert to quantity") + upperBound, err := token2.UInt64ToQuantity(241, precision) + assert.NoError(err, "failed to covert to quantity") + assert.True(history.Sum(precision).Add(q).Cmp(upperBound) <= 0) } // At this point, the issuer is ready to prepare the token transaction. diff --git a/integration/token/fungible/views/transfer.go b/integration/token/fungible/views/transfer.go index 4dfb6984c..b77edab32 100644 --- a/integration/token/fungible/views/transfer.go +++ b/integration/token/fungible/views/transfer.go @@ -156,6 +156,10 @@ func (t *TransferWithSelectorView) Call(context view.Context) (interface{}, erro assert.NotNil(senderWallet, "sender wallet [%s] not found", t.Wallet) // If no specific tokens are requested, then a custom token selection process start + precision := token2.GetManagementService(context).PublicParametersManager().Precision() + amount, err := token.UInt64ToQuantity(t.Amount, precision) + assert.NoError(err, "failed to convert to quantity") + if len(t.TokenIDs) == 0 { // The sender uses the default token selector each transaction comes equipped with selector, err := tx.Selector() @@ -171,7 +175,7 @@ func (t *TransferWithSelectorView) Call(context view.Context) (interface{}, erro // Select the request amount of tokens of the given type ids, sum, err = selector.Select( ttx.GetWallet(context, t.Wallet), - token.NewQuantityFromUInt64(t.Amount).Decimal(), + amount.Decimal(), t.Type, ) // If an error occurs and retry has been asked, then wait first a bit @@ -207,7 +211,6 @@ func (t *TransferWithSelectorView) Call(context view.Context) (interface{}, erro assert.NoError(err, "failed getting tokens from ids") // Then, the sender double check that what returned by the selector is correct - precision := tx.TokenService().PublicParametersManager().Precision() recomputedSum := token.NewZeroQuantity(precision) for _, tok := range tokens { // Is the token of the right type? @@ -220,7 +223,7 @@ func (t *TransferWithSelectorView) Call(context view.Context) (interface{}, erro // Is the recomputed sum correct? assert.True(sum.Cmp(recomputedSum) == 0, "sums do not match") // Is the amount selected equal or larger than what requested? - assert.False(sum.Cmp(token.NewQuantityFromUInt64(t.Amount)) < 0, "if this point is reached, funds are sufficients") + assert.False(sum.Cmp(amount) < 0, "if this point is reached, funds are sufficient") t.TokenIDs = ids } diff --git a/samples/fungible/views/accept.go b/samples/fungible/views/accept.go index 43ca114c3..185b7233b 100644 --- a/samples/fungible/views/accept.go +++ b/samples/fungible/views/accept.go @@ -39,13 +39,13 @@ func (a *AcceptCashView) Call(context view.Context) (interface{}, error) { // The recipient here is checking that, for each type of token she is receiving, // she does not hold already more than 3000 units of that type. // Just a fancy query to show the capabilities of the services we are using. + precision := tx.TokenService().PublicParametersManager().Precision() for _, output := range outputs.ByRecipient(id).Outputs() { unspentTokens, err := ttx.MyWallet(context).ListUnspentTokens(ttx.WithType(output.Type)) assert.NoError(err, "failed retrieving the unspent tokens for type [%s]", output.Type) - assert.True( - unspentTokens.Sum(tx.TokenService().PublicParametersManager().Precision()).Cmp(token2.NewQuantityFromUInt64(3000)) <= 0, - "cannot have more than 3000 unspent quantity for type [%s]", output.Type, - ) + upperLimit, err := token2.UInt64ToQuantity(3000, precision) + assert.NoError(err, "failed to convert to quantity") + assert.True(unspentTokens.Sum(precision).Cmp(upperLimit) <= 0, "cannot have more than 3000 unspent quantity for type [%s]", output.Type) } // If everything is fine, the recipient accepts and sends back her signature. diff --git a/samples/fungible/views/issue.go b/samples/fungible/views/issue.go index 83b1740ca..318891434 100644 --- a/samples/fungible/views/issue.go +++ b/samples/fungible/views/issue.go @@ -61,7 +61,11 @@ func (p *IssueCashView) Call(context view.Context) (interface{}, error) { fmt.Printf("History [%s,%s]<[230]?\n", history.Sum(precision).ToBigInt().Text(10), p.TokenType) // Fail if the sum of the issued tokens and the current quest is larger than 230 - assert.True(history.Sum(precision).Add(token2.NewQuantityFromUInt64(p.Quantity)).Cmp(token2.NewQuantityFromUInt64(230)) <= 0) + q, err := token2.UInt64ToQuantity(p.Quantity, precision) + assert.NoError(err, "failed to convert quantity") + upperBound, err := token2.UInt64ToQuantity(230, precision) + assert.NoError(err, "failed to convert upper bound") + assert.True(history.Sum(precision).Add(q).Cmp(upperBound) <= 0) } // At this point, the issuer is ready to prepare the token transaction. diff --git a/token/core/fabtoken/issuer.go b/token/core/fabtoken/issuer.go index 8b37a17fe..1b71f0fcb 100644 --- a/token/core/fabtoken/issuer.go +++ b/token/core/fabtoken/issuer.go @@ -26,14 +26,19 @@ func (s *Service) Issue(issuerIdentity view.Identity, typ string, values []uint6 var outs []*Output var metas [][]byte + precision := s.PublicParamsManager().PublicParameters().Precision() for i, v := range values { + q, err := token2.UInt64ToQuantity(v, precision) + if err != nil { + return nil, nil, nil, errors.Wrapf(err, "failed to convert [%d] to quantity of precision [%d]", v, precision) + } outs = append(outs, &Output{ Output: &token2.Token{ Owner: &token2.Owner{ Raw: owners[i], }, Type: typ, - Quantity: token2.NewQuantityFromUInt64(v).Hex(), + Quantity: q.Hex(), }, }) diff --git a/token/request.go b/token/request.go index bf65fc4d6..7fc0b2227 100644 --- a/token/request.go +++ b/token/request.go @@ -1010,17 +1010,21 @@ func (r *Request) prepareTransfer(redeem bool, wallet *OwnerWallet, typ string, } // Compute output tokens - outputSum := uint64(0) + precision := r.TokenService.PublicParametersManager().Precision() + outputSum := token.NewZeroQuantity(precision) var outputTokens []*token.Token for i, value := range values { - outputSum += value + q, err := token.UInt64ToQuantity(value, precision) + if err != nil { + return nil, nil, errors.Wrapf(err, "failed to convert [%d] to quantity of precision [%d]", value, precision) + } + outputSum = outputSum.Add(q) outputTokens = append(outputTokens, &token.Token{ Owner: &token.Owner{Raw: owners[i]}, Type: typ, - Quantity: token.NewQuantityFromUInt64(value).Hex(), + Quantity: q.Hex(), }) } - qOutputSum := token.NewQuantityFromUInt64(outputSum) // Select input tokens, if not passed as opt if len(transferOpts.TokenIDs) == 0 { @@ -1032,15 +1036,15 @@ func (r *Request) prepareTransfer(redeem bool, wallet *OwnerWallet, typ string, return nil, nil, errors.Wrapf(err, "failed getting default selector") } } - tokenIDs, inputSum, err = selector.Select(wallet, token.NewQuantityFromUInt64(outputSum).Decimal(), typ) + tokenIDs, inputSum, err = selector.Select(wallet, outputSum.Decimal(), typ) if err != nil { return nil, nil, errors.Wrap(err, "failed selecting tokens") } } // Is there a rest? - if inputSum.Cmp(qOutputSum) == 1 { - diff := inputSum.Sub(qOutputSum) + if inputSum.Cmp(outputSum) == 1 { + diff := inputSum.Sub(outputSum) logger.Debugf("reassign rest [%s] to sender", diff.Decimal()) pseudonym, err := wallet.GetRecipientIdentity() diff --git a/token/services/nfttx/qe.go b/token/services/nfttx/qe.go index 053ca6d12..6a6836230 100644 --- a/token/services/nfttx/qe.go +++ b/token/services/nfttx/qe.go @@ -73,7 +73,7 @@ func (s *QueryExecutor) QueryByKey(state interface{}, key string, value string) if err != nil { return errors.Wrap(err, "failed to convert quantity") } - if q.Cmp(token2.NewQuantityFromUInt64(1)) == 0 { + if q.Cmp(token2.NewOneQuantity(s.precision)) == 0 { // this is the token decoded, err := base64.StdEncoding.DecodeString(t.Type) if err != nil { diff --git a/token/token/quantity.go b/token/token/quantity.go index 432bb1aa5..216c34c6c 100644 --- a/token/token/quantity.go +++ b/token/token/quantity.go @@ -38,12 +38,12 @@ type Quantity interface { ToBigInt() *big.Int } -// ToQuantity converts a string q to a BigQuantity of a given precision. +// ToQuantity converts a string q to a Quantity of a given precision. // Argument q is supposed to be formatted following big.Int#scan specification. // The precision is expressed in bits. func ToQuantity(q string, precision uint64) (Quantity, error) { if precision == 0 { - return nil, errors.New("precision be larger than 0") + return nil, errors.New("precision must be larger than 0") } v, success := big.NewInt(0).SetString(q, 0) if !success { @@ -64,6 +64,29 @@ func ToQuantity(q string, precision uint64) (Quantity, error) { } } +// UInt64ToQuantity converts a uint64 q to a Quantity of a given precision. +// Argument q is supposed to be formatted following big.Int#scan specification. +// The precision is expressed in bits. +func UInt64ToQuantity(u uint64, precision uint64) (Quantity, error) { + if precision == 0 { + return nil, errors.New("precision must be larger than 0") + } + v := big.NewInt(0).SetUint64(u) + if v.Cmp(big.NewInt(0)) < 0 { + return nil, errors.New("quantity must be larger than 0") + } + if v.BitLen() > int(precision) { + return nil, errors.Errorf("%d has precision %d > %d", u, v.BitLen(), precision) + } + + switch precision { + case 64: + return &UInt64Quantity{Value: v.Uint64()}, nil + default: + return &BigQuantity{Int: v, Precision: precision}, nil + } +} + // NewZeroQuantity returns to zero quantity at the passed precision/ // The precision is expressed in bits. func NewZeroQuantity(precision uint64) Quantity { @@ -91,7 +114,7 @@ type BigQuantity struct { func NewUBigQuantity(q string, precision uint64) (*BigQuantity, error) { if precision == 0 { - return nil, errors.New("precision be larger than 0") + return nil, errors.New("precision must be larger than 0") } v, success := big.NewInt(0).SetString(q, 0) if !success { diff --git a/token/token/quantity_test.go b/token/token/quantity_test.go index 3d761586e..09207ebda 100644 --- a/token/token/quantity_test.go +++ b/token/token/quantity_test.go @@ -18,7 +18,7 @@ import ( func TestToQuantity(t *testing.T) { _, err := token2.ToQuantity(ToHex(100), 0) - assert.Equal(t, "precision be larger than 0", err.Error()) + assert.Equal(t, "precision must be larger than 0", err.Error()) _, err = token2.ToQuantity(IntToHex(-100), 64) assert.Equal(t, "invalid input [0x-64,64]", err.Error())