Search Apps Documentation Source Content File Folder Download Copy Actions Download State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

exact_in.gno

7.51 Kb · 265 lines
  1package router
  2
  3import (
  4	"chain"
  5	"errors"
  6
  7	ufmt "gno.land/p/nt/ufmt/v0"
  8
  9	u256 "gno.land/p/gnoswap/uint256"
 10	"gno.land/p/gnoswap/utils"
 11
 12	"gno.land/r/gnoswap/common"
 13	"gno.land/r/gnoswap/emission"
 14	"gno.land/r/gnoswap/halt"
 15	"gno.land/r/gnoswap/referral"
 16)
 17
 18// ExactInSwapRoute swaps an exact amount of input tokens for output tokens.
 19//
 20// Executes multi-hop swaps through specified route.
 21// Supports splitting across multiple paths for price optimization.
 22// Applies slippage protection via minimum output amount.
 23//
 24// Parameters:
 25//   - inputToken, outputToken: Token contract paths
 26//   - amountIn: Exact input amount to swap
 27//   - routeArr: Swap route (max 3 hops per path, multiple paths separated by comma)
 28//   - quoteArr: Split percentages "70,30" (must sum to 100)
 29//   - amountOutMin: Minimum acceptable output (slippage protection)
 30//   - deadline: Unix timestamp for expiration
 31//   - referrer: Optional referral address
 32//
 33// Route format:
 34//   - Single-hop: "TOKEN0:TOKEN1:FEE"
 35//   - Multi-hop: "TOKEN0:TOKEN1:FEE*POOL*TOKEN1:TOKEN2:FEE" (use *POOL* separator)
 36//   - Multi-path: "route1,route2" (use comma separator between different routes)
 37//
 38// Returns:
 39//   - amountIn: Actual input consumed
 40//   - amountOut: Actual output received
 41//
 42// Reverts if output < amountOutMin or deadline passed.
 43func (r *routerV1) ExactInSwapRoute(
 44	_ int,
 45	rlm realm,
 46	inputToken string,
 47	outputToken string,
 48	amountIn string,
 49	routeArr string,
 50	quoteArr string,
 51	amountOutMin string,
 52	deadline int64,
 53	referrer string,
 54) (string, string) {
 55	if !rlm.IsCurrent() {
 56		panic(errors.New(errSpoofedRealm))
 57	}
 58
 59	halt.AssertIsNotHaltedRouter()
 60
 61	common.AssertIsNotHandleNativeCoin()
 62
 63	assertIsValidRoutePaths(routeArr, inputToken, outputToken)
 64	assertIsNotExpired(deadline)
 65	assertIsExistsPools(routeArr)
 66
 67	emission.MintAndDistributeGns(cross(rlm))
 68
 69	params := SwapRouteParams{
 70		inputToken:        inputToken,
 71		outputToken:       outputToken,
 72		routeArr:          routeArr,
 73		quoteArr:          quoteArr,
 74		deadline:          deadline,
 75		typ:               ExactIn,
 76		exactAmount:       utils.SafeParseInt64(amountIn),
 77		limitAmount:       utils.SafeParseInt64(amountOutMin),
 78		sqrtPriceLimitX96: u256.Zero(), // multi-hop swap is not allowed to set sqrtPriceLimitX96
 79	}
 80
 81	inputAmount, outputAmount := r.exactInSwapRoute(0, rlm, params, referrer)
 82
 83	return utils.FormatInt(inputAmount), utils.FormatInt(outputAmount)
 84}
 85
 86// ExactInSingleSwapRoute swaps an exact amount of input tokens for output tokens through a single route.
 87//
 88// Executes single-hop swaps through a single specified route.
 89// Allows price limit control via sqrtPriceLimitX96 parameter.
 90// Applies slippage protection via minimum output amount.
 91//
 92// Parameters:
 93//   - inputToken, outputToken: Token contract paths
 94//   - amountIn: Exact input amount to swap
 95//   - routeArr: Single swap route (just 1 hop)
 96//   - amountOutMin: Minimum acceptable output (slippage protection)
 97//   - sqrtPriceLimitX96: Price limit for swap execution (0 for no limit)
 98//   - deadline: Unix timestamp for expiration
 99//   - referrer: Optional referral address
