diff --git a/go/staking/api/commission.go b/go/staking/api/commission.go index b49834eb252..6227ef3fb44 100644 --- a/go/staking/api/commission.go +++ b/go/staking/api/commission.go @@ -5,7 +5,6 @@ import ( "fmt" "io" "math/big" - "strconv" "github.com/oasisprotocol/oasis-core/go/common/prettyprint" "github.com/oasisprotocol/oasis-core/go/common/quantity" @@ -28,17 +27,6 @@ var ( _ prettyprint.PrettyPrinter = (*CommissionSchedule)(nil) ) -// CommissionRatePercentage returns the string representing the commission rate -// in percentage for the given commission rate numerator. -func CommissionRatePercentage(rateNumerator quantity.Quantity) string { - rate := big.NewRat(rateNumerator.ToBigInt().Int64(), CommissionRateDenominator.ToBigInt().Int64()) - // Multiply rate by 100 to convert it to percentage. - rate.Mul(rate, big.NewRat(100, 1)) - // Return string representation of the rate that omits the trailing zeros. - rateFloat, _ := rate.Float64() - return strconv.FormatFloat(rateFloat, 'f', -1, 64) -} - // CommissionScheduleRules controls how commission schedule rates and rate // bounds are allowed to be changed. type CommissionScheduleRules struct { @@ -66,7 +54,7 @@ type CommissionRateStep struct { func (crs CommissionRateStep) PrettyPrint(ctx context.Context, prefix string, w io.Writer) { fmt.Fprintf(w, "%s- Start: epoch %d\n", prefix, crs.Start) - fmt.Fprintf(w, "%s Rate: %s %%\n", prefix, CommissionRatePercentage(crs.Rate)) + fmt.Fprintf(w, "%s Rate: %s\n", prefix, PrettyPrintCommissionRatePercentage(crs.Rate)) } // PrettyType returns a representation of CommissionRateStep that can be used @@ -91,8 +79,8 @@ type CommissionRateBoundStep struct { func (crbs CommissionRateBoundStep) PrettyPrint(ctx context.Context, prefix string, w io.Writer) { fmt.Fprintf(w, "%s- Start: epoch %d\n", prefix, crbs.Start) - fmt.Fprintf(w, "%s Minimum Rate: %s %%\n", prefix, CommissionRatePercentage(crbs.RateMin)) - fmt.Fprintf(w, "%s Maximum Rate: %s %%\n", prefix, CommissionRatePercentage(crbs.RateMax)) + fmt.Fprintf(w, "%s Minimum Rate: %s\n", prefix, PrettyPrintCommissionRatePercentage(crbs.RateMin)) + fmt.Fprintf(w, "%s Maximum Rate: %s\n", prefix, PrettyPrintCommissionRatePercentage(crbs.RateMax)) } // PrettyType returns a representation of CommissionRateBoundStep that can be diff --git a/go/staking/api/prettyprint.go b/go/staking/api/prettyprint.go index 5f076fdb7f9..e87d33ac819 100644 --- a/go/staking/api/prettyprint.go +++ b/go/staking/api/prettyprint.go @@ -4,9 +4,8 @@ import ( "context" "fmt" "io" - "math/big" - "strings" + "github.com/oasisprotocol/oasis-core/go/common/prettyprint" "github.com/oasisprotocol/oasis-core/go/common/quantity" ) @@ -29,24 +28,7 @@ func ConvertToTokenAmount(amount quantity.Quantity, tokenValueExponent uint8) (s return "", ErrInvalidTokenValueExponent } - divisor := new(big.Int).Exp(big.NewInt(10), big.NewInt(int64(tokenValueExponent)), nil) - - // NOTE: We use DivMod() and manual string construction to avoid conversion - // to other types and support arbitrarily large amounts. - var quotient, remainder *big.Int - quotient, remainder = new(big.Int).DivMod(amount.ToBigInt(), divisor, new(big.Int)) - - // Prefix the remainder with the appropriate number of zeros. - remainderStr := fmt.Sprintf("%0*s", tokenValueExponent, remainder) - // Trim trailing zeros from the remainder. - remainderStr = strings.TrimRight(remainderStr, "0") - // Ensure remainder is not empty. - if remainderStr == "" { - remainderStr = "0" - } - - // Combine quotient and remainder to a string representing the token amount. - return fmt.Sprintf("%s.%s", quotient, remainderStr), nil + return prettyprint.FractionBase10(amount, tokenValueExponent), nil } // PrettyPrintAmount writes a pretty-printed representation of the given amount @@ -82,3 +64,17 @@ func PrettyPrintAmount(ctx context.Context, amount quantity.Quantity, w io.Write fmt.Fprintf(w, "%s %s", symbol, tokenAmount) } } + +// PrettyPrintCommissionRatePercentage returns the string representing the +// commission rate (bound) in percentage for the given commission rate (bound) +// numerator. +func PrettyPrintCommissionRatePercentage(rateNumerator quantity.Quantity) string { + // Handle invalid commission rate (bound) numerator. + if rateNumerator.Cmp(CommissionRateDenominator) > 0 { + return fmt.Sprintf("(invalid)") + } + // Reduce commission rate denominator's base-10 exponent by 2 to obtain the + // value in percentage. + denominatorExp := commissionRateDenominatorExponent - 2 + return fmt.Sprintf("%s%%", prettyprint.FractionBase10(rateNumerator, denominatorExp)) +} diff --git a/go/staking/api/prettyprint_test.go b/go/staking/api/prettyprint_test.go index f25445a11e4..5502a1d73d4 100644 --- a/go/staking/api/prettyprint_test.go +++ b/go/staking/api/prettyprint_test.go @@ -89,3 +89,25 @@ func TestPrettyPrintAmount(t *testing.T) { "pretty printing stake amount didn't return the expected result") } } + +func TestPrettyPrintCommissionRatePercentage(t *testing.T) { + require := require.New(t) + + for _, t := range []struct { + expectedRate string + rateNumerator *quantity.Quantity + }{ + {"0.0%", quantity.NewFromUint64(0)}, + {"50.0%", quantity.NewFromUint64(50_000)}, + {"100.0%", quantity.NewFromUint64(100_000)}, + {"20.2%", quantity.NewFromUint64(20_200)}, + {"30.03%", quantity.NewFromUint64(30_030)}, + {"12.345%", quantity.NewFromUint64(12_345)}, + // Checks for invalid commission rate numerators. + {"(invalid)", quantity.NewFromUint64(100_001)}, + {"(invalid)", quantity.NewFromUint64(123_456)}, + } { + rate := PrettyPrintCommissionRatePercentage(*t.rateNumerator) + require.Equal(t.expectedRate, rate, "obtained pretty print didn't match expected value") + } +}