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

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