Skip to content

Commit

Permalink
feat(vpool): sqrt of liquidity depth tracked on pool (#1243)
Browse files Browse the repository at this point in the history
* feat: implement function for getting the exact square root of an sdk.Dec

* (vpool): get vpool/types passing

* (vpool): keeper tests passing again

* CHANGELOG #wip

* #wip all vpool tests passing. perp in progress

* fix tests in perp module

* feat(common): SqrtDec changes

* add changes from refactor

* run golangci-lint

* fix(vpool): cli_test.go

* rm unused file

* test(common): Finalize new functions, add fn docs, add large number test
cases

* more fn docs

* refactor!: change proto order so that the Vpool.Config is still on 4

* refactor: rename MICRO → TO_MICRO

* refactor(vpool): types.NewVpool abstraction for simplified constructor

---------

Co-authored-by: Kevin Yang <5478483+k-yang@users.noreply.github.com>
  • Loading branch information
Unique-Divine and k-yang authored Mar 31, 2023
1 parent 73dd798 commit b7149d1
Show file tree
Hide file tree
Showing 52 changed files with 988 additions and 485 deletions.
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

### Breaking

* [#1252](https://github.com/NibiruChain/nibiru/pull/1220) - feat: reduce gas fees when posting price
* [#1243](https://github.com/NibiruChain/nibiru/pull/1243) - feat(vpool): sqrt of liquidity depth tracked on pool
* [#1220](https://github.com/NibiruChain/nibiru/pull/1220) - feat: reduce gas fees when posting price
* [#1229](https://github.com/NibiruChain/nibiru/pull/1229) - feat: upgrade ibc to v4.2.0 and wasm v0.30.0

### Improvements
Expand Down
22 changes: 22 additions & 0 deletions proto/vpool/v1/state.proto
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ enum TwapCalcOption {
// A virtual pool used only for price discovery of perpetual futures contracts.
// No real liquidity exists in this pool.
message Vpool {
option (gogoproto.goproto_stringer) = false;

// always BASE:QUOTE, e.g. BTC:NUSD or ETH:NUSD
string pair = 1 [
(gogoproto.customtype) = "github.com/NibiruChain/nibiru/x/common/asset.Pair",
Expand All @@ -51,6 +53,26 @@ message Vpool {
VpoolConfig config = 4 [
(gogoproto.nullable) = false
];

// The square root of the liquidity depth. Liquidity depth is the product of
// the reserves.
string sqrt_depth = 5 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
(gogoproto.nullable) = false
];

// Bias refers to the net long-short bias of the market. The bias of a pool
// is equivalent to the sum of all base reserve changes since its creation.
// string bias = 5 [
// (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
// (gogoproto.nullable) = false
// ];

// TD docs. For now, leave this as 1 until the feature PR.
// string price_mult = 7 [
// (gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Dec",
// (gogoproto.nullable) = false
// ];
}

message VpoolConfig {
Expand Down
4 changes: 2 additions & 2 deletions x/common/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,6 @@ package common

const (
TreasuryPoolModuleAccount = "treasury_pool"
// Precision for int representation in sdk.Int objects
Precision = int64(1_000_000)
// TO_MICRO: multiplier for converting between units and micro-units.
TO_MICRO = int64(1_000_000)
)
89 changes: 89 additions & 0 deletions x/common/dec.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
package common

import (
"fmt"
"math/big"
"strings"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// MustSqrtDec computes the square root of the input decimal using its
// underlying big.Int. The big.Int.Sqrt method is part of the standard library,
// thoroughly tested, works at seemingly unbound precision (e.g. for numbers as
// large as 10**99.
// - NOTE, MustSqrtDec panics if it is called on a negative number, similar to the
// sdk.NewCoin and SqrtBigInt functions. A panic safe version of MustSqrtDec
// is available in the SqrtDec method.
func MustSqrtDec(dec sdk.Dec) sdk.Dec {
sqrtBigInt := MustSqrtBigInt(dec.BigInt())
precision := sdk.NewDecFromBigInt(PRECISION_MULT)
return sdk.NewDecFromBigInt(sqrtBigInt).Quo(precision)
}

// SqrtDec computes the square root of the input decimal using its
// underlying big.Int. SqrtDec is panic-safe and returns an error if the input
// decimal is negative.
//
// The big.Int.Sqrt method is part of the standard library,
// thoroughly tested, works at seemingly unbound precision (e.g. for numbers as
// large as 10**99.
func SqrtDec(dec sdk.Dec) (sdk.Dec, error) {
var sqrtDec sdk.Dec
var panicErr error = TryCatch(func() {
sqrtDec = MustSqrtDec(dec)
})()
return sqrtDec, panicErr
}

// MustSqrtBigInt returns the square root of the input.
// - NOTE: MustSqrtBigInt panics if it is called on a negative number because it uses
// the `big.Int.Sqrt` from the "math/big" package.
func MustSqrtBigInt(i *big.Int) *big.Int {
sqrtInt := &big.Int{}
return sqrtInt.Sqrt(i)
}

// SqrtInt is the panic-safe version of MustSqrtBigInt
func SqrtBigInt(i *big.Int) (*big.Int, error) {
sqrtInt := new(big.Int)
var panicErr error = TryCatch(func() {
*sqrtInt = *MustSqrtBigInt(i)
})()
return sqrtInt, panicErr
}

// BigIntPow10 returns a big int that is a power of 10, e.g. BigIngPow10(3)
// returns 1000. This function is useful for creating large numbers outside the
// range of an int64 or 18 decimal precision.
func BigIntPow10(power int64) *big.Int {
bigInt, _ := new(big.Int).SetString("1"+strings.Repeat("0", int(power)), 10)
return bigInt
}

// ————————————————————————————————————————————————
// Logic needed from private code in the Cosmos-SDK
// See https://github.com/cosmos/cosmos-sdk/blob/v0.45.12/types/decimal.go
//

const (
PRECISION = 18
)

var (
PRECISION_MULT = calcPrecisionMultiplier(0)
PRECISION_SQRT = int64(PRECISION / 2)
tenInt = big.NewInt(10)
)

// calcPrecisionMultiplier computes a multiplier needed to maintain a target
// precision defined by 10 ** (PRECISION_SQRT - prec).
// The maximum available precision is PRECISION_SQRT (9).
func calcPrecisionMultiplier(prec int64) *big.Int {
if prec > PRECISION_SQRT {
panic(fmt.Sprintf("too much precision, maximum %v, provided %v", PRECISION_SQRT, prec))
}
zerosToAdd := PRECISION_SQRT - prec
multiplier := new(big.Int).Exp(tenInt, big.NewInt(zerosToAdd), nil)
return multiplier
}
81 changes: 81 additions & 0 deletions x/common/dec_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package common_test

import (
"fmt"
"math/big"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"

"github.com/stretchr/testify/assert"

"github.com/NibiruChain/nibiru/x/common"
)

func TestSqrtBigInt(t *testing.T) {
testCases := []struct {
bigInt *big.Int
sqrtBigInt *big.Int
}{
{bigInt: big.NewInt(1), sqrtBigInt: big.NewInt(1)},
{bigInt: big.NewInt(4), sqrtBigInt: big.NewInt(2)},
{bigInt: big.NewInt(250_000), sqrtBigInt: big.NewInt(500)},
{bigInt: big.NewInt(4_819_136_400), sqrtBigInt: big.NewInt(69_420)},
{
bigInt: new(big.Int).Mul(big.NewInt(4_819_136_400), common.BigIntPow10(32)),
sqrtBigInt: new(big.Int).Mul(big.NewInt(69_420), common.BigIntPow10(16)),
},
{
bigInt: new(big.Int).Mul(big.NewInt(9), common.BigIntPow10(100)),
sqrtBigInt: new(big.Int).Mul(big.NewInt(3), common.BigIntPow10(50)),
},
}

for _, testCase := range testCases {
tc := testCase
t.Run(fmt.Sprintf(`bigInt: %s, sqrtBigInt: %s`, tc.bigInt, tc.sqrtBigInt), func(t *testing.T) {
sqrtInt := common.MustSqrtBigInt(tc.bigInt)
assert.Equal(t, tc.sqrtBigInt.String(), sqrtInt.String())
})
}
}

func TestSqrtDec(t *testing.T) {
testCases := []struct {
dec sdk.Dec
sqrtDec sdk.Dec
}{
// --------------------------------------------------------------------
// Cases: 1 or higher
{dec: sdk.NewDec(1), sqrtDec: sdk.NewDec(1)},
{dec: sdk.NewDec(4), sqrtDec: sdk.NewDec(2)},
{dec: sdk.NewDec(250_000), sqrtDec: sdk.NewDec(500)},
{dec: sdk.NewDec(4_819_136_400), sqrtDec: sdk.NewDec(69_420)},

// --------------------------------------------------------------------
// Cases: Between 0 and 1
{dec: sdk.MustNewDecFromStr("0.81"), sqrtDec: sdk.MustNewDecFromStr("0.9")},
{dec: sdk.MustNewDecFromStr("0.25"), sqrtDec: sdk.MustNewDecFromStr("0.5")},
// ↓ dec 1e-12, sqrtDec: 1e-6
{dec: sdk.MustNewDecFromStr("0.000000000001"), sqrtDec: sdk.MustNewDecFromStr("0.000001")},

// --------------------------------------------------------------------
// The math/big library panics if you call sqrt() on a negative number.
}

t.Run("negative sqrt should panic", func(t *testing.T) {
panicString := common.TryCatch(func() {
common.MustSqrtDec(sdk.NewDec(-9))
})().Error()

assert.Contains(t, panicString, "square root of negative number")
})

for _, testCase := range testCases {
tc := testCase
t.Run(fmt.Sprintf(`dec: %s, sqrtDec: %s`, tc.dec, tc.sqrtDec), func(t *testing.T) {
sqrtDec := common.MustSqrtDec(tc.dec)
assert.Equal(t, tc.sqrtDec.String(), sqrtDec.String())
})
}
}
2 changes: 1 addition & 1 deletion x/common/testutil/cli/tx.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ func (n *Network) SendTx(addr sdk.AccAddress, msgs ...sdk.Msg) (*sdk.TxResponse,
txBuilder := cfg.TxConfig.NewTxBuilder()
require.NoError(n.T, txBuilder.SetMsgs(msgs...))
txBuilder.SetFeeAmount(sdk.NewCoins(sdk.NewCoin(cfg.BondDenom, sdk.NewInt(1))))
txBuilder.SetGasLimit(uint64(1 * common.Precision))
txBuilder.SetGasLimit(uint64(1 * common.TO_MICRO))

acc, err := cfg.AccountRetriever.GetAccount(n.Validators[0].ClientCtx, addr)
require.NoError(n.T, err)
Expand Down
4 changes: 2 additions & 2 deletions x/oracle/keeper/reward_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ func TestKeeperRewardsDistributionMultiVotePeriods(t *testing.T) {
fixture, msgServer := Setup(t)
votePeriod := fixture.OracleKeeper.VotePeriod(fixture.Ctx)

rewards := sdk.NewInt64Coin("reward", 1*common.Precision)
rewards := sdk.NewInt64Coin("reward", 1*common.TO_MICRO)
valPeriodicRewards := sdk.NewDecCoinsFromCoins(rewards).
QuoDec(sdk.NewDec(int64(periods))).
QuoDec(sdk.NewDec(int64(validators)))
Expand Down Expand Up @@ -75,7 +75,7 @@ func TestAllocateRewardsForUnlistedPair(t *testing.T) {
fixture.Ctx,
faucetAccountName,
asset.Registry.Pair("foo", "bar"), // pair doesn't exist
sdk.NewCoins(sdk.NewInt64Coin("reward", 1*common.Precision)),
sdk.NewCoins(sdk.NewInt64Coin("reward", 1*common.TO_MICRO)),
1,
))
}
6 changes: 3 additions & 3 deletions x/oracle/keeper/update_exchange_rates_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -314,15 +314,15 @@ func TestOracleExchangeRate(t *testing.T) {
{Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD), ExchangeRate: sdk.ZeroDec()},
}, 3)

ethNusdRewards := sdk.NewInt64Coin("ETHREWARD", 1*common.Precision)
nibiNusdRewards := sdk.NewInt64Coin("NIBIREWARD", 1*common.Precision)
ethNusdRewards := sdk.NewInt64Coin("ETHREWARD", 1*common.TO_MICRO)
nibiNusdRewards := sdk.NewInt64Coin("NIBIREWARD", 1*common.TO_MICRO)

AllocateRewards(t, input, asset.Registry.Pair(denoms.ETH, denoms.NUSD), sdk.NewCoins(ethNusdRewards), 1)
AllocateRewards(t, input, asset.Registry.Pair(denoms.NIBI, denoms.NUSD), sdk.NewCoins(nibiNusdRewards), 1)

input.OracleKeeper.UpdateExchangeRates(input.Ctx)

// total reward pool for the current vote period is 1* common.Precision for eth:nusd and 1* common.Precision for nibi:nusd
// total reward pool for the current vote period is 1* common.TO_MICRO for eth:nusd and 1* common.TO_MICRO for nibi:nusd
// val 1,2,3,4 all won on 2 pairs
// so total votes are 2 * 2 + 2 + 2 = 8
expectedRewardAmt := sdk.NewDecCoinsFromCoins(ethNusdRewards, nibiNusdRewards).
Expand Down
42 changes: 23 additions & 19 deletions x/perp/client/cli/cli_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -54,8 +54,9 @@ func (s *IntegrationTestSuite) SetupSuite() {
vpoolGenesis.Vpools = []vpooltypes.Vpool{
{
Pair: asset.Registry.Pair(denoms.BTC, denoms.NUSD),
BaseAssetReserve: sdk.NewDec(10 * common.Precision),
QuoteAssetReserve: sdk.NewDec(60_000 * common.Precision),
BaseAssetReserve: sdk.NewDec(10 * common.TO_MICRO),
QuoteAssetReserve: sdk.NewDec(60_000 * common.TO_MICRO),
SqrtDepth: common.MustSqrtDec(sdk.NewDec(10 * 60_000 * common.TO_MICRO * common.TO_MICRO)),
Config: vpooltypes.VpoolConfig{
TradeLimitRatio: sdk.MustNewDecFromStr("0.8"),
FluctuationLimitRatio: sdk.OneDec(),
Expand All @@ -66,8 +67,9 @@ func (s *IntegrationTestSuite) SetupSuite() {
},
{
Pair: asset.Registry.Pair(denoms.ETH, denoms.NUSD),
BaseAssetReserve: sdk.NewDec(10 * common.Precision),
QuoteAssetReserve: sdk.NewDec(60_000 * common.Precision),
BaseAssetReserve: sdk.NewDec(10 * common.TO_MICRO),
QuoteAssetReserve: sdk.NewDec(60_000 * common.TO_MICRO),
SqrtDepth: common.MustSqrtDec(sdk.NewDec(10 * 60_000 * common.TO_MICRO * common.TO_MICRO)),
Config: vpooltypes.VpoolConfig{
TradeLimitRatio: sdk.MustNewDecFromStr("0.8"),
FluctuationLimitRatio: sdk.MustNewDecFromStr("0.2"),
Expand All @@ -78,8 +80,9 @@ func (s *IntegrationTestSuite) SetupSuite() {
},
{
Pair: asset.Registry.Pair(denoms.ATOM, denoms.NUSD),
BaseAssetReserve: sdk.NewDec(10 * common.Precision),
QuoteAssetReserve: sdk.NewDec(60_000 * common.Precision),
BaseAssetReserve: sdk.NewDec(10 * common.TO_MICRO),
QuoteAssetReserve: sdk.NewDec(60_000 * common.TO_MICRO),
SqrtDepth: common.MustSqrtDec(sdk.NewDec(10 * 60_000 * common.TO_MICRO * common.TO_MICRO)),
Config: vpooltypes.VpoolConfig{
TradeLimitRatio: sdk.MustNewDecFromStr("0.8"),
FluctuationLimitRatio: sdk.MustNewDecFromStr("0.2"),
Expand All @@ -90,8 +93,9 @@ func (s *IntegrationTestSuite) SetupSuite() {
},
{
Pair: asset.Registry.Pair(denoms.OSMO, denoms.NUSD),
BaseAssetReserve: sdk.NewDec(10 * common.Precision),
QuoteAssetReserve: sdk.NewDec(60_000 * common.Precision),
BaseAssetReserve: sdk.NewDec(10 * common.TO_MICRO),
QuoteAssetReserve: sdk.NewDec(60_000 * common.TO_MICRO),
SqrtDepth: common.MustSqrtDec(sdk.NewDec(10 * 60_000 * common.TO_MICRO * common.TO_MICRO)),
Config: vpooltypes.VpoolConfig{
TradeLimitRatio: sdk.MustNewDecFromStr("0.8"),
FluctuationLimitRatio: sdk.MustNewDecFromStr("0.2"),
Expand Down Expand Up @@ -156,9 +160,9 @@ func (s *IntegrationTestSuite) SetupSuite() {
s.NoError(
testutilcli.FillWalletFromValidator(newUser,
sdk.NewCoins(
sdk.NewInt64Coin(denoms.NIBI, 10*common.Precision),
sdk.NewInt64Coin(denoms.USDC, 1e3*common.Precision),
sdk.NewInt64Coin(denoms.NUSD, 5e3*common.Precision),
sdk.NewInt64Coin(denoms.NIBI, 10*common.TO_MICRO),
sdk.NewInt64Coin(denoms.USDC, 1e3*common.TO_MICRO),
sdk.NewInt64Coin(denoms.NUSD, 5e3*common.TO_MICRO),
),
val,
denoms.NIBI,
Expand Down Expand Up @@ -266,8 +270,8 @@ func (s *IntegrationTestSuite) TestOpenPositionsAndCloseCmd() {
reserveAssets, err := testutilcli.QueryVpoolReserveAssets(val.ClientCtx, asset.Registry.Pair(denoms.BTC, denoms.NUSD))
s.T().Logf("reserve assets: %+v", reserveAssets)
s.NoError(err)
s.EqualValues(sdk.NewDec(10*common.Precision), reserveAssets.BaseAssetReserve)
s.EqualValues(sdk.NewDec(60_000*common.Precision), reserveAssets.QuoteAssetReserve)
s.EqualValues(sdk.NewDec(10*common.TO_MICRO), reserveAssets.BaseAssetReserve)
s.EqualValues(sdk.NewDec(60_000*common.TO_MICRO), reserveAssets.QuoteAssetReserve)

s.T().Log("A. check trader has no existing positions")
_, err = testutilcli.QueryPosition(val.ClientCtx, asset.Registry.Pair(denoms.BTC, denoms.NUSD), user)
Expand All @@ -289,7 +293,7 @@ func (s *IntegrationTestSuite) TestOpenPositionsAndCloseCmd() {
s.T().Logf("reserve assets: %+v", reserveAssets)
s.NoError(err)
s.EqualValues(sdk.MustNewDecFromStr("9999833.336111064815586407"), reserveAssets.BaseAssetReserve)
s.EqualValues(sdk.NewDec(60_001*common.Precision), reserveAssets.QuoteAssetReserve)
s.EqualValues(sdk.NewDec(60_001*common.TO_MICRO), reserveAssets.QuoteAssetReserve)

s.T().Log("B. check trader position")
queryResp, err := testutilcli.QueryPosition(val.ClientCtx, asset.Registry.Pair(denoms.BTC, denoms.NUSD), user)
Expand All @@ -298,8 +302,8 @@ func (s *IntegrationTestSuite) TestOpenPositionsAndCloseCmd() {
s.EqualValues(user.String(), queryResp.Position.TraderAddress)
s.EqualValues(asset.Registry.Pair(denoms.BTC, denoms.NUSD), queryResp.Position.Pair)
s.EqualValues(sdk.MustNewDecFromStr("166.663888935184413593"), queryResp.Position.Size_)
s.EqualValues(sdk.NewDec(1*common.Precision), queryResp.Position.Margin)
s.EqualValues(sdk.NewDec(1*common.Precision), queryResp.Position.OpenNotional)
s.EqualValues(sdk.NewDec(1*common.TO_MICRO), queryResp.Position.Margin)
s.EqualValues(sdk.NewDec(1*common.TO_MICRO), queryResp.Position.OpenNotional)
s.EqualValues(sdk.MustNewDecFromStr("999999.999999999999999359"), queryResp.PositionNotional)
s.EqualValues(sdk.MustNewDecFromStr("-0.000000000000000641"), queryResp.UnrealizedPnl)
s.EqualValues(sdk.NewDec(1), queryResp.MarginRatioMark)
Expand All @@ -323,8 +327,8 @@ func (s *IntegrationTestSuite) TestOpenPositionsAndCloseCmd() {
s.EqualValues(user.String(), queryResp.Position.TraderAddress)
s.EqualValues(asset.Registry.Pair(denoms.BTC, denoms.NUSD), queryResp.Position.Pair)
s.EqualValues(sdk.MustNewDecFromStr("499.975001249937503125"), queryResp.Position.Size_)
s.EqualValues(sdk.NewDec(2*common.Precision), queryResp.Position.Margin)
s.EqualValues(sdk.NewDec(3*common.Precision), queryResp.Position.OpenNotional)
s.EqualValues(sdk.NewDec(2*common.TO_MICRO), queryResp.Position.Margin)
s.EqualValues(sdk.NewDec(3*common.TO_MICRO), queryResp.Position.OpenNotional)
s.EqualValues(sdk.MustNewDecFromStr("3000000.000000000000000938"), queryResp.PositionNotional)
s.EqualValues(sdk.MustNewDecFromStr("0.000000000000000938"), queryResp.UnrealizedPnl)
s.EqualValues(sdk.MustNewDecFromStr("0.666666666666666667"), queryResp.MarginRatioMark)
Expand Down Expand Up @@ -354,7 +358,7 @@ func (s *IntegrationTestSuite) TestOpenPositionsAndCloseCmd() {
s.EqualValues(user.String(), queryResp.Position.TraderAddress)
s.EqualValues(asset.Registry.Pair(denoms.BTC, denoms.NUSD), queryResp.Position.Pair)
s.EqualValues(sdk.MustNewDecFromStr("499.958336249784737846"), queryResp.Position.Size_)
s.EqualValues(sdk.NewDec(2*common.Precision), queryResp.Position.Margin)
s.EqualValues(sdk.NewDec(2*common.TO_MICRO), queryResp.Position.Margin)
s.EqualValues(sdk.NewDec(2_999_900), queryResp.Position.OpenNotional)
s.EqualValues(sdk.MustNewDecFromStr("2999899.999999999999999506"), queryResp.PositionNotional)
s.EqualValues(sdk.MustNewDecFromStr("-0.000000000000000494"), queryResp.UnrealizedPnl)
Expand Down
Loading

0 comments on commit b7149d1

Please sign in to comment.