Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Coinswap Upgrade based on Cosmos-SDK v0.46.1 #308

Merged
merged 25 commits into from
Sep 6, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
b94b05e
add simulation and update docs
taramakage May 20, 2022
2b8def3
feat: add the functionality of adding/removing liquidity unilaterally…
taramakage May 13, 2022
5a51dbd
Update keeper.go
taramakage May 16, 2022
cc2cf70
fix: replace pool-id with counterparty-name
taramakage May 24, 2022
f5a5b41
fix: modifty field of unilateral msg
taramakage May 26, 2022
5a86f0d
fix: appmodule version
taramakage May 31, 2022
4f52ac4
fix: register migration
taramakage May 31, 2022
0564ca5
fix: coinswap unit test
taramakage Jun 1, 2022
a8e3d0b
fix: add coinswap migration v153
taramakage Jun 2, 2022
aee0755
fix: panic by invalid token
taramakage Jun 13, 2022
a4192ae
fix: simplify calculation for removing unilateral liquidity and impro…
taramakage Jun 15, 2022
0151f56
fix: panic by insufficient funds
taramakage Jun 15, 2022
6c53314
fix: set zero unilateral fee valid
taramakage Jun 15, 2022
04c18a6
fix: improve calculation precision and set constraint of unilateral w…
taramakage Jun 17, 2022
daf62b1
fix and regenerate coinswap proto
taramakage Aug 31, 2022
fc32927
fix unit test
taramakage Aug 31, 2022
9b2829a
format coinswap proto
taramakage Aug 31, 2022
960eaf1
update changelog
taramakage Sep 1, 2022
349c52b
add wip github action
taramakage Sep 1, 2022
c8a033f
remove rest querier
taramakage Sep 1, 2022
7846bb1
rm beginblock & endblock
taramakage Sep 2, 2022
dee4946
refactor some code
taramakage Sep 2, 2022
b46f3b1
modify tx.proto and regenerate proto
taramakage Sep 2, 2022
51e34ea
refactor add/rm unilateral liquidity function
taramakage Sep 5, 2022
4fe8ba6
Merge branch 'develop' into yuandu/coinswap-upgrade-v046
taramakage Sep 6, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions .github/workflows/wip.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
name: WIP
on:
pull_request:
types: [opened, synchronize, reopened, edited]

jobs:
wip:
runs-on: ubuntu-latest
steps:
- uses: wip/action@v1
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
35 changes: 0 additions & 35 deletions modules/coinswap/handler.go

This file was deleted.

247 changes: 227 additions & 20 deletions modules/coinswap/keeper/keeper.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@ package keeper

