Search Apps Documentation Source Content File Folder Download Copy Actions Download

exact_in.gno

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