-
Notifications
You must be signed in to change notification settings - Fork 44
/
swap_in_amt_given_out.go
159 lines (136 loc) · 6.14 KB
/
swap_in_amt_given_out.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
package types
import (
fmt "fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
)
func (p Pool) CalcGivenOutSlippage(
ctx sdk.Context,
oracleKeeper OracleKeeper,
snapshot *Pool,
tokensOut sdk.Coins,
tokenInDenom string,
accPoolKeeper AccountedPoolKeeper,
) (sdk.Dec, error) {
balancerInCoin, _, err := p.CalcInAmtGivenOut(ctx, oracleKeeper, snapshot, tokensOut, tokenInDenom, sdk.ZeroDec(), accPoolKeeper)
if err != nil {
return sdk.ZeroDec(), err
}
tokenOut, poolAssetOut, poolAssetIn, err := p.parsePoolAssets(tokensOut, tokenInDenom)
if err != nil {
return sdk.ZeroDec(), err
}
// ensure token prices for in/out tokens set properly
inTokenPrice := oracleKeeper.GetAssetPriceFromDenom(ctx, tokenInDenom)
if inTokenPrice.IsZero() {
return sdk.ZeroDec(), fmt.Errorf("price for inToken not set: %s", poolAssetIn.Token.Denom)
}
outTokenPrice := oracleKeeper.GetAssetPriceFromDenom(ctx, tokenOut.Denom)
if outTokenPrice.IsZero() {
return sdk.ZeroDec(), fmt.Errorf("price for outToken not set: %s", poolAssetOut.Token.Denom)
}
// in amount is calculated in this formula
oracleInAmount := sdk.NewDecFromInt(tokenOut.Amount).Mul(outTokenPrice).Quo(inTokenPrice)
balancerIn := sdk.NewDecFromInt(balancerInCoin.Amount)
balancerSlippage := balancerIn.Sub(oracleInAmount)
if balancerSlippage.IsNegative() {
return sdk.ZeroDec(), nil
}
return balancerSlippage, nil
}
// SwapInAmtGivenOut is a mutative method for CalcOutAmtGivenIn, which includes the actual swap.
func (p *Pool) SwapInAmtGivenOut(
ctx sdk.Context, oracleKeeper OracleKeeper, snapshot *Pool,
tokensOut sdk.Coins, tokenInDenom string, swapFee sdk.Dec, accPoolKeeper AccountedPoolKeeper) (
tokenIn sdk.Coin, slippage, slippageAmount sdk.Dec, weightBalanceBonus sdk.Dec, err error,
) {
// early return with balancer swap if normal amm pool
if !p.PoolParams.UseOracle {
balancerInCoin, slippage, err := p.CalcInAmtGivenOut(ctx, oracleKeeper, snapshot, tokensOut, tokenInDenom, swapFee, accPoolKeeper)
if err != nil {
return sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), err
}
err = p.applySwap(ctx, sdk.Coins{balancerInCoin}, tokensOut, swapFee, sdk.ZeroDec(), accPoolKeeper)
if err != nil {
return sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), err
}
return balancerInCoin, slippage, sdk.ZeroDec(), sdk.ZeroDec(), nil
}
tokenOut, poolAssetOut, poolAssetIn, err := p.parsePoolAssets(tokensOut, tokenInDenom)
if err != nil {
return sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), err
}
// ensure token prices for in/out tokens set properly
inTokenPrice := oracleKeeper.GetAssetPriceFromDenom(ctx, tokenInDenom)
if inTokenPrice.IsZero() {
return sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), fmt.Errorf("price for inToken not set: %s", poolAssetIn.Token.Denom)
}
outTokenPrice := oracleKeeper.GetAssetPriceFromDenom(ctx, tokenOut.Denom)
if outTokenPrice.IsZero() {
return sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), fmt.Errorf("price for outToken not set: %s", poolAssetOut.Token.Denom)
}
initialWeightDistance := p.WeightDistanceFromTarget(ctx, oracleKeeper, p.PoolAssets)
// in amount is calculated in this formula
// balancer slippage amount = Max(oracleOutAmount-balancerOutAmount, 0)
// resizedAmount = tokenIn / externalLiquidityRatio
// actualSlippageAmount = balancer slippage(resizedAmount)
oracleInAmount := sdk.NewDecFromInt(tokenOut.Amount).Mul(outTokenPrice).Quo(inTokenPrice)
// Ensure p.PoolParams.ExternalLiquidityRatio is not zero to avoid division by zero
if p.PoolParams.ExternalLiquidityRatio.IsZero() {
return sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), ErrAmountTooLow
}
resizedAmount := sdk.NewDecFromInt(tokenOut.Amount).Quo(p.PoolParams.ExternalLiquidityRatio).RoundInt()
slippageAmount, err = p.CalcGivenOutSlippage(
ctx,
oracleKeeper,
snapshot,
sdk.Coins{sdk.NewCoin(tokenOut.Denom, resizedAmount)},
tokenInDenom,
accPoolKeeper,
)
if err != nil {
return sdk.Coin{}, sdk.OneDec(), sdk.ZeroDec(), sdk.ZeroDec(), err
}
inAmountAfterSlippage := oracleInAmount.Add(slippageAmount)
slippage = slippageAmount.Quo(inAmountAfterSlippage)
// calculate weight distance difference to calculate bonus/cut on the operation
newAssetPools, err := p.NewPoolAssetsAfterSwap(
sdk.Coins{sdk.NewCoin(tokenInDenom, inAmountAfterSlippage.TruncateInt())},
tokensOut,
)
if err != nil {
return sdk.Coin{}, sdk.OneDec(), sdk.ZeroDec(), sdk.ZeroDec(), err
}
weightDistance := p.WeightDistanceFromTarget(ctx, oracleKeeper, newAssetPools)
distanceDiff := weightDistance.Sub(initialWeightDistance)
// cut is valid when distance higher than original distance
weightBreakingFee := sdk.ZeroDec()
if distanceDiff.IsPositive() {
// old weight breaking fee implementation
// weightBreakingFee = p.PoolParams.WeightBreakingFeeMultiplier.Mul(distanceDiff)
// target weight
targetWeightIn := NormalizedWeight(ctx, p.PoolAssets, tokenInDenom)
targetWeightOut := NormalizedWeight(ctx, p.PoolAssets, tokenOut.Denom)
// weight breaking fee as in Plasma pool
weightIn := OracleAssetWeight(ctx, oracleKeeper, newAssetPools, tokenInDenom)
weightOut := OracleAssetWeight(ctx, oracleKeeper, newAssetPools, tokenOut.Denom)
weightBreakingFee = GetWeightBreakingFee(weightIn, weightOut, targetWeightIn, targetWeightOut, p.PoolParams)
}
// bonus is valid when distance is lower than original distance and when threshold weight reached
weightBalanceBonus = weightBreakingFee.Neg()
if initialWeightDistance.GT(p.PoolParams.ThresholdWeightDifference) && distanceDiff.IsNegative() {
weightBalanceBonus = p.PoolParams.WeightBreakingFeeMultiplier.Mul(distanceDiff).Abs()
}
if swapFee.GTE(sdk.OneDec()) {
return sdk.Coin{}, sdk.OneDec(), sdk.ZeroDec(), sdk.ZeroDec(), ErrTooMuchSwapFee
}
tokenAmountInInt := inAmountAfterSlippage.
Mul(sdk.OneDec().Add(weightBreakingFee)).
Quo(sdk.OneDec().Sub(swapFee)).
TruncateInt()
tokenIn = sdk.NewCoin(tokenInDenom, tokenAmountInInt)
err = p.applySwap(ctx, sdk.Coins{tokenIn}, tokensOut, swapFee, sdk.ZeroDec(), accPoolKeeper)
if err != nil {
return sdk.Coin{}, sdk.ZeroDec(), sdk.ZeroDec(), sdk.ZeroDec(), err
}
return tokenIn, slippage, slippageAmount, weightBalanceBonus, nil
}