Skip to content
Draft
16 changes: 13 additions & 3 deletions x/amm/keeper/calc_swap_estimation_by_denom.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ func (k Keeper) CalcSwapEstimationByDenom(
amount sdk.Coin,
denomIn string,
denomOut string,
baseCurrency string,
baseAssetsDenoms []string,
address string,
overrideSwapFee osmomath.BigDec,
decimals uint64,
Expand Down Expand Up @@ -48,9 +48,19 @@ func (k Keeper) CalcSwapEstimationByDenom(

// Determine the correct route based on the amount's denom
if amount.Denom == denomIn {
inRoute, err = k.CalcInRouteByDenom(ctx, denomIn, denomOut, baseCurrency)
for _, baseCurrency := range baseAssetsDenoms {
inRoute, err = k.CalcInRouteByDenom(ctx, denomIn, denomOut, baseCurrency)
if err == nil {
break
}
}
} else if amount.Denom == denomOut {
outRoute, err = k.CalcOutRouteByDenom(ctx, denomOut, denomIn, baseCurrency)
for _, baseCurrency := range baseAssetsDenoms {
outRoute, err = k.CalcOutRouteByDenom(ctx, denomOut, denomIn, baseCurrency)
if err == nil {
break
}
}
} else {
err = types.ErrInvalidDenom
return
Expand Down
6 changes: 3 additions & 3 deletions x/amm/keeper/calc_swap_estimation_by_denom_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func (suite *AmmKeeperTestSuite) TestCalcSwapEstimationByDenom() {
inRoute, outRoute, tokenOut, spotPrice, _, _, _, _, _, _, err := suite.app.AmmKeeper.CalcSwapEstimationByDenom(
suite.ctx,
amount,
ptypes.Elys, "uusda", ptypes.BaseCurrency,
ptypes.Elys, "uusda", []string{ptypes.BaseCurrency},
"",
osmomath.ZeroBigDec(),
1,
Expand All @@ -121,7 +121,7 @@ func (suite *AmmKeeperTestSuite) TestCalcSwapEstimationByDenom() {
inRoute, outRoute, tokenOut, spotPrice, _, _, _, _, _, _, err = suite.app.AmmKeeper.CalcSwapEstimationByDenom(
suite.ctx,
amount,
ptypes.Elys, "uusda", ptypes.BaseCurrency,
ptypes.Elys, "uusda", []string{ptypes.BaseCurrency},
"",
osmomath.ZeroBigDec(),
1,
Expand All @@ -136,7 +136,7 @@ func (suite *AmmKeeperTestSuite) TestCalcSwapEstimationByDenom() {
amount = sdk.NewCoin("invalid", sdkmath.NewInt(1000))
_, _, _, _, _, _, _, _, _, _, err = suite.app.AmmKeeper.CalcSwapEstimationByDenom(
suite.ctx, amount,
ptypes.Elys, "uusda", ptypes.BaseCurrency,
ptypes.Elys, "uusda", []string{ptypes.BaseCurrency},
"",
osmomath.ZeroBigDec(),
1,
Expand Down
17 changes: 12 additions & 5 deletions x/amm/keeper/msg_server_create_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ package keeper

import (
"context"
"errors"
"fmt"
"strconv"
"strings"
Expand All @@ -22,10 +21,6 @@ func (k msgServer) CreatePool(goCtx context.Context, msg *types.MsgCreatePool) (
// Pay pool creation fee
params := k.GetParams(ctx)

if !params.IsCreatorAllowed(msg.Sender) {
return nil, errors.New("sender is not allowed to create pool")
}

sender := sdk.MustAccAddressFromBech32(msg.Sender)

baseAssetExists := false
Expand All @@ -39,6 +34,18 @@ func (k msgServer) CreatePool(goCtx context.Context, msg *types.MsgCreatePool) (
return nil, errorsmod.Wrapf(types.ErrOnlyBaseAssetsPoolAllowed, "one of the asset must be from %s", strings.Join(params.BaseAssets, ", "))
}

// gov module is allowed to create pools
if !params.IsCreatorAllowed(msg.Sender) {
if msg.PoolParams.UseOracle {
return nil, errorsmod.Wrapf(types.ErrPoolCreationNotAllowed, "oracle pool is not allowed to be created by %s", msg.Sender)
}

poolExistForSameAssets := k.Keeper.CheckExistingPoolWithSameAssets(ctx, msg.PoolAssets)
if poolExistForSameAssets {
return nil, types.ErrPoolExistsWithSameAssets
}
}

feeAssetExists := k.CheckBaseAssetExist(ctx, msg.PoolParams.FeeDenom)
if !feeAssetExists {
return nil, fmt.Errorf("fee denom must be from %s", strings.Join(params.BaseAssets, ", "))
Expand Down
13 changes: 8 additions & 5 deletions x/amm/keeper/msg_server_swap_by_denom.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,13 +23,16 @@ func (k Keeper) SwapByDenom(ctx sdk.Context, msg *types.MsgSwapByDenom) (*types.
return nil, err
}

// retrieve base currency denom
baseCurrency, found := k.assetProfileKeeper.GetUsdcDenom(ctx)
if !found {
return nil, errorsmod.Wrapf(assetprofiletypes.ErrAssetProfileNotFound, "asset %s not found", ptypes.BaseCurrency)
baseAssetsDenoms := k.GetParams(ctx).BaseAssets
if len(baseAssetsDenoms) == 0 {
baseCurrency, found := k.assetProfileKeeper.GetUsdcDenom(ctx)
if !found {
return nil, errorsmod.Wrapf(assetprofiletypes.ErrAssetProfileNotFound, "asset %s not found", ptypes.BaseCurrency)
}
baseAssetsDenoms = []string{baseCurrency}
}

inRoute, outRoute, _, spotPrice, _, _, _, slippage, weightBonus, _, err := k.CalcSwapEstimationByDenom(ctx, msg.Amount, msg.DenomIn, msg.DenomOut, baseCurrency, msg.Sender, osmomath.ZeroBigDec(), 0)
inRoute, outRoute, _, spotPrice, _, _, _, slippage, weightBonus, _, err := k.CalcSwapEstimationByDenom(ctx, msg.Amount, msg.DenomIn, msg.DenomOut, baseAssetsDenoms, msg.Sender, osmomath.ZeroBigDec(), 0)
if err != nil {
return nil, err
}
Expand Down
33 changes: 8 additions & 25 deletions x/amm/keeper/msg_server_update_pool_params.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,45 +19,28 @@ func (k Keeper) UpdatePoolParams(ctx sdk.Context, poolId uint64, newPoolParams t
return 0, types.PoolParams{}, types.ErrPoolNotFound
}

baseCurrency, found := k.assetProfileKeeper.GetUsdcDenom(ctx)
usdcDenom, found := k.assetProfileKeeper.GetUsdcDenom(ctx)
if !found {
return 0, types.PoolParams{}, errorsmod.Wrapf(assetprofiletypes.ErrAssetProfileNotFound, "asset %s not found", ptypes.BaseCurrency)
}

// If the fee denom is empty, set it to the base currency
if newPoolParams.FeeDenom == "" {
newPoolParams.FeeDenom = baseCurrency
newPoolParams.FeeDenom = usdcDenom
}

// changing from non-oracle pool to oracle pool
if !pool.PoolParams.UseOracle && newPoolParams.UseOracle {

nonBaseCurrencyDenom := ""
usdcDenomFound := false

for _, asset := range pool.PoolAssets {
if asset.Token.Denom != baseCurrency {
nonBaseCurrencyDenom = asset.Token.Denom
entry, found := k.assetProfileKeeper.GetEntryByDenom(ctx, asset.Token.Denom)
if !found {
return 0, types.PoolParams{}, fmt.Errorf("asset profile for %s not found", asset.Token.Denom)
}
if asset.Token.Denom == baseCurrency {
usdcDenomFound = true
_, found = k.oracleKeeper.GetAssetPrice(ctx, entry.DisplayName)
if !found {
return 0, types.PoolParams{}, fmt.Errorf("oracle price for %s not found", entry.DisplayName)
}
}
if !usdcDenomFound {
return 0, types.PoolParams{}, fmt.Errorf("no usdc denom in the amm pool %d", poolId)
}
if nonBaseCurrencyDenom == "" {
return 0, types.PoolParams{}, fmt.Errorf("no non-usdc denom in the amm pool %d", poolId)
}

entry, found := k.assetProfileKeeper.GetEntryByDenom(ctx, nonBaseCurrencyDenom)
if !found {
return 0, types.PoolParams{}, fmt.Errorf("asset profile for %s not found", nonBaseCurrencyDenom)
}
_, found = k.oracleKeeper.GetAssetPrice(ctx, entry.DisplayName)
if !found {
return 0, types.PoolParams{}, fmt.Errorf("oracle price for %s not found", entry.DisplayName)
}
}
pool.PoolParams = newPoolParams
err := pool.Validate()
Expand Down
29 changes: 29 additions & 0 deletions x/amm/keeper/params.go
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,35 @@ func (k Keeper) CheckBaseAssetExist(ctx sdk.Context, denom string) bool {
return found
}

// CheckExistingPoolWithSameAssets returns true if a pool with the same set of assets already exists.
func (k Keeper) CheckExistingPoolWithSameAssets(ctx sdk.Context, newAssets []types.PoolAsset) bool {
newAssetsMap := make(map[string]bool, len(newAssets))
for _, asset := range newAssets {
newAssetsMap[asset.Token.Denom] = true
}

existingPools := k.GetAllPool(ctx)
for _, pool := range existingPools {
if len(pool.PoolAssets) != len(newAssets) {
continue
}

matches := true
for _, poolAsset := range pool.PoolAssets {
if !newAssetsMap[poolAsset.Token.Denom] {
matches = false
break
}
}

if matches {
return true
}
}

return false
}

func (k Keeper) V8Migrate(ctx sdk.Context) error {
baseCurrencyDenom, found := k.assetProfileKeeper.GetUsdcDenom(ctx)
if !found {
Expand Down
83 changes: 83 additions & 0 deletions x/amm/keeper/params_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,15 @@ package keeper_test
import (
"testing"

sdkmath "cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
authtypes "github.com/cosmos/cosmos-sdk/x/auth/types"
govtypes "github.com/cosmos/cosmos-sdk/x/gov/types"
minttypes "github.com/cosmos/cosmos-sdk/x/mint/types"
testkeeper "github.com/elys-network/elys/v6/testutil/keeper"
"github.com/elys-network/elys/v6/x/amm/keeper"
"github.com/elys-network/elys/v6/x/amm/types"
ptypes "github.com/elys-network/elys/v6/x/parameter/types"
"github.com/stretchr/testify/require"
)

Expand All @@ -17,3 +24,79 @@ func TestGetParams(t *testing.T) {

require.EqualValues(t, params, k.GetParams(ctx))
}

func (suite *AmmKeeperTestSuite) TestCheckExistingPoolWithSameAssets() {
suite.SetupTest()
suite.SetupCoinPrices()
suite.SetAmmParams()
suite.SetupAssetProfile()

// Bootstrap accounts
sender := authtypes.NewModuleAddress(govtypes.ModuleName)
params := suite.app.AmmKeeper.GetParams(suite.ctx)

// Bootstrap balances
poolCreationFee := sdk.NewCoin(ptypes.Elys, params.PoolCreationFee)
coins := sdk.NewCoins(
sdk.NewCoin("uatom", sdkmath.NewInt(1000)),
sdk.NewCoin("uusdc", sdkmath.NewInt(1000)),
poolCreationFee,
)
err := suite.app.BankKeeper.MintCoins(suite.ctx, minttypes.ModuleName, coins)
suite.Require().NoError(err)
err = suite.app.BankKeeper.SendCoinsFromModuleToAccount(suite.ctx, minttypes.ModuleName, sender, coins)
suite.Require().NoError(err)

// Create an initial pool
msgServer := keeper.NewMsgServerImpl(*suite.app.AmmKeeper)
poolAssets := []types.PoolAsset{
{
Token: sdk.NewCoin("uatom", sdkmath.NewInt(500)),
Weight: sdkmath.NewInt(10),
ExternalLiquidityRatio: sdkmath.LegacyOneDec(),
},
{
Token: sdk.NewCoin("uusdc", sdkmath.NewInt(500)),
Weight: sdkmath.NewInt(10),
ExternalLiquidityRatio: sdkmath.LegacyOneDec(),
},
}
poolParams := types.PoolParams{
SwapFee: sdkmath.LegacyZeroDec(),
UseOracle: false,
FeeDenom: ptypes.BaseCurrency,
}
_, err = msgServer.CreatePool(
suite.ctx,
&types.MsgCreatePool{
Sender: sender.String(),
PoolParams: poolParams,
PoolAssets: poolAssets,
},
)
suite.Require().NoError(err)

// Test case 1: No matching denoms
newAssets := []types.PoolAsset{
{
Token: sdk.NewCoin("uosmo", sdkmath.NewInt(500)),
},
{
Token: sdk.NewCoin("uusdc", sdkmath.NewInt(500)),
},
}
exists := suite.app.AmmKeeper.CheckExistingPoolWithSameAssets(suite.ctx, newAssets)
suite.Require().False(exists, "Expected no matching pool for new assets")

// Test case 2: Matching denoms
matchingAssets := []types.PoolAsset{
{
Token: sdk.NewCoin("uusdc", sdkmath.NewInt(500)),
},
{
Token: sdk.NewCoin("uatom", sdkmath.NewInt(500)),
},
}
exists = suite.app.AmmKeeper.CheckExistingPoolWithSameAssets(suite.ctx, matchingAssets)
suite.Require().True(exists, "Expected a matching pool for the same assets")
}
13 changes: 8 additions & 5 deletions x/amm/keeper/query_swap_estimation_by_denom.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,10 +20,13 @@ func (k Keeper) SwapEstimationByDenom(goCtx context.Context, req *types.QuerySwa

ctx := sdk.UnwrapSDKContext(goCtx)

// retrieve base currency denom
baseCurrency, found := k.assetProfileKeeper.GetUsdcDenom(ctx)
if !found {
return nil, errorsmod.Wrapf(assetprofiletypes.ErrAssetProfileNotFound, "asset %s not found", ptypes.BaseCurrency)
baseAssetsDenoms := k.GetParams(ctx).BaseAssets
if len(baseAssetsDenoms) == 0 {
baseCurrency, found := k.assetProfileKeeper.GetUsdcDenom(ctx)
if !found {
return nil, errorsmod.Wrapf(assetprofiletypes.ErrAssetProfileNotFound, "asset %s not found", ptypes.BaseCurrency)
}
baseAssetsDenoms = []string{baseCurrency}
}

// retrieve denom in decimals
Expand All @@ -32,7 +35,7 @@ func (k Keeper) SwapEstimationByDenom(goCtx context.Context, req *types.QuerySwa
return nil, errorsmod.Wrapf(assetprofiletypes.ErrAssetProfileNotFound, "asset %s not found", req.DenomIn)
}

inRoute, outRoute, amount, spotPrice, swapFee, discount, availableLiquidity, slippage, weightBonus, priceImpact, err := k.CalcSwapEstimationByDenom(ctx, req.Amount, req.DenomIn, req.DenomOut, baseCurrency, req.Address, osmomath.ZeroBigDec(), entry.Decimals)
inRoute, outRoute, amount, spotPrice, swapFee, discount, availableLiquidity, slippage, weightBonus, priceImpact, err := k.CalcSwapEstimationByDenom(ctx, req.Amount, req.DenomIn, req.DenomOut, baseAssetsDenoms, req.Address, osmomath.ZeroBigDec(), entry.Decimals)
if err != nil {
return nil, err
}
Expand Down
3 changes: 3 additions & 0 deletions x/amm/types/errors.go
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ var (
ErrTokenOutAmountZero = errors.Register(ModuleName, 115, "token out amount is zero")

ErrUnauthorizedUpFrontSwap = errors.Register(ModuleName, 116, "sender is not allowed to make upfront swaps")

ErrPoolExistsWithSameAssets = errors.Register(ModuleName, 117, "pool already exists with the same assets")
ErrPoolCreationNotAllowed = errors.Register(ModuleName, 118, "sender is not allowed to create pool")
)

const (
Expand Down