diff --git a/x/amm/types/calc_in_amt_given_out.go b/x/amm/types/calc_in_amt_given_out.go index 3e902d12e..0a5aebac0 100644 --- a/x/amm/types/calc_in_amt_given_out.go +++ b/x/amm/types/calc_in_amt_given_out.go @@ -1,6 +1,8 @@ package types import ( + "fmt" + sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" ) @@ -19,6 +21,15 @@ func (p Pool) CalcInAmtGivenOut( return sdk.Coin{}, err } + // print tokensOut + fmt.Println("tokensOut: ", tokensOut) + // print tokenOut + fmt.Println("tokenOut: ", tokenOut) + // print poolAssetOut + fmt.Println("poolAssetOut: ", poolAssetOut) + // print poolAssetIn + fmt.Println("poolAssetIn: ", poolAssetIn) + outWeight := sdk.NewDecFromInt(poolAssetOut.Weight) inWeight := sdk.NewDecFromInt(poolAssetIn.Weight) if p.PoolParams.UseOracle { @@ -55,6 +66,8 @@ func (p Pool) CalcInAmtGivenOut( inWeight, ).Neg() + fmt.Println("tokenAmountIn: ", tokenAmountIn) + // We deduct a swap fee on the input asset. The swap happens by following the invariant curve on the input * (1 - swap fee) // and then the swap fee is added to the pool. // Thus in order to give X amount out, we solve the invariant for the invariant input. However invariant input = (1 - swapfee) * trade input. @@ -65,6 +78,9 @@ func (p Pool) CalcInAmtGivenOut( // Otherwise, the pool would under-charge by this rounding error. tokenInAmt := tokenAmountInBeforeFee.Ceil().TruncateInt() + // print tokenInAmt + fmt.Println("tokenInAmt: ", tokenInAmt) + if !tokenInAmt.IsPositive() { return sdk.Coin{}, sdkerrors.Wrapf(ErrInvalidMathApprox, "token amount must be positive") } diff --git a/x/margin/keeper/begin_blocker_process_mtp.go b/x/margin/keeper/begin_blocker_process_mtp.go index 30a6a9800..8fe9b9036 100644 --- a/x/margin/keeper/begin_blocker_process_mtp.go +++ b/x/margin/keeper/begin_blocker_process_mtp.go @@ -19,6 +19,14 @@ func BeginBlockerProcessMTP(ctx sdk.Context, k Keeper, mtp *types.MTP, pool type } } }() + var err error + // update mtp take profit liabilities + // calculate mtp take profit liablities, delta x_tp_l = delta y_tp_c * current price (take profit liabilities = take profit custody * current price) + mtp.TakeProfitLiabilities, err = k.CalcMTPTakeProfitLiability(ctx, mtp, baseCurrency) + if err != nil { + ctx.Logger().Error(errors.Wrap(err, fmt.Sprintf("error calculating mtp take profit liabilities: %s", mtp.String())).Error()) + return + } h, err := k.UpdateMTPHealth(ctx, *mtp, ammPool, baseCurrency) if err != nil { ctx.Logger().Error(errors.Wrap(err, fmt.Sprintf("error updating mtp health: %s", mtp.String())).Error()) diff --git a/x/margin/keeper/calc_mtp_take_profit_liabilities.go b/x/margin/keeper/calc_mtp_take_profit_liabilities.go new file mode 100644 index 000000000..543f86710 --- /dev/null +++ b/x/margin/keeper/calc_mtp_take_profit_liabilities.go @@ -0,0 +1,30 @@ +package keeper + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/elys-network/elys/x/margin/types" +) + +func (k Keeper) CalcMTPTakeProfitLiability(ctx sdk.Context, mtp *types.MTP, baseCurrency string) (sdk.Int, error) { + takeProfitLiabilities := sdk.ZeroInt() + if types.IsTakeProfitPriceInifite(mtp) { + return takeProfitLiabilities, nil + } + for _, takeProfitCustody := range mtp.TakeProfitCustodies { + takeProfitCustodyAsset := takeProfitCustody.Denom + // Retrieve AmmPool + ammPool, err := k.GetAmmPool(ctx, mtp.AmmPoolId, takeProfitCustodyAsset) + if err != nil { + return sdk.ZeroInt(), err + } + + // convert custody amount to base currency + C, err := k.EstimateSwapGivenOut(ctx, takeProfitCustody, baseCurrency, ammPool) + if err != nil { + return sdk.ZeroInt(), err + } + takeProfitLiabilities = takeProfitLiabilities.Add(C) + } + + return takeProfitLiabilities, nil +} diff --git a/x/margin/keeper/keeper.go b/x/margin/keeper/keeper.go index 0bc0f436e..97ea63d17 100644 --- a/x/margin/keeper/keeper.go +++ b/x/margin/keeper/keeper.go @@ -173,14 +173,16 @@ func (k Keeper) Borrow(ctx sdk.Context, collateralAsset string, custodyAsset str mtp.Collaterals[collateralIndex].Amount = mtp.Collaterals[collateralIndex].Amount.Add(collateralAmount) mtp.Liabilities = mtp.Liabilities.Add(sdk.NewIntFromBigInt(liabilitiesDec.TruncateInt().BigInt())) - - // we divide liabilities by take profit price to get take profit liabilities in base currency - mtp.TakeProfitLiabilities = mtp.Liabilities.Quo(sdk.NewIntFromBigInt(mtp.TakeProfitPrice.TruncateInt().BigInt())) - mtp.Custodies[custodyIndex].Amount = mtp.Custodies[custodyIndex].Amount.Add(custodyAmount) - // we divide custody amount by take profit price to get take profit custody amount in base currency - mtp.TakeProfitCustodies[custodyIndex].Amount = mtp.Custodies[custodyIndex].Amount.Quo(sdk.NewIntFromBigInt(mtp.TakeProfitPrice.TruncateInt().BigInt())) + // calculate mtp take profit custody, delta y_tp_c = delta x_l / take profit price (take profit custody = liabilities / take profit price) + mtp.TakeProfitCustodies = types.CalcMTPTakeProfitCustodies(mtp) + + // calculate mtp take profit liablities, delta x_tp_l = delta y_tp_c * current price (take profit liabilities = take profit custody * current price) + mtp.TakeProfitLiabilities, err = k.CalcMTPTakeProfitLiability(ctx, mtp, baseCurrency) + if err != nil { + return err + } mtp.Leverages = append(mtp.Leverages, eta.Add(sdk.OneDec())) @@ -216,6 +218,18 @@ func (k Keeper) Borrow(ctx sdk.Context, collateralAsset string, custodyAsset str return err } + // All take profit liability has to be in base currency + err = pool.UpdateTakeProfitLiabilities(ctx, baseCurrency, mtp.TakeProfitLiabilities, true, mtp.Position) + if err != nil { + return err + } + + // All take profit custody has to be in base currency + err = pool.UpdateTakeProfitCustody(ctx, baseCurrency, mtp.TakeProfitCustodies[custodyIndex].Amount, true, mtp.Position) + if err != nil { + return err + } + k.SetPool(ctx, *pool) return k.SetMTP(ctx, mtp) @@ -238,7 +252,9 @@ func (k Keeper) CalculatePoolHealthByPosition(ctx sdk.Context, pool *types.Pool, } balance := sdk.NewDecFromInt(asset.AssetBalance.Add(ammBalance)) - liabilities := sdk.NewDecFromInt(asset.Liabilities) + + // X_L = X_P_L - X_TP_L (pool liabilities = pool synthetic liabilities - pool take profit liabilities) + liabilities := sdk.NewDecFromInt(asset.Liabilities.Sub(asset.TakeProfitLiabilities)) if balance.Add(liabilities).IsZero() { return sdk.ZeroDec() @@ -311,7 +327,7 @@ func (k Keeper) IncrementalBorrowInterestPayment(ctx sdk.Context, collateralAsse return sdk.ZeroInt(), err } - // If collateralAset is not in base currency, convert it to original asset format + // If collateralAsset is not in base currency, convert it to original asset format if collateralAsset != baseCurrency { // swap custody amount to collateral for updating borrow interest unpaid amtTokenIn := sdk.NewCoin(baseCurrency, borrowInterestPayment) diff --git a/x/margin/keeper/open_long_process.go b/x/margin/keeper/open_long_process.go index af6cfaa66..fdb962652 100644 --- a/x/margin/keeper/open_long_process.go +++ b/x/margin/keeper/open_long_process.go @@ -101,8 +101,8 @@ func (k Keeper) ProcessOpenLong(ctx sdk.Context, mtp *types.MTP, leverage sdk.De // Update consolidated collateral amount k.OpenLongChecker.CalcMTPConsolidateCollateral(ctx, mtp, baseCurrency) - // Calculate consolidate liabiltiy - types.CalcMTPConsolidateLiability(mtp) + // Calculate consolidate liabiltiy and update consolidate leverage + mtp.ConsolidateLeverage = types.CalcMTPConsolidateLiability(mtp) // Set MTP k.OpenLongChecker.SetMTP(ctx, mtp) diff --git a/x/margin/keeper/open_short_process.go b/x/margin/keeper/open_short_process.go index 2030b8617..2581f5adc 100644 --- a/x/margin/keeper/open_short_process.go +++ b/x/margin/keeper/open_short_process.go @@ -102,8 +102,8 @@ func (k Keeper) ProcessOpenShort(ctx sdk.Context, mtp *types.MTP, leverage sdk.D // Update consolidated collateral amount k.OpenShortChecker.CalcMTPConsolidateCollateral(ctx, mtp, baseCurrency) - // Calculate consolidate liabiltiy - types.CalcMTPConsolidateLiability(mtp) + // Calculate consolidate liabiltiy and update consolidate leverage + mtp.ConsolidateLeverage = types.CalcMTPConsolidateLiability(mtp) // Set MTP k.OpenShortChecker.SetMTP(ctx, mtp) diff --git a/x/margin/keeper/repay.go b/x/margin/keeper/repay.go index 78a5bc433..86ecaaf46 100644 --- a/x/margin/keeper/repay.go +++ b/x/margin/keeper/repay.go @@ -126,6 +126,16 @@ func (k Keeper) Repay(ctx sdk.Context, mtp *types.MTP, pool *types.Pool, ammPool return err } + err = pool.UpdateTakeProfitLiabilities(ctx, baseCurrency, mtp.TakeProfitLiabilities, false, mtp.Position) + if err != nil { + return err + } + + err = pool.UpdateTakeProfitCustody(ctx, baseCurrency, mtp.TakeProfitCustodies[collateralIndex].Amount, false, mtp.Position) + if err != nil { + return err + } + err = k.DestroyMTP(ctx, mtp.Address, mtp.Id) if err != nil { return err diff --git a/x/margin/types/calc_mtp_consolidate_leverage.go b/x/margin/types/calc_mtp_consolidate_leverage.go index a2bd370b0..6655f1534 100644 --- a/x/margin/types/calc_mtp_consolidate_leverage.go +++ b/x/margin/types/calc_mtp_consolidate_leverage.go @@ -4,11 +4,11 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" ) -func CalcMTPConsolidateLiability(mtp *MTP) { +func CalcMTPConsolidateLiability(mtp *MTP) sdk.Dec { if mtp.SumCollateral.IsZero() { - return + return mtp.ConsolidateLeverage } leverage := mtp.Liabilities.Quo(mtp.SumCollateral) - mtp.ConsolidateLeverage = sdk.NewDecFromInt(leverage) + return sdk.NewDecFromInt(leverage) } diff --git a/x/margin/types/calc_mtp_take_profit_custodies.go b/x/margin/types/calc_mtp_take_profit_custodies.go new file mode 100644 index 000000000..c25e6c911 --- /dev/null +++ b/x/margin/types/calc_mtp_take_profit_custodies.go @@ -0,0 +1,16 @@ +package types + +import ( + sdk "github.com/cosmos/cosmos-sdk/types" +) + +func CalcMTPTakeProfitCustodies(mtp *MTP) sdk.Coins { + takeProfitCustodies := mtp.TakeProfitCustodies + if IsTakeProfitPriceInifite(mtp) { + return takeProfitCustodies + } + for custodyIndex := range mtp.Custodies { + takeProfitCustodies[custodyIndex].Amount = sdk.NewDecFromInt(mtp.Liabilities).Quo(mtp.TakeProfitPrice).TruncateInt() + } + return takeProfitCustodies +} diff --git a/x/margin/types/is_take_profit_price_inifite.go b/x/margin/types/is_take_profit_price_inifite.go new file mode 100644 index 000000000..ad4c14f27 --- /dev/null +++ b/x/margin/types/is_take_profit_price_inifite.go @@ -0,0 +1,5 @@ +package types + +func IsTakeProfitPriceInifite(mtp *MTP) bool { + return mtp.TakeProfitPrice.TruncateInt().String() == TakeProfitPriceDefault +} diff --git a/x/margin/types/pool.go b/x/margin/types/pool.go index e631b78bb..4f5c59c7c 100644 --- a/x/margin/types/pool.go +++ b/x/margin/types/pool.go @@ -63,6 +63,42 @@ func (p *Pool) UpdateLiabilities(ctx sdk.Context, assetDenom string, amount sdk. return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "invalid asset denom") } +// Update the asset take profit liabilities +func (p *Pool) UpdateTakeProfitLiabilities(ctx sdk.Context, assetDenom string, amount sdk.Int, isIncrease bool, position Position) error { + poolAssets := p.GetPoolAssets(position) + for i, asset := range *poolAssets { + if asset.AssetDenom == assetDenom { + if isIncrease { + (*poolAssets)[i].TakeProfitLiabilities = asset.TakeProfitLiabilities.Add(amount) + } else { + (*poolAssets)[i].TakeProfitLiabilities = asset.TakeProfitLiabilities.Sub(amount) + } + + return nil + } + } + + return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "invalid asset denom") +} + +// Update the asset take profit custody +func (p *Pool) UpdateTakeProfitCustody(ctx sdk.Context, assetDenom string, amount sdk.Int, isIncrease bool, position Position) error { + poolAssets := p.GetPoolAssets(position) + for i, asset := range *poolAssets { + if asset.AssetDenom == assetDenom { + if isIncrease { + (*poolAssets)[i].TakeProfitCustody = asset.TakeProfitCustody.Add(amount) + } else { + (*poolAssets)[i].TakeProfitCustody = asset.TakeProfitCustody.Sub(amount) + } + + return nil + } + } + + return sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "invalid asset denom") +} + // Update the asset custody func (p *Pool) UpdateCustody(ctx sdk.Context, assetDenom string, amount sdk.Int, isIncrease bool, position Position) error { poolAssets := p.GetPoolAssets(position) diff --git a/x/margin/types/pool_test.go b/x/margin/types/pool_test.go index 71de20c99..2eaa9e5c2 100644 --- a/x/margin/types/pool_test.go +++ b/x/margin/types/pool_test.go @@ -119,6 +119,116 @@ func TestPool_UpdateLiabilitiesInvalid(t *testing.T) { assert.Equal(t, pool.PoolAssetsLong[0].Liabilities, sdk.NewInt(0)) } +func TestPool_UpdateTakeProfitLiabilitiesValid(t *testing.T) { + ctx := sdk.Context{} // mock or setup a context + + // Define the margin pool with assets + pool := types.NewPool(1) + pool.PoolAssetsLong = []types.PoolAsset{ + { + Liabilities: sdk.NewInt(0), + TakeProfitLiabilities: sdk.NewInt(0), + Custody: sdk.NewInt(0), + AssetBalance: sdk.NewInt(0), + BlockBorrowInterest: sdk.NewInt(0), + AssetDenom: "testAsset", + }, + } + + // Test scenario, increase 100 and decrease 150. + denom := "testAsset" + err := pool.UpdateTakeProfitLiabilities(ctx, denom, sdk.NewInt(100), true, types.Position_LONG) + // Expect that there is no error + assert.Nil(t, err) + // Expect that there is 100 liabilities + assert.Equal(t, pool.PoolAssetsLong[0].TakeProfitLiabilities, sdk.NewInt(100)) + err = pool.UpdateTakeProfitLiabilities(ctx, denom, sdk.NewInt(150), false, types.Position_LONG) + // Expect that there is no error + assert.Nil(t, err) + // Expect that there is -50 liabilities + assert.Equal(t, pool.PoolAssetsLong[0].TakeProfitLiabilities, sdk.NewInt(-50)) +} + +func TestPool_UpdateTakeProfitLiabilitiesInvalid(t *testing.T) { + ctx := sdk.Context{} // mock or setup a context + + // Define the margin pool with assets + pool := types.NewPool(1) + pool.PoolAssetsLong = []types.PoolAsset{ + { + Liabilities: sdk.NewInt(0), + TakeProfitLiabilities: sdk.NewInt(0), + Custody: sdk.NewInt(0), + AssetBalance: sdk.NewInt(0), + BlockBorrowInterest: sdk.NewInt(0), + AssetDenom: "testAsset", + }, + } + + // Test scenario, increase 100 and decrease 50. + denom := "testAsset2" + err := pool.UpdateTakeProfitLiabilities(ctx, denom, sdk.NewInt(100), true, types.Position_LONG) + // Expect that there is invalid asset denom error. + assert.True(t, errors.Is(err, sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "invalid asset denom"))) + + // Expect that there is still 0 liabilities + assert.Equal(t, pool.PoolAssetsLong[0].TakeProfitLiabilities, sdk.NewInt(0)) +} + +func TestPool_UpdateTakeProfitCustodyValid(t *testing.T) { + ctx := sdk.Context{} // mock or setup a context + + // Define the margin pool with assets + pool := types.NewPool(1) + pool.PoolAssetsLong = []types.PoolAsset{ + { + TakeProfitCustody: sdk.NewInt(0), + Custody: sdk.NewInt(0), + AssetBalance: sdk.NewInt(0), + BlockBorrowInterest: sdk.NewInt(0), + AssetDenom: "testAsset", + }, + } + + // Test scenario, increase 100 and decrease 150. + denom := "testAsset" + err := pool.UpdateTakeProfitCustody(ctx, denom, sdk.NewInt(100), true, types.Position_LONG) + // Expect that there is no error + assert.Nil(t, err) + // Expect that there is 100 liabilities + assert.Equal(t, pool.PoolAssetsLong[0].TakeProfitCustody, sdk.NewInt(100)) + err = pool.UpdateTakeProfitCustody(ctx, denom, sdk.NewInt(150), false, types.Position_LONG) + // Expect that there is no error + assert.Nil(t, err) + // Expect that there is -50 liabilities + assert.Equal(t, pool.PoolAssetsLong[0].TakeProfitCustody, sdk.NewInt(-50)) +} + +func TestPool_UpdateTakeProfitCustodyInvalid(t *testing.T) { + ctx := sdk.Context{} // mock or setup a context + + // Define the margin pool with assets + pool := types.NewPool(1) + pool.PoolAssetsLong = []types.PoolAsset{ + { + TakeProfitCustody: sdk.NewInt(0), + Custody: sdk.NewInt(0), + AssetBalance: sdk.NewInt(0), + BlockBorrowInterest: sdk.NewInt(0), + AssetDenom: "testAsset", + }, + } + + // Test scenario, increase 100 and decrease 50. + denom := "testAsset2" + err := pool.UpdateTakeProfitCustody(ctx, denom, sdk.NewInt(100), true, types.Position_LONG) + // Expect that there is invalid asset denom error. + assert.True(t, errors.Is(err, sdkerrors.Wrap(sdkerrors.ErrInvalidCoins, "invalid asset denom"))) + + // Expect that there is still 0 liabilities + assert.Equal(t, pool.PoolAssetsLong[0].TakeProfitCustody, sdk.NewInt(0)) +} + func TestPool_UpdateCustodyValid(t *testing.T) { ctx := sdk.Context{} // mock or setup a context