Skip to content

Commit

Permalink
add megavault withdrawal fee estimation (#2242)
Browse files Browse the repository at this point in the history
  • Loading branch information
tqin7 authored Sep 17, 2024
1 parent f2fb2b9 commit 4fc6109
Show file tree
Hide file tree
Showing 11 changed files with 1,010 additions and 46 deletions.
13 changes: 13 additions & 0 deletions protocol/lib/big_math.go
Original file line number Diff line number Diff line change
Expand Up @@ -88,6 +88,19 @@ func BigMin(a, b *big.Int) *big.Int {
return result
}

// BigRatMin takes two `big.Rat` as parameters and returns the smaller one.
func BigRatMin(a, b *big.Rat) *big.Rat {
result := new(big.Rat)
// If `a` is greater than `b`, return `b` since it is smaller.
// Else, return `a` since it is smaller than or equal to `b`.
if a.Cmp(b) > 0 {
result.Set(b)
} else {
result.Set(a)
}
return result
}

// BigMax takes two `big.Int` as parameters and returns the larger one.
func BigMax(a, b *big.Int) *big.Int {
result := new(big.Int)
Expand Down
50 changes: 50 additions & 0 deletions protocol/lib/big_math_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,56 @@ func TestBigMin(t *testing.T) {
}
}

func TestBigRatMin(t *testing.T) {
tests := map[string]struct {
a *big.Rat
b *big.Rat
expected *big.Rat
}{
"a is smaller than b": {
a: big.NewRat(5, 2),
b: big.NewRat(6, 2),
expected: big.NewRat(5, 2),
},
"b is smaller than a": {
a: big.NewRat(7, 1),
b: big.NewRat(4, 1),
expected: big.NewRat(4, 1),
},
"a is equal to b": {
a: big.NewRat(8, 7),
b: big.NewRat(8, 7),
expected: big.NewRat(8, 7),
},
"a and b are negative, a is less than b": {
a: big.NewRat(-8, 3),
b: big.NewRat(-7, 3),
expected: big.NewRat(-8, 3),
},
"a and b are negative, b is less than a": {
a: big.NewRat(-9, 5),
b: big.NewRat(-10, 5),
expected: big.NewRat(-10, 5),
},
"a is positive, b is negative, and abs(a) is less than abs(b)": {
a: big.NewRat(4, 3),
b: big.NewRat(-7, 2),
expected: big.NewRat(-7, 2),
},
"a is positive, b is negative, and abs(a) is greater than abs(b)": {
a: big.NewRat(7, 2),
b: big.NewRat(-4, 3),
expected: big.NewRat(-4, 3),
},
}
for name, tc := range tests {
t.Run(name, func(t *testing.T) {
result := lib.BigRatMin(tc.a, tc.b)
require.Equal(t, tc.expected, result)
})
}
}

func TestBigMax(t *testing.T) {
tests := map[string]struct {
a *big.Int
Expand Down
52 changes: 52 additions & 0 deletions protocol/lib/vault/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package vault

import (
"math/big"

"github.com/dydxprotocol/v4-chain/protocol/lib"
pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types"
"github.com/dydxprotocol/v4-chain/protocol/x/vault/types"
)

// SkewAntiderivative returns the antiderivative of skew given a vault's skew
// factor and leverage.
// skew_antiderivative = skew_factor * leverage^2 + skew_factor^2 * leverage^3 / 3
func SkewAntiderivative(
skewFactorPpm uint32,
leverage *big.Rat,
) *big.Rat {
bigSkewFactorPpm := new(big.Rat).SetUint64(uint64(skewFactorPpm))
bigOneMillion := lib.BigRatOneMillion()

// a = skew_factor * leverage^2.
a := new(big.Rat).Mul(leverage, leverage)
a.Mul(a, bigSkewFactorPpm)

// b = skew_factor^2 * leverage^3 / 3.
b := new(big.Rat).Set(a)
b.Mul(b, leverage)
b.Mul(b, bigSkewFactorPpm)
b.Quo(b, big.NewRat(3, 1))

// normalize `a` whose unit currently is ppm.
a.Quo(a, bigOneMillion)
// normalize `b` whose unit currently is ppm * ppm.
b.Quo(b, bigOneMillion)
b.Quo(b, bigOneMillion)

// return a + b.
return a.Add(a, b)
}

// SpreadPpm returns the spread that a vault should quote at given its
// quoting params and corresponding market param.
// spread_ppm = max(spread_min_ppm, spread_buffer_ppm + min_price_change_ppm)
func SpreadPpm(
quotingParams *types.QuotingParams,
marketParam *pricestypes.MarketParam,
) uint32 {
return lib.Max(
quotingParams.SpreadMinPpm,
quotingParams.SpreadBufferPpm+marketParam.MinPriceChangePpm,
)
}
116 changes: 116 additions & 0 deletions protocol/lib/vault/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
package vault_test

import (
"math/big"
"testing"

"github.com/stretchr/testify/require"

"github.com/dydxprotocol/v4-chain/protocol/lib/vault"
pricestypes "github.com/dydxprotocol/v4-chain/protocol/x/prices/types"
"github.com/dydxprotocol/v4-chain/protocol/x/vault/types"
)

func TestSkewAntiderivativePpm(t *testing.T) {
tests := map[string]struct {
skewFactorPpm uint32
leverage *big.Rat
expected *big.Rat
}{
"Zero skew factor and leverage": {
skewFactorPpm: 0,
leverage: big.NewRat(0, 1),
expected: big.NewRat(0, 1),
},
"Non-zero skew factor, zero leverage": {
skewFactorPpm: 1_000_000,
leverage: big.NewRat(0, 1),
expected: big.NewRat(0, 1),
},
"Zero skew factor, non-zero leverage": {
skewFactorPpm: 0,
leverage: big.NewRat(1_000_000, 1),
expected: big.NewRat(0, 1),
},
"Small skew factor and small positive leverage": {
skewFactorPpm: 500_000, // 0.5
leverage: big.NewRat(4, 5), // 0.8
// 0.5 * 0.8^2 + 0.5^2 * 0.8^3 / 3 = 136/375
expected: big.NewRat(136, 375),
},
"Small skew factor and small negative leverage": {
skewFactorPpm: 500_000, // 0.5
leverage: big.NewRat(-4, 5), // -0.8
// 0.5 * (-0.8)^2 + 0.5^2 * (-0.8)^3 / 3 = 104/375
expected: big.NewRat(104, 375),
},
"Large skew factor and large positive leverage": {
skewFactorPpm: 5_000_000, // 5
leverage: big.NewRat(87, 10), // 8.7
// 5 * (8.7)^2 + 5^2 * (8.7)^3 / 3 = 234639/40
expected: big.NewRat(234_639, 40),
},
"Large skew factor and large negative leverage": {
skewFactorPpm: 5_000_000, // 5
leverage: big.NewRat(-87, 10), // -8.7
// 5 * (-8.7)^2 + 5^2 * (-8.7)^3 / 3 = -204363/40
expected: big.NewRat(-204_363, 40),
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
actual := vault.SkewAntiderivative(tc.skewFactorPpm, tc.leverage)
require.Equal(t, tc.expected, actual)
})
}
}

func TestSpreadPpm(t *testing.T) {
tests := map[string]struct {
quotingParams *types.QuotingParams
marketParam *pricestypes.MarketParam
expected uint32
}{
"SpreadMinPpm > SpreadBufferPpm + MinPriceChangePpm": {
quotingParams: &types.QuotingParams{
SpreadMinPpm: 1000,
SpreadBufferPpm: 200,
},
marketParam: &pricestypes.MarketParam{
MinPriceChangePpm: 500,
},
expected: 1000,
},
"SpreadMinPpm < SpreadBufferPpm + MinPriceChangePpm": {
quotingParams: &types.QuotingParams{
SpreadMinPpm: 1000,
SpreadBufferPpm: 600,
},
marketParam: &pricestypes.MarketParam{
MinPriceChangePpm: 500,
},
expected: 1100,
},
"SpreadMinPpm = SpreadBufferPpm + MinPriceChangePpm": {
quotingParams: &types.QuotingParams{
SpreadMinPpm: 1000,
SpreadBufferPpm: 400,
},
marketParam: &pricestypes.MarketParam{
MinPriceChangePpm: 600,
},
expected: 1000,
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
require.Equal(
t,
tc.expected,
vault.SpreadPpm(tc.quotingParams, tc.marketParam),
)
})
}
}
28 changes: 28 additions & 0 deletions protocol/mocks/PerpetualsKeeper.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

56 changes: 11 additions & 45 deletions protocol/x/vault/keeper/orders.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keeper

import (
"errors"
"fmt"
"math"
"math/big"
Expand All @@ -11,6 +12,7 @@ import (
"github.com/dydxprotocol/v4-chain/protocol/lib"
"github.com/dydxprotocol/v4-chain/protocol/lib/log"
"github.com/dydxprotocol/v4-chain/protocol/lib/metrics"
"github.com/dydxprotocol/v4-chain/protocol/lib/vault"
clobtypes "github.com/dydxprotocol/v4-chain/protocol/x/clob/types"
"github.com/dydxprotocol/v4-chain/protocol/x/vault/types"
)
Expand Down Expand Up @@ -154,31 +156,11 @@ func (k Keeper) GetVaultClobOrders(
vaultId types.VaultId,
) (orders []*clobtypes.Order, err error) {
// Get clob pair, perpetual, market parameter, and market price that correspond to this vault.
clobPair, exists := k.clobKeeper.GetClobPair(ctx, clobtypes.ClobPairId(vaultId.Number))
if !exists || clobPair.Status == clobtypes.ClobPair_STATUS_FINAL_SETTLEMENT {
clobPair, perpetual, marketParam, marketPrice, err := k.GetVaultClobPerpAndMarket(ctx, vaultId)
if errors.Is(err, types.ErrClobPairNotFound) || clobPair.Status == clobtypes.ClobPair_STATUS_FINAL_SETTLEMENT {
return []*clobtypes.Order{}, nil
}
perpId := clobPair.Metadata.(*clobtypes.ClobPair_PerpetualClobMetadata).PerpetualClobMetadata.PerpetualId
perpetual, err := k.perpetualsKeeper.GetPerpetual(ctx, perpId)
if err != nil {
return orders, errorsmod.Wrap(
err,
fmt.Sprintf("VaultId: %v", vaultId),
)
}
marketParam, exists := k.pricesKeeper.GetMarketParam(ctx, perpetual.Params.MarketId)
if !exists {
return orders, errorsmod.Wrap(
types.ErrMarketParamNotFound,
fmt.Sprintf("VaultId: %v", vaultId),
)
}
marketPrice, err := k.pricesKeeper.GetMarketPrice(ctx, perpetual.Params.MarketId)
if err != nil {
return orders, errorsmod.Wrap(
err,
fmt.Sprintf("VaultId: %v", vaultId),
)
} else if err != nil {
return orders, err
} else if marketPrice.Price == 0 {
// Market price can be zero upon market initialization or due to invalid exchange config.
return orders, errorsmod.Wrap(
Expand All @@ -187,26 +169,13 @@ func (k Keeper) GetVaultClobOrders(
)
}

// Calculate leverage = open notional / equity.
equity, err := k.GetVaultEquity(ctx, vaultId)
// Get vault leverage and equity.
leverage, equity, err := k.GetVaultLeverageAndEquity(ctx, vaultId, perpetual, marketPrice)
if err != nil {
return orders, err
}
if equity.Sign() <= 0 {
return orders, errorsmod.Wrap(
types.ErrNonPositiveEquity,
fmt.Sprintf("VaultId: %v", vaultId),
)
}
inventory := k.GetVaultInventoryInPerpetual(ctx, vaultId, perpId)
openNotional := lib.BaseToQuoteQuantums(
inventory,
perpetual.Params.AtomicResolution,
marketPrice.GetPrice(),
marketPrice.GetExponent(),
)
leveragePpm := new(big.Int).Mul(openNotional, lib.BigIntOneMillion())
leveragePpm.Quo(leveragePpm, equity)
leveragePpm := new(big.Int).Mul(leverage.Num(), lib.BigIntOneMillion())
leveragePpm = lib.BigDivCeil(leveragePpm, leverage.Denom())

// Get vault parameters.
quotingParams, exists := k.GetVaultQuotingParams(ctx, vaultId)
Expand Down Expand Up @@ -247,10 +216,7 @@ func (k Keeper) GetVaultClobOrders(
}

// Calculate spread.
spreadPpm := lib.BigU(lib.Max(
quotingParams.SpreadMinPpm,
quotingParams.SpreadBufferPpm+marketParam.MinPriceChangePpm,
))
spreadPpm := lib.BigU(vault.SpreadPpm(&quotingParams, &marketParam))
// Get oracle price in subticks.
oracleSubticks := clobtypes.PriceToSubticks(
marketPrice,
Expand Down
Loading

0 comments on commit 4fc6109

Please sign in to comment.