Skip to content

Commit

Permalink
support single side liquidity addition & add example scripts for test
Browse files Browse the repository at this point in the history
  • Loading branch information
jelysn committed Sep 28, 2023
1 parent be505a9 commit 4801186
Show file tree
Hide file tree
Showing 12 changed files with 272 additions and 113 deletions.
1 change: 1 addition & 0 deletions proto/elys/amm/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ message MsgJoinPool {
uint64 poolId = 2;
repeated cosmos.base.v1beta1.Coin maxAmountsIn = 3 [(gogoproto.nullable) = false];
string shareAmountOut = 4 [(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int", (gogoproto.nullable) = false];
bool noRemaining = 5;
}

message MsgJoinPoolResponse {
Expand Down
17 changes: 17 additions & 0 deletions scripts/examples/amm/amm.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
#!/usr/bin/env bash

TREASURY=$(elysd keys show -a treasury --keyring-backend=test)

elysd tx amm create-pool 10uatom,10uusdt 10000uatom,10000uusdt 0.00 0.00 --from=treasury --keyring-backend=test --chain-id=elystestnet-1 --yes --gas=1000000

# single asset add-liquidity
elysd tx amm join-pool 0 2000uatom 90000000000000000 true --from=treasury --keyring-backend=test --chain-id=elystestnet-1 --yes --gas=1000000
# multiple asset add-liquidity
elysd tx amm join-pool 0 2000uatom,2000uusdt 200000000000000000 true --from=treasury --keyring-backend=test --chain-id=elystestnet-1 --yes --gas=1000000

# swap
elysd tx amm swap-exact-amount-in 10uatom 1 0 uusdt --from=treasury --keyring-backend=test --chain-id=elystestnet-1 --yes --gas=1000000

elysd query commitment show-commitments $TREASURY
elysd query bank balances $TREASURY
elysd query amm show-pool 0
19 changes: 11 additions & 8 deletions x/amm/client/cli/tx_join_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,18 +18,18 @@ func CmdJoinPool() *cobra.Command {
cmd := &cobra.Command{
Use: "join-pool [pool-id] [max-amounts-in] [share-amount-out]",
Short: "join a new pool and provide the liquidity to it",
Example: `elysd tx amm join-pool 0 2000uatom,2000uusdc 200000000000000000 --from=treasury --keyring-backend=test --chain-id=elystestnet-1 --yes --gas=1000000`,
Args: cobra.ExactArgs(3),
Example: `elysd tx amm join-pool 0 2000uatom,2000uusdc 200000000000000000 true --from=treasury --keyring-backend=test --chain-id=elystestnet-1 --yes --gas=1000000`,
Args: cobra.ExactArgs(4),
RunE: func(cmd *cobra.Command, args []string) (err error) {
argPoolId, err := cast.ToUint64E(args[0])
poolId, err := cast.ToUint64E(args[0])
if err != nil {
return err
}
argMaxAmountsIn, err := sdk.ParseCoinsNormalized(args[1])
maxAmountsIn, err := sdk.ParseCoinsNormalized(args[1])
if err != nil {
return err
}
argShareAmountOut, ok := sdk.NewIntFromString(args[2])
shareAmountOut, ok := sdk.NewIntFromString(args[2])
if !ok {
return err
}
Expand All @@ -39,11 +39,14 @@ func CmdJoinPool() *cobra.Command {
return err
}

noRemaining, err := strconv.ParseBool(args[3])

msg := types.NewMsgJoinPool(
clientCtx.GetFromAddress().String(),
argPoolId,
argMaxAmountsIn,
argShareAmountOut,
poolId,
maxAmountsIn,
shareAmountOut,
noRemaining,
)
if err := msg.ValidateBasic(); err != nil {
return err
Expand Down
54 changes: 30 additions & 24 deletions x/amm/keeper/keeper_join_pool_no_swap.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ func (k Keeper) JoinPoolNoSwap(
poolId uint64,
shareOutAmount sdk.Int,
tokenInMaxs sdk.Coins,
noRemaining bool,
) (tokenIn sdk.Coins, sharesOut sdk.Int, err error) {
// defer to catch panics, in case something internal overflows.
defer func() {
Expand All @@ -37,30 +38,35 @@ func (k Keeper) JoinPoolNoSwap(
}

if !pool.PoolParams.UseOracle {
// we do an abstract calculation on the lp liquidity coins needed to have
// the designated amount of given shares of the pool without performing swap
neededLpLiquidity, err := types.GetMaximalNoSwapLPAmount(pool, shareOutAmount)
if err != nil {
return nil, sdk.ZeroInt(), err
}

// check that needed lp liquidity does not exceed the given `tokenInMaxs` parameter. Return error if so.
//if tokenInMaxs == 0, don't do this check.
if tokenInMaxs.Len() != 0 {
if !(neededLpLiquidity.DenomsSubsetOf(tokenInMaxs)) {
return nil, sdk.ZeroInt(), sdkerrors.Wrapf(types.ErrLimitMaxAmount, "TokenInMaxs does not include all the tokens that are part of the target pool,"+
" upperbound: %v, needed %v", tokenInMaxs, neededLpLiquidity)
} else if !(tokenInMaxs.DenomsSubsetOf(neededLpLiquidity)) {
return nil, sdk.ZeroInt(), sdkerrors.Wrapf(types.ErrDenomNotFoundInPool, "TokenInMaxs includes tokens that are not part of the target pool,"+
" input tokens: %v, pool tokens %v", tokenInMaxs, neededLpLiquidity)
tokensIn := tokenInMaxs
if !noRemaining {
// we do an abstract calculation on the lp liquidity coins needed to have
// the designated amount of given shares of the pool without performing swap
neededLpLiquidity, err := types.GetMaximalNoSwapLPAmount(pool, shareOutAmount)
if err != nil {
return nil, sdk.ZeroInt(), err
}
if !(tokenInMaxs.IsAllGTE(neededLpLiquidity)) {
return nil, sdk.ZeroInt(), sdkerrors.Wrapf(types.ErrLimitMaxAmount, "TokenInMaxs is less than the needed LP liquidity to this JoinPoolNoSwap,"+
" upperbound: %v, needed %v", tokenInMaxs, neededLpLiquidity)

// check that needed lp liquidity does not exceed the given `tokenInMaxs` parameter. Return error if so.
//if tokenInMaxs == 0, don't do this check.
if tokenInMaxs.Len() != 0 {
if !(neededLpLiquidity.DenomsSubsetOf(tokenInMaxs)) {
return nil, sdk.ZeroInt(), sdkerrors.Wrapf(types.ErrLimitMaxAmount, "TokenInMaxs does not include all the tokens that are part of the target pool,"+
" upperbound: %v, needed %v", tokenInMaxs, neededLpLiquidity)
} else if !(tokenInMaxs.DenomsSubsetOf(neededLpLiquidity)) {
return nil, sdk.ZeroInt(), sdkerrors.Wrapf(types.ErrDenomNotFoundInPool, "TokenInMaxs includes tokens that are not part of the target pool,"+
" input tokens: %v, pool tokens %v", tokenInMaxs, neededLpLiquidity)
}
if !(tokenInMaxs.IsAllGTE(neededLpLiquidity)) {
return nil, sdk.ZeroInt(), sdkerrors.Wrapf(types.ErrLimitMaxAmount, "TokenInMaxs is less than the needed LP liquidity to this JoinPoolNoSwap,"+
" upperbound: %v, needed %v", tokenInMaxs, neededLpLiquidity)
}
}

tokensIn = neededLpLiquidity
}

sharesOut, err = pool.JoinPoolNoSwap(ctx, k.oracleKeeper, k.accountedPoolKeeper, neededLpLiquidity)
sharesOut, err = pool.JoinPool(ctx, k.oracleKeeper, k.accountedPoolKeeper, tokensIn)
if err != nil {
return nil, sdk.ZeroInt(), err
}
Expand All @@ -71,16 +77,16 @@ func (k Keeper) JoinPoolNoSwap(
shareOutAmount, sharesOut))
}

err = k.applyJoinPoolStateChange(ctx, pool, sender, sharesOut, neededLpLiquidity)
err = k.applyJoinPoolStateChange(ctx, pool, sender, sharesOut, tokensIn)

// Increase liquidty amount
k.RecordTotalLiquidityIncrease(ctx, neededLpLiquidity)
k.RecordTotalLiquidityIncrease(ctx, tokensIn)

return neededLpLiquidity, sharesOut, err
return tokensIn, sharesOut, err
}

// on oracle pool, full tokenInMaxs are used regardless shareOutAmount
sharesOut, err = pool.JoinPoolNoSwap(ctx, k.oracleKeeper, k.accountedPoolKeeper, tokenInMaxs)
sharesOut, err = pool.JoinPool(ctx, k.oracleKeeper, k.accountedPoolKeeper, tokenInMaxs)
if err != nil {
return nil, sdk.ZeroInt(), err
}
Expand Down
4 changes: 2 additions & 2 deletions x/amm/keeper/mint_pool_share_to_account.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ func (k Keeper) MintPoolShareToAccount(ctx sdk.Context, pool types.Pool, addr sd
}

// Deposit LP token
_, err = msgServer.DepositTokens(ctx, msgDepositToken)
_, err = msgServer.DepositTokens(sdk.WrapSDKContext(ctx), msgDepositToken)
if err != nil {
return err
}
Expand All @@ -85,7 +85,7 @@ func (k Keeper) MintPoolShareToAccount(ctx sdk.Context, pool types.Pool, addr sd
}

// Commit LP token
_, err = msgServer.CommitTokens(ctx, msgCommitToken)
_, err = msgServer.CommitTokens(sdk.WrapSDKContext(ctx), msgCommitToken)
if err != nil {
return err
}
Expand Down
2 changes: 1 addition & 1 deletion x/amm/keeper/msg_server_join_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ func (k msgServer) JoinPool(goCtx context.Context, msg *types.MsgJoinPool) (*typ
return nil, err
}

neededLp, sharesOut, err := k.Keeper.JoinPoolNoSwap(ctx, sender, msg.PoolId, msg.ShareAmountOut, msg.MaxAmountsIn)
neededLp, sharesOut, err := k.Keeper.JoinPoolNoSwap(ctx, sender, msg.PoolId, msg.ShareAmountOut, msg.MaxAmountsIn, msg.NoRemaining)
if err != nil {
return nil, err
}
Expand Down
3 changes: 2 additions & 1 deletion x/amm/types/message_join_pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ const TypeMsgJoinPool = "join_pool"

var _ sdk.Msg = &MsgJoinPool{}

func NewMsgJoinPool(sender string, poolId uint64, maxAmountsIn sdk.Coins, shareAmountOut sdk.Int) *MsgJoinPool {
func NewMsgJoinPool(sender string, poolId uint64, maxAmountsIn sdk.Coins, shareAmountOut sdk.Int, noRemaining bool) *MsgJoinPool {
return &MsgJoinPool{
Sender: sender,
PoolId: poolId,
MaxAmountsIn: maxAmountsIn,
ShareAmountOut: shareAmountOut,
NoRemaining: noRemaining,
}
}

Expand Down
58 changes: 58 additions & 0 deletions x/amm/types/pool_calc_join_pool_shares.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package types

import (
"errors"

sdk "github.com/cosmos/cosmos-sdk/types"
)

// calcPoolOutGivenSingleIn - balance pAo.
func (p *Pool) calcSingleAssetJoin(tokenIn sdk.Coin, spreadFactor sdk.Dec, tokenInPoolAsset PoolAsset, totalShares sdk.Int) (numShares sdk.Int, err error) {
totalWeight := p.TotalWeight
if totalWeight.IsZero() {
return sdk.ZeroInt(), errors.New("pool misconfigured, total weight = 0")
}
normalizedWeight := sdk.NewDecFromInt(tokenInPoolAsset.Weight).Quo(sdk.NewDecFromInt(totalWeight))
return calcPoolSharesOutGivenSingleAssetIn(
sdk.NewDecFromInt(tokenInPoolAsset.Token.Amount),
normalizedWeight,
sdk.NewDecFromInt(totalShares),
sdk.NewDecFromInt(tokenIn.Amount),
spreadFactor,
).TruncateInt(), nil
}

// CalcJoinPoolShares calculates the number of shares created to join pool with the provided amount of `tokenIn`.
// The input tokens must either be:
// - a single token
// - contain exactly the same tokens as the pool contains
//
// It returns the number of shares created, the amount of coins actually joined into the pool
// (in case of not being able to fully join), or an error.
func (p *Pool) CalcSingleAssetJoinPoolShares(tokensIn sdk.Coins) (numShares sdk.Int, tokensJoined sdk.Coins, err error) {
// get all 'pool assets' (aka current pool liquidity + balancer weight)
poolAssetsByDenom, err := GetPoolAssetsByDenom(p.GetAllPoolAssets())
if err != nil {
return sdk.ZeroInt(), sdk.NewCoins(), err
}

err = EnsureDenomInPool(poolAssetsByDenom, tokensIn)
if err != nil {
return sdk.ZeroInt(), sdk.NewCoins(), err
}

// ensure that there aren't too many or too few assets in `tokensIn`
if tokensIn.Len() != 1 {
return sdk.ZeroInt(), sdk.NewCoins(), errors.New("pool only supports LP'ing with one asset")
}

// 2) Single token provided, so do single asset join and exit.
totalShares := p.GetTotalShares()
numShares, err = p.calcSingleAssetJoin(tokensIn[0], p.PoolParams.SwapFee, poolAssetsByDenom[tokensIn[0].Denom], totalShares.Amount)
if err != nil {
return sdk.ZeroInt(), sdk.NewCoins(), err
}
// we join all the tokens.
tokensJoined = tokensIn
return numShares, tokensJoined, nil
}
14 changes: 12 additions & 2 deletions x/amm/types/pool_join_pool_no_swap.go
Original file line number Diff line number Diff line change
Expand Up @@ -111,10 +111,20 @@ func (p *Pool) CalcJoinValueWithoutSlippage(ctx sdk.Context, oracleKeeper Oracle
return joinValueWithoutSlippage, nil
}

// JoinPoolNoSwap calculates the number of shares needed for an all-asset join given tokensIn with swapFee applied.
// JoinPool calculates the number of shares needed for an all-asset join given tokensIn with swapFee applied.
// It updates the liquidity if the pool is joined successfully. If not, returns error.
func (p *Pool) JoinPoolNoSwap(ctx sdk.Context, oracleKeeper OracleKeeper, accountedPoolKeeper AccountedPoolKeeper, tokensIn sdk.Coins) (numShares math.Int, err error) {
func (p *Pool) JoinPool(ctx sdk.Context, oracleKeeper OracleKeeper, accountedPoolKeeper AccountedPoolKeeper, tokensIn sdk.Coins) (numShares math.Int, err error) {
if !p.PoolParams.UseOracle {
if len(tokensIn) == 1 {
numShares, tokensJoined, err := p.CalcSingleAssetJoinPoolShares(tokensIn)
if err != nil {
return math.Int{}, err
}

// update pool with the calculated share and liquidity needed to join pool
p.IncreaseLiquidity(numShares, tokensJoined)
return numShares, nil
}
numShares, tokensJoined, err := p.CalcJoinPoolNoSwapShares(tokensIn)
if err != nil {
return math.Int{}, err
Expand Down
19 changes: 1 addition & 18 deletions x/amm/types/query.pb.gw.go

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

Loading

0 comments on commit 4801186

Please sign in to comment.