-
Notifications
You must be signed in to change notification settings - Fork 41
/
route_exact_amount_out.go
129 lines (111 loc) · 5.05 KB
/
route_exact_amount_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
package keeper
import (
"fmt"
errorsmod "cosmossdk.io/errors"
"cosmossdk.io/math"
sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/elys-network/elys/x/amm/types"
)
// 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) RouteExactAmountOut(ctx sdk.Context,
sender sdk.AccAddress,
recipient sdk.AccAddress,
routes []types.SwapAmountOutRoute,
tokenInMaxAmount math.Int,
tokenOut sdk.Coin,
discount sdk.Dec,
) (tokenInAmount math.Int, totalDiscountedSwapFee sdk.Dec, discountOut sdk.Dec, err error) {
isMultiHopRouted, routeSwapFee, sumOfSwapFees := false, sdk.Dec{}, sdk.Dec{}
route := types.SwapAmountOutRoutes(routes)
if err := route.Validate(); err != nil {
return math.Int{}, sdk.ZeroDec(), sdk.ZeroDec(), err
}
defer func() {
if r := recover(); r != nil {
tokenInAmount = math.Int{}
err = fmt.Errorf("function RouteExactAmountOut failed due to internal reason: %v", r)
}
}()
// in this loop, we check if:
// - the route is of length 2
// - route 1 and route 2 don't trade via the same pool
// - route 1 contains uosmo
// - both route 1 and route 2 are incentivized pools
// if all of the above is true, then we collect the additive and max fee between the two pools to later calculate the following:
// total_swap_fee = total_swap_fee = max(swapfee1, swapfee2)
// fee_per_pool = total_swap_fee * ((pool_fee) / (swapfee1 + swapfee2))
if k.isElysRoutedMultihop(ctx, route, routes[0].TokenInDenom, tokenOut.Denom) {
isMultiHopRouted = true
routeSwapFee, sumOfSwapFees, err = k.getElysRoutedMultihopTotalSwapFee(ctx, route)
if err != nil {
return math.Int{}, sdk.ZeroDec(), sdk.ZeroDec(), err
}
}
// Initialize the total discounted swap fee
totalDiscountedSwapFee = sdk.ZeroDec()
// Determine what the estimated input would be for each pool along the multi-hop route
// if we determined the route is an osmo multi-hop and both routes are incentivized,
// we utilize a separate function that calculates the discounted swap fees
var insExpected []math.Int
if isMultiHopRouted {
insExpected, err = k.createElysMultihopExpectedSwapOuts(ctx, routes, tokenOut, routeSwapFee, sumOfSwapFees)
} else {
insExpected, err = k.createMultihopExpectedSwapOuts(ctx, routes, tokenOut)
}
if err != nil {
return math.Int{}, sdk.ZeroDec(), sdk.ZeroDec(), err
}
if len(insExpected) == 0 {
return math.Int{}, sdk.ZeroDec(), sdk.ZeroDec(), 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
pool, poolExists := k.GetPool(ctx, route.PoolId)
if !poolExists {
return math.Int{}, sdk.ZeroDec(), sdk.ZeroDec(), types.ErrInvalidPoolId
}
// // check if pool is active, if not error
// if !pool.IsActive(ctx) {
// return math.Int{}, fmt.Errorf("pool %d is not active", pool.GetId())
// }
swapFee := pool.GetPoolParams().SwapFee
if isMultiHopRouted && sumOfSwapFees.IsPositive() {
swapFee = routeSwapFee.Mul((swapFee.Quo(sumOfSwapFees)))
}
// Apply discount to swap fee if applicable
brokerAddress := k.parameterKeeper.GetParams(ctx).BrokerAddress
if discount.IsNil() {
discount = sdk.ZeroDec()
}
if discount.IsPositive() && sender.String() != brokerAddress {
return math.Int{}, sdk.ZeroDec(), sdk.ZeroDec(), errorsmod.Wrapf(types.ErrInvalidDiscount, "discount %s is positive and signer address %s is not broker address %s", discount, sender, brokerAddress)
}
swapFee = types.ApplyDiscount(swapFee, discount)
// Calculate the total discounted swap fee
totalDiscountedSwapFee = totalDiscountedSwapFee.Add(swapFee)
_tokenInAmount, swapErr := k.SwapExactAmountOut(ctx, sender, recipient, pool, route.TokenInDenom, insExpected[i], _tokenOut, swapFee)
if swapErr != nil {
return math.Int{}, sdk.ZeroDec(), sdk.ZeroDec(), swapErr
}
// 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, totalDiscountedSwapFee, discount, nil
}