From dceaa951142e58016a319f125f45a84f5e4d0e20 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=B1=EB=AF=BC?= <98kimsungmin@naver.com> Date: Fri, 30 Aug 2024 15:25:29 +0900 Subject: [PATCH] =?UTF-8?q?feat:=20Router=EC=9D=98=20PoC=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(#9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: Router의 PoC 추가 * Apply suggestions from code review Co-authored-by: Lee ByeongJun * review 지적사항 수정 * MyRouter.Swap() error message 수정 --------- Co-authored-by: tolelom Co-authored-by: Lee ByeongJun --- core/alpha_router.go | 95 ++++++++++++++++------------------ core/currency/base_currency.go | 10 ++-- core/currency/currency.go | 1 - core/currency/token.go | 1 + core/math/fraction_test.go | 33 +++--------- core/tokens/gnot.go | 17 ++++++ poc/my_router.go | 83 +++++++++++++++++++++++++++++ poc/my_router_test.go | 54 +++++++++++++++++++ poc/pool.go | 16 ++++++ poc/swap_request.go | 9 ++++ poc/swap_result.go | 6 +++ poc/token.go | 7 +++ 12 files changed, 251 insertions(+), 81 deletions(-) create mode 100644 core/tokens/gnot.go create mode 100644 poc/my_router.go create mode 100644 poc/my_router_test.go create mode 100644 poc/pool.go create mode 100644 poc/swap_request.go create mode 100644 poc/swap_result.go create mode 100644 poc/token.go diff --git a/core/alpha_router.go b/core/alpha_router.go index 681ca5d..f96a4a5 100644 --- a/core/alpha_router.go +++ b/core/alpha_router.go @@ -1,55 +1,50 @@ package core -//import ( -// "router/core/coins/fractions" -// "router/core/currency" -//) -// -//type AlphaRouter struct { -// portionProvider IPortionProvider -//} -// -//func NewAlphaRouter(params AlphaRouterParams) *AlphaRouter { -// return &AlphaRouter{} -//} -// -//// TODO: 원본 코드에서는 async 함수 -//// 라우트 한 결과는 SwapRoute -//func (a AlphaRouter) route( -// amount fractions.CurrencyAmount, -// quoteCurrency currency.Currency, -// tradeType TradeType, -// swapConfig SwapOptions, -//) SwapRoute { -// //originalAmount := amount -// // -// //currencyIn, currencyOut := a.determineCurrencyInOutFromTradeType(tradeType, amount, quoteCurrency) -// // -// //// 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) -// // //} -// //} -// // -// //swapRoute := SwapRoute{} -// //return swapRoute -// return SwapRoute{} -//} +import "router/core/currency" + +type AlphaRouter struct { + //chainId ChainId + //portionProvider IPortionProvider +} + +func NewAlphaRouter(params AlphaRouterParams) *AlphaRouter { + return &AlphaRouter{} +} + +func (a AlphaRouter) route( + baseCurrency currency.Currency, // currencyIn + quoteCurrency currency.Currency, // currencyOut으로 바꿔도 될 것 같다. + amount float64, + // amount fractions.CurrencyAmount, + // 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) + //} + //} + + return SwapRoute{} +} + // //func (a AlphaRouter) determineCurrencyInOutFromTradeType( // tradeType TradeType, diff --git a/core/currency/base_currency.go b/core/currency/base_currency.go index 31df962..e031d53 100644 --- a/core/currency/base_currency.go +++ b/core/currency/base_currency.go @@ -2,11 +2,12 @@ package currency type BaseCurrency struct { // The chain ID on which this currency resides - chainId int + ChainId int64 // The decimals used in representing currency amounts - decimals int + decimals int64 // 이 아래 필드는 옵션 + // The symbol of the currency, i.e. a short textual non-unique identifier symbol string // The name of the currency, i.e. a descriptive textual non-unique identifier @@ -14,7 +15,8 @@ type BaseCurrency struct { address string } -func NewBaseCurrency(chainId int, decimals int, symbol string, name string) *BaseCurrency { +// 이게 필요할까?? +func NewBaseCurrency(chainId int64, decimals int64, symbol string, name string) *BaseCurrency { // 아래 코드는 원문 //invariant(Number.isSafeInteger(chainId), 'CHAIN_ID'); //invariant( @@ -28,7 +30,7 @@ func NewBaseCurrency(chainId int, decimals int, symbol string, name string) *Bas } return &BaseCurrency{ - chainId: chainId, + ChainId: chainId, decimals: decimals, symbol: symbol, name: name, diff --git a/core/currency/currency.go b/core/currency/currency.go index 2079b96..d822769 100644 --- a/core/currency/currency.go +++ b/core/currency/currency.go @@ -2,5 +2,4 @@ package currency // Currency는 Token | NativeCurrency type Currency interface { - Wrapped() Token } diff --git a/core/currency/token.go b/core/currency/token.go index bee6c6f..31ca4e2 100644 --- a/core/currency/token.go +++ b/core/currency/token.go @@ -2,4 +2,5 @@ package currency type Token struct { BaseCurrency + address string } diff --git a/core/math/fraction_test.go b/core/math/fraction_test.go index bbe1fdc..2fd3d72 100644 --- a/core/math/fraction_test.go +++ b/core/math/fraction_test.go @@ -8,7 +8,7 @@ func TestNewFraction(t *testing.T) { tests := []struct { numerator, denominator int64 expectedNumerator, expectedDenominator int64 - expectedError bool + expectedPanic bool }{ {1, 1, 1, 1, false}, {2, 2, 1, 1, false}, @@ -25,7 +25,7 @@ func TestNewFraction(t *testing.T) { t.Run("", func(t *testing.T) { defer func() { if r := recover(); r != nil { - if !test.expectedError { + if !test.expectedPanic { t.Fatalf("NewFraction: unexpected panic: %v", r) } } @@ -109,7 +109,7 @@ func TestFraction_Mul(t *testing.T) { numerator1, denominator1 int64 numerator2, denominator2 int64 expectedNumerator, expectedDenominator int64 - shouldPanic bool + expectedPanic bool }{ {1, 2, 1, 3, 1, 6, false}, {-100, 10, 256, -10, 256, 1, false}, @@ -128,32 +128,15 @@ func TestFraction_Mul(t *testing.T) { t.Run("", func(t *testing.T) { defer func() { if r := recover(); r != nil { - if tt.shouldPanic { + if tt.expectedPanic { return } t.Fatalf("Mul: unexpected panic: %v", r) } }() fraction1 := NewFraction(tt.numerator1, tt.denominator1) - - defer func() { - if r := recover(); r != nil { - if tt.shouldPanic { - return - } - t.Fatalf("Mul: unexpected panic: %v", r) - } - }() fraction2 := NewFraction(tt.numerator2, tt.denominator2) - defer func() { - if r := recover(); r != nil { - if tt.shouldPanic { - return - } - t.Fatalf("Mul: unexpected panic: %v", r) - } - }() result := fraction1.Mul(fraction2) expected := NewFraction(tt.expectedNumerator, tt.expectedDenominator) @@ -170,7 +153,7 @@ func TestFraction_Div(t *testing.T) { numerator1, denominator1 int64 numerator2, denominator2 int64 expectedNumerator, expectedDenominator int64 - expectedError bool + expectedPanic bool }{ {1, 2, 1, 3, 3, 2, false}, {-100, 10, 256, -10, 100, 256, false}, @@ -186,10 +169,8 @@ func TestFraction_Div(t *testing.T) { t.Run("", func(t *testing.T) { defer func() { if r := recover(); r != nil { - if tt.denominator2 == 0 { - if !tt.expectedError { - t.Fatalf("Div: unexpected panic: %v", r) - } + if !tt.expectedPanic { + t.Fatalf("Div: unexpected panic: %v", r) } } }() diff --git a/core/tokens/gnot.go b/core/tokens/gnot.go new file mode 100644 index 0000000..3663863 --- /dev/null +++ b/core/tokens/gnot.go @@ -0,0 +1,17 @@ +package tokens + +import "router/core/currency" + +type Gnot struct { + currency.NativeCurrency +} + +func NewGnot(chainId1 int64) *Gnot { + return &Gnot{ + NativeCurrency: currency.NativeCurrency{ + BaseCurrency: currency.BaseCurrency{ + ChainId: chainId1, + }, + }, + } +} diff --git a/poc/my_router.go b/poc/my_router.go new file mode 100644 index 0000000..4205533 --- /dev/null +++ b/poc/my_router.go @@ -0,0 +1,83 @@ +package poc + +import ( + "fmt" + "math" +) + +type MyRouter struct { + network map[string]*Pool +} + +func NewMyRouter(edges []*Pool) *MyRouter { + router := &MyRouter{ + network: make(map[string]*Pool), + } + + for _, edge := range edges { + router.network[edge.Address] = edge + } + + return router +} + +func (m *MyRouter) Swap(request SwapRequest) (SwapResult, error) { + // poolName은 from:to가 아니라 to:from일 수 있다. + poolName := request.FromToken + ":" + request.ToToken + + 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 + } + + return SwapResult{ + AmountIn: request.AmountIn, + AmountOut: math.Abs(exchangedAmount), + }, nil + } + + return SwapResult{}, fmt.Errorf("pool %s not found", poolName) +} + +func (m *MyRouter) getReserveOfTokenFromPool(fromTokenName string, toTokenName string, pool Pool) (float64, float64) { + if fromTokenName == pool.TokenA.Symbol { + return pool.ReserveA, pool.ReserveB + } + return pool.ReserveB, pool.ReserveA +} + +func (m *MyRouter) calculateAmountOfToToken(reserveFromToken, reserveToToken, amountIn float64, pool Pool) float64 { + X := reserveFromToken + Y := reserveToToken + dX := amountIn + + K := X * Y + L := math.Sqrt(K) + P := X / Y + + X_ := X + dX + P_ := (X_ / L) * (X_ / L) + + dY := L * (1/math.Sqrt(P_) - 1/math.Sqrt(P)) + + // 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 new file mode 100644 index 0000000..8b869ea --- /dev/null +++ b/poc/my_router_test.go @@ -0,0 +1,54 @@ +package poc + +import ( + "fmt" + "math" + "testing" +) + +func TestMyRouter(t *testing.T) { + tokens := map[string]Token{ + "a": Token{Symbol: "a"}, + "b": Token{Symbol: "b"}, + } + + tests := []struct { + edges []*Pool + requests []SwapRequest + results []SwapResult + }{ + { + []*Pool{ + {"a:b", tokens["a"], tokens["b"], 4000, 1000}}, + []SwapRequest{ + {"a", "b", 2000}}, + []SwapResult{ + {2000.0, 2000.0 / 6.0}, + }, + }, + } + + for _, test := range tests { + t.Run("", func(t *testing.T) { + router := NewMyRouter(test.edges) + + for i, request := range test.requests { + result, err := router.Swap(request) + if err != nil { + t.Fatalf("Swap Error: can't find pool: %v:%v", request.FromToken, request.ToToken) + } + + diff := math.Abs(result.AmountOut - test.results[i].AmountOut) + tolerance := 0.00000001 + if diff > tolerance { + t.Fatalf("Swap: Unexpected Token output number, expected: %v, got %v", test.results[i].AmountOut, result.AmountOut) + } + fmt.Println(result) + + for _, pool := range router.network { + fmt.Println(pool) + } + } + }) + } +} diff --git a/poc/pool.go b/poc/pool.go new file mode 100644 index 0000000..12c4707 --- /dev/null +++ b/poc/pool.go @@ -0,0 +1,16 @@ +package poc + +// Pool +// kind of edge +type Pool struct { + Address string + + TokenA Token // 효율을 위해서는 포인터로 가져오는게 좋을만 하다. + TokenB Token + + ReserveA float64 + ReserveB float64 + + // 거래 수수료 + //Fee uint32 +} diff --git a/poc/swap_request.go b/poc/swap_request.go new file mode 100644 index 0000000..af13e08 --- /dev/null +++ b/poc/swap_request.go @@ -0,0 +1,9 @@ +package poc + +type SwapRequest struct { + FromToken string + ToToken string + AmountIn float64 + //MinAmountOut int + //UserAddress string // option +} diff --git a/poc/swap_result.go b/poc/swap_result.go new file mode 100644 index 0000000..22bfb71 --- /dev/null +++ b/poc/swap_result.go @@ -0,0 +1,6 @@ +package poc + +type SwapResult struct { + AmountIn float64 + AmountOut float64 +} diff --git a/poc/token.go b/poc/token.go new file mode 100644 index 0000000..5c8ac1f --- /dev/null +++ b/poc/token.go @@ -0,0 +1,7 @@ +package poc + +type Token struct { + Address string + Symbol string + Decimals int +}