Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add megavault withdrawal fee estimation #2242

Merged
merged 5 commits into from
Sep 17, 2024
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
51 changes: 51 additions & 0 deletions protocol/lib/vault/utils.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
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"
)

// SkewAntiderivativePpm returns the antiderivative of skew given a vault's skew
// factor and leverage.
// skew_antiderivative_ppm = skew_factor * leverage^2 + skew_factor^2 * leverage^3 / 3
func SkewAntiderivativePpm(
skewFactorPpm uint32,
leveragePpm *big.Int,
) *big.Int {
bigSkewFactorPpm := new(big.Int).SetUint64(uint64(skewFactorPpm))
bigOneTrillion := lib.BigIntOneTrillion()

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

// b = skew_factor^2 * leverage^3 / 3.
b := new(big.Int).Set(a)
b.Mul(b, leveragePpm)
b.Mul(b, bigSkewFactorPpm)
b = lib.BigDivCeil(b, big.NewInt(3))

// normalize a and b.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: Maybe a bit more of a comment on the normalization (ppm * ppm * ppm / 1,000,000,000,000) = (ppm)?

a = lib.BigDivCeil(a, bigOneTrillion)
b = lib.BigDivCeil(b, bigOneTrillion)
b = lib.BigDivCeil(b, bigOneTrillion)

// 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,
)
}
118 changes: 118 additions & 0 deletions protocol/lib/vault/utils_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
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
leveragePpm *big.Int
expected *big.Int
}{
"Zero skew factor and leverage": {
skewFactorPpm: 0,
leveragePpm: big.NewInt(0),
expected: big.NewInt(0),
},
"Non-zero skew factor, zero leverage": {
skewFactorPpm: 1_000_000,
leveragePpm: big.NewInt(0),
expected: big.NewInt(0),
},
"Zero skew factor, non-zero leverage": {
skewFactorPpm: 0,
leveragePpm: big.NewInt(1_000_000),
expected: big.NewInt(0),
},
"Small skew factor and small positive leverage": {
skewFactorPpm: 500_000, // 0.5
leveragePpm: big.NewInt(800_000), // 0.8
// 0.5 * 0.8^2 + 0.5^2 * 0.8^3 / 3 ~= 0.362666
// round up to 0.362667
expected: big.NewInt(362_667),
},
"Small skew factor and small negative leverage": {
skewFactorPpm: 500_000, // 0.5
leveragePpm: big.NewInt(-800_000), // -0.8
// 0.5 * (-0.8)^2 + 0.5^2 * (-0.8)^3 / 3 ~= 0.277333
// round up to 0.277334
expected: big.NewInt(277_334),
},
"Large skew factor and large positive leverage": {
skewFactorPpm: 5_000_000, // 5
leveragePpm: big.NewInt(8_700_000), // 8.7
// 5 * (8.7)^2 + 5^2 * (8.7)^3 / 3 = 5865.975
expected: big.NewInt(5_865_975_000),
},
"Large skew factor and large negative leverage": {
skewFactorPpm: 5_000_000, // 5
leveragePpm: big.NewInt(-8_700_000), // -8.7
// 5 * (-8.7)^2 + 5^2 * (-8.7)^3 / 3 = -5109.075
expected: big.NewInt(-5_109_075_000),
},
}

for name, tc := range tests {
t.Run(name, func(t *testing.T) {
actual := vault.SkewAntiderivativePpm(tc.skewFactorPpm, tc.leveragePpm)
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),
)
})
}
}
30 changes: 29 additions & 1 deletion protocol/mocks/PerpetualsKeeper.go

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

54 changes: 9 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,11 @@ func (k Keeper) GetVaultClobOrders(
)
}

// Calculate leverage = open notional / equity.
equity, err := k.GetVaultEquity(ctx, vaultId)
// Get vault leverage and equity.
leveragePpm, 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)

// Get vault parameters.
quotingParams, exists := k.GetVaultQuotingParams(ctx, vaultId)
Expand Down Expand Up @@ -247,10 +214,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
Loading