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 21 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 }}
3 changes: 2 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,8 @@ Ref: https://keepachangelog.com/en/1.0.0/

## [Unreleased]

### Improvements
### Features
* [\#308](https://github.com/irisnet/irismod/pull/308) Add the functionality of adding/removing liquidity unilaterally for coinswap.

## [v1.6.0] - 2022-08-08

Expand Down
35 changes: 0 additions & 35 deletions modules/coinswap/handler.go

This file was deleted.

215 changes: 209 additions & 6 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 @@ -103,13 +104,22 @@ func (k Keeper) Swap(ctx sdk.Context, msg *types.MsgSwapOrder) error {
}

// AddLiquidity adds liquidity to the specified pool
func (k Keeper) AddLiquidity(ctx sdk.Context, msg *types.MsgAddLiquidity) (sdk.Coin, error) {
func (k Keeper) AddLiquidity(ctx sdk.Context, imsg interface{}) (sdk.Coin, error) {
var msg *types.MsgAddLiquidity

switch i := imsg.(type) {
case *types.MsgAddUnilateralLiquidity:
return k.addUnilateralLiquidity(ctx, i)
case *types.MsgAddLiquidity:
msg = i
}

standardDenom := k.GetStandardDenom(ctx)
taramakage marked this conversation as resolved.
Show resolved Hide resolved
if standardDenom == msg.MaxToken.Denom {
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)

Expand Down Expand Up @@ -177,7 +187,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,8 +207,94 @@ func (k Keeper) addLiquidity(ctx sdk.Context,
return mintToken, nil
}

func (k Keeper) addUnilateralLiquidity(ctx sdk.Context, msg *types.MsgAddUnilateralLiquidity) (sdk.Coin, error) {

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

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

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, fmt.Sprintf("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.Wrap(types.ErrConstraintNotMet, fmt.Sprintf("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),
),
)

// add liquidity
exactCoins := sdk.NewCoins(msg.ExactToken)
if err := k.bk.SendCoins(ctx, sender, poolAddr, exactCoins); err != nil {
return sdk.Coin{}, err
}

// mint and send lpt
mintLpt := sdk.NewCoin(pool.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) {
func (k Keeper) RemoveLiquidity(ctx sdk.Context, imsg interface{}) (sdk.Coins, error) {
var msg *types.MsgRemoveLiquidity

switch i := imsg.(type) {
case *types.MsgRemoveUnilateralLiquidity:
return k.removeUnilateralLiquidity(ctx, i)
case *types.MsgRemoveLiquidity:
msg = i
}

taramakage marked this conversation as resolved.
Show resolved Hide resolved
standardDenom := k.GetStandardDenom(ctx)

pool, exists := k.GetPoolByLptDenom(ctx, msg.WithdrawLiquidity.Denom)
Expand Down Expand Up @@ -282,6 +378,113 @@ func (k Keeper) removeLiquidity(ctx sdk.Context, poolAddr, sender sdk.AccAddress
return coins, k.bk.SendCoins(ctx, poolAddr, sender, coins)
}

func (k Keeper) removeUnilateralLiquidity(ctx sdk.Context, msg *types.MsgRemoveUnilateralLiquidity) (sdk.Coins, error) {
var targetTokenDenom string

poolId := types.GetPoolId(msg.CounterpartyDenom)
pool, exist := k.GetPool(ctx, poolId)
if exist != true {
taramakage marked this conversation as resolved.
Show resolved Hide resolved
return sdk.Coins{}, sdkerrors.Wrap(types.ErrReservePoolNotExists, fmt.Sprintf("liquidity pool: %s ", poolId))
}

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

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,
fmt.Sprintf("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,
fmt.Sprintf("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,
fmt.Sprintf("forbid to withdraw all liquidity unilaterally"))
taramakage marked this conversation as resolved.
Show resolved Hide resolved
}

if targetBalanceAmt.LT(msg.MinToken.Amount) {
return sdk.Coins{}, sdkerrors.Wrapf(types.ErrInsufficientFunds,
fmt.Sprintf("insufficient %s funds, user expected: %s, actual: %s",
taramakage marked this conversation as resolved.
Show resolved Hide resolved
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.Wrap(types.ErrConstraintNotMet,
fmt.Sprintf("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),
),
)

// send lpt and burn lpt
lptCoins := sdk.NewCoins(sdk.NewCoin(lptDenom, msg.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