diff --git a/x/amm/types/pow_approx.go b/x/amm/types/pow_approx.go index 44470823f..bfb67aeaa 100644 --- a/x/amm/types/pow_approx.go +++ b/x/amm/types/pow_approx.go @@ -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() { diff --git a/x/amm/types/pow_approx_test.go b/x/amm/types/pow_approx_test.go index a7714f017..06cf47f91 100644 --- a/x/amm/types/pow_approx_test.go +++ b/x/amm/types/pow_approx_test.go @@ -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 @@ -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"), @@ -115,9 +105,8 @@ 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"), @@ -125,15 +114,18 @@ func TestPowApprox(t *testing.T) { } 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) + } }) } } diff --git a/x/amm/types/utils.go b/x/amm/types/utils.go index 51258dc01..131b4ac4c 100644 --- a/x/amm/types/utils.go +++ b/x/amm/types/utils.go @@ -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)) diff --git a/x/leveragelp/genesis.go b/x/leveragelp/genesis.go index 5c1a2cdae..ae86ca188 100644 --- a/x/leveragelp/genesis.go +++ b/x/leveragelp/genesis.go @@ -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 diff --git a/x/leveragelp/keeper/msg_server_open.go b/x/leveragelp/keeper/msg_server_open.go index 94b585a6c..176fff5c1 100644 --- a/x/leveragelp/keeper/msg_server_open.go +++ b/x/leveragelp/keeper/msg_server_open.go @@ -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 { @@ -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 } diff --git a/x/leveragelp/keeper/position.go b/x/leveragelp/keeper/position.go index 3cd07510d..d2dcb8dd4 100644 --- a/x/leveragelp/keeper/position.go +++ b/x/leveragelp/keeper/position.go @@ -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) @@ -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 { diff --git a/x/leveragelp/keeper/position_close.go b/x/leveragelp/keeper/position_close.go index a9be82ce2..23d5cf6fb 100644 --- a/x/leveragelp/keeper/position_close.go +++ b/x/leveragelp/keeper/position_close.go @@ -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 } @@ -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 } diff --git a/x/leveragelp/types/errors.go b/x/leveragelp/types/errors.go index 97ace4e47..404cb38ac 100644 --- a/x/leveragelp/types/errors.go +++ b/x/leveragelp/types/errors.go @@ -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") ) diff --git a/x/leveragelp/types/msgs.go b/x/leveragelp/types/msgs.go index a4e55d591..5a04f6902 100644 --- a/x/leveragelp/types/msgs.go +++ b/x/leveragelp/types/msgs.go @@ -101,6 +101,19 @@ func (msg *MsgOpen) ValidateBasic() error { if err != nil { return errorsmod.Wrapf(sdkerrors.ErrInvalidAddress, "invalid creator address (%s)", err) } + // leverage should be greater than 1 + if msg.Leverage.LTE(sdk.OneDec()) { + return ErrLeverageTooSmall + } + collateralCoin := sdk.NewCoin(msg.CollateralAsset, msg.CollateralAmount) + err = collateralCoin.Validate() + if err != nil { + return ErrInvalidCollateralAsset.Wrapf("(%s)", err) + } + // coin.Validate() does not check if amount is 0 + if collateralCoin.IsZero() { + return ErrInvalidCollateralAsset.Wrapf("(amount cannot be equal to 0)") + } return nil } @@ -179,8 +192,8 @@ func (msg *MsgWhitelist) ValidateBasic() error { func NewMsgUpdatePools(signer string, pool UpdatePool) *MsgUpdatePools { return &MsgUpdatePools{ - Authority: signer, - UpdatePool: &pool, + Authority: signer, + UpdatePool: &pool, } } @@ -204,7 +217,7 @@ func NewMsgAddPools(signer string, pool AddPool) *MsgAddPool { return &MsgAddPool{ Authority: signer, - Pool: pool, + Pool: pool, } } diff --git a/x/leveragelp/types/msgs_test.go b/x/leveragelp/types/msgs_test.go index 9d1328028..f656b564b 100644 --- a/x/leveragelp/types/msgs_test.go +++ b/x/leveragelp/types/msgs_test.go @@ -1,6 +1,7 @@ package types import ( + sdk "github.com/cosmos/cosmos-sdk/types" "testing" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" @@ -54,7 +55,12 @@ func TestMsgOpen_ValidateBasic(t *testing.T) { }, { name: "valid address", msg: MsgOpen{ - Creator: sample.AccAddress(), + Creator: sample.AccAddress(), + CollateralAsset: "uusdc", + CollateralAmount: sdk.NewInt(100), + AmmPoolId: 1, + Leverage: sdk.OneDec().MulInt64(10), + StopLossPrice: sdk.OneDec().MulInt64(10), }, }, }