import (
"fmt"
"math/big"
"strconv"

sdkmath "cosmossdk.io/math"
gogotypes "github.com/gogo/protobuf/types"

"github.com/tendermint/tendermint/libs/log"

"github.com/cosmos/cosmos-sdk/codec"
Expand Down Expand Up @@ -69,7 +70,7 @@ func (k Keeper) Logger(ctx sdk.Context) log.Logger {

// Swap execute swap order in specified pool
func (k Keeper) Swap(ctx sdk.Context, msg *types.MsgSwapOrder) error {
var amount sdk.Int
var amount sdkmath.Int
var err error

standardDenom := k.GetStandardDenom(ctx)
Expand Down Expand Up @@ -109,18 +110,18 @@ func (k Keeper) AddLiquidity(ctx sdk.Context, msg *types.MsgAddLiquidity) (sdk.C
return sdk.Coin{}, sdkerrors.Wrapf(types.ErrInvalidDenom,
"MaxToken: %s should not be StandardDenom", msg.MaxToken.String())
}
var mintLiquidityAmt sdk.Int
var mintLiquidityAmt sdkmath.Int
var depositToken sdk.Coin
var standardCoin = sdk.NewCoin(standardDenom, msg.ExactStandardAmt)

poolId := types.GetPoolId(msg.MaxToken.Denom)
pool, exists := k.GetPool(ctx, poolId)

sender, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
return sdk.Coin{}, err
}

poolId := types.GetPoolId(msg.MaxToken.Denom)
pool, exists := k.GetPool(ctx, poolId)

// calculate amount of UNI to be minted for sender
// and coin amount to be deposited
if !exists {
Expand All @@ -131,7 +132,7 @@ func (k Keeper) AddLiquidity(ctx sdk.Context, msg *types.MsgAddLiquidity) (sdk.C

mintLiquidityAmt = msg.ExactStandardAmt
if mintLiquidityAmt.LT(msg.MinLiquidity) {
return sdk.Coin{}, sdkerrors.Wrap(types.ErrConstraintNotMet, fmt.Sprintf("liquidity amount not met, user expected: no less than %s, actual: %s", msg.MinLiquidity.String(), mintLiquidityAmt.String()))
return sdk.Coin{}, sdkerrors.Wrapf(types.ErrConstraintNotMet, "liquidity amount not met, user expected: no less than %s, actual: %s", msg.MinLiquidity.String(), mintLiquidityAmt.String())
}
depositToken = sdk.NewCoin(msg.MaxToken.Denom, msg.MaxToken.Amount)
pool = k.CreatePool(ctx, msg.MaxToken.Denom)
Expand All @@ -147,13 +148,13 @@ func (k Keeper) AddLiquidity(ctx sdk.Context, msg *types.MsgAddLiquidity) (sdk.C

mintLiquidityAmt = (liquidity.Mul(msg.ExactStandardAmt)).Quo(standardReserveAmt)
if mintLiquidityAmt.LT(msg.MinLiquidity) {
return sdk.Coin{}, sdkerrors.Wrap(types.ErrConstraintNotMet, fmt.Sprintf("liquidity amount not met, user expected: no less than %s, actual: %s", msg.MinLiquidity.String(), mintLiquidityAmt.String()))
return sdk.Coin{}, sdkerrors.Wrapf(types.ErrConstraintNotMet, "liquidity amount not met, user expected: no less than %s, actual: %s", msg.MinLiquidity.String(), mintLiquidityAmt.String())
}
depositAmt := (tokenReserveAmt.Mul(msg.ExactStandardAmt)).Quo(standardReserveAmt).AddRaw(1)
depositToken = sdk.NewCoin(msg.MaxToken.Denom, depositAmt)

if depositAmt.GT(msg.MaxToken.Amount) {
return sdk.Coin{}, sdkerrors.Wrap(types.ErrConstraintNotMet, fmt.Sprintf("token amount not met, user expected: no more than %s, actual: %s", msg.MaxToken.String(), depositToken.String()))
return sdk.Coin{}, sdkerrors.Wrapf(types.ErrConstraintNotMet, "token amount not met, user expected: no more than %s, actual: %s", msg.MaxToken.String(), depositToken.String())
}
}

Expand All @@ -177,7 +178,7 @@ func (k Keeper) addLiquidity(ctx sdk.Context,
reservePoolAddress sdk.AccAddress,
standardCoin, token sdk.Coin,
lptDenom string,
mintLiquidityAmt sdk.Int,
mintLiquidityAmt sdkmath.Int,
) (sdk.Coin, error) {
depositedTokens := sdk.NewCoins(standardCoin, token)
// transfer deposited token into coinswaps Account
Expand All @@ -197,10 +198,102 @@ func (k Keeper) addLiquidity(ctx sdk.Context,
return mintToken, nil
}

// AddUnilateralLiquidity adds liquidity unilaterally to the specified pool
func (k Keeper) AddUnilateralLiquidity(ctx sdk.Context, msg *types.MsgAddUnilateralLiquidity) (sdk.Coin, error) {
sender, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
return sdk.Coin{}, err
}

poolId := types.GetPoolId(msg.CounterpartyDenom)
pool, exist := k.GetPool(ctx, poolId)
if !exist {
return sdk.Coin{}, sdkerrors.Wrapf(types.ErrReservePoolNotExists, "liquidity pool: %s ", poolId)
}

poolAddr, err := sdk.AccAddressFromBech32(pool.EscrowAddress)
if err != nil {
return sdk.Coin{}, err
}

balances, err := k.GetPoolBalances(ctx, pool.EscrowAddress)
if err != nil {
return sdk.Coin{}, err
}

if msg.ExactToken.Denom != msg.CounterpartyDenom && msg.ExactToken.Denom != k.GetStandardDenom(ctx) {
return sdk.Coin{}, sdkerrors.Wrapf(types.ErrInvalidDenom, "liquidity pool %s has no %s", poolId, msg.ExactToken.Denom)
}

// square = ( token_balance + ( 1- fee_unilateral ) * exact_token ) / token_balance * lpt_balance^2
// 1 - fee_unilateral = numerator / denominator
tokenBalanceAmt := balances.AmountOf(msg.ExactToken.Denom)
lptBalanceAmt := k.bk.GetSupply(ctx, pool.LptDenom).Amount
exactTokenAmt := msg.ExactToken.Amount

deltaFeeUnilateral := sdk.OneDec().Sub(k.GetParams(ctx).UnilateralLiquidityFee)
numerator := sdkmath.NewIntFromBigInt(deltaFeeUnilateral.BigInt())
denominator := sdkmath.NewIntWithDecimal(1, sdk.Precision)

square := denominator.Mul(tokenBalanceAmt).Add(numerator.Mul(exactTokenAmt)).Mul(lptBalanceAmt).Mul(lptBalanceAmt).Quo(denominator.Mul(tokenBalanceAmt))

// lpt = square^0.5 - lpt_balance
var squareBigInt = &big.Int{}
squareBigInt.Sqrt(square.BigInt())
mintLptAmt := sdkmath.NewIntFromBigInt(squareBigInt).Sub(lptBalanceAmt)

if mintLptAmt.LT(msg.MinLiquidity) {
return sdk.Coin{}, sdkerrors.Wrapf(types.ErrConstraintNotMet, "liquidity amount not met, user expected: no less than %s, actual: %s", msg.MinLiquidity.String(), mintLptAmt.String())
}

// event
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeAddUnilateralLiquidity,
sdk.NewAttribute(types.AttributeValueSender, msg.Sender),
sdk.NewAttribute(types.AttributeValueTokenUnilateral, msg.ExactToken.Denom),
sdk.NewAttribute(types.AttributeValueLptDenom, pool.LptDenom),
),
)

return k.addUnilateralLiquidity(ctx, sender, poolAddr, msg.ExactToken, pool.LptDenom, mintLptAmt)
}

func (k Keeper) addUnilateralLiquidity(ctx sdk.Context,
sender sdk.AccAddress,
poolAddr sdk.AccAddress,
exactToken sdk.Coin,
lptDenom string,
mintLptAmt sdkmath.Int,
) (sdk.Coin, error) {
// add liquidity
exactCoins := sdk.NewCoins(exactToken)
if err := k.bk.SendCoins(ctx, sender, poolAddr, exactCoins); err != nil {
return sdk.Coin{}, err
}

// mint and send lpt
mintLpt := sdk.NewCoin(lptDenom, mintLptAmt)
mintLpts := sdk.NewCoins(mintLpt)
if err := k.bk.MintCoins(ctx, types.ModuleName, mintLpts); err != nil {
return sdk.Coin{}, err
}
if err := k.bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, sender, mintLpts); err != nil {
return sdk.Coin{}, err
}

return mintLpt, nil
}

// RemoveLiquidity removes liquidity from the specified pool
func (k Keeper) RemoveLiquidity(ctx sdk.Context, msg *types.MsgRemoveLiquidity) (sdk.Coins, error) {
standardDenom := k.GetStandardDenom(ctx)

sender, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
return nil, err
}

pool, exists := k.GetPoolByLptDenom(ctx, msg.WithdrawLiquidity.Denom)
if !exists {
return nil, sdkerrors.Wrapf(types.ErrReservePoolNotExists, "liquidity pool token: %s", msg.WithdrawLiquidity.Denom)
Expand All @@ -218,13 +311,13 @@ func (k Keeper) RemoveLiquidity(ctx sdk.Context, msg *types.MsgRemoveLiquidity)
tokenReserveAmt := balances.AmountOf(minTokenDenom)
liquidityReserve := k.bk.GetSupply(ctx, lptDenom).Amount
if standardReserveAmt.LT(msg.MinStandardAmt) {
return nil, sdkerrors.Wrap(types.ErrInsufficientFunds, fmt.Sprintf("insufficient %s funds, user expected: %s, actual: %s", standardDenom, msg.MinStandardAmt.String(), standardReserveAmt.String()))
return nil, sdkerrors.Wrapf(types.ErrInsufficientFunds, "insufficient %s funds, user expected: %s, actual: %s", standardDenom, msg.MinStandardAmt.String(), standardReserveAmt.String())
}
if tokenReserveAmt.LT(msg.MinToken) {
return nil, sdkerrors.Wrap(types.ErrInsufficientFunds, fmt.Sprintf("insufficient %s funds, user expected: %s, actual: %s", minTokenDenom, msg.MinToken.String(), tokenReserveAmt.String()))
return nil, sdkerrors.Wrapf(types.ErrInsufficientFunds, "insufficient %s funds, user expected: %s, actual: %s", minTokenDenom, msg.MinToken.String(), tokenReserveAmt.String())
}
if liquidityReserve.LT(msg.WithdrawLiquidity.Amount) {
return nil, sdkerrors.Wrap(types.ErrInsufficientFunds, fmt.Sprintf("insufficient %s funds, user expected: %s, actual: %s", lptDenom, msg.WithdrawLiquidity.Amount.String(), liquidityReserve.String()))
return nil, sdkerrors.Wrapf(types.ErrInsufficientFunds, "insufficient %s funds, user expected: %s, actual: %s", lptDenom, msg.WithdrawLiquidity.Amount.String(), liquidityReserve.String())
}

// calculate amount of UNI to be burned for sender
Expand All @@ -237,10 +330,10 @@ func (k Keeper) RemoveLiquidity(ctx sdk.Context, msg *types.MsgRemoveLiquidity)
deductUniCoin := msg.WithdrawLiquidity

if irisWithdrawCoin.Amount.LT(msg.MinStandardAmt) {
return nil, sdkerrors.Wrap(types.ErrConstraintNotMet, fmt.Sprintf("iris amount not met, user expected: no less than %s, actual: %s", sdk.NewCoin(standardDenom, msg.MinStandardAmt).String(), irisWithdrawCoin.String()))
return nil, sdkerrors.Wrapf(types.ErrConstraintNotMet, "iris amount not met, user expected: no less than %s, actual: %s", sdk.NewCoin(standardDenom, msg.MinStandardAmt).String(), irisWithdrawCoin.String())
}
if tokenWithdrawCoin.Amount.LT(msg.MinToken) {
return nil, sdkerrors.Wrap(types.ErrConstraintNotMet, fmt.Sprintf("token amount not met, user expected: no less than %s, actual: %s", sdk.NewCoin(minTokenDenom, msg.MinToken).String(), tokenWithdrawCoin.String()))
return nil, sdkerrors.Wrapf(types.ErrConstraintNotMet, "token amount not met, user expected: no less than %s, actual: %s", sdk.NewCoin(minTokenDenom, msg.MinToken).String(), tokenWithdrawCoin.String())
}

ctx.EventManager().EmitEvent(
Expand All @@ -251,11 +344,6 @@ func (k Keeper) RemoveLiquidity(ctx sdk.Context, msg *types.MsgRemoveLiquidity)
),
)

sender, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
return nil, err
}