100//
101// Route format:
102//   - Single-hop: "INPUT_TOKEN:OUTPUT_TOKEN:FEE"
103//
104// Returns:
105//   - amountIn: Actual input consumed
106//   - amountOut: Actual output received
107//
108// Reverts the transaction if the output amount is less than amountOutMin,
109// the deadline has passed, or the price limit is exceeded.
110func (r *routerV1) ExactInSingleSwapRoute(
111	_ int,
112	rlm realm,
113	inputToken string,
114	outputToken string,
115	amountIn string,
116	routeArr string,
117	amountOutMin string,
118	sqrtPriceLimitX96 string,
119	deadline int64,
120	referrer string,
121) (string, string) {
122	if !rlm.IsCurrent() {
123		panic(errors.New(errSpoofedRealm))
124	}
125
126	halt.AssertIsNotHaltedRouter()
127
128	common.AssertIsNotHandleNativeCoin()
129
130	assertIsValidSingleSwapRouteArrPath(routeArr, inputToken, outputToken)
131	assertIsValidSqrtPriceLimitX96(sqrtPriceLimitX96)
132	assertIsNotExpired(deadline)
133	assertIsExistsPools(routeArr)
134
135	emission.MintAndDistributeGns(cross(rlm))
136
137	params := SwapRouteParams{
138		inputToken:        inputToken,
139		outputToken:       outputToken,
140		routeArr:          routeArr,
141		quoteArr:          "100",
142		deadline:          deadline,
143		typ:               ExactIn,
144		exactAmount:       utils.SafeParseInt64(amountIn),
145		limitAmount:       utils.SafeParseInt64(amountOutMin),
146		sqrtPriceLimitX96: u256.MustFromDecimal(sqrtPriceLimitX96), // single swap is allowed to set sqrtPriceLimitX96
147	}
148
149	inputAmount, outputAmount := r.exactInSwapRoute(0, rlm, params, referrer)
150
151	return utils.FormatInt(inputAmount), utils.FormatInt(outputAmount)
152}
153
154// exactInSwapRoute executes the swap operation and handles token transfers and referral registration.
155//
156// Performs the actual swap operation using commonSwapRoute and handles:
157// - Safe token transfers to the caller
158// - Referral registration and tracking
159// - Event emission for swap completion
160//
161// Parameters:
162//   - params: SwapRouteParams containing all swap configuration
163//   - referrer: Referral address for registration
164//
165// Returns:
166//   - inputAmount: Actual input amount consumed as string
167//   - outputAmount: Actual output amount received as string (negative value)
168//
169// Panics if swap execution fails or token transfer fails.
170func (r *routerV1) exactInSwapRoute(
171	_ int,
172	rlm realm,
173	params SwapRouteParams,
174	referrer string,
175) (int64, int64) {
176	inputAmount, outputAmount, err := r.commonSwapRoute(0, rlm, params)
177	if err != nil {
178		panic(err)
179	}
180
181	previousRealm := rlm.Previous()
182	caller := previousRealm.Address()
183
184	common.SafeGRC20Transfer(cross(rlm), params.outputToken, caller, outputAmount)
185
186	// handle referral registration
187	actualReferrer := referral.TryRegister(cross(rlm), caller, referrer)
188
189	resultInputAmount := inputAmount
190	resultOutputAmount := -outputAmount
191
192	eventAttrs := append([]string{
193		"prevAddr", caller.String(),
194		"prevRealm", previousRealm.PkgPath(),
195		"input", params.inputToken,
196		"output", params.outputToken,
197		"exactAmount", utils.FormatInt(params.exactAmount),
198		"quote", params.quoteArr,
199		"resultInputAmount", utils.FormatInt(resultInputAmount),
200		"resultOutputAmount", utils.FormatInt(resultOutputAmount),
201		"referrer", actualReferrer,
202	}, buildRouteEventAttrs(params.routeArr)...)
203
204	chain.Emit(
205		"ExactInSwap",
206		eventAttrs...,
207	)
208
209	return resultInputAmount, resultOutputAmount
210}
211
212type ExactInSwapOperation struct {
213	baseSwapOperation
214	params ExactInParams
215	router *routerV1
216}
217
218func NewExactInSwapOperation(r *routerV1, pp ExactInParams) *ExactInSwapOperation {
219	return &ExactInSwapOperation{
220		router: r,
221		params: pp,
222		baseSwapOperation: baseSwapOperation{
223			sqrtPriceLimitX96: pp.SqrtPriceLimitX96,
224		},
225	}
226}
227
228// Validate validates the exact-in swap operation parameters.
229func (op *ExactInSwapOperation) Validate() error {
230	amountIn := op.params.AmountIn
231
232	if amountIn <= 0 {
233		return ufmt.Errorf("invalid amountIn(%d), must be positive", amountIn)
234	}
235
236	// when `SwapType` is `ExactIn`, assign `amountSpecified` the `amountIn`
237	// obtained from above.
238	op.amountSpecified = amountIn
239
240	routes, quotes, err := validateRoutesAndQuotes(op.params.RouteArr, op.params.QuoteArr)
241	if err != nil {
242		return err
243	}
244
245	op.routes = routes
246	op.quotes = quotes
247
248	return nil
249}
250
251// Process executes the exact-in swap operation.
252func (op *ExactInSwapOperation) Process(_ int, rlm realm) (*SwapResult, error) {
253	resultAmountIn, resultAmountOut, err := op.processRoutes(0, rlm, op.router, ExactIn)
254	if err != nil {
255		return nil, err
256	}
257
258	return &SwapResult{
259		AmountIn:        resultAmountIn,
260		AmountOut:       resultAmountOut,
261		Routes:          op.routes,
262		Quotes:          op.quotes,
263		AmountSpecified: op.amountSpecified,
264	}, nil
265}