Skip to content

Commit

Permalink
Merge pull request #10 from c-osmosis/frostornge/single-token
Browse files Browse the repository at this point in the history
feat: single token deposit
  • Loading branch information
Thunnini authored Oct 19, 2020
2 parents f3752d0 + ff6e089 commit 45a34da
Show file tree
Hide file tree
Showing 6 changed files with 2,563 additions and 769 deletions.
1 change: 0 additions & 1 deletion proto/osmosis/gamm/v1beta1/pool.proto
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@ syntax = "proto3";
package osmosis.gamm.v1beta1;

import "gogoproto/gogo.proto";
import "cosmos/base/v1beta1/coin.proto";

option go_package = "github.com/c-osmosis/osmosis/x/gamm/types";

Expand Down
98 changes: 97 additions & 1 deletion proto/osmosis/gamm/v1beta1/tx.proto
Original file line number Diff line number Diff line change
Expand Up @@ -191,4 +191,100 @@ message MsgSwapExactAmountOut {
(gogoproto.moretags) = "yaml:\"max_price\"",
(gogoproto.nullable) = false
];
}
}

// ===================== MsgJoinSwapExternAmountIn
message MsgJoinSwapExternAmountIn {
bytes sender = 1 [
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress",
(gogoproto.moretags) = "yaml:\"sender\""
];
uint64 targetPool = 2 [
(gogoproto.moretags) = "yaml:\"target_pool\""
];
string tokenIn = 3 [
(gogoproto.moretags) = "yaml:\"token_in\""
];
string tokenAmountIn = 4 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.moretags) = "yaml:\"token_amount_in\"",
(gogoproto.nullable) = false
];
string minPoolAmountOut = 5 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.moretags) = "yaml:\"min_pool_amount_out\"",
(gogoproto.nullable) = false
];
}

// ===================== MsgJoinSwapPoolAmountOut
message MsgJoinSwapPoolAmountOut {
bytes sender = 1 [
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress",
(gogoproto.moretags) = "yaml:\"sender\""
];
uint64 targetPool = 2 [
(gogoproto.moretags) = "yaml:\"target_pool\""
];
string tokenIn = 3 [
(gogoproto.moretags) = "yaml:\"token_in\""
];
string poolAmountOut = 4 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.moretags) = "yaml:\"pool_amount_out\"",
(gogoproto.nullable) = false
];
string maxAmountIn = 5 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.moretags) = "yaml:\"max_amount_in\"",
(gogoproto.nullable) = false
];
}

// ===================== MsgExitSwapExternAmountOut
message MsgExitSwapExternAmountOut {
bytes sender = 1 [
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress",
(gogoproto.moretags) = "yaml:\"sender\""
];
uint64 targetPool = 2 [
(gogoproto.moretags) = "yaml:\"target_pool\""
];
string tokenOut = 3 [
(gogoproto.moretags) = "yaml:\"token_out\""
];
string poolAmountIn = 4 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.moretags) = "yaml:\"pool_amount_in\"",
(gogoproto.nullable) = false
];
string minAmountOut = 5 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.moretags) = "yaml:\"min_amount_out\"",
(gogoproto.nullable) = false
];
}

// ===================== MsgExitSwapPoolAmountIn
message MsgExitSwapPoolAmountIn {
bytes sender = 1 [
(gogoproto.casttype) = "github.com/cosmos/cosmos-sdk/types.AccAddress",
(gogoproto.moretags) = "yaml:\"sender\""
];
uint64 targetPool = 2 [
(gogoproto.moretags) = "yaml:\"target_pool\""
];
string tokenOut = 3 [
(gogoproto.moretags) = "yaml:\"token_out\""
];
string tokenAmountOut = 4 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.moretags) = "yaml:\"token_amount_out\"",
(gogoproto.nullable) = false
];
string maxPoolAmountIn = 5 [
(gogoproto.customtype) = "github.com/cosmos/cosmos-sdk/types.Int",
(gogoproto.moretags) = "yaml:\"max_pool_amount_in\"",
(gogoproto.nullable) = false
];
}
251 changes: 4 additions & 247 deletions x/gamm/keeper/pool/service.go
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package pool

