/
multihop.go
115 lines (98 loc) · 4.34 KB
/
multihop.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
package keeper
import (
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/osmosis-labs/osmosis/v12/x/gamm/types"
)
// MultihopSwapExactAmountIn defines the input denom and input amount for the first pool,
// the output of the first pool is chained as the input for the next routed pool
// transaction succeeds when final amount out is greater than tokenOutMinAmount defined.
func (k Keeper) MultihopSwapExactAmountIn(
ctx sdk.Context,
sender sdk.AccAddress,
routes []types.SwapAmountInRoute,
tokenIn sdk.Coin,
tokenOutMinAmount sdk.Int,
) (tokenOutAmount sdk.Int, err error) {
for i, route := range routes {
// To prevent the multihop swap from being interrupted prematurely, we keep
// the minimum expected output at a very low number until the last pool
_outMinAmount := sdk.NewInt(1)
if len(routes)-1 == i {
_outMinAmount = tokenOutMinAmount
}
// Execute the expected swap on the current routed pool
tokenOutAmount, err = k.SwapExactAmountIn(ctx, sender, route.PoolId, tokenIn, route.TokenOutDenom, _outMinAmount)
if err != nil {
return sdk.Int{}, err
}
// Chain output of current pool as the input for the next routed pool
tokenIn = sdk.NewCoin(route.TokenOutDenom, tokenOutAmount)
}
return
}
// MultihopSwapExactAmountOut defines the output denom and output amount for the last pool.
// Calculation starts by providing the tokenOutAmount of the final pool to calculate the required tokenInAmount
// the calculated tokenInAmount is used as defined tokenOutAmount of the previous pool, calculating in reverse order of the swap
// Transaction succeeds if the calculated tokenInAmount of the first pool is less than the defined tokenInMaxAmount defined.
func (k Keeper) MultihopSwapExactAmountOut(
ctx sdk.Context,
sender sdk.AccAddress,
routes []types.SwapAmountOutRoute,
tokenInMaxAmount sdk.Int,
tokenOut sdk.Coin,
) (tokenInAmount sdk.Int, err error) {
// Determine what the estimated input would be for each pool along the multihop route
insExpected, err := k.createMultihopExpectedSwapOuts(ctx, routes, tokenOut)
if err != nil {
return sdk.Int{}, err
}
if len(insExpected) == 0 {
return sdk.Int{}, nil
}
insExpected[0] = tokenInMaxAmount
// Iterates through each routed pool and executes their respective swaps. Note that all of the work to get the return
// value of this method is done when we calculate insExpected – this for loop primarily serves to execute the actual
// swaps on each pool.
for i, route := range routes {
_tokenOut := tokenOut
// If there is one pool left in the route, set the expected output of the current swap
// to the estimated input of the final pool.
if i != len(routes)-1 {
_tokenOut = sdk.NewCoin(routes[i+1].TokenInDenom, insExpected[i+1])
}
// Execute the expected swap on the current routed pool
_tokenInAmount, err := k.SwapExactAmountOut(ctx, sender, route.PoolId, route.TokenInDenom, insExpected[i], _tokenOut)
if err != nil {
return sdk.Int{}, err
}
// Sets the final amount of tokens that need to be input into the first pool. Even though this is the final return value for the
// whole method and will not change after the first iteration, we still iterate through the rest of the pools to execute their respective
// swaps.
if i == 0 {
tokenInAmount = _tokenInAmount
}
}
return tokenInAmount, nil
}
// createMultihopExpectedSwapOuts defines the output denom and output amount for the last pool in
// the route of pools the caller is intending to hop through in a fixed-output multihop tx. It estimates the input
// amount for this last pool and then chains that input as the output of the previous pool in the route, repeating
// until the first pool is reached. It returns an array of inputs, each of which correspond to a pool ID in the
// route of pools for the original multihop transaction.
func (k Keeper) createMultihopExpectedSwapOuts(ctx sdk.Context, routes []types.SwapAmountOutRoute, tokenOut sdk.Coin) ([]sdk.Int, error) {
insExpected := make([]sdk.Int, len(routes))
for i := len(routes) - 1; i >= 0; i-- {
route := routes[i]
pool, err := k.getPoolForSwap(ctx, route.PoolId)
if err != nil {
return nil, err
}
tokenIn, err := pool.CalcInAmtGivenOut(ctx, sdk.NewCoins(tokenOut), route.TokenInDenom, pool.GetSwapFee(ctx))
if err != nil {
return nil, err
}
insExpected[i] = tokenIn.Amount
tokenOut = tokenIn
}
return insExpected, nil
}