poolAddr, err := sdk.AccAddressFromBech32(pool.EscrowAddress)
if err != nil {
return nil, err
Expand All @@ -282,6 +370,125 @@ func (k Keeper) removeLiquidity(ctx sdk.Context, poolAddr, sender sdk.AccAddress
return coins, k.bk.SendCoins(ctx, poolAddr, sender, coins)
}

// RemoveUnilateralLiquidity removes liquidity unilaterally from the specified pool
func (k Keeper) RemoveUnilateralLiquidity(ctx sdk.Context, msg *types.MsgRemoveUnilateralLiquidity) (sdk.Coins, error) {
var targetTokenDenom string

sender, err := sdk.AccAddressFromBech32(msg.Sender)
if err != nil {
return sdk.Coins{}, err
}
taramakage marked this conversation as resolved.
Show resolved Hide resolved

poolId := types.GetPoolId(msg.CounterpartyDenom)
pool, exist := k.GetPool(ctx, poolId)
if !exist {
return sdk.Coins{}, sdkerrors.Wrapf(types.ErrReservePoolNotExists, "liquidity pool: %s ", poolId)
}

poolAddr, err := sdk.AccAddressFromBech32(pool.EscrowAddress)
if err != nil {
return sdk.Coins{}, err
}

balances, err := k.GetPoolBalances(ctx, pool.EscrowAddress)
if err != nil {
return sdk.Coins{}, err
}

if msg.MinToken.Denom != msg.CounterpartyDenom && msg.MinToken.Denom != k.GetStandardDenom(ctx) {
return sdk.Coins{}, sdkerrors.Wrapf(types.ErrInvalidDenom,
"liquidity pool %s has no %s", poolId, msg.MinToken.Denom)
}

lptDenom := pool.LptDenom
targetTokenDenom = msg.MinToken.Denom

targetBalanceAmt := balances.AmountOf(targetTokenDenom)
lptBalanceAmt := k.bk.GetSupply(ctx, lptDenom).Amount

if lptBalanceAmt.LT(msg.ExactLiquidity) {
return sdk.Coins{}, sdkerrors.Wrapf(types.ErrInsufficientFunds,
"insufficient %s funds, user expected: %s, actual: %s",
lptDenom, msg.ExactLiquidity.String(), lptBalanceAmt.String())
}

if lptBalanceAmt.Equal(msg.ExactLiquidity) {
return sdk.Coins{}, sdkerrors.Wrapf(types.ErrConstraintNotMet,
"forbid to withdraw all liquidity unilaterally, should be less than: %s", lptBalanceAmt.String())
}

if targetBalanceAmt.LT(msg.MinToken.Amount) {
return sdk.Coins{}, sdkerrors.Wrapf(types.ErrInsufficientFunds,
"insufficient %s funds, user expected: %s, actual: %s",
targetTokenDenom, msg.MinToken.Amount.String(), targetBalanceAmt.String())
}

// Calculate Withdrawn Amount
// t_withdrawn = t_balance * delta_lpt / lpt_balance
// c_withdrawn = c_balance * delta_lpt / lpt_balance
//
// Calculate Swap Amount
// As `(t_balance - t_withdraw)(c_balance - c_withdraw) = (t_balance - t_withdraw - t_swap) * c_balance`,
// we get `t_swap = (t_balance - t_withdraw) * c_withdraw / c_balance`
//
// Simplify the formula:
// target_amt = t_balance * (2 * lpt_balance - delta_lpt) * delta_lpt / (lpt_balance^2)
//
// Deduce with fee
// target_amt' = target_amt * ( 1 - fee_unilateral)
// fee_unilateral = numerator / denominator
deltaFeeUnilateral := sdk.OneDec().Sub(k.GetParams(ctx).UnilateralLiquidityFee)
feeNumerator := sdkmath.NewIntFromBigInt(deltaFeeUnilateral.BigInt())
feeDenominator := sdkmath.NewIntWithDecimal(1, sdk.Precision)

targetTokenNumerator := lptBalanceAmt.Add(lptBalanceAmt).Sub(msg.ExactLiquidity).
Mul(msg.ExactLiquidity).Mul(targetBalanceAmt).Mul(feeNumerator)
targetTokenDenominator := lptBalanceAmt.Mul(lptBalanceAmt).Mul(feeDenominator)

targetTokenAmtAfterFee := targetTokenNumerator.Quo(targetTokenDenominator)

if targetTokenAmtAfterFee.LT(msg.MinToken.Amount) {
return nil, sdkerrors.Wrapf(types.ErrConstraintNotMet,
"token withdrawn amount not met, user expected: no less than %s, actual: %s",
msg.MinToken.String(), sdk.NewCoin(targetTokenDenom, targetTokenAmtAfterFee).String())
}

// event
ctx.EventManager().EmitEvent(
sdk.NewEvent(
types.EventTypeRemoveUnilateralLiquidity,
sdk.NewAttribute(types.AttributeValueSender, msg.Sender),
sdk.NewAttribute(types.AttributeValueTokenUnilateral, targetTokenDenom),
sdk.NewAttribute(types.AttributeValueLptDenom, pool.LptDenom),
),
)

return k.removeUnilateralLiquidity(ctx, sender, poolAddr, lptDenom, targetTokenDenom, msg.ExactLiquidity, targetTokenAmtAfterFee)
}

func (k Keeper) removeUnilateralLiquidity(ctx sdk.Context,
sender sdk.AccAddress,
poolAddr sdk.AccAddress,
lptDenom string,
targetTokenDenom string,
exactLiquidity sdkmath.Int,
targetTokenAmtAfterFee sdkmath.Int,
) (sdk.Coins, error) {
// send lpt and burn lpt
lptCoins := sdk.NewCoins(sdk.NewCoin(lptDenom, exactLiquidity))
if err := k.bk.SendCoinsFromAccountToModule(ctx, sender, types.ModuleName, lptCoins); err != nil {
return nil, err
}
if err := k.bk.BurnCoins(ctx, types.ModuleName, lptCoins); err != nil {
return nil, err
}

// send withdraw coins
coins := sdk.NewCoins(sdk.NewCoin(targetTokenDenom, targetTokenAmtAfterFee))

return coins, k.bk.SendCoins(ctx, poolAddr, sender, coins)
}

// GetParams gets the parameters for the coinswap module.
func (k Keeper) GetParams(ctx sdk.Context) types.Params {
var swapParams types.Params
Expand Down
Loading