import (
"fmt"

"github.com/c-osmosis/osmosis/x/gamm/types"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
Expand All @@ -18,7 +16,11 @@ type Service interface {
// Sender
CreatePool(sdk.Context, sdk.AccAddress, sdk.Dec, types.LPTokenInfo, []types.BindTokenInfo) error
JoinPool(sdk.Context, sdk.AccAddress, uint64, sdk.Int, []types.MaxAmountIn) error
JoinPoolWithExternAmountIn(sdk.Context, sdk.AccAddress, uint64, string, sdk.Int, sdk.Int) (sdk.Int, error)
JoinPoolWithPoolAmountOut(sdk.Context, sdk.AccAddress, uint64, string, sdk.Int, sdk.Int) (sdk.Int, error)
ExitPool(sdk.Context, sdk.AccAddress, uint64, sdk.Int, []types.MinAmountOut) error
ExitPoolWithPoolAmountIn(sdk.Context, sdk.AccAddress, uint64, string, sdk.Int, sdk.Int) (sdk.Int, error)
ExitPoolWithExternAmountOut(sdk.Context, sdk.AccAddress, uint64, string, sdk.Int, sdk.Int) (sdk.Int, error)
SwapExactAmountIn(sdk.Context, sdk.AccAddress, uint64, sdk.Coin, sdk.Int, sdk.Coin, sdk.Int, sdk.Int) (sdk.Dec, sdk.Dec, error)
SwapExactAmountOut(sdk.Context, sdk.AccAddress, uint64, sdk.Coin, sdk.Int, sdk.Coin, sdk.Int, sdk.Int) (sdk.Dec, sdk.Dec, error)
}
Expand Down Expand Up @@ -102,251 +104,6 @@ func (p poolService) GetSpotPrice(ctx sdk.Context, poolId uint64, tokenIn, token
return spotPrice, nil
}

func (p poolService) CreatePool(
ctx sdk.Context,
sender sdk.AccAddress,
swapFee sdk.Dec,
lpToken types.LPTokenInfo,
bindTokens []types.BindTokenInfo,
) error {
if len(bindTokens) < 2 {
return sdkerrors.Wrapf(
types.ErrInvalidRequest,
"token info length should be at least 2",
)
}

records := make(map[string]types.Record, len(bindTokens))
for _, info := range bindTokens {
records[info.Denom] = types.Record{
DenormalizedWeight: info.Weight,
Balance: info.Amount,
}
}

poolId := p.store.GetNextPoolNumber(ctx)
if lpToken.Denom == "" {
lpToken.Denom = fmt.Sprintf("osmosis/pool/%d", poolId)
} else {
lpToken.Denom = fmt.Sprintf("osmosis/custom/%s", lpToken.Denom)
}

pool := types.Pool{
Id: poolId,
SwapFee: swapFee,
Token: types.LP{
Denom: lpToken.Denom,
Description: lpToken.Description,
TotalSupply: sdk.NewInt(0),
},
TotalWeight: sdk.NewInt(0),
Records: records,
}

p.store.StorePool(ctx, pool)

var coins sdk.Coins
for denom, record := range records {
coins = append(coins, sdk.Coin{
Denom: denom,
Amount: record.Balance,
})
}
if coins == nil {
panic("oh my god")
}
coins = coins.Sort()

return p.bankKeeper.SendCoinsFromAccountToModule(
ctx,
sender,
types.ModuleName,
coins,
)
}

func (p poolService) JoinPool(
ctx sdk.Context,
sender sdk.AccAddress,
targetPoolId uint64,
poolAmountOut sdk.Int,
maxAmountsIn []types.MaxAmountIn,
) error {
pool, err := p.store.FetchPool(ctx, targetPoolId)
if err != nil {
return err
}
lpToken := pool.Token

poolTotal := lpToken.TotalSupply.ToDec()
poolRatio := poolAmountOut.ToDec().Quo(poolTotal)
if poolRatio.Equal(sdk.NewDec(0)) {
return sdkerrors.Wrapf(types.ErrMathApprox, "calc poolRatio")
}

checker := map[string]bool{}
for _, m := range maxAmountsIn {
if check := checker[m.Denom]; check {
return sdkerrors.Wrapf(
types.ErrInvalidRequest,
"do not use duplicated denom",
)
}
checker[m.Denom] = true
}
if len(pool.Records) != len(checker) {
return sdkerrors.Wrapf(
types.ErrInvalidRequest,
"invalid maxAmountsIn argument",
)
}

var sendTargets sdk.Coins
for _, maxAmountIn := range maxAmountsIn {
var (
tokenDenom = maxAmountIn.Denom
record, ok = pool.Records[tokenDenom]
tokenAmountIn = poolRatio.Mul(record.Balance.ToDec()).TruncateInt()
)
if !ok {
return sdkerrors.Wrapf(types.ErrInvalidRequest, "token is not bound to pool")
}
if tokenAmountIn.Equal(sdk.NewInt(0)) {
return sdkerrors.Wrapf(types.ErrMathApprox, "calc tokenAmountIn")
}
if tokenAmountIn.GT(maxAmountIn.MaxAmount) {
return sdkerrors.Wrapf(types.ErrLimitExceed, "max amount limited")
}
record.Balance = record.Balance.Add(tokenAmountIn)
pool.Records[tokenDenom] = record // update record

sendTargets = append(sendTargets, sdk.Coin{
Denom: tokenDenom,
Amount: tokenAmountIn,
})
}

// process token transfer
err = p.bankKeeper.SendCoinsFromAccountToModule(
ctx,
sender,
types.ModuleName,
sendTargets,
)
if err != nil {
return err
}

// process lpToken transfer
poolShare := lpService{
denom: lpToken.Denom,
bankKeeper: p.bankKeeper,
}
if err := poolShare.mintPoolShare(ctx, poolAmountOut); err != nil {
return err
}
if err := poolShare.pushPoolShare(ctx, sender, poolAmountOut); err != nil {
return err
}

// save changes
lpToken.TotalSupply = lpToken.TotalSupply.Add(poolAmountOut)
pool.Token = lpToken
p.store.StorePool(ctx, pool)
return nil
}

func (p poolService) ExitPool(
ctx sdk.Context,
sender sdk.AccAddress,
targetPoolId uint64,
poolAmountIn sdk.Int,
minAmountsOut []types.MinAmountOut,
) error {
pool, err := p.store.FetchPool(ctx, targetPoolId)
if err != nil {
return err
}
lpToken := pool.Token

poolTotal := lpToken.TotalSupply.ToDec()
poolRatio := poolAmountIn.ToDec().Quo(poolTotal)
if poolRatio.Equal(sdk.NewDec(0)) {
return sdkerrors.Wrapf(types.ErrMathApprox, "calc poolRatio")
}

checker := map[string]bool{}
for _, m := range minAmountsOut {
if check := checker[m.Denom]; check {
return sdkerrors.Wrapf(
types.ErrInvalidRequest,
"do not use duplicated denom",
)
}
checker[m.Denom] = true
}
if len(pool.Records) != len(checker) {
return sdkerrors.Wrapf(
types.ErrInvalidRequest,
"invalid minAmountsOut argument",
)
}

var sendTargets sdk.Coins
for _, minAmountOut := range minAmountsOut {
var (
tokenDenom = minAmountOut.Denom
record, ok = pool.Records[tokenDenom]
tokenAmountOut = poolRatio.Mul(record.Balance.ToDec()).TruncateInt()
)
if !ok {
return sdkerrors.Wrapf(types.ErrInvalidRequest, "token is not bound to pool")
}
if tokenAmountOut.Equal(sdk.NewInt(0)) {
return sdkerrors.Wrapf(types.ErrMathApprox, "calc tokenAmountOut")
}
if tokenAmountOut.LT(minAmountOut.MinAmount) {
return sdkerrors.Wrapf(types.ErrLimitExceed, "min amount limited")
}
record.Balance = record.Balance.Sub(tokenAmountOut)
pool.Records[tokenDenom] = record

sendTargets = append(sendTargets, sdk.Coin{
Denom: tokenDenom,
Amount: tokenAmountOut,
})
}

// process token transfer
err = p.bankKeeper.SendCoinsFromModuleToAccount(
ctx,
types.ModuleName,
sender,
sendTargets,
)
if err != nil {
return err
}

// process lpToken transfer
poolShare := lpService{
denom: lpToken.Denom,
bankKeeper: p.bankKeeper,
}
if err := poolShare.pullPoolShare(ctx, sender, poolAmountIn); err != nil {
return err
}
if err := poolShare.burnPoolShare(ctx, poolAmountIn); err != nil {
return err
}

// save changes
lpToken.TotalSupply = lpToken.TotalSupply.Sub(poolAmountIn)
pool.Token = lpToken
p.store.StorePool(ctx, pool)
return nil
}

func (p poolService) SwapExactAmountIn(
ctx sdk.Context,
sender sdk.AccAddress,
Expand Down
Loading

0 comments on commit 45a34da

Please sign in to comment.