Skip to content

Commit

Permalink
avkr003/leverageLPFixes (#683)
Browse files Browse the repository at this point in the history
* Leverage LP fixes

* Correcting DestroyPosition order

* Fixing unit test case

* Fixing AccAddress conversion

* Removing unnecessary code and fixing corresponding test cases
  • Loading branch information
avkr003 authored Jul 31, 2024
1 parent 1542bbc commit 8ca39e3
Show file tree
Hide file tree
Showing 10 changed files with 98 additions and 168 deletions.
93 changes: 0 additions & 93 deletions x/amm/types/pow_approx.go
Original file line number Diff line number Diff line change
Expand Up @@ -99,99 +99,6 @@ func computeLn(x sdk.Dec) (result sdk.Dec, err error) {
return result.Add(ln2.MulInt64(int64(k))), nil
}

func PowApprox(originalBase sdk.Dec, exp sdk.Dec, precision sdk.Dec) sdk.Dec {
if !originalBase.IsPositive() {
panic(fmt.Errorf("base must be greater than 0"))
}

if exp.IsZero() {
return sdk.OneDec()
}

// Common case optimization
// Optimize for it being equal to one-half
if exp.Equal(oneHalf) {
output, err := originalBase.ApproxSqrt()
if err != nil {
panic(err)
}
return output
}
// TODO: Make an approx-equal function, and then check if exp * 3 = 1, and do a check accordingly

// We compute this via taking the maclaurin series of (1 + x)^a
// where x = base - 1.
// The maclaurin series of (1 + x)^a = sum_{k=0}^{infty} binom(a, k) x^k
// Binom(a, k) takes the natural continuation on the first parameter, namely that
// Binom(a, k) = N/D, where D = k!, and N = a(a-1)(a-2)...(a-k+1)
// Next we show that the absolute value of each term is less than the last term.
// Note that the change in term n's value vs term n + 1 is a multiplicative factor of
// v_n = x(a - n) / (n+1)
// So if |v_n| < 1, we know that each term has a lesser impact on the result than the last.
// For our bounds on |x| < 1, |a| < 1,
// it suffices to see for what n is |v_n| < 1,
// in the worst parameterization of x = 1, a = -1.
// v_n = |(-1 + epsilon - n) / (n+1)|
// So |v_n| is always less than 1, as n ranges over the integers.
//
// Note that term_n of the expansion is 1 * prod_{i=0}^{n-1} v_i
// The error if we stop the expansion at term_n is:
// error_n = sum_{k=n+1}^{infty} term_k
// At this point we further restrict a >= 0, so 0 <= a < 1.
// Now we take the _INCORRECT_ assumption that if term_n < p, then
// error_n < p.
// This assumption is obviously wrong.
// However our usages of this function don't use the full domain.
// With a > 0, |x| << 1, and p sufficiently low, perhaps this actually is true.

// TODO: Check with our parameterization
// TODO: If there's a bug, balancer is also wrong here :thonk:

base := originalBase.Clone()
x, xneg := AbsDifferenceWithSign(base, sdk.OneDec())
term := sdk.OneDec()
sum := sdk.OneDec()
negative := false

a := exp.Clone()
bigK := sdk.NewDec(0)
// TODO: Document this computation via taylor expansion
for i := int64(1); term.GTE(precision); i++ {
// At each iteration, we need two values, i and i-1.
// To avoid expensive big.Int allocation, we reuse bigK variable.
// On this line, bigK == i-1.
c, cneg := AbsDifferenceWithSign(a, bigK)
// On this line, bigK == i.
bigK.SetInt64(i)
term.MulMut(c).MulMut(x).QuoMut(bigK)

// a is mutated on absDifferenceWithSign, reset
a.Set(exp)

if term.IsZero() {
break
}
if xneg {
negative = !negative
}

if cneg {
negative = !negative
}

if negative {
sum.SubMut(term)
} else {
sum.AddMut(term)
}

if i == powIterationLimit {
panic(fmt.Errorf("failed to reach precision within %d iterations, best guess: %s for %s^%s", powIterationLimit, sum, originalBase, exp))
}
}
return sum
}

// powerApproximation Check exponentialLogarithmicMethod and maclaurinSeriesApproximation to understand the limits of this function
func powerApproximation(base sdk.Dec, exp sdk.Dec) (sdk.Dec, error) {
if !base.IsPositive() {
Expand Down
62 changes: 27 additions & 35 deletions x/amm/types/pow_approx_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,82 +18,73 @@ func ConditionalPanic(t *testing.T, expectPanic bool, sut func()) {
func TestPowApprox(t *testing.T) {
testCases := []struct {
expectPanic bool
expectErr bool
base sdk.Dec
exp sdk.Dec
powPrecision sdk.Dec
expectedResult sdk.Dec
}{
{
// medium base, small exp
base: sdk.MustNewDecFromStr("0.8"),
exp: sdk.MustNewDecFromStr("0.32"),
powPrecision: sdk.MustNewDecFromStr("0.00000001"),
expectedResult: sdk.MustNewDecFromStr("0.93108385"),
},
{
// zero exp
base: sdk.MustNewDecFromStr("0.8"),
exp: sdk.ZeroDec(),
powPrecision: sdk.MustNewDecFromStr("0.00001"),
expectedResult: sdk.OneDec(),
},
{
// zero base, this should panic
base: sdk.ZeroDec(),
exp: sdk.OneDec(),
powPrecision: sdk.MustNewDecFromStr("0.00001"),
expectPanic: true,
base: sdk.ZeroDec(),
exp: sdk.OneDec(),
expectPanic: false,
expectErr: true,
},
{
// large base, small exp
base: sdk.MustNewDecFromStr("1.9999"),
exp: sdk.MustNewDecFromStr("0.23"),
powPrecision: sdk.MustNewDecFromStr("0.000000001"),
expectedResult: sdk.MustNewDecFromStr("1.172821461"),
},
{
// large base, large integer exp
base: sdk.MustNewDecFromStr("1.777"),
exp: sdk.MustNewDecFromStr("20"),
powPrecision: sdk.MustNewDecFromStr("0.000000000001"),
expectedResult: sdk.MustNewDecFromStr("98570.862372081602"),
},
{
// medium base, large exp, high precision
base: sdk.MustNewDecFromStr("1.556"),
exp: sdk.MustNewDecFromStr("20.9123"),
powPrecision: sdk.MustNewDecFromStr("0.0000000000000001"),
expectedResult: sdk.MustNewDecFromStr("10360.058421529811344618"),
exp: sdk.MustNewDecFromStr("0.9123"),
expectedResult: sdk.MustNewDecFromStr("1.4968226674708064"),
},
{
// high base, large exp, high precision
base: sdk.MustNewDecFromStr("1.886"),
exp: sdk.MustNewDecFromStr("31.9123"),
powPrecision: sdk.MustNewDecFromStr("0.00000000000001"),
expectedResult: sdk.MustNewDecFromStr("621110716.84727942280335811"),
exp: sdk.MustNewDecFromStr("1.9123"),
expectedResult: sdk.MustNewDecFromStr("3.364483251631"),
},
{
// base equal one
base: sdk.MustNewDecFromStr("1"),
exp: sdk.MustNewDecFromStr("123"),
powPrecision: sdk.MustNewDecFromStr("0.00000001"),
expectedResult: sdk.OneDec(),
},
{
// base close to 2

base: sdk.MustNewDecFromStr("1.999999999999999999"),
exp: sdk.SmallestDec(),
powPrecision: powPrecision,
base: sdk.MustNewDecFromStr("1.999999999999999999"),
exp: sdk.SmallestDec(),
// In Python: 1.000000000000000000693147181
expectedResult: sdk.OneDec(),
},
{
// base close to 2 and hitting iteration bound

base: sdk.MustNewDecFromStr("1.999999999999999999"),
exp: sdk.MustNewDecFromStr("0.1"),
powPrecision: powPrecision,
base: sdk.MustNewDecFromStr("1.999999999999999999"),
exp: sdk.MustNewDecFromStr("0.1"),

// In Python: 1.071773462536293164

Expand All @@ -102,9 +93,8 @@ func TestPowApprox(t *testing.T) {
{
// base close to 2 under iteration limit

base: sdk.MustNewDecFromStr("1.99999"),
exp: sdk.MustNewDecFromStr("0.1"),
powPrecision: powPrecision,
base: sdk.MustNewDecFromStr("1.99999"),
exp: sdk.MustNewDecFromStr("0.1"),

// expectedResult: sdk.MustNewDecFromStr("1.071772926648356147"),

Expand All @@ -115,25 +105,27 @@ func TestPowApprox(t *testing.T) {
{
// base close to 2 under iteration limit

base: sdk.MustNewDecFromStr("1.9999"),
exp: sdk.MustNewDecFromStr("0.1"),
powPrecision: powPrecision,
base: sdk.MustNewDecFromStr("1.9999"),
exp: sdk.MustNewDecFromStr("0.1"),

// In Python: 1.071768103548402149880477100
expectedResult: sdk.MustNewDecFromStr("1.071768103548402149"),
},
}

for i, tc := range testCases {
var actualResult sdk.Dec
ConditionalPanic(t, tc.expectPanic, func() {
fmt.Println(tc.base)
actualResult = PowApprox(tc.base, tc.exp, tc.powPrecision)
require.True(
t,
tc.expectedResult.Sub(actualResult).Abs().LTE(tc.powPrecision),
fmt.Sprintf("test %d failed: expected value & actual value's difference should be less than precision, expected: %s, actual: %s, precision: %s", i, tc.expectedResult, actualResult, tc.powPrecision),
)
actualResult, err := powerApproximation(tc.base, tc.exp)
if !tc.expectErr {
require.True(
t,
tc.expectedResult.Sub(actualResult).Abs().LTE(powPrecision),
fmt.Sprintf("test %d failed: expected value & actual value's difference should be less than precision, expected: %s, actual: %s, precision: %s", i, tc.expectedResult.String(), actualResult.String(), powPrecision.String()),
)
} else {
require.Error(t, err)
}
})
}
}
2 changes: 1 addition & 1 deletion x/amm/types/utils.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ func ApplyDiscount(swapFee sdk.Dec, discount sdk.Dec) sdk.Dec {

func GetWeightBreakingFee(weightIn, weightOut, targetWeightIn, targetWeightOut sdk.Dec, poolParams PoolParams) sdk.Dec {
weightBreakingFee := sdk.ZeroDec()
if !weightOut.IsZero() && !weightIn.IsZero() && !targetWeightOut.IsZero() && !targetWeightIn.IsZero() {
if !weightOut.IsZero() && !weightIn.IsZero() && !targetWeightOut.IsZero() && !targetWeightIn.IsZero() && !poolParams.WeightBreakingFeeMultiplier.IsZero() {
// (45/55*60/40) ^ 2.5
weightBreakingFee = poolParams.WeightBreakingFeeMultiplier.
Mul(Pow(weightIn.Mul(targetWeightOut).Quo(weightOut).Quo(targetWeightIn), poolParams.WeightBreakingFeeExponent))
Expand Down
5 changes: 4 additions & 1 deletion x/leveragelp/genesis.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,10 @@ func InitGenesis(ctx sdk.Context, k keeper.Keeper, genState types.GenesisState)
}

// this line is used by starport scaffolding # genesis/module/init
k.SetParams(ctx, &genState.Params)
err := k.SetParams(ctx, &genState.Params)
if err != nil {
panic(err)
}
}

// ExportGenesis returns the module's exported genesis
Expand Down
11 changes: 9 additions & 2 deletions x/leveragelp/keeper/msg_server_open.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,14 @@ func (k Keeper) Open(ctx sdk.Context, msg *types.MsgOpen) (*types.MsgOpenRespons

// Check if it is the same direction position for the same trader.
if position := k.CheckSamePosition(ctx, msg); position != nil {
return k.OpenConsolidate(ctx, position, msg)
response, err := k.OpenConsolidate(ctx, position, msg)
if err != nil {
return nil, err
}
if err = k.CheckPoolHealth(ctx, msg.AmmPoolId); err != nil {
return nil, err
}
return response, nil
}

if err := k.CheckMaxOpenPositions(ctx); err != nil {
Expand All @@ -33,7 +40,7 @@ func (k Keeper) Open(ctx sdk.Context, msg *types.MsgOpen) (*types.MsgOpenRespons
return nil, err
}

if err := k.CheckPoolHealth(ctx, msg.AmmPoolId); err != nil {
if err = k.CheckPoolHealth(ctx, msg.AmmPoolId); err != nil {
return nil, err
}

Expand Down
13 changes: 7 additions & 6 deletions x/leveragelp/keeper/position.go
Original file line number Diff line number Diff line change
Expand Up @@ -78,12 +78,7 @@ func (k Keeper) SetPosition(ctx sdk.Context, position *types.Position, oldDebt s
}

func (k Keeper) DestroyPosition(ctx sdk.Context, positionAddress string, id uint64, oldDebt sdk.Int) error {
key := types.GetPositionKey(positionAddress, id)
store := ctx.KVStore(k.storeKey)
if !store.Has(key) {
return types.ErrPositionDoesNotExist
}
store.Delete(key)

// Remove position sort keys
old, err := k.GetPosition(ctx, positionAddress, id)
Expand All @@ -97,9 +92,15 @@ func (k Keeper) DestroyPosition(ctx sdk.Context, positionAddress string, id uint
if len(stopLossKey) > 0 {
store.Delete(stopLossKey)
}
store.Delete([]byte(old.GetPositionAddress()))
store.Delete(old.GetPositionAddress())
}

key := types.GetPositionKey(positionAddress, id)
if !store.Has(key) {
return types.ErrPositionDoesNotExist
}
store.Delete(key)

// decrement open position count
openCount := k.GetOpenPositionCount(ctx)
if openCount != 0 {
Expand Down
4 changes: 2 additions & 2 deletions x/leveragelp/keeper/position_close.go
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ func (k Keeper) ForceCloseLong(ctx sdk.Context, position types.Position, pool ty
oldDebt := k.stableKeeper.GetDebt(ctx, position.GetPositionAddress())

if position.LeveragedLpAmount.IsZero() {
err := k.masterchefKeeper.ClaimRewards(ctx, position.GetPositionAddress(), []uint64{position.AmmPoolId}, sdk.AccAddress(position.Address))
err := k.masterchefKeeper.ClaimRewards(ctx, position.GetPositionAddress(), []uint64{position.AmmPoolId}, sdk.MustAccAddressFromBech32(position.Address))
if err != nil {
return sdk.ZeroInt(), err
}
Expand Down Expand Up @@ -82,7 +82,7 @@ func (k Keeper) ForceCloseLong(ctx sdk.Context, position types.Position, pool ty
// Update leveragedLpAmount
position.LeveragedLpAmount = position.LeveragedLpAmount.Sub(lpAmount)
if position.LeveragedLpAmount.IsZero() {
err = k.masterchefKeeper.ClaimRewards(ctx, position.GetPositionAddress(), []uint64{position.AmmPoolId}, sdk.AccAddress(position.Address))
err = k.masterchefKeeper.ClaimRewards(ctx, position.GetPositionAddress(), []uint64{position.AmmPoolId}, sdk.MustAccAddressFromBech32(position.Address))
if err != nil {
return sdk.ZeroInt(), err
}
Expand Down
49 changes: 25 additions & 24 deletions x/leveragelp/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,28 +8,29 @@ import (

// x/leveragelp module sentinel errors
var (
ErrPositionDoesNotExist = errorsmod.Register(ModuleName, 1, "position not found")
ErrPositionInvalid = errorsmod.Register(ModuleName, 2, "position invalid")
ErrPositionDisabled = errorsmod.Register(ModuleName, 3, "leveragelp not enabled for pool")
ErrInvalidPosition = errorsmod.Register(ModuleName, 6, "position position invalid")
ErrMaxOpenPositions = errorsmod.Register(ModuleName, 7, "max open positions reached")
ErrUnauthorised = errorsmod.Register(ModuleName, 8, "address not on whitelist")
ErrBorrowTooLow = errorsmod.Register(ModuleName, 9, "borrowed amount is too low")
ErrBorrowTooHigh = errorsmod.Register(ModuleName, 10, "borrowed amount is higher than pool depth")
ErrCustodyTooHigh = errorsmod.Register(ModuleName, 11, "custody amount is higher than pool depth")
ErrPositionUnhealthy = errorsmod.Register(ModuleName, 12, "position health would be too low for safety factor")
ErrInvalidCollateralAsset = errorsmod.Register(ModuleName, 13, "invalid collateral asset")
ErrInvalidBorrowingAsset = errorsmod.Register(ModuleName, 14, "invalid borrowing asset")
ErrPoolDoesNotExist = errorsmod.Register(ModuleName, 15, "pool does not exist")
ErrBalanceNotAvailable = errorsmod.Register(ModuleName, 18, "user does not have enough balance of the required coin")
ErrAmountTooLow = errorsmod.Register(ModuleName, 32, "Tx amount is too low")
ErrLeveragelpDisabled = errorsmod.Register(ModuleName, 33, "leveragelp disabled pool")
ErrAmmPoolNotFound = errorsmod.Register(ModuleName, 34, "amm pool not found")
ErrOnlyBaseCurrencyAllowed = errorsmod.Register(ModuleName, 35, "only base currency is allowed for leverage lp")
ErrInsufficientUsdcAfterOp = errorsmod.Register(ModuleName, 36, "insufficient amount of usdc after the operation for leveragelp withdrawal")
ErrInvalidCloseSize = errorsmod.Register(ModuleName, 37, "invalid close size")
ErrNegUserAmountAfterRepay = errorsmod.Register(ModuleName, 38, "negative user amount after repay")
ErrInvalidLeverage = errorsmod.Register(ModuleName, 39, "leverage should be same as existing position")
ErrInvalidCollateral = errorsmod.Register(ModuleName, 40, "collateral should not be more than total liability")
ErrPoolLeverageAmountNotZero= errorsmod.Register(ModuleName, 41, "pool leverage amount is greater than zero")
ErrPositionDoesNotExist = errorsmod.Register(ModuleName, 1, "position not found")
ErrPositionInvalid = errorsmod.Register(ModuleName, 2, "position invalid")
ErrPositionDisabled = errorsmod.Register(ModuleName, 3, "leveragelp not enabled for pool")
ErrInvalidPosition = errorsmod.Register(ModuleName, 6, "position position invalid")
ErrMaxOpenPositions = errorsmod.Register(ModuleName, 7, "max open positions reached")
ErrUnauthorised = errorsmod.Register(ModuleName, 8, "address not on whitelist")
ErrBorrowTooLow = errorsmod.Register(ModuleName, 9, "borrowed amount is too low")
ErrBorrowTooHigh = errorsmod.Register(ModuleName, 10, "borrowed amount is higher than pool depth")
ErrCustodyTooHigh = errorsmod.Register(ModuleName, 11, "custody amount is higher than pool depth")
ErrPositionUnhealthy = errorsmod.Register(ModuleName, 12, "position health would be too low for safety factor")
ErrInvalidCollateralAsset = errorsmod.Register(ModuleName, 13, "invalid collateral asset")
ErrInvalidBorrowingAsset = errorsmod.Register(ModuleName, 14, "invalid borrowing asset")
ErrPoolDoesNotExist = errorsmod.Register(ModuleName, 15, "pool does not exist")
ErrBalanceNotAvailable = errorsmod.Register(ModuleName, 18, "user does not have enough balance of the required coin")
ErrAmountTooLow = errorsmod.Register(ModuleName, 32, "Tx amount is too low")
ErrLeveragelpDisabled = errorsmod.Register(ModuleName, 33, "leveragelp disabled pool")
ErrAmmPoolNotFound = errorsmod.Register(ModuleName, 34, "amm pool not found")
ErrOnlyBaseCurrencyAllowed = errorsmod.Register(ModuleName, 35, "only base currency is allowed for leverage lp")
ErrInsufficientUsdcAfterOp = errorsmod.Register(ModuleName, 36, "insufficient amount of usdc after the operation for leveragelp withdrawal")
ErrInvalidCloseSize = errorsmod.Register(ModuleName, 37, "invalid close size")
ErrNegUserAmountAfterRepay = errorsmod.Register(ModuleName, 38, "negative user amount after repay")
ErrInvalidLeverage = errorsmod.Register(ModuleName, 39, "leverage should be same as existing position")
ErrInvalidCollateral = errorsmod.Register(ModuleName, 40, "collateral should not be more than total liability")
ErrPoolLeverageAmountNotZero = errorsmod.Register(ModuleName, 41, "pool leverage amount is greater than zero")
ErrLeverageTooSmall = errorsmod.Register(ModuleName, 42, "leverage should be more than 1")
)
Loading

0 comments on commit 8ca39e3

Please sign in to comment.