diff --git a/protocol/daemons/pricefeed/client/price_function/util.go b/protocol/daemons/pricefeed/client/price_function/util.go index 1e533f88ce..218a3a301b 100644 --- a/protocol/daemons/pricefeed/client/price_function/util.go +++ b/protocol/daemons/pricefeed/client/price_function/util.go @@ -144,26 +144,21 @@ func reverseShiftBigFloatSlice( values []*big.Float, exponent int32, ) []*big.Float { - unsignedExponent := lib.AbsInt32(exponent) - - pow10 := new(big.Float).SetInt(lib.BigPow10(uint64(unsignedExponent))) + p10, inverse := lib.BigPow10(exponent) + p10Float := new(big.Float).SetInt(p10) updatedValues := make([]*big.Float, 0, len(values)) for _, value := range values { - updatedValues = append(updatedValues, reverseShiftFloatWithPow10(value, pow10, exponent)) + newValue := new(big.Float).Set(value) + if inverse { + newValue.Mul(newValue, p10Float) + } else { + newValue.Quo(newValue, p10Float) + } + updatedValues = append(updatedValues, newValue) } return updatedValues } -func reverseShiftFloatWithPow10(value *big.Float, pow10 *big.Float, exponent int32) *big.Float { - if exponent == 0 { - return value - } else if exponent > 0 { - return new(big.Float).Quo(value, pow10) - } else { // exponent < 0 - return new(big.Float).Mul(value, pow10) - } -} - // Ticker encodes a ticker response returned by an exchange API. It contains accessors for the ticker's // ask price, bid price, and last price, which are medianized to compute an exchange price. type Ticker interface { diff --git a/protocol/daemons/pricefeed/client/price_function/util_test.go b/protocol/daemons/pricefeed/client/price_function/util_test.go index 9034241f37..fa50f0d8ce 100644 --- a/protocol/daemons/pricefeed/client/price_function/util_test.go +++ b/protocol/daemons/pricefeed/client/price_function/util_test.go @@ -261,54 +261,6 @@ func TestGetUint64MedianFromShiftedBigFloatValues(t *testing.T) { } } -func TestReverseShiftBigFloatWithPow10(t *testing.T) { - tests := map[string]struct { - // parameters - floatValue *big.Float - exponent int32 - - // expectations - expectedUpdatedFloatValue *big.Float - }{ - "Success with negative exponent": { - floatValue: new(big.Float).SetPrec(64).SetFloat64(100.123), - exponent: -3, - expectedUpdatedFloatValue: new(big.Float).SetPrec(64).SetFloat64(100_123), - }, - "Success with positive exponent": { - floatValue: new(big.Float).SetPrec(64).SetFloat64(100.1), - exponent: 1, - expectedUpdatedFloatValue: new(big.Float).SetPrec(64).SetFloat64(10.01), - }, - "Success with exponent of 0": { - floatValue: new(big.Float).SetPrec(64).SetFloat64(100), - exponent: 0, - expectedUpdatedFloatValue: new(big.Float).SetPrec(64).SetFloat64(100), - }, - } - - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - unsignedExponent := lib.AbsInt32(tc.exponent) - - pow10 := new(big.Float).SetInt(lib.BigPow10(uint64(unsignedExponent))) - - updatedFloatValue := reverseShiftFloatWithPow10( - tc.floatValue, - pow10, - tc.exponent, - ) - - require.InDeltaSlice( - t, - bigSliceToFloatSlice([]*big.Float{tc.expectedUpdatedFloatValue}), - bigSliceToFloatSlice([]*big.Float{updatedFloatValue}), - deltaPrecision, - ) - }) - } -} - func TestReverseShiftBigFloatSlice(t *testing.T) { tests := map[string]struct { // parameters diff --git a/protocol/lib/big_math.go b/protocol/lib/big_math.go index d9befbcf1d..0a155e155d 100644 --- a/protocol/lib/big_math.go +++ b/protocol/lib/big_math.go @@ -27,43 +27,44 @@ func BigMulPpm(val *big.Int, ppm *big.Int, roundUp bool) *big.Int { } } -// BigMulPow10 returns the result of `val * 10^exponent`, in *big.Rat. -func BigMulPow10( - val *big.Int, - exponent int32, -) ( - result *big.Rat, -) { - ratPow10 := RatPow10(exponent) - return ratPow10.Mul( - new(big.Rat).SetInt(val), - ratPow10, - ) -} - // bigPow10Memo is a cache of the most common exponent value requests. Since bigPow10Memo will be // accessed from different go-routines, the map should only ever be read from or collision // could occur. var bigPow10Memo = warmCache() -// BigPow10 returns the result of `10^exponent`. Caches all calculated values and -// re-uses cached values in any following calls to BigPow10. -func BigPow10(exponent uint64) *big.Int { - result := bigPow10Helper(exponent) - // Copy the result, such that no values can be modified by reference in the - // `bigPow10Memo` cache. - copy := new(big.Int).Set(result) - return copy +// BigPow10 returns the result of `10^abs(exponent)` and whether the exponent is non-negative. +func BigPow10[T int | int32 | int64 | uint | uint32 | uint64]( + exponent T, +) ( + result *big.Int, + inverse bool, +) { + inverse = exponent < 0 + var absExponent uint64 + if inverse { + absExponent = uint64(-exponent) + } else { + absExponent = uint64(exponent) + } + + return new(big.Int).Set(bigPow10Helper(absExponent)), inverse } -// RatPow10 returns the result of `10^exponent`. Re-uses the cached values by -// calling bigPow10Helper. -func RatPow10(exponent int32) *big.Rat { - result := new(big.Rat).SetInt(bigPow10Helper(uint64(AbsInt32(exponent)))) - if exponent < 0 { - result.Inv(result) +// BigIntMulPow10 returns the result of `input * 10^exponent`, rounding in the direction indicated. +// There is no rounding if `exponent` is non-negative. +func BigIntMulPow10[T int | int32 | int64 | uint | uint32 | uint64]( + input *big.Int, + exponent T, + roundUp bool, +) *big.Int { + p10, inverse := BigPow10(exponent) + if inverse { + if roundUp { + return BigDivCeil(input, p10) + } + return new(big.Int).Div(input, p10) } - return result + return new(big.Int).Mul(p10, input) } // BigIntMulPpm takes a `big.Int` and returns the result of `input * ppm / 1_000_000`. This method rounds towards diff --git a/protocol/lib/big_math_test.go b/protocol/lib/big_math_test.go index 553421d30c..f53cae6c74 100644 --- a/protocol/lib/big_math_test.go +++ b/protocol/lib/big_math_test.go @@ -146,146 +146,29 @@ func TestBigMulPpm(t *testing.T) { func TestBigPow10(t *testing.T) { tests := map[string]struct { - exponent uint64 - expectedResult *big.Int - }{ - "Regular exponent": { - exponent: 3, - expectedResult: new(big.Int).SetUint64(1000), - }, - "Zero exponent": { - exponent: 0, - expectedResult: new(big.Int).SetUint64(1), - }, - "One exponent": { - exponent: 1, - expectedResult: new(big.Int).SetUint64(10), - }, - "Power of 2": { - exponent: 8, - expectedResult: new(big.Int).SetUint64(100_000_000), - }, - "Non-power of 2": { - exponent: 6, - expectedResult: new(big.Int).SetUint64(1_000_000), - }, - "Greater than max uint64": { - exponent: 20, - expectedResult: big_testutil.MustFirst(new(big.Int).SetString("100000000000000000000", 10)), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - result := lib.BigPow10(tc.exponent) - require.Equal(t, tc.expectedResult, result) - }) - } -} - -func TestBigMulPow10(t *testing.T) { - tests := map[string]struct { - val *big.Int - exponent int32 - expectedResult *big.Rat + exponent int64 + expectedValue *big.Int + expectedInverse bool }{ - "exponent = 2": { - val: new(big.Int).SetUint64(12345678), - exponent: 2, - expectedResult: big.NewRat(1234567800, 1), - }, - "exponent = 10": { - val: new(big.Int).SetUint64(12345678), - exponent: 10, - expectedResult: big.NewRat(123456780000000000, 1), - }, - "exponent = 0": { - val: new(big.Int).SetUint64(12345678), - exponent: 0, - expectedResult: big.NewRat(12345678, 1), - }, - "exponent = -1": { - val: new(big.Int).SetUint64(12345678), - exponent: -1, - expectedResult: big.NewRat(12345678, 10), - }, - "exponent = -3": { - val: new(big.Int).SetUint64(12345678), - exponent: -3, - expectedResult: big.NewRat(12345678, 1000), - }, - "exponent = -8": { - val: new(big.Int).SetUint64(12345678), - exponent: -8, - expectedResult: big.NewRat(12345678, 100000000), - }, + "0": {0, big.NewInt(1), false}, + "1": {1, big.NewInt(10), false}, + "2": {2, big.NewInt(100), false}, + "3": {3, big.NewInt(1000), false}, + "4": {4, big.NewInt(10000), false}, + "5": {5, big.NewInt(100000), false}, + "20": {20, big_testutil.MustFirst(new(big.Int).SetString("100000000000000000000", 10)), false}, + "-1": {-1, big.NewInt(10), true}, + "-2": {-2, big.NewInt(100), true}, + "-3": {-3, big.NewInt(1000), true}, + "-4": {-4, big.NewInt(10000), true}, + "-5": {-5, big.NewInt(100000), true}, + "-20": {-20, big_testutil.MustFirst(new(big.Int).SetString("100000000000000000000", 10)), true}, } for name, tc := range tests { t.Run(name, func(t *testing.T) { - result := lib.BigMulPow10(tc.val, tc.exponent) - require.Equal(t, tc.expectedResult, result) - }) - } -} - -func TestRatPow10(t *testing.T) { - tests := map[string]struct { - exponent int32 - expectedResult *big.Rat - }{ - "Positive exponent": { - exponent: 3, - expectedResult: new(big.Rat).SetUint64(1000), - }, - "Negative exponent": { - exponent: -3, - expectedResult: new(big.Rat).SetFrac64(1, 1000), - }, - "Zero exponent": { - exponent: 0, - expectedResult: new(big.Rat).SetUint64(1), - }, - "One exponent": { - exponent: 1, - expectedResult: new(big.Rat).SetUint64(10), - }, - "Negative one exponent": { - exponent: -1, - expectedResult: new(big.Rat).SetFrac64(1, 10), - }, - "Power of 2": { - exponent: 8, - expectedResult: new(big.Rat).SetUint64(100_000_000), - }, - "Negative power of 2": { - exponent: -8, - expectedResult: new(big.Rat).SetFrac64(1, 100_000_000), - }, - "Non-power of 2": { - exponent: 6, - expectedResult: new(big.Rat).SetUint64(1_000_000), - }, - "Negative non-power of 2": { - exponent: -6, - expectedResult: new(big.Rat).SetFrac64(1, 1_000_000), - }, - "Greater than max uint64": { - exponent: 20, - expectedResult: big_testutil.MustFirst(new(big.Rat).SetString("100000000000000000000")), - }, - "Denom greater than max uint64": { - exponent: -20, - expectedResult: new(big.Rat).SetFrac( - new(big.Int).SetInt64(1), - big_testutil.MustFirst( - new(big.Int).SetString("100000000000000000000", 10), - ), - ), - }, - } - for name, tc := range tests { - t.Run(name, func(t *testing.T) { - result := lib.RatPow10(tc.exponent) - require.Equal(t, tc.expectedResult, result) + value, inverse := lib.BigPow10(tc.exponent) + require.Equal(t, tc.expectedValue, value) + require.Equal(t, tc.expectedInverse, inverse) }) } } @@ -293,10 +176,11 @@ func TestRatPow10(t *testing.T) { func TestBigPow10AllValuesInMemo(t *testing.T) { exponentString := "1" for i := 0; i < 100; i++ { - bigValue, ok := new(big.Int).SetString(exponentString, 0) + expected, ok := new(big.Int).SetString(exponentString, 10) require.True(t, ok) - require.Equal(t, lib.BigPow10(uint64(i)), bigValue) + result, _ := lib.BigPow10(i) + require.Equal(t, expected, result) exponentString = exponentString + "0" } @@ -304,9 +188,10 @@ func TestBigPow10AllValuesInMemo(t *testing.T) { func TestBigPow10AllValueNotInMemo(t *testing.T) { exponentString := "1" + strings.Repeat("0", 110) - bigValue, ok := new(big.Int).SetString(exponentString, 0) + expected, ok := new(big.Int).SetString(exponentString, 10) require.True(t, ok) - require.Equal(t, lib.BigPow10(uint64(110)), bigValue) + result, _ := lib.BigPow10(110) + require.Equal(t, expected, result) } func TestBigIntMulPpm(t *testing.T) { diff --git a/protocol/lib/quantums.go b/protocol/lib/quantums.go index b10286e490..f381bf78ca 100644 --- a/protocol/lib/quantums.go +++ b/protocol/lib/quantums.go @@ -40,12 +40,12 @@ func BaseToQuoteQuantums( } // Otherwise multiply or divide by the 1e^exponent. - ePow := bigPow10Helper(uint64(AbsInt32(exponent))) - if exponent > 0 { - return numResult.Mul(numResult, ePow) - } else { + pow10, inverse := BigPow10(exponent) + if inverse { // Trucated division (towards zero) instead of Euclidean division. - return numResult.Quo(numResult, ePow) + return numResult.Quo(numResult, pow10) + } else { + return numResult.Mul(numResult, pow10) } } @@ -75,11 +75,11 @@ func QuoteToBaseQuantums( // Divide result (towards zero) by 10^(exponent). exponent := priceExponent + baseCurrencyAtomicResolution - QuoteCurrencyAtomicResolution - power10Exponent := BigPow10(uint64(AbsInt32(exponent))) - if exponent > 0 { - result.Quo(result, power10Exponent) + p10, inverse := BigPow10(exponent) + if inverse { + result.Mul(result, p10) } else { - result.Mul(result, power10Exponent) + result.Quo(result, p10) } // Divide result (towards zero) by priceValue. diff --git a/protocol/testutil/big/big.go b/protocol/testutil/big/big.go index bf1e9a7a17..c1f61ac500 100644 --- a/protocol/testutil/big/big.go +++ b/protocol/testutil/big/big.go @@ -23,8 +23,5 @@ func Int64MulPow10( ) ( result *big.Int, ) { - return new(big.Int).Mul( - big.NewInt(val), - lib.BigPow10(exponent), - ) + return lib.BigIntMulPow10(big.NewInt(val), exponent, false) } diff --git a/protocol/testutil/perpetuals/perpetuals.go b/protocol/testutil/perpetuals/perpetuals.go index 2c468b7977..bbd17779b1 100644 --- a/protocol/testutil/perpetuals/perpetuals.go +++ b/protocol/testutil/perpetuals/perpetuals.go @@ -1,6 +1,7 @@ package perpetuals import ( + "fmt" "math/big" "testing" @@ -8,6 +9,7 @@ import ( "github.com/stretchr/testify/require" "github.com/dydxprotocol/v4-chain/protocol/dtypes" + "github.com/dydxprotocol/v4-chain/protocol/lib" "github.com/dydxprotocol/v4-chain/protocol/testutil/constants" perptypes "github.com/dydxprotocol/v4-chain/protocol/x/perpetuals/types" ) @@ -88,42 +90,23 @@ func GeneratePerpetual(optionalModifications ...PerpetualModifierOption) *perpty return perpetual } +// MustHumanSizeToBaseQuantums converts a human-readable size to quantums. +// It uses the inverse of the exponent to convert the human size to quantums, +// since the exponent applies to the quantums to derive the human-readable size. func MustHumanSizeToBaseQuantums( humanSize string, atomicResolution int32, ) (baseQuantums uint64) { - // Parse the humanSize string to a big rational - ratValue, ok := new(big.Rat).SetString(humanSize) + ratio, ok := new(big.Rat).SetString(humanSize) if !ok { - panic("Failed to parse humanSize to big.Rat") + panic(fmt.Sprintf("MustHumanSizeToBaseQuantums: Failed to parse humanSize: %s", humanSize)) } - - // Convert atomicResolution to int64 for calculations - resolution := int64(atomicResolution) - - // Create a multiplier which is 10 raised to the power of the absolute atomicResolution - multiplier := new(big.Int).Exp(big.NewInt(10), big.NewInt(abs(resolution)), nil) - - // Depending on the sign of atomicResolution, multiply or divide - if atomicResolution > 0 { - ratValue.Mul(ratValue, new(big.Rat).SetInt(multiplier)) - } else if atomicResolution < 0 { - divisor := new(big.Rat).SetInt(multiplier) - ratValue.Mul(ratValue, divisor) - } - - // Convert the result to an unsigned 64-bit integer - resultInt := ratValue.Num() // Get the numerator which now represents the whole value - - return resultInt.Uint64() -} - -// Helper function to get the absolute value of an int64 -func abs(n int64) int64 { - if n < 0 { - return -n + result := lib.BigIntMulPow10(ratio.Num(), -atomicResolution, false) + result.Quo(result, ratio.Denom()) + if !result.IsUint64() { + panic("MustHumanSizeToBaseQuantums: result is not a uint64") } - return n + return result.Uint64() } // Helper function to set up default open interest for input perpetuals. diff --git a/protocol/testutil/perpetuals/perpetuals_test.go b/protocol/testutil/perpetuals/perpetuals_test.go index 630797c19a..1e2b85c5b4 100644 --- a/protocol/testutil/perpetuals/perpetuals_test.go +++ b/protocol/testutil/perpetuals/perpetuals_test.go @@ -17,7 +17,8 @@ func TestMustHumanSizeToBaseQuantums(t *testing.T) { {"1.123", -8, 112_300_000, false}, {"0.55", -9, 550_000_000, false}, {"0.00000001", -8, 1, false}, - {"235", 1, 2350, false}, + {"235", -1, 2350, false}, + {"235", 1, 23, false}, {"1", -10, 10_000_000_000, false}, {"0.0000000001", -10, 1, false}, {"abc", -8, 0, true}, // Invalid humanSize diff --git a/protocol/testutil/prices/market_param_price.go b/protocol/testutil/prices/market_param_price.go index 493734e991..be1b7d798f 100644 --- a/protocol/testutil/prices/market_param_price.go +++ b/protocol/testutil/prices/market_param_price.go @@ -1,8 +1,10 @@ package prices import ( + "fmt" "math/big" + "github.com/dydxprotocol/v4-chain/protocol/lib" pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types" ) @@ -86,32 +88,21 @@ func GenerateMarketParamPrice(optionalModifications ...MarketParamPriceModifierO return marketParamPrice } +// MustHumanPriceToMarketPrice converts a human-readable price to a market price. +// It uses the inverse of the exponent to convert the human price to a market price, +// since the exponent applies to the market price to derive the human price. func MustHumanPriceToMarketPrice( humanPrice string, exponent int32, ) (marketPrice uint64) { - // Ensure the exponent is negative - if exponent >= 0 { - panic("Only negative exponents are supported") - } - - // Parse the humanPrice string to a big rational - ratValue, ok := new(big.Rat).SetString(humanPrice) + ratio, ok := new(big.Rat).SetString(humanPrice) if !ok { - panic("Failed to parse humanPrice to big.Rat") + panic(fmt.Sprintf("MustHumanPriceToMarketPrice: Failed to parse humanPrice: %s", humanPrice)) } - - // Convert exponent to its absolute value for calculations - absResolution := int64(-exponent) - - // Create a multiplier which is 10 raised to the power of the absolute exponent - multiplier := new(big.Int).Exp(big.NewInt(10), big.NewInt(absResolution), nil) - - // Multiply the parsed humanPrice with the multiplier - ratValue.Mul(ratValue, new(big.Rat).SetInt(multiplier)) - - // Convert the result to an unsigned 64-bit integer - resultInt := ratValue.Num() // Get the numerator which now represents the whole value - - return resultInt.Uint64() + result := lib.BigIntMulPow10(ratio.Num(), -exponent, false) + result.Quo(result, ratio.Denom()) + if !result.IsUint64() { + panic("MustHumanPriceToMarketPrice: result is not a uint64") + } + return result.Uint64() } diff --git a/protocol/testutil/prices/market_param_price_test.go b/protocol/testutil/prices/market_param_price_test.go index ebe565c3b0..a026f43ce9 100644 --- a/protocol/testutil/prices/market_param_price_test.go +++ b/protocol/testutil/prices/market_param_price_test.go @@ -20,6 +20,8 @@ func TestMustHumanPriceToMarketPrice(t *testing.T) { {"0.00000001", -8, 1, false}, {"1", -10, 10_000_000_000, false}, {"0.0000000001", -10, 1, false}, + {"500", 2, 5, false}, + {"500", 0, 500, false}, {"abc", -8, 0, true}, // Invalid humanPrice } diff --git a/protocol/x/assets/keeper/asset.go b/protocol/x/assets/keeper/asset.go index a71b6d407e..8910a34299 100644 --- a/protocol/x/assets/keeper/asset.go +++ b/protocol/x/assets/keeper/asset.go @@ -299,22 +299,19 @@ func (k Keeper) ConvertAssetToCoin( ) } - bigRatDenomAmount := lib.BigMulPow10( - quantums, - asset.AtomicResolution-asset.DenomExponent, - ) - - // round down to get denom amount that was converted. - bigConvertedDenomAmount := lib.BigRatRound(bigRatDenomAmount, false) - - bigRatConvertedQuantums := lib.BigMulPow10( - bigConvertedDenomAmount, - asset.DenomExponent-asset.AtomicResolution, - ) - - bigConvertedQuantums := bigRatConvertedQuantums.Num() + exponent := asset.AtomicResolution - asset.DenomExponent + p10, inverse := lib.BigPow10(exponent) + var resultDenom *big.Int + var resultQuantums *big.Int + if inverse { + resultDenom = new(big.Int).Div(quantums, p10) + resultQuantums = new(big.Int).Mul(resultDenom, p10) + } else { + resultDenom = new(big.Int).Mul(quantums, p10) + resultQuantums = new(big.Int).Div(resultDenom, p10) + } - return bigConvertedQuantums, sdk.NewCoin(asset.Denom, sdkmath.NewIntFromBigInt(bigConvertedDenomAmount)), nil + return resultQuantums, sdk.NewCoin(asset.Denom, sdkmath.NewIntFromBigInt(resultDenom)), nil } // IsPositionUpdatable returns whether position of an asset is updatable. diff --git a/protocol/x/clob/keeper/liquidations.go b/protocol/x/clob/keeper/liquidations.go index b96284095b..7f89c5b9a4 100644 --- a/protocol/x/clob/keeper/liquidations.go +++ b/protocol/x/clob/keeper/liquidations.go @@ -1052,20 +1052,22 @@ func (k Keeper) ConvertFillablePriceToSubticks( panic("ConvertFillablePriceToSubticks: FillablePrice should not be negative") } - exponent := clobPair.QuantumConversionExponent - absExponentiatedValueBig := lib.BigPow10(uint64(lib.AbsInt32(exponent))) - quoteQuantumsPerBaseQuantumAndSubtickRat := new(big.Rat).SetInt(absExponentiatedValueBig) - // If `exponent` is negative, invert the fraction to set the result to `1 / 10^exponent`. - if exponent < 0 { - quoteQuantumsPerBaseQuantumAndSubtickRat.Inv(quoteQuantumsPerBaseQuantumAndSubtickRat) - } - // Assuming `fillablePrice` is in units of `quote quantums / base quantum`, then dividing by // `quote quantums / (base quantum * subtick)` will give the resulting units of subticks. - subticksRat := new(big.Rat).Quo( - fillablePrice, - quoteQuantumsPerBaseQuantumAndSubtickRat, - ) + exponent := clobPair.QuantumConversionExponent + p10, inverse := lib.BigPow10(exponent) + subticksRat := new(big.Rat) + if inverse { + subticksRat.SetFrac( + new(big.Int).Mul(p10, fillablePrice.Num()), + fillablePrice.Denom(), + ) + } else { + subticksRat.SetFrac( + fillablePrice.Num(), + new(big.Int).Mul(p10, fillablePrice.Denom()), + ) + } // If we are liquidating a long position with a sell order, then we round up to the nearest // subtick (and vice versa for liquidating shorts). diff --git a/protocol/x/clob/types/price_to_subticks.go b/protocol/x/clob/types/price_to_subticks.go index 1d7bbae40b..455e9fdd76 100644 --- a/protocol/x/clob/types/price_to_subticks.go +++ b/protocol/x/clob/types/price_to_subticks.go @@ -34,11 +34,14 @@ func PriceToSubticks( exponent := int32( marketPrice.Exponent - clobPair.QuantumConversionExponent + baseAtomicResolution - quoteAtomicResolution, ) - return lib.BigMulPow10( - // TODO(DEC-1256): Use index price from the price daemon, instead of oracle price. - new(big.Int).SetUint64(marketPrice.Price), - exponent, - ) + // TODO(DEC-1256): Use index price from the price daemon, instead of oracle price. + bigPrice := new(big.Int).SetUint64(marketPrice.Price) + p10, inverse := lib.BigPow10(exponent) + if inverse { + return new(big.Rat).SetFrac(bigPrice, p10) + } else { + return new(big.Rat).SetInt(bigPrice.Mul(bigPrice, p10)) + } } // SubticksToPrice converts subticks into price value from Prices module. @@ -71,12 +74,9 @@ func SubticksToPrice( exponent := int32( -marketPriceExponent + clobPair.QuantumConversionExponent - baseAtomicResolution + quoteAtomicResolution, ) - return lib.BigRatRound( - lib.BigMulPow10( - // TODO(DEC-1256): Use index price from the price daemon, instead of oracle price. - new(big.Int).SetUint64(uint64(subticks)), - exponent, - ), - false, - ).Uint64() + result := lib.BigIntMulPow10(new(big.Int).SetUint64(uint64(subticks)), exponent, false) + if !result.IsUint64() { + panic("SubticksToPrice: result is not a uint64") + } + return result.Uint64() } diff --git a/protocol/x/clob/types/quantums.go b/protocol/x/clob/types/quantums.go index 5e3c239cda..8d2d0b423e 100644 --- a/protocol/x/clob/types/quantums.go +++ b/protocol/x/clob/types/quantums.go @@ -33,26 +33,8 @@ func FillAmountToQuoteQuantums( bigSubticks := subticks.ToBigInt() bigBaseQuantums := baseQuantums.ToBigInt() - bigSubticksMulBaseQuantums := new(big.Int).Mul(bigSubticks, bigBaseQuantums) - - exponent := int64(quantumConversionExponent) - - // To ensure we are always doing math with integers, we take the absolute - // value of the exponent. If `exponent` is non-negative, then `10^exponent` is an - // integer and we can multiply by it. Else, `10^exponent` is less than 1 and we should - // multiply by `1 / 10^exponent` (which must be an integer if `exponent < 0`). - bigExponentValue := lib.BigPow10(lib.AbsInt64(exponent)) - - bigQuoteQuantums := new(big.Int) - if exponent < 0 { - // `1 / 10^exponent` is an integer. - bigQuoteQuantums.Div(bigSubticksMulBaseQuantums, bigExponentValue) - } else { - // `10^exponent` is an integer. - bigQuoteQuantums.Mul(bigSubticksMulBaseQuantums, bigExponentValue) - } - - return bigQuoteQuantums + result := new(big.Int).Mul(bigSubticks, bigBaseQuantums) + return lib.BigIntMulPow10(result, quantumConversionExponent, false) } // GetAveragePriceSubticks computes the average price (in subticks) of filled @@ -73,10 +55,13 @@ func GetAveragePriceSubticks( if bigBaseQuantums.Sign() == 0 { panic(errors.New("GetAveragePriceSubticks: bigBaseQuantums = 0")) } - - result := lib.BigMulPow10(bigQuoteQuantums, -quantumConversionExponent) - return result.Quo( - result, - new(big.Rat).SetInt(bigBaseQuantums), - ) + numerator := new(big.Int).Set(bigQuoteQuantums) + denominator := new(big.Int).Set(bigBaseQuantums) + p10, inverse := lib.BigPow10(-quantumConversionExponent) + if inverse { + denominator.Mul(denominator, p10) + } else { + numerator.Mul(numerator, p10) + } + return new(big.Rat).SetFrac(numerator, denominator) } diff --git a/protocol/x/prices/keeper/market_price.go b/protocol/x/prices/keeper/market_price.go index 46e0e5e120..7d168f87c6 100644 --- a/protocol/x/prices/keeper/market_price.go +++ b/protocol/x/prices/keeper/market_price.go @@ -65,13 +65,17 @@ func (k Keeper) UpdateMarketPrices( updatedMarketPrices = append(updatedMarketPrices, marketPrice) // Report the oracle price. - updatedPrice, _ := lib.BigMulPow10( - new(big.Int).SetUint64(update.Price), - marketPrice.Exponent, - ).Float32() + p10, inverse := lib.BigPow10(marketPrice.Exponent) + updatePrice := new(big.Int).SetUint64(update.Price) + var result float32 + if inverse { + result, _ = new(big.Rat).SetFrac(updatePrice, p10).Float32() + } else { + result, _ = new(big.Rat).SetInt(updatePrice.Mul(updatePrice, p10)).Float32() + } telemetry.SetGaugeWithLabels( []string{types.ModuleName, metrics.CurrentMarketPrices}, - updatedPrice, + result, []gometrics.Label{ // To track per market, include the id as a label. pricefeedmetrics.GetLabelForMarketId(marketPrice.Id), }, diff --git a/protocol/x/ratelimit/types/params.go b/protocol/x/ratelimit/types/params.go index 291f5b64b6..c61ea03dfe 100644 --- a/protocol/x/ratelimit/types/params.go +++ b/protocol/x/ratelimit/types/params.go @@ -12,15 +12,17 @@ import ( ) // BigBaselineMinimum1Hr defines the minimum baseline USDC for the 1-hour rate-limit. -var BigBaselineMinimum1Hr = new(big.Int).Mul( +var BigBaselineMinimum1Hr = lib.BigIntMulPow10( big.NewInt(1_000_000), // 1m full coins - lib.BigPow10(-assettypes.UusdcDenomExponent), + -assettypes.UusdcDenomExponent, + false, ) // BigBaselineMinimum1Day defines the minimum baseline USDC for the 1-day rate-limit. -var BigBaselineMinimum1Day = new(big.Int).Mul( +var BigBaselineMinimum1Day = lib.BigIntMulPow10( big.NewInt(10_000_000), // 10m full coins - lib.BigPow10(-assettypes.UusdcDenomExponent), + -assettypes.UusdcDenomExponent, + false, ) var DefaultUsdcHourlyLimter = Limiter{