Search Apps Documentation Source Content File Folder Download Copy Actions Download

exact_in.gno

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