From ffd96ee0e01c01b877d583ea50c0f35ba2c9a8d4 Mon Sep 17 00:00:00 2001 From: tolelom Date: Wed, 4 Sep 2024 12:26:18 +0900 Subject: [PATCH 1/7] =?UTF-8?q?PoC=20=EC=A4=91=EA=B0=84=20Token=20?= =?UTF-8?q?=EA=B0=9C=EC=88=98=20=EC=A0=95=ED=95=B4=20Route=20=EA=B2=BD?= =?UTF-8?q?=EB=A1=9C=20=EC=B0=BE=EA=B8=B0=20=EC=99=84=EB=A3=8C,=20router?= =?UTF-8?q?=20=ED=8F=AC=ED=8C=85=20=ED=94=84=EB=A1=9C=EC=A0=9D=ED=8A=B8=20?= =?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EC=9E=91=EC=97=85=20=EC=A4=91=20save?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- best_swap_route.go | 4 + core/alpha_router.go | 118 ++++++++++++++++++----------- core/alpha_router_config.go | 9 +++ core/best_swap_route.go | 4 + core/currency/base_currency.go | 1 - core/currency/currency.go | 1 + core/currency/native_currency.go | 4 + core/currency/token.go | 4 + core/portion_provider.go | 8 ++ core/protocol_pool_selection.go | 14 ++++ core/router.go | 1 + core/routes.go | 4 + core/trade.go | 6 ++ core/types.go | 9 --- poc/my_router.go | 123 +++++++++++++++++++++++-------- poc/my_router_test.go | 67 ++++++++++++++--- poc/swap_request.go | 6 +- poc/swap_result.go | 6 +- 18 files changed, 294 insertions(+), 95 deletions(-) create mode 100644 best_swap_route.go create mode 100644 core/alpha_router_config.go create mode 100644 core/best_swap_route.go create mode 100644 core/portion_provider.go create mode 100644 core/protocol_pool_selection.go create mode 100644 core/routes.go create mode 100644 core/trade.go delete mode 100644 core/types.go diff --git a/best_swap_route.go b/best_swap_route.go new file mode 100644 index 0000000..4259232 --- /dev/null +++ b/best_swap_route.go @@ -0,0 +1,4 @@ +package router + +type BestSwapRoute struct { +} diff --git a/core/alpha_router.go b/core/alpha_router.go index f96a4a5..8090f24 100644 --- a/core/alpha_router.go +++ b/core/alpha_router.go @@ -4,56 +4,90 @@ import "router/core/currency" type AlphaRouter struct { //chainId ChainId - //portionProvider IPortionProvider + portionProvider IPortionProvider } func NewAlphaRouter(params AlphaRouterParams) *AlphaRouter { return &AlphaRouter{} } -func (a AlphaRouter) route( - baseCurrency currency.Currency, // currencyIn - quoteCurrency currency.Currency, // currencyOut으로 바꿔도 될 것 같다. +// Todo: goroutine +func (a *AlphaRouter) route( + baseCurrency currency.Currency, + quoteCurrency currency.Currency, amount float64, - // amount fractions.CurrencyAmount, - // tradeType TradeType, - // swapConfig SwapOptions, + tradeType TradeType, + swapConfig SwapOptions, ) SwapRoute { - //originalAmount := amount - - // currencyIn, currencyOut은 Currency 타입이고 - // Currency 타입은 NativeCurrency(GNOT)이거나 Token 타입이다. - // 아래에서 Token 타입이길 원하는 듯하다. - //tokenIn := currencyIn.Wrapped() - //tokenOut := currencyOut.Wrapped() - - //core 패키지를 TradeType 패키지로 변경하면 가독성이 더 좋아질 듯 하다. - //if tradeType == EXACT_OUTPUT { - // // TODO: GetPortionAmount에서 반환 값인 CurrencyAmount을 반환하지 못할 경우가 있을 수도 있다.(높은 확률로) - // portionAmount := a.portionProvider.GetPortionAmount( - // amount, - // tradeType, - // swapConfig, - // ) - // - //result := portionAmount.GreaterThan(0) - //if result { - // amount = amount.add(portionAmount) - //} - //} + originalAmount := amount - return SwapRoute{} + currencyIn, currencyOut := a.determineCurrencyInOutFromTradeType(tradeType, baseCurrency, quoteCurrency) + + // token은 currency의 wrapped된 버전이다. + tokenIn := currencyIn.GetToken() + tokenOut := currencyOut.GetToken() + + // 왠만하면 함수로 뺄 것 + // 내용 이해 필요 + if tradeType == EXACT_OUTPUT { + portionAmount, portionErr := a.portionProvider.GetPortionAmount( + amount, + tradeType, + swapConfig, + ) + + if portionErr == nil && portionAmount > 0 { + amount += portionAmount + } + } + + // routing config 다루는 부분 패스 + routingConfig := AlphaRouterConfig{} + + // tokenIn 또는 tokenOut과 동일한 값... + quoteToken := quoteCurrency.GetToken() + + // main logic? + routes := a.getSwapRouteFromChain(tokenIn, tokenOut, amount, tradeType, routingConfig) + + if routes == nil { + // todo: error 처리 해 줄 것 + } + + trade := a.buildTrade(currencyIn, currencyOut, tradeType, routes) + + swapRoute := a.buildSwapRoute() + return swapRoute +} + +func (a *AlphaRouter) determineCurrencyInOutFromTradeType( + tradeType TradeType, + baseCurrency currency.Currency, + quoteCurrency currency.Currency, +) (currency.Currency, currency.Currency) { + if tradeType == EXACT_INPUT { + return baseCurrency, quoteCurrency + } + return quoteCurrency, baseCurrency } -// -//func (a AlphaRouter) determineCurrencyInOutFromTradeType( -// tradeType TradeType, -// amount fractions.CurrencyAmount, -// quoteCurrency currency.Currency, -//) (currency.Currency, currency.Currency) { -// if tradeType == EXACT_INPUT { -// return amount.Currency, quoteCurrency -// } else { -// return quoteCurrency, amount.Currency -// } -//} +// todo: goroutine +func (a *AlphaRouter) getSwapRouteFromChain(tokenIn, tokenOut currency.Token, amount float64, tradeType TradeType, routingConfig AlphaRouterConfig) *BestSwapRoute { + percents, amount := a.getAmountDistribution(amount, routingConfig) + + return &BestSwapRoute{} +} + +func (a *AlphaRouter) getAmountDistribution(amount float64, routingConfig AlphaRouterConfig) (float64, float64) { + + return 0, 0 +} + +func (a *AlphaRouter) buildTrade(currencyIn currency.Currency, currencyOut currency.Currency, tradeType TradeType, routes Routes) Trade { + + return Trade{} +} + +func (a *AlphaRouter) buildSwapRoute() SwapRoute { + return SwapRoute{} +} diff --git a/core/alpha_router_config.go b/core/alpha_router_config.go new file mode 100644 index 0000000..4d0a9f5 --- /dev/null +++ b/core/alpha_router_config.go @@ -0,0 +1,9 @@ +package core + +type AlphaRouterConfig struct { + v3ProtocolPoolSelection ProtocolPoolSelection + maxSwapsPerPath int + maxSplits int + minSplits int + distributionPercent int +} diff --git a/core/best_swap_route.go b/core/best_swap_route.go new file mode 100644 index 0000000..0138ae7 --- /dev/null +++ b/core/best_swap_route.go @@ -0,0 +1,4 @@ +package core + +type BestSwapRoute struct { +} diff --git a/core/currency/base_currency.go b/core/currency/base_currency.go index e031d53..afd93bc 100644 --- a/core/currency/base_currency.go +++ b/core/currency/base_currency.go @@ -15,7 +15,6 @@ type BaseCurrency struct { address string } -// 이게 필요할까?? func NewBaseCurrency(chainId int64, decimals int64, symbol string, name string) *BaseCurrency { // 아래 코드는 원문 //invariant(Number.isSafeInteger(chainId), 'CHAIN_ID'); diff --git a/core/currency/currency.go b/core/currency/currency.go index d822769..097e8b4 100644 --- a/core/currency/currency.go +++ b/core/currency/currency.go @@ -2,4 +2,5 @@ package currency // Currency는 Token | NativeCurrency type Currency interface { + GetToken() Token } diff --git a/core/currency/native_currency.go b/core/currency/native_currency.go index 746310b..cf106da 100644 --- a/core/currency/native_currency.go +++ b/core/currency/native_currency.go @@ -3,3 +3,7 @@ package currency type NativeCurrency struct { BaseCurrency } + +func (n *NativeCurrency) getToken() Token { + return Token{} // 임시 +} diff --git a/core/currency/token.go b/core/currency/token.go index 31ca4e2..e4988cd 100644 --- a/core/currency/token.go +++ b/core/currency/token.go @@ -4,3 +4,7 @@ type Token struct { BaseCurrency address string } + +func (t *Token) getToken() Token { + return *t +} diff --git a/core/portion_provider.go b/core/portion_provider.go new file mode 100644 index 0000000..b30b5a1 --- /dev/null +++ b/core/portion_provider.go @@ -0,0 +1,8 @@ +package core + +type IPortionProvider interface { + GetPortionAmount(tokenOutAmount float64, tradeType TradeType, swapConfig SwapOptions) (float64, error) +} + +type PortionProvider struct { +} diff --git a/core/protocol_pool_selection.go b/core/protocol_pool_selection.go new file mode 100644 index 0000000..4993b99 --- /dev/null +++ b/core/protocol_pool_selection.go @@ -0,0 +1,14 @@ +package core + +type ProtocolPoolSelection struct { + topN int + topNDirectSwaps int + topNTokenInOut int + topNSecondHop int + topNWithEachBaseToken int + topNWithBaseToken int + + // selectable variable + //topNSecondHopForTokenAddress + //tokensToAvoidOnSecondHops +} diff --git a/core/router.go b/core/router.go index 3ff9580..f4958ae 100644 --- a/core/router.go +++ b/core/router.go @@ -1,6 +1,7 @@ package core type SwapRoute struct { + route Routes } type IRouter interface { diff --git a/core/routes.go b/core/routes.go new file mode 100644 index 0000000..6d73547 --- /dev/null +++ b/core/routes.go @@ -0,0 +1,4 @@ +package core + +type Routes struct { +} diff --git a/core/trade.go b/core/trade.go new file mode 100644 index 0000000..ca1fff0 --- /dev/null +++ b/core/trade.go @@ -0,0 +1,6 @@ +package core + +type Trade struct { + v3Routes V3Routes + tradeType TradeType +} diff --git a/core/types.go b/core/types.go deleted file mode 100644 index 2c40893..0000000 --- a/core/types.go +++ /dev/null @@ -1,9 +0,0 @@ -package core - -// interface는 I 접두사를 붙이는 것이 관행인가? -type IPortionProvider interface { - // GetPortionAmount(tokenOutAmount fractions.CurrencyAmount, tradeType TradeType, swapConfig SwapOptions) fractions.CurrencyAmount -} - -type PortionProvider struct { -} diff --git a/poc/my_router.go b/poc/my_router.go index 4205533..c3609d0 100644 --- a/poc/my_router.go +++ b/poc/my_router.go @@ -7,56 +7,126 @@ import ( type MyRouter struct { network map[string]*Pool + adj map[string][]string } func NewMyRouter(edges []*Pool) *MyRouter { router := &MyRouter{ network: make(map[string]*Pool), + adj: make(map[string][]string), } for _, edge := range edges { router.network[edge.Address] = edge + router.adj[edge.TokenA.Symbol] = append(router.adj[edge.TokenA.Symbol], edge.TokenB.Symbol) + router.adj[edge.TokenB.Symbol] = append(router.adj[edge.TokenB.Symbol], edge.TokenA.Symbol) } return router } -func (m *MyRouter) Swap(request SwapRequest) (SwapResult, error) { - // poolName은 from:to가 아니라 to:from일 수 있다. - poolName := request.FromToken + ":" + request.ToToken +// Route +// 두 개의 토큰 사이의 효율적인 경로를 계산하는 함수 +func (m *MyRouter) Route(request SwapRequest) ([]SwapResult, error) { + // V1 Router + return m.findRouteV1(request) - if pool, ok := m.network[poolName]; ok { - fmt.Printf("pool found: %v\n", pool) - - reserveFromToken, reserveToToken := m.getReserveOfTokenFromPool(request.FromToken, request.ToToken, *pool) - exchangedAmount := m.calculateAmountOfToToken(reserveFromToken, reserveToToken, request.AmountIn, *pool) - - //saveSwap() - // TODO: 지금은 간이로 코드 작성하고 나중에 함수로 빼든 리팩토링 할 것 - if pool.TokenA.Symbol == request.FromToken { - pool.ReserveA += request.AmountIn - pool.ReserveB += exchangedAmount - } else { - pool.ReserveA += exchangedAmount - pool.ReserveB += request.AmountIn + // V2 Router + //return m.findRouteV2(startTokenSymbol, endTokenSymbol, AmountIn, 1) +} + +func (m *MyRouter) findRouteV1(request SwapRequest) ([]SwapResult, error) { + return m.swap(request.FromTokenSymbol, request.ToTokenSymbol, request.AmountIn) +} + +// 경로가 maxLength 이하의 길이인 경로를 탐색해 route를 구한다. +func (m *MyRouter) findRouteV2(request SwapRequest, maxLength int, routes []SwapResult) ([]SwapResult, error) { + startTokenSymbol, beforeTokenSymbol, amountIn := m.setSymbolAndAmountIn(request, routes) + if startTokenSymbol == request.ToTokenSymbol { + return routes, nil + } + if len(routes) >= maxLength { + return nil, fmt.Errorf("the length of routes exceeds maxLength") + } + + var bestPath []SwapResult + for _, toTokenSymbol := range m.adj[startTokenSymbol] { + if toTokenSymbol == beforeTokenSymbol { // 경로 2인 cycle은 허용하지 않음 + continue + } + + route, swapErr := m.swap(startTokenSymbol, toTokenSymbol, amountIn) + if swapErr != nil { + continue } - return SwapResult{ - AmountIn: request.AmountIn, - AmountOut: math.Abs(exchangedAmount), - }, nil + workablePath, _ := m.findRouteV2(request, maxLength, append(routes, route...)) + // TODO: 여기에 스왑한 내용 복구가 필요합니다! + + if len(workablePath) != 0 && (bestPath == nil || (bestPath[len(bestPath)-1].AmountOut < workablePath[len(workablePath)-1].AmountOut)) { + bestPath = workablePath + } + } + + return bestPath, nil +} + +func (m *MyRouter) setSymbolAndAmountIn(request SwapRequest, routes []SwapResult) (string, string, float64) { + if routes == nil { // 처음 함수가 호출된 거라면 + return request.FromTokenSymbol, "", request.AmountIn + } + return routes[len(routes)-1].OutTokenSymbol, routes[len(routes)-1].InTokenSymbol, routes[len(routes)-1].AmountOut +} + +// Swap +// 두 개의 토큰 사이의 직접적인 Pool을 통해 두 개의 토큰을 교환하는 함수 +func (m *MyRouter) swap(fromTokenSymbol string, toTokenSymbol string, amountIn float64) ([]SwapResult, error) { + // TODO: poolName은 from:to가 아니라 to:from일 수 있다. + // TODO: 문자열 연산 최적화 + poolName := fromTokenSymbol + ":" + toTokenSymbol + + if pool, ok := m.network[poolName]; ok { + //fmt.Printf("pool found: %v\n", pool) // for debug + reserveFromToken, reserveToToken := m.getReserveOfTokenFromPool(fromTokenSymbol, toTokenSymbol, *pool) + amountOut := m.calculateAmountOfToToken(reserveFromToken, reserveToToken, amountIn, *pool) + + // 같은 경로를 두 번 이상 탐색하지 않으므로 일단 주석 처리 + //m.saveSwap(fromTokenSymbol, amountIn, amountOut, pool) + + return []SwapResult{{ + InTokenSymbol: fromTokenSymbol, + OutTokenSymbol: toTokenSymbol, + AmountIn: amountIn, + AmountOut: math.Abs(amountOut), + }}, nil } - return SwapResult{}, fmt.Errorf("pool %s not found", poolName) + return nil, fmt.Errorf("pool %s not found", poolName) } -func (m *MyRouter) getReserveOfTokenFromPool(fromTokenName string, toTokenName string, pool Pool) (float64, float64) { - if fromTokenName == pool.TokenA.Symbol { +func (m *MyRouter) saveSwap(fromTokenSymbol string, amountIn, amountOut float64, pool *Pool) { + if pool.TokenA.Symbol == fromTokenSymbol { + pool.ReserveA += amountIn + pool.ReserveB += amountOut + } else { + pool.ReserveA += amountOut + pool.ReserveB += amountIn + } +} + +// getReserveOfTokenFromPool +// Pool에 있는 fromToken과 toToken의 reserve 쌍을 반환하는 함수 +func (m *MyRouter) getReserveOfTokenFromPool(fromTokenSymbol string, toTokenSymbol string, pool Pool) (float64, float64) { + if fromTokenSymbol == pool.TokenA.Symbol { return pool.ReserveA, pool.ReserveB } return pool.ReserveB, pool.ReserveA } +// calculateAmountOfToToken +// 토큰이 교환될 때 교환자에게 지급해야 할 toToken의 양을 계산하는 함수 +// 계산 과정 최적화 하면 곱셈 5번, 덧셈 2번 정도에 해결 가능함 +// ref: https://hyun-jeong.medium.com/uniswap-series-2-cpmm-%EC%9D%B4%ED%95%B4%ED%95%98%EA%B8%B0-4a82de8aba9 func (m *MyRouter) calculateAmountOfToToken(reserveFromToken, reserveToToken, amountIn float64, pool Pool) float64 { X := reserveFromToken Y := reserveToToken @@ -74,10 +144,5 @@ func (m *MyRouter) calculateAmountOfToToken(reserveFromToken, reserveToToken, am // X 코인이 dX개 만큼 증가했을 때 // Y 코인은 dY개 만큼 감소해야 한다. // X -> X + dX, Y -> Y + dY - return dY } - -func (m *MyRouter) dijskrtra() { - -} diff --git a/poc/my_router_test.go b/poc/my_router_test.go index 8b869ea..1c9cf4b 100644 --- a/poc/my_router_test.go +++ b/poc/my_router_test.go @@ -6,7 +6,8 @@ import ( "testing" ) -func TestMyRouter(t *testing.T) { +func TestMyRouterV1(t *testing.T) { + tolerance := 0.00000001 // 오차 범위 tokens := map[string]Token{ "a": Token{Symbol: "a"}, "b": Token{Symbol: "b"}, @@ -23,7 +24,7 @@ func TestMyRouter(t *testing.T) { []SwapRequest{ {"a", "b", 2000}}, []SwapResult{ - {2000.0, 2000.0 / 6.0}, + {"a", "b", 2000.0, 2000.0 / 6.0}, }, }, } @@ -33,20 +34,68 @@ func TestMyRouter(t *testing.T) { router := NewMyRouter(test.edges) for i, request := range test.requests { - result, err := router.Swap(request) + result, err := router.findRouteV1(request) if err != nil { - t.Fatalf("Swap Error: can't find pool: %v:%v", request.FromToken, request.ToToken) + t.Fatalf("Swap Error: can't find pool: %v:%v", request.FromTokenSymbol, request.ToTokenSymbol) } - diff := math.Abs(result.AmountOut - test.results[i].AmountOut) - tolerance := 0.00000001 + diff := math.Abs(result[0].AmountOut - test.results[i].AmountOut) if diff > tolerance { - t.Fatalf("Swap: Unexpected Token output number, expected: %v, got %v", test.results[i].AmountOut, result.AmountOut) + t.Fatalf("Swap: Unexpected Token output number, expected: %v, got %v", test.results[i].AmountOut, result[0].AmountOut) } - fmt.Println(result) + fmt.Println(result[0]) + fmt.Println("스왑 결과") for _, pool := range router.network { - fmt.Println(pool) + fmt.Printf("pool (%s) %s: %f %s: %f\n", pool.Address, pool.TokenA.Symbol, pool.ReserveA, pool.TokenB.Symbol, pool.ReserveB) + } + } + }) + } +} + +func TestMyRouterV2(t *testing.T) { + tolerance := 0.00000001 // 오차 범위 + tokens := map[string]Token{ + "a": Token{Symbol: "a"}, + "b": Token{Symbol: "b"}, + "c": Token{Symbol: "c"}, + "d": Token{Symbol: "d"}, + } + + tests := []struct { + edges []*Pool + requests []SwapRequest + results []SwapResult + }{ + { + []*Pool{ + {"a:b", tokens["a"], tokens["b"], 4000, 1000}, + {"a:c", tokens["a"], tokens["c"], 2000, 1000}, + {"b:c", tokens["b"], tokens["c"], 2000, 4000}}, + []SwapRequest{ + {"a", "c", 2000}}, + []SwapResult{ + {"a", "c", 2000.0, 571.4285714285}, + }, + }, + } + + for _, test := range tests { + t.Run("", func(t *testing.T) { + router := NewMyRouter(test.edges) + + for i, request := range test.requests { + result, err := router.findRouteV2(request, 10, nil) + if err != nil { + t.Fatalf("Router: can't find path: %v:%v", request.FromTokenSymbol, request.ToTokenSymbol) + } + fmt.Print("result path: ") + fmt.Println(result) + + diff := math.Abs(result[len(result)-1].AmountOut - test.results[i].AmountOut) + if diff > tolerance { + t.Fatalf("Router: Unexpected Token output number, expected: %v, got %v", test.results[i].AmountOut, result[len(result)-1].AmountOut) } } }) diff --git a/poc/swap_request.go b/poc/swap_request.go index af13e08..d146258 100644 --- a/poc/swap_request.go +++ b/poc/swap_request.go @@ -1,9 +1,9 @@ package poc type SwapRequest struct { - FromToken string - ToToken string - AmountIn float64 + FromTokenSymbol string + ToTokenSymbol string + AmountIn float64 //MinAmountOut int //UserAddress string // option } diff --git a/poc/swap_result.go b/poc/swap_result.go index 22bfb71..1ba3ab3 100644 --- a/poc/swap_result.go +++ b/poc/swap_result.go @@ -1,6 +1,8 @@ package poc type SwapResult struct { - AmountIn float64 - AmountOut float64 + InTokenSymbol string + OutTokenSymbol string + AmountIn float64 + AmountOut float64 } From c24ff182c81f5d515549f8689e30932b444de038 Mon Sep 17 00:00:00 2001 From: tolelom Date: Wed, 4 Sep 2024 14:02:02 +0900 Subject: [PATCH 2/7] =?UTF-8?q?PoC=20=EC=A0=9C=EC=9E=91=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20=EB=B0=8F=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91?= =?UTF-8?q?=EC=84=B1=20=EC=99=84=EB=A3=8C(=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BC=80=EC=9D=B4=EC=8A=A4=EB=8A=94=20=EB=8D=94=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=ED=95=98=EB=A9=B4=20=EC=A2=8B=EC=9D=8C)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- core/alpha_router.go | 45 +++++++++++++++++++++++++------------------ core/trade.go | 2 +- poc/my_router.go | 16 +++++++++++---- poc/my_router_test.go | 24 ++++++++++++++++++----- poc/pool.go | 4 +--- poc/swap_request.go | 2 +- 6 files changed, 60 insertions(+), 33 deletions(-) diff --git a/core/alpha_router.go b/core/alpha_router.go index 8090f24..53d33f9 100644 --- a/core/alpha_router.go +++ b/core/alpha_router.go @@ -15,46 +15,49 @@ func NewAlphaRouter(params AlphaRouterParams) *AlphaRouter { func (a *AlphaRouter) route( baseCurrency currency.Currency, quoteCurrency currency.Currency, - amount float64, + amount float64, // todo: float64 -> fraction tradeType TradeType, swapConfig SwapOptions, + routerConfig AlphaRouterConfig, ) SwapRoute { - originalAmount := amount + //originalAmount := amount // for save - currencyIn, currencyOut := a.determineCurrencyInOutFromTradeType(tradeType, baseCurrency, quoteCurrency) + //currencyIn, currencyOut := a.determineCurrencyInOutFromTradeType(tradeType, baseCurrency, quoteCurrency) // token은 currency의 wrapped된 버전이다. - tokenIn := currencyIn.GetToken() - tokenOut := currencyOut.GetToken() + //tokenIn := currencyIn.GetToken() + //tokenOut := currencyOut.GetToken() // 왠만하면 함수로 뺄 것 // 내용 이해 필요 if tradeType == EXACT_OUTPUT { - portionAmount, portionErr := a.portionProvider.GetPortionAmount( - amount, - tradeType, - swapConfig, - ) + portionAmount, portionErr := a.portionProvider.GetPortionAmount(amount, tradeType, swapConfig) if portionErr == nil && portionAmount > 0 { + // In case of exact out swap, before we route, we need to make sure that the + // token out amount accounts for flat portion, and token in amount after the best swap route contains the token in equivalent of portion. + // In other words, in case a pool's LP fee bps is lower than the portion bps (0.01%/0.05% for v3), a pool can go insolvency. + // This is because instead of the swapper being responsible for the portion, + // the pool instead gets responsible for the portion. + // The addition below avoids that situation. amount += portionAmount } } - // routing config 다루는 부분 패스 - routingConfig := AlphaRouterConfig{} + // routing config merge다루는 부분 패스 + //routerConfig = setRouterConfig(routingConfig, chainId) // tokenIn 또는 tokenOut과 동일한 값... - quoteToken := quoteCurrency.GetToken() + //quoteToken := quoteCurrency.GetToken() // main logic? - routes := a.getSwapRouteFromChain(tokenIn, tokenOut, amount, tradeType, routingConfig) + //routes := a.getSwapRouteFromChain(tokenIn, tokenOut, amount, tradeType, routingConfig) - if routes == nil { - // todo: error 처리 해 줄 것 - } + //if routes == nil { + // // todo: error 처리 해 줄 것 + //} - trade := a.buildTrade(currencyIn, currencyOut, tradeType, routes) + //trade := a.buildTrade(currencyIn, currencyOut, tradeType, routes) swapRoute := a.buildSwapRoute() return swapRoute @@ -73,7 +76,7 @@ func (a *AlphaRouter) determineCurrencyInOutFromTradeType( // todo: goroutine func (a *AlphaRouter) getSwapRouteFromChain(tokenIn, tokenOut currency.Token, amount float64, tradeType TradeType, routingConfig AlphaRouterConfig) *BestSwapRoute { - percents, amount := a.getAmountDistribution(amount, routingConfig) + //percents, amount := a.getAmountDistribution(amount, routingConfig) return &BestSwapRoute{} } @@ -91,3 +94,7 @@ func (a *AlphaRouter) buildTrade(currencyIn currency.Currency, currencyOut curre func (a *AlphaRouter) buildSwapRoute() SwapRoute { return SwapRoute{} } + +func (a *AlphaRouter) setRouterConfig(routerConfig AlphaRouterConfig, chainId int) AlphaRouterConfig { + return AlphaRouterConfig{} +} diff --git a/core/trade.go b/core/trade.go index ca1fff0..805e0b8 100644 --- a/core/trade.go +++ b/core/trade.go @@ -1,6 +1,6 @@ package core type Trade struct { - v3Routes V3Routes + //v3Routes V3Routes tradeType TradeType } diff --git a/poc/my_router.go b/poc/my_router.go index c3609d0..cf8acd9 100644 --- a/poc/my_router.go +++ b/poc/my_router.go @@ -5,6 +5,8 @@ import ( "math" ) +// MyRouter +// router PoC type MyRouter struct { network map[string]*Pool adj map[string][]string @@ -35,11 +37,14 @@ func (m *MyRouter) Route(request SwapRequest) ([]SwapResult, error) { //return m.findRouteV2(startTokenSymbol, endTokenSymbol, AmountIn, 1) } +// findRouteV1 +// 두 토큰을 direct swap한다 func (m *MyRouter) findRouteV1(request SwapRequest) ([]SwapResult, error) { return m.swap(request.FromTokenSymbol, request.ToTokenSymbol, request.AmountIn) } -// 경로가 maxLength 이하의 길이인 경로를 탐색해 route를 구한다. +// findRouteV2 +// 경로가 maxLength 이하의 길이인 경로를 탐색해 route를 구한다 func (m *MyRouter) findRouteV2(request SwapRequest, maxLength int, routes []SwapResult) ([]SwapResult, error) { startTokenSymbol, beforeTokenSymbol, amountIn := m.setSymbolAndAmountIn(request, routes) if startTokenSymbol == request.ToTokenSymbol { @@ -60,8 +65,10 @@ func (m *MyRouter) findRouteV2(request SwapRequest, maxLength int, routes []Swap continue } - workablePath, _ := m.findRouteV2(request, maxLength, append(routes, route...)) - // TODO: 여기에 스왑한 내용 복구가 필요합니다! + workablePath, findRouteErr := m.findRouteV2(request, maxLength, append(routes, route...)) + if findRouteErr != nil { + continue + } if len(workablePath) != 0 && (bestPath == nil || (bestPath[len(bestPath)-1].AmountOut < workablePath[len(workablePath)-1].AmountOut)) { bestPath = workablePath @@ -71,6 +78,7 @@ func (m *MyRouter) findRouteV2(request SwapRequest, maxLength int, routes []Swap return bestPath, nil } +// setSymbolAndAmountIn func (m *MyRouter) setSymbolAndAmountIn(request SwapRequest, routes []SwapResult) (string, string, float64) { if routes == nil { // 처음 함수가 호출된 거라면 return request.FromTokenSymbol, "", request.AmountIn @@ -86,7 +94,6 @@ func (m *MyRouter) swap(fromTokenSymbol string, toTokenSymbol string, amountIn f poolName := fromTokenSymbol + ":" + toTokenSymbol if pool, ok := m.network[poolName]; ok { - //fmt.Printf("pool found: %v\n", pool) // for debug reserveFromToken, reserveToToken := m.getReserveOfTokenFromPool(fromTokenSymbol, toTokenSymbol, *pool) amountOut := m.calculateAmountOfToToken(reserveFromToken, reserveToToken, amountIn, *pool) @@ -104,6 +111,7 @@ func (m *MyRouter) swap(fromTokenSymbol string, toTokenSymbol string, amountIn f return nil, fmt.Errorf("pool %s not found", poolName) } +// saveSwap func (m *MyRouter) saveSwap(fromTokenSymbol string, amountIn, amountOut float64, pool *Pool) { if pool.TokenA.Symbol == fromTokenSymbol { pool.ReserveA += amountIn diff --git a/poc/my_router_test.go b/poc/my_router_test.go index 1c9cf4b..6866c6d 100644 --- a/poc/my_router_test.go +++ b/poc/my_router_test.go @@ -64,9 +64,10 @@ func TestMyRouterV2(t *testing.T) { } tests := []struct { - edges []*Pool - requests []SwapRequest - results []SwapResult + edges []*Pool + requests []SwapRequest + results []SwapResult + maxSearchLength int }{ { []*Pool{ @@ -76,8 +77,21 @@ func TestMyRouterV2(t *testing.T) { []SwapRequest{ {"a", "c", 2000}}, []SwapResult{ - {"a", "c", 2000.0, 571.4285714285}, + {"a", "c", 2000, 571.4285714285}, + }, + 2, + }, + { + []*Pool{ + {"a:b", tokens["a"], tokens["b"], 4000, 1000}, + {"a:c", tokens["a"], tokens["c"], 2000, 1000}, + {"b:c", tokens["b"], tokens["c"], 2000, 4000}}, + []SwapRequest{ + {"a", "c", 2000}}, + []SwapResult{ + {"a", "c", 2000, 500}, }, + 1, }, } @@ -86,7 +100,7 @@ func TestMyRouterV2(t *testing.T) { router := NewMyRouter(test.edges) for i, request := range test.requests { - result, err := router.findRouteV2(request, 10, nil) + result, err := router.findRouteV2(request, test.maxSearchLength, nil) if err != nil { t.Fatalf("Router: can't find path: %v:%v", request.FromTokenSymbol, request.ToTokenSymbol) } diff --git a/poc/pool.go b/poc/pool.go index 12c4707..202f01e 100644 --- a/poc/pool.go +++ b/poc/pool.go @@ -1,11 +1,9 @@ package poc -// Pool -// kind of edge type Pool struct { Address string - TokenA Token // 효율을 위해서는 포인터로 가져오는게 좋을만 하다. + TokenA Token // Todo: 효율을 위해서는 포인터로 가져오는게 좋을만 하다. TokenB Token ReserveA float64 diff --git a/poc/swap_request.go b/poc/swap_request.go index d146258..31e2ba2 100644 --- a/poc/swap_request.go +++ b/poc/swap_request.go @@ -5,5 +5,5 @@ type SwapRequest struct { ToTokenSymbol string AmountIn float64 //MinAmountOut int - //UserAddress string // option + //UserAddress string // optional } From e49d95d2da10259a0ad33542c6d803719f2709f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=B1=EB=AF=BC?= <98kimsungmin@naver.com> Date: Wed, 4 Sep 2024 14:49:09 +0900 Subject: [PATCH 3/7] Update poc/my_router_test.go Co-authored-by: Lee ByeongJun --- poc/my_router_test.go | 1 + 1 file changed, 1 insertion(+) diff --git a/poc/my_router_test.go b/poc/my_router_test.go index 6866c6d..fe6dde4 100644 --- a/poc/my_router_test.go +++ b/poc/my_router_test.go @@ -64,6 +64,7 @@ func TestMyRouterV2(t *testing.T) { } tests := []struct { + name string edges []*Pool requests []SwapRequest results []SwapResult From 65af84c759ca873cb635bb6a3d668ba923783ac6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=B1=EB=AF=BC?= <98kimsungmin@naver.com> Date: Wed, 4 Sep 2024 14:49:23 +0900 Subject: [PATCH 4/7] Update poc/my_router_test.go Co-authored-by: Lee ByeongJun --- poc/my_router_test.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/poc/my_router_test.go b/poc/my_router_test.go index fe6dde4..f6f61db 100644 --- a/poc/my_router_test.go +++ b/poc/my_router_test.go @@ -83,7 +83,8 @@ func TestMyRouterV2(t *testing.T) { 2, }, { - []*Pool{ + name: "최대 검색 길이 2의 다중 홉 스왑", + edges: []*Pool{ {"a:b", tokens["a"], tokens["b"], 4000, 1000}, {"a:c", tokens["a"], tokens["c"], 2000, 1000}, {"b:c", tokens["b"], tokens["c"], 2000, 4000}}, From bffe53b574611d965349ce1538e0e75d9af23bc4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=B1=EB=AF=BC?= <98kimsungmin@naver.com> Date: Wed, 4 Sep 2024 05:57:24 +0000 Subject: [PATCH 5/7] =?UTF-8?q?my=5Frouter=5Ftest=EC=97=90=20test=20case?= =?UTF-8?q?=20name=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- oryxBuildBinary | Bin 0 -> 1958 bytes poc/my_router_test.go | 7 ++++--- 2 files changed, 4 insertions(+), 3 deletions(-) create mode 100644 oryxBuildBinary diff --git a/oryxBuildBinary b/oryxBuildBinary new file mode 100644 index 0000000000000000000000000000000000000000..63f6a45762a1c4b93e2e1573e80b9fc564fa2bb5 GIT binary patch literal 1958 zcmd6o&udWM9_=GfjCH9xH$L&l=?pJJIj=9LR>t_XP@W&zRx`GyuW7G?$CW> zphMC$rEgDfw!25>mX19+9`oOfs_oMgMQn;*1{P%b#7XiV0NW=555w^?av zRnl_r{?2{bjJk8ASw7nCTehBeMt$A$EI(-AP2IPH2E!e(eBHKu%d`T=E=l>wDVVZj z%Ioa@!F=h6x_8*@9uGzaw8mkrv8gv4=^Id6+YZ>>U45em9QT+zwoa0y_3OK&NEEnL z!_|QKpcX6u5?BZpfyFU!EN=_^Hn1D?fIZ+aI0URP!Km(OOi*mM3(6q~2kDVi6&p$v*h`8+N)W414(${g2&)j`ZBr>>9@MHDmY6~c zx6_mNraop%n=w;K&}>&z+;}T~>=kcU+P7v^jW_Op+qbonfFskAE|}#B(sVCjk%(*H z26zRg!A7jdsRS1g-vX}zt)T+Cu=_;)JdW+Q84o%@`~AOa88so=M&1bnCEc8!W#PDIwJTKE)I(j_%tuycoeYH~ zRqSFtm7XcTOFS$e#wmKPA9{K?5YIx{p+}*MiQ$Tcmf7(~r zII8rUWw(XGda#O%yVz4Ct^toIpgp1cM^L#oo{3G0jcUJRF)A-dKnW{AmS>9WQHp7x z96&MF#llti9cNKOTnga|6H${!CR+w>vM3?`EW~<*;@QvI+xJtGX+t=EwfJGUy6xt~ zow`rASNHZ$JbzI?L|OD{p-;(Nls_?)`!`0PpUdES%w710;4ydxUV=A(7AG;;nVFe0 zw4Cy6`5R;?!X{9Sdl1{#SWcszDDnj|twn{}xL1{GQ}!IS8_<;)l4?U?2L?mtD2A1X z>_eQ$rWd5zWQiA?hJ22zzseUWv)mQ??dLn6*4EZ$;^rT_i3eh#P88{={gW-h)8$!+ T%PL-Mp@!dT{!hr`-^Tn7iA#IY literal 0 HcmV?d00001 diff --git a/poc/my_router_test.go b/poc/my_router_test.go index f6f61db..dc0c03f 100644 --- a/poc/my_router_test.go +++ b/poc/my_router_test.go @@ -64,13 +64,14 @@ func TestMyRouterV2(t *testing.T) { } tests := []struct { - name string + name string edges []*Pool requests []SwapRequest results []SwapResult maxSearchLength int }{ { + "최대 검색 길이 1의 다중 홉 스왑", []*Pool{ {"a:b", tokens["a"], tokens["b"], 4000, 1000}, {"a:c", tokens["a"], tokens["c"], 2000, 1000}, @@ -83,8 +84,8 @@ func TestMyRouterV2(t *testing.T) { 2, }, { - name: "최대 검색 길이 2의 다중 홉 스왑", - edges: []*Pool{ + "최대 검색 길이 2의 다중 홉 스왑", + []*Pool{ {"a:b", tokens["a"], tokens["b"], 4000, 1000}, {"a:c", tokens["a"], tokens["c"], 2000, 1000}, {"b:c", tokens["b"], tokens["c"], 2000, 4000}}, From 6af7deb6a22fc0d95609c4ff826ef65df7b4f1e4 Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Wed, 4 Sep 2024 15:12:34 +0900 Subject: [PATCH 6/7] =?UTF-8?q?=ED=95=84=EB=93=9C=20=EC=9D=B4=EB=A6=84=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poc/my_router_test.go | 83 ++++++++++++++++++++++++++++++------------- 1 file changed, 58 insertions(+), 25 deletions(-) diff --git a/poc/my_router_test.go b/poc/my_router_test.go index dc0c03f..2cefed5 100644 --- a/poc/my_router_test.go +++ b/poc/my_router_test.go @@ -6,27 +6,59 @@ import ( "testing" ) +const tolerance = 0.00000001 // 오차 범위 + func TestMyRouterV1(t *testing.T) { - tolerance := 0.00000001 // 오차 범위 tokens := map[string]Token{ - "a": Token{Symbol: "a"}, - "b": Token{Symbol: "b"}, + "a": {Symbol: "a"}, + "b": {Symbol: "b"}, } tests := []struct { + name string edges []*Pool requests []SwapRequest results []SwapResult }{ { - []*Pool{ - {"a:b", tokens["a"], tokens["b"], 4000, 1000}}, - []SwapRequest{ - {"a", "b", 2000}}, - []SwapResult{ + name: "단일 홉 스왑", + edges: []*Pool{ + {"a:b", tokens["a"], tokens["b"], 4000, 1000}, + }, + requests: []SwapRequest{ + {"a", "b", 2000}, + }, + results: []SwapResult{ {"a", "b", 2000.0, 2000.0 / 6.0}, }, }, + // { + // name: "극단적인 비율 스왑", + // edges: []*Pool{ + // {"a:b", tokens["a"], tokens["b"], 1000000, 1}, + // }, + // requests: []SwapRequest{ + // {"a", "b", 500}, + // }, + // results: []SwapResult{ + // {"a", "b", 500, 0.0004999999999999999}, + // }, + // }, + // { + // name: "양방향 스왑", + // edges: []*Pool{ + // {"a:b", tokens["a"], tokens["b"], 4000, 1000}, + // {"b:a", tokens["b"], tokens["a"], 1000, 4000}, + // }, + // requests: []SwapRequest{ + // {"a", "b", 2000}, + // {"b", "a", 500}, + // }, + // results: []SwapResult{ + // {"a", "b", 2000, 2000.0 / 6.0}, + // {"b", "a", 500, 500.0 / 6.0}, + // }, + // }, } for _, test := range tests { @@ -55,12 +87,11 @@ func TestMyRouterV1(t *testing.T) { } func TestMyRouterV2(t *testing.T) { - tolerance := 0.00000001 // 오차 범위 tokens := map[string]Token{ - "a": Token{Symbol: "a"}, - "b": Token{Symbol: "b"}, - "c": Token{Symbol: "c"}, - "d": Token{Symbol: "d"}, + "a": {Symbol: "a"}, + "b": {Symbol: "b"}, + "c": {Symbol: "c"}, + "d": {Symbol: "d"}, } tests := []struct { @@ -71,30 +102,32 @@ func TestMyRouterV2(t *testing.T) { maxSearchLength int }{ { - "최대 검색 길이 1의 다중 홉 스왑", - []*Pool{ + name: "최대 검색 길이 1의 다중 홉 스왑", + edges: []*Pool{ {"a:b", tokens["a"], tokens["b"], 4000, 1000}, {"a:c", tokens["a"], tokens["c"], 2000, 1000}, {"b:c", tokens["b"], tokens["c"], 2000, 4000}}, - []SwapRequest{ + requests: []SwapRequest{ {"a", "c", 2000}}, - []SwapResult{ + results: []SwapResult{ {"a", "c", 2000, 571.4285714285}, }, - 2, + maxSearchLength: 2, }, { - "최대 검색 길이 2의 다중 홉 스왑", - []*Pool{ + name: "최대 검색 길이 2의 다중 홉 스왑", + edges: []*Pool{ {"a:b", tokens["a"], tokens["b"], 4000, 1000}, {"a:c", tokens["a"], tokens["c"], 2000, 1000}, - {"b:c", tokens["b"], tokens["c"], 2000, 4000}}, - []SwapRequest{ - {"a", "c", 2000}}, - []SwapResult{ + {"b:c", tokens["b"], tokens["c"], 2000, 4000}, + }, + requests: []SwapRequest{ + {"a", "c", 2000}, + }, + results: []SwapResult{ {"a", "c", 2000, 500}, }, - 1, + maxSearchLength: 1, }, } From 3f47eed4d2616c29a1f07cdb46870b43fb386f3b Mon Sep 17 00:00:00 2001 From: Lee ByeongJun Date: Wed, 4 Sep 2024 15:19:55 +0900 Subject: [PATCH 7/7] =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=BC=80?= =?UTF-8?q?=EC=9D=B4=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- poc/my_router_test.go | 41 +++++++++++++++++++++++++++++++++++++---- 1 file changed, 37 insertions(+), 4 deletions(-) diff --git a/poc/my_router_test.go b/poc/my_router_test.go index 2cefed5..8cb31eb 100644 --- a/poc/my_router_test.go +++ b/poc/my_router_test.go @@ -32,6 +32,7 @@ func TestMyRouterV1(t *testing.T) { {"a", "b", 2000.0, 2000.0 / 6.0}, }, }, + // TODO: 아래 테스트 케이스도 통과해야 함. 값은 검증 필요. // { // name: "극단적인 비율 스왑", // edges: []*Pool{ @@ -100,6 +101,7 @@ func TestMyRouterV2(t *testing.T) { requests []SwapRequest results []SwapResult maxSearchLength int + expectError bool }{ { name: "최대 검색 길이 1의 다중 홉 스왑", @@ -129,19 +131,50 @@ func TestMyRouterV2(t *testing.T) { }, maxSearchLength: 1, }, + { + name: "다양한 경로 스왑", + edges: []*Pool{ + {"a:b", tokens["a"], tokens["b"], 4000, 1000}, + {"b:c", tokens["b"], tokens["c"], 2000, 4000}, + {"a:c", tokens["a"], tokens["c"], 2000, 1000}, + {"c:d", tokens["c"], tokens["d"], 1000, 500}, + }, + requests: []SwapRequest{ + {"a", "d", 1000}, + }, + results: []SwapResult{ + {"a", "d", 1000, 133.33333333333334}, // TODO: 임의로 넣은 값이라 검증 필요 + }, + maxSearchLength: 3, + }, + { + name: "검색 길이가 음수인 경우", + edges: []*Pool{ + {"a:b", tokens["a"], tokens["b"], 4000, 1000}, + {"b:c", tokens["b"], tokens["c"], 2000, 4000}, + {"c:d", tokens["c"], tokens["d"], 1000, 500}, + {"a:d", tokens["a"], tokens["d"], 3000, 1500}, + }, + requests: []SwapRequest{ + {"a", "d", 1500}, + }, + maxSearchLength: -1, + expectError: true, + }, } for _, test := range tests { - t.Run("", func(t *testing.T) { + t.Run(test.name, func(t *testing.T) { router := NewMyRouter(test.edges) for i, request := range test.requests { result, err := router.findRouteV2(request, test.maxSearchLength, nil) if err != nil { - t.Fatalf("Router: can't find path: %v:%v", request.FromTokenSymbol, request.ToTokenSymbol) + if !test.expectError { + t.Fatalf("Router: can't find path: %v:%v", request.FromTokenSymbol, request.ToTokenSymbol) + } + continue } - fmt.Print("result path: ") - fmt.Println(result) diff := math.Abs(result[len(result)-1].AmountOut - test.results[i].AmountOut) if diff > tolerance {