From 1523bd526169df05d60661f333169c2637adf20a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B9=80=EC=84=B1=EB=AF=BC?= <98kimsungmin@naver.com> Date: Tue, 27 Aug 2024 17:42:47 +0900 Subject: [PATCH] feat: Basic Fraction Entity Added (#2) * feat: fraction entity added * resolve dependency cycle change router/router -> router/core * fix * delete router/main.go * Apply suggestions from code review Co-authored-by: Lee ByeongJun --------- Co-authored-by: tolelom Co-authored-by: Lee ByeongJun --- {router => core}/alpha-router-params.go | 2 +- core/alpha-router.go | 64 ++++++++++ {router/core => core}/constants.go | 0 core/entities/baseCurrency.go | 29 +++++ {router/core => core}/entities/currency.go | 2 +- core/entities/fractions/currencyAmount.go | 16 +++ core/entities/fractions/fraction.go | 119 ++++++++++++++++++ core/entities/fractions/fraction_test.go | 39 ++++++ core/entities/gnot.go | 21 ++++ core/entities/nativeCurrency.go | 6 + core/entities/token.go | 13 ++ core/entities/utils/math.go | 8 ++ core/entities/wgnot.go | 10 ++ core/router.go | 14 +++ core/types.go | 13 ++ main.go | 11 -- router/alpha-router.go | 47 ------- .../core/entities/fractions/currencyAmount.go | 7 -- router/core/entities/token.go | 5 - router/router.go | 4 - 20 files changed, 354 insertions(+), 76 deletions(-) rename {router => core}/alpha-router-params.go (70%) create mode 100644 core/alpha-router.go rename {router/core => core}/constants.go (100%) create mode 100644 core/entities/baseCurrency.go rename {router/core => core}/entities/currency.go (93%) create mode 100644 core/entities/fractions/currencyAmount.go create mode 100644 core/entities/fractions/fraction.go create mode 100644 core/entities/fractions/fraction_test.go create mode 100644 core/entities/gnot.go create mode 100644 core/entities/nativeCurrency.go create mode 100644 core/entities/token.go create mode 100644 core/entities/utils/math.go create mode 100644 core/entities/wgnot.go create mode 100644 core/router.go create mode 100644 core/types.go delete mode 100644 main.go delete mode 100644 router/alpha-router.go delete mode 100644 router/core/entities/fractions/currencyAmount.go delete mode 100644 router/core/entities/token.go delete mode 100644 router/router.go diff --git a/router/alpha-router-params.go b/core/alpha-router-params.go similarity index 70% rename from router/alpha-router-params.go rename to core/alpha-router-params.go index 6c258dd..e9c8e8d 100644 --- a/router/alpha-router-params.go +++ b/core/alpha-router-params.go @@ -1,4 +1,4 @@ -package router +package core type AlphaRouterParams struct { } diff --git a/core/alpha-router.go b/core/alpha-router.go new file mode 100644 index 0000000..89b25ec --- /dev/null +++ b/core/alpha-router.go @@ -0,0 +1,64 @@ +package core + +import ( + "router/core/entities" + "router/core/entities/fractions" +) + +type AlphaRouter struct { + portionProvider IPortionProvider +} + +func NewAlphaRouter(params AlphaRouterParams) *AlphaRouter { + return &AlphaRouter{} +} + +// TODO: 원본 코드에서는 async 함수 +// 라우트 한 결과는 SwapRoute +func (a AlphaRouter) route( + amount fractions.CurrencyAmount, + quoteCurrency entities.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{} +} + +func (a AlphaRouter) determineCurrencyInOutFromTradeType( + tradeType TradeType, + amount fractions.CurrencyAmount, + quoteCurrency entities.Currency, +) (entities.Currency, entities.Currency) { + if tradeType == EXACT_INPUT { + return amount.Currency, quoteCurrency + } else { + return quoteCurrency, amount.Currency + } +} diff --git a/router/core/constants.go b/core/constants.go similarity index 100% rename from router/core/constants.go rename to core/constants.go diff --git a/core/entities/baseCurrency.go b/core/entities/baseCurrency.go new file mode 100644 index 0000000..ba333a4 --- /dev/null +++ b/core/entities/baseCurrency.go @@ -0,0 +1,29 @@ +package entities + +type BaseCurrency struct { + isNative bool + isToken bool + + chainId int + decimals int + + symbol string + name string + address string +} + +func NewBaseCurrency(chainId int, decimals int, symbol string, name string) *BaseCurrency { + return &BaseCurrency{ + // 아래 코드는 원문 + //invariant(Number.isSafeInteger(chainId), 'CHAIN_ID'); + //invariant( + // decimals >= 0 && decimals < 255 && Number.isInteger(decimals), + // 'DECIMALS', + //); + + chainId: chainId, + decimals: decimals, + symbol: symbol, + name: name, + } +} diff --git a/router/core/entities/currency.go b/core/entities/currency.go similarity index 93% rename from router/core/entities/currency.go rename to core/entities/currency.go index 30a6b32..bebeb9a 100644 --- a/router/core/entities/currency.go +++ b/core/entities/currency.go @@ -4,5 +4,5 @@ package entities // Token과 NativeCurrency 모두 BaseCurrency를 상속받는다. // 하지만 여기서의 Currency는 NativeCurrency와 Token에 포함된다. type Currency interface { - wrapped() Token + Wrapped() Token } diff --git a/core/entities/fractions/currencyAmount.go b/core/entities/fractions/currencyAmount.go new file mode 100644 index 0000000..4fe107a --- /dev/null +++ b/core/entities/fractions/currencyAmount.go @@ -0,0 +1,16 @@ +package fractions + +import ( + "router/core/entities" +) + +type CurrencyAmount struct { + Fraction + Currency entities.Currency +} + +//func (c CurrencyAmount) add(other CurrencyAmount) CurrencyAmount { +// added := c.Fraction.add(other) +// +// return CurrencyAmount.fromFractionAmount() +//} diff --git a/core/entities/fractions/fraction.go b/core/entities/fractions/fraction.go new file mode 100644 index 0000000..4a72349 --- /dev/null +++ b/core/entities/fractions/fraction.go @@ -0,0 +1,119 @@ +package fractions + +import ( + "errors" + "math/big" +) + +type Fraction struct { + Numerator *big.Int + Denominator *big.Int +} + +func NewFraction(numerator int64, denominator int64) *Fraction { + return &Fraction{ + Numerator: big.NewInt(numerator), + Denominator: big.NewInt(denominator), + } +} + +func tryParseFraction(value interface{}) (Fraction, error) { + switch v := value.(type) { + case Fraction: + return v, nil + case *big.Int: + return Fraction{Numerator: v, Denominator: big.NewInt(1)}, nil + case big.Int: + return Fraction{Numerator: &v, Denominator: big.NewInt(1)}, nil + // TODO: int64 등 추가 바람 + default: + return Fraction{}, errors.New("not a fraction") + } +} + +// --------------------------- +// TODO: 형을 자유롭게 받은 다음 tryParseFraction으로 한 번 거르고 사용하도록 하기 +// 덧셈 +func (f Fraction) Add(other *Fraction) *Fraction { + numerator1 := new(big.Int).Mul(f.Numerator, other.Denominator) + numerator2 := new(big.Int).Mul(other.Numerator, f.Denominator) + denominator := new(big.Int).Mul(f.Denominator, other.Denominator) + resultNumerator := new(big.Int).Add(numerator1, numerator2) + + // TODO: 약분 + //g := utils.Gcd(resultNumerator, denominator) + + return &Fraction{ + Numerator: resultNumerator, + Denominator: denominator, + } +} + +// 뺄셈 +// TODO: 덧셈 활용해서 코드 단순화 할 수도 있을지도... +func (f Fraction) Sub(other *Fraction) *Fraction { + numerator1 := new(big.Int).Mul(f.Numerator, other.Denominator) + numerator2 := new(big.Int).Mul(other.Numerator, f.Denominator) + denominator := new(big.Int).Mul(f.Denominator, other.Denominator) + resultNumerator := new(big.Int).Sub(numerator1, numerator2) + + // TODO: 약분 + //g := utils.Gcd(resultNumerator, denominator) + + return &Fraction{ + Numerator: resultNumerator, + Denominator: denominator, + } +} + +func (f Fraction) LessThan(other *Fraction) bool { + leftValue := new(big.Int).Mul(f.Numerator, other.Denominator) + rightValue := new(big.Int).Mul(other.Numerator, f.Denominator) + return leftValue.Cmp(rightValue) < 0 +} + +func (f Fraction) GreaterThan(other *Fraction) bool { + leftValue := new(big.Int).Mul(f.Numerator, other.Denominator) + rightValue := new(big.Int).Mul(other.Numerator, f.Denominator) + return leftValue.Cmp(rightValue) > 0 +} + +// 약분이 완료되었다는 가정 +// WARN: 약분이 안되었다는 가정이라면 약분하는 로직 추가해야 함 +func (f Fraction) Equals(other *Fraction) bool { + return f.Numerator.Cmp(other.Numerator) == 0 && + f.Denominator.Cmp(other.Denominator) == 0 +} + +// 몫 +func (f Fraction) Quotient() *big.Int { + return new(big.Int).Quo(f.Numerator, f.Denominator) +} + +// NOTE: 단순 나머지가 아니라 분수 형태로 표시한다 +// 좋은 형태의 함수는 아니라 생각 +func (f Fraction) Remainder() Fraction { + return Fraction{ + Numerator: new(big.Int).Rem(f.Numerator, f.Denominator), + Denominator: f.Denominator, + } +} + +// 역 +func (f Fraction) Invert() Fraction { + return Fraction{ + Numerator: f.Denominator, + Denominator: f.Numerator, + } +} + +//func (f Fraction) GreaterThan(other interface{}) (bool, error) { +// otherParsed, err := tryParseFraction(other) +// if err != nil { +// return false, err +// } +//left := f.Numerator * otherParsed.Denominator +//right := otherParsed.Denominator * f.Numerator +// +// return left > right, nil +//} diff --git a/core/entities/fractions/fraction_test.go b/core/entities/fractions/fraction_test.go new file mode 100644 index 0000000..6a1ab85 --- /dev/null +++ b/core/entities/fractions/fraction_test.go @@ -0,0 +1,39 @@ +package fractions + +import ( + "testing" +) + +func TestFraction_Add(t *testing.T) { + fraction1 := NewFraction(1, 2) + fraction2 := NewFraction(1, 3) + + result := fraction1.Add(fraction2) + + expected := NewFraction(5, 6) + + if result.Numerator.Cmp(expected.Numerator) != 0 || result.Denominator.Cmp(expected.Denominator) != 0 { + t.Fatalf("Add: expected %v, got %v", expected, result) + } +} + +func TestFraction_Sub(t *testing.T) { + fraction1 := NewFraction(1, 2) + fraction2 := NewFraction(1, 3) + result := fraction1.Sub(fraction2) + expected := NewFraction(1, 6) + if result.Numerator.Cmp(expected.Numerator) != 0 || result.Denominator.Cmp(expected.Denominator) != 0 { + t.Fatalf("Sub: expected %v, got %v", expected, result) + } +} + +func TestFraction_LessThan(t *testing.T) { + fraction1 := NewFraction(1, 2) + fraction2 := NewFraction(1, 3) + result := fraction1.LessThan(fraction2) + expected := false + + if result != expected { + t.Fatalf("LessThan: expected %v, got %v", expected, result) + } +} diff --git a/core/entities/gnot.go b/core/entities/gnot.go new file mode 100644 index 0000000..2db7dbb --- /dev/null +++ b/core/entities/gnot.go @@ -0,0 +1,21 @@ +package entities + +type Gnot struct { + NativeCurrency +} + +func NewGnot(chainId int) *Gnot { + return &Gnot{ + NativeCurrency: NativeCurrency{ + BaseCurrency: BaseCurrency{ + chainId: chainId, + }, + }, + } +} + +func (g Gnot) Wrapped() Token { + wgnot := WGNOT[g.chainId] + // invariant(!!wgnot, 'WRAPPED') + return wgnot +} diff --git a/core/entities/nativeCurrency.go b/core/entities/nativeCurrency.go new file mode 100644 index 0000000..aa2862d --- /dev/null +++ b/core/entities/nativeCurrency.go @@ -0,0 +1,6 @@ +package entities + +// Gnot이 NativeCurrency를 상속한다. +type NativeCurrency struct { + BaseCurrency +} diff --git a/core/entities/token.go b/core/entities/token.go new file mode 100644 index 0000000..af61500 --- /dev/null +++ b/core/entities/token.go @@ -0,0 +1,13 @@ +package entities + +type Token struct { + BaseCurrency +} + +func NewToken() *Token { + return &Token{} +} + +func (t Token) Wrapped() Token { + return t +} diff --git a/core/entities/utils/math.go b/core/entities/utils/math.go new file mode 100644 index 0000000..0b6bd44 --- /dev/null +++ b/core/entities/utils/math.go @@ -0,0 +1,8 @@ +package utils + +import "math/big" + +func Gcd(a, b *big.Int) *big.Int { + + return a +} diff --git a/core/entities/wgnot.go b/core/entities/wgnot.go new file mode 100644 index 0000000..2740d5c --- /dev/null +++ b/core/entities/wgnot.go @@ -0,0 +1,10 @@ +package entities + +var WGNOT = map[int]Token{ + //1: NewToken(), + //2: NewToken(), + //3: NewToken(), + //4: NewToken(), + //5: NewToken(), + //99: NewToken(), +} diff --git a/core/router.go b/core/router.go new file mode 100644 index 0000000..3ff9580 --- /dev/null +++ b/core/router.go @@ -0,0 +1,14 @@ +package core + +type SwapRoute struct { +} + +type IRouter interface { +} + +// TODO: 원문: type SwapOptions = SwapOptionsUniversalRouter | SwapOptionsSwapRouter02 +type SwapOptions struct { +} + +type SwapOptionsUniversalRouter struct { +} diff --git a/core/types.go b/core/types.go new file mode 100644 index 0000000..f80a40a --- /dev/null +++ b/core/types.go @@ -0,0 +1,13 @@ +package core + +import ( + "router/core/entities/fractions" +) + +// interface는 I 접두사를 붙이는 것이 관행인가? +type IPortionProvider interface { + GetPortionAmount(tokenOutAmount fractions.CurrencyAmount, tradeType TradeType, swapConfig SwapOptions) fractions.CurrencyAmount +} + +type PortionProvider struct { +} diff --git a/main.go b/main.go deleted file mode 100644 index 725c460..0000000 --- a/main.go +++ /dev/null @@ -1,11 +0,0 @@ -package main - -import ( - "fmt" -) - -func main() { - s := "gopher" - fmt.Println("Hello and welcome, %s!", s) - -} diff --git a/router/alpha-router.go b/router/alpha-router.go deleted file mode 100644 index a388b5c..0000000 --- a/router/alpha-router.go +++ /dev/null @@ -1,47 +0,0 @@ -package router - -import ( - "router/router/core" - "router/router/core/entities" - "router/router/core/entities/fractions" -) - -type AlphaRouter struct { -} - -func NewAlphaRouter(params AlphaRouterParams) *AlphaRouter { - return &AlphaRouter{} -} - -// TODO: 원본 코드에서는 async 함수 -// 라우트 한 결과는 SwapRoute -func (a AlphaRouter) route( - amount fractions.CurrencyAmount, - quoteCurrency entities.Currency, - tradeType core.TradeType, -) SwapRoute { - originalAmount := amount - - currencyIn, currencyOut := a.determineCurrencyInOutFromTradeType(tradeType, amount, quoteCurrency) - - // currencyIn, currencyOut은 Currency 타입이고 - // Currency 타입은 NativeCurrency이거나 Token 타입이다. - // 아래에서 Token 타입이길 원하는 듯하다. - tokenIn := currencyIn - tokenOut := currencyOut - - swapRoute := SwapRoute{} - return swapRoute -} - -func (a AlphaRouter) determineCurrencyInOutFromTradeType( - tradeType core.TradeType, - amount fractions.CurrencyAmount, - quoteCurrency entities.Currency, -) (entities.Currency, entities.Currency) { - if tradeType == core.EXACT_INPUT { - return amount.Currency, quoteCurrency - } else { - return quoteCurrency, amount.Currency - } -} diff --git a/router/core/entities/fractions/currencyAmount.go b/router/core/entities/fractions/currencyAmount.go deleted file mode 100644 index 2fafe19..0000000 --- a/router/core/entities/fractions/currencyAmount.go +++ /dev/null @@ -1,7 +0,0 @@ -package fractions - -import "router/router/core/entities" - -type CurrencyAmount struct { - Currency entities.Currency -} diff --git a/router/core/entities/token.go b/router/core/entities/token.go deleted file mode 100644 index cc76145..0000000 --- a/router/core/entities/token.go +++ /dev/null @@ -1,5 +0,0 @@ -package entities - -type Token struct { - Currency -} diff --git a/router/router.go b/router/router.go deleted file mode 100644 index 70026b2..0000000 --- a/router/router.go +++ /dev/null @@ -1,4 +0,0 @@ -package router - -type SwapRoute struct { -}