From 1fa681a1b4bf313dc5b107849f2bf002a0798be8 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Wed, 25 Oct 2023 19:10:40 +0900 Subject: [PATCH 01/35] fix: Collect() --- pool/_TEST_pool_multi_token_test.gno | 95 +++++++++++++++++++++++++++- pool/pool.gno | 6 +- 2 files changed, 96 insertions(+), 5 deletions(-) diff --git a/pool/_TEST_pool_multi_token_test.gno b/pool/_TEST_pool_multi_token_test.gno index 482f7be5..5c5303e9 100644 --- a/pool/_TEST_pool_multi_token_test.gno +++ b/pool/_TEST_pool_multi_token_test.gno @@ -152,11 +152,102 @@ func TestSwapBarBazBarToBaz(t *testing.T) { shouldEQ(t, oldPoolBazBalance-newPoolBazBalance, bigint(43457)) } -// 8. Burn Foo:Bar Liquidity by lp01 +// 8. Collect Foo:Bar Fees by lp01 +func TestCollectFooBarFees(t *testing.T) { + std.TestSetOrigCaller(lp01) + std.TestSetPrevRealm("gno.land/r/position") + + oldLp01FooBalance := balanceOfByRegisterCall(fooPath, lp01) + oldLp01BarBalance := balanceOfByRegisterCall(barPath, lp01) + oldPoolFooBalance := balanceOfByRegisterCall(fooPath, poolAddr) + oldPoolBarBalance := balanceOfByRegisterCall(barPath, poolAddr) + + // burn 0 to collect swap fees + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, 0) + + c0, c1 := Collect( + fooPath, + barPath, + pFee, + lp01, + test_tickLower, + test_tickUpper, + 100_000, + 100_000, + ) + + shouldNEQ(t, c0, bigint(0)) // swap was foo > bar, so only foo has fees + shouldEQ(t, c1, bigint(0)) // swap was foo > bar, so bar has no fees + + newLp01FooBalance := balanceOfByRegisterCall(fooPath, lp01) + newLp01BarBalance := balanceOfByRegisterCall(barPath, lp01) + newPoolFooBalance := balanceOfByRegisterCall(fooPath, poolAddr) + newPoolBarBalance := balanceOfByRegisterCall(barPath, poolAddr) + + shouldEQ(t, newLp01FooBalance-oldLp01FooBalance, uint64(c0)) + shouldEQ(t, newLp01BarBalance-oldLp01BarBalance, uint64(c1)) + shouldEQ(t, oldPoolFooBalance-newPoolFooBalance, uint64(c0)) + shouldEQ(t, oldPoolBarBalance-newPoolBarBalance, uint64(c1)) +} + +// 9. Burn Foo:Bar Liquidity by lp01 func TestBurnFooBarLiquidity(t *testing.T) { std.TestSetOrigCaller(lp01) + std.TestSetPrevRealm("gno.land/r/position") + + pool := GetPool(fooPath, barPath, pFee) + poolOldLiquidity := pool.GetLiquidity() + + b0, b1 := Burn( + fooPath, + barPath, + pFee, + test_tickLower, + test_tickUpper, + test_liquidityExpect, + ) + + shouldNEQ(t, b0, bigint(0)) + shouldNEQ(t, b1, bigint(0)) + + poolNewLiquidity := pool.GetLiquidity() + + shouldEQ(t, poolOldLiquidity-poolNewLiquidity, test_liquidityExpect) +} + +// 10. Collect Foo:Bar burned Liquidity by lp01 +func TestCollectFooBarLiquidity(t *testing.T) { + std.TestSetOrigCaller(lp01) + std.TestSetPrevRealm("gno.land/r/position") + + oldLp01FooBalance := balanceOfByRegisterCall(fooPath, lp01) + oldLp01BarBalance := balanceOfByRegisterCall(barPath, lp01) + oldPoolFooBalance := balanceOfByRegisterCall(fooPath, poolAddr) + oldPoolBarBalance := balanceOfByRegisterCall(barPath, poolAddr) + + c0, c1 := Collect( + fooPath, + barPath, + pFee, + lp01, + test_tickLower, + test_tickUpper, + 100_000, + 100_000, + ) + + shouldNEQ(t, c0, bigint(0)) + shouldNEQ(t, c1, bigint(0)) + + newLp01FooBalance := balanceOfByRegisterCall(fooPath, lp01) + newLp01BarBalance := balanceOfByRegisterCall(barPath, lp01) + newPoolFooBalance := balanceOfByRegisterCall(fooPath, poolAddr) + newPoolBarBalance := balanceOfByRegisterCall(barPath, poolAddr) - + shouldEQ(t, newLp01FooBalance-oldLp01FooBalance, uint64(c0)) + shouldEQ(t, newLp01BarBalance-oldLp01BarBalance, uint64(c1)) + shouldEQ(t, oldPoolFooBalance-newPoolFooBalance, uint64(c0)) + shouldEQ(t, oldPoolBarBalance-newPoolBarBalance, uint64(c1)) } /* HELPER */ diff --git a/pool/pool.gno b/pool/pool.gno index c596c99e..c01b8eef 100644 --- a/pool/pool.gno +++ b/pool/pool.gno @@ -125,7 +125,7 @@ func Burn( return amount0, amount1 } -// // only position contract can call this function +// only position contract can call this function func Collect( pToken0Path string, pToken1Path string, @@ -154,10 +154,10 @@ func Collect( requireUnsigned(amount1, ufmt.Sprintf("[POOL] pool.gno__Collect() || amount1(%s) >= 0", amount1)) require(pool.balances.token0 >= amount0, ufmt.Sprintf("[POOL] pool.gno__Collect() || pool.balances.token0(%s) >= amount0(%s)", pool.balances.token0, amount0)) - transferFromByRegisterCall(pToken0Path, GetOrigPkgAddr(), recipient, uint64(amount0)) + transferByRegisterCall(pToken0Path, recipient, uint64(amount0)) require(pool.balances.token1 >= amount1, ufmt.Sprintf("[POOL] pool.gno__Collect() || pool.balances.token1(%s) >= amount1(%s)", pool.balances.token1, amount1)) - transferFromByRegisterCall(pToken1Path, GetOrigPkgAddr(), recipient, uint64(amount1)) + transferByRegisterCall(pToken1Path, recipient, uint64(amount1)) // adjust position position.tokensOwed0 -= amount0 From 8bc90a67719a2c2655753d49ffc3e77b88e71a47 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Thu, 26 Oct 2023 13:16:37 +0900 Subject: [PATCH 02/35] fix: CollectProtocol --- pool/pool.gno | 68 +++++++++++++++++++++++++++------------------------ 1 file changed, 36 insertions(+), 32 deletions(-) diff --git a/pool/pool.gno b/pool/pool.gno index c01b8eef..5940ccff 100644 --- a/pool/pool.gno +++ b/pool/pool.gno @@ -469,38 +469,42 @@ func SetFeeProtocol( g.SetGovParameter("protocoL_fees", feeProtocol0+(feeProtocol1<<4)) } -// // ADMIN -// func CollectProtocol( -// pToken0 string, -// pToken1 string, -// pFee uint16, -// recipient std.Address, -// amount0Requested bigint, -// amount1Requested bigint, -// ) (bigint, bigint) { -// requireUnsigned(amount0Requested, ufmt.Sprintf("[POOL] pool.gno__CollectProtocol() || amount0Requested(%s) >= 0", amount0Requested)) -// requireUnsigned(amount1Requested, ufmt.Sprintf("[POOL] pool.gno__CollectProtocol() || amount1Requested(%s) >= 0", amount1Requested)) -// require(isAdmin(PrevRealmAddr()), ufmt.Sprintf("[POOL] pool.gno__CollectProtocol() || caller(%s) must be admin", PrevRealmAddr())) - -// pool := GetPool(pToken0, pToken1, pFee) - -// amount0 := min(amount0Requested, pool.protocolFees.token0) -// requireUnsigned(amount0, ufmt.Sprintf("[POOL] pool.gno__CollectProtocol() || amount0(%s) >= 0", amount0)) - -// amount1 := min(amount1Requested, pool.protocolFees.token1) -// requireUnsigned(amount1, ufmt.Sprintf("[POOL] pool.gno__CollectProtocol() || amount1(%s) >= 0", amount1)) - -// // without procotol fee -// amount0, amount1 = pool.saveProtocolFees(amount0, amount1) - -// // pool.token0.Transfer(a2u(recipient), uint64(amount0)) -// foo.Transfer(a2u(recipient), uint64(amount0)) - -// // pool.token1.Transfer(a2u(recipient), uint64(amount1)) -// bar.Transfer(a2u(recipient), uint64(amount1)) - -// return amount0, amount1 -// } +// ADMIN +func CollectProtocol( + pToken0Path string, + pToken1Path string, + pFee uint16, + recipient std.Address, + amount0Requested bigint, + amount1Requested bigint, +) (bigint, bigint) { + requireUnsigned(amount0Requested, ufmt.Sprintf("[POOL] pool.gno__CollectProtocol() || amount0Requested(%s) >= 0", amount0Requested)) + requireUnsigned(amount1Requested, ufmt.Sprintf("[POOL] pool.gno__CollectProtocol() || amount1Requested(%s) >= 0", amount1Requested)) + require(isAdmin(PrevRealmAddr()), ufmt.Sprintf("[POOL] pool.gno__CollectProtocol() || caller(%s) must be admin", PrevRealmAddr())) + + pool := GetPool(pToken0Path, pToken1Path, pFee) + + amount0 := min(amount0Requested, pool.protocolFees.token0) + requireUnsigned(amount0, ufmt.Sprintf("[POOL] pool.gno__CollectProtocol() || amount0(%s) >= 0", amount0)) + + amount1 := min(amount1Requested, pool.protocolFees.token1) + requireUnsigned(amount1, ufmt.Sprintf("[POOL] pool.gno__CollectProtocol() || amount1(%s) >= 0", amount1)) + + // without procotol fee + amount0, amount1 = pool.saveProtocolFees(amount0, amount1) + + ok := transferByRegisterCall(pToken0Path, recipient, uint64(amount0)) + if !ok { + panic("[POOL] pool.gno__CollectProtocol() || transferByRegisterCall(pToken0Path, recipient, uint64(amount0)) failed") + } + + ok = transferByRegisterCall(pToken1Path, recipient, uint64(amount1)) + if !ok { + panic("[POOL] pool.gno__CollectProtocol() || transferByRegisterCall(pToken1Path, recipient, uint64(amount1)) failed") + } + + return amount0, amount1 +} func (pool *Pool) modifyPosition(params ModifyPositionParams) (PositionInfo, bigint, bigint) { position := pool.updatePosition( From dccce648b0de1b5769d7f7e9597430fe58e14687 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Thu, 26 Oct 2023 13:17:15 +0900 Subject: [PATCH 03/35] test: multi-lp prepare --- _setup/bar/bar.gno | 9 ++------- _setup/baz/baz.gno | 11 +++-------- _setup/foo/foo.gno | 9 ++------- 3 files changed, 7 insertions(+), 22 deletions(-) diff --git a/_setup/bar/bar.gno b/_setup/bar/bar.gno index 42bad433..d38d9d1c 100644 --- a/_setup/bar/bar.gno +++ b/_setup/bar/bar.gno @@ -37,15 +37,10 @@ func init() { bar.Mint(tr01, 50000000) bar.Approve(lp01, poolAddr, 50000000) - // bar.Approve(lp02, poolAddr, 50000000) - // bar.Approve(lp03, poolAddr, 50000000) + bar.Approve(lp02, poolAddr, 50000000) + bar.Approve(lp03, poolAddr, 50000000) bar.Approve(tr01, poolAddr, 50000000) - // bar.Approve(lp01, lp01, 50000000) - // bar.Approve(lp02, lp02, 50000000) - // bar.Approve(lp03, lp03, 50000000) - // bar.Approve(tr01, tr01, 50000000) - // bar.Approve(poolAddr, registryAddr, 50000000) } diff --git a/_setup/baz/baz.gno b/_setup/baz/baz.gno index 46b56794..2b52206b 100644 --- a/_setup/baz/baz.gno +++ b/_setup/baz/baz.gno @@ -37,16 +37,11 @@ func init() { baz.Mint(tr01, 50000000) baz.Approve(lp01, poolAddr, 50000000) - // baz.Approve(lp02, poolAddr, 50000000) - // baz.Approve(lp03, poolAddr, 50000000) + baz.Approve(lp02, poolAddr, 50000000) + baz.Approve(lp03, poolAddr, 50000000) baz.Approve(tr01, poolAddr, 50000000) - // baz.Approve(lp01, lp01, 50000000) - // baz.Approve(lp02, lp02, 50000000) - // baz.Approve(lp03, lp03, 50000000) - // baz.Approve(tr01, tr01, 50000000) - - // baz.Approve(posAddr, poolAddr, 50000000) + baz.Approve(posAddr, poolAddr, 50000000) } // method proxies as public functions. diff --git a/_setup/foo/foo.gno b/_setup/foo/foo.gno index 4a9aa981..066ba7ba 100644 --- a/_setup/foo/foo.gno +++ b/_setup/foo/foo.gno @@ -38,15 +38,10 @@ func init() { foo.Mint(tr01, 50000000) foo.Approve(lp01, poolAddr, 50000000) - // foo.Approve(lp02, poolAddr, 50000000) - // foo.Approve(lp03, poolAddr, 50000000) + foo.Approve(lp02, poolAddr, 50000000) + foo.Approve(lp03, poolAddr, 50000000) foo.Approve(tr01, poolAddr, 50000000) - // foo.Approve(lp01, lp01, 50000000) - // foo.Approve(lp02, lp02, 50000000) - // foo.Approve(lp03, lp03, 50000000) - // foo.Approve(tr01, tr01, 50000000) - // foo.Approve(posAddr, poolAddr, 50000000) } From 44ae31949df4d5b5fd587b5ac5ea2ed9f8d2b96b Mon Sep 17 00:00:00 2001 From: n3wbie Date: Thu, 26 Oct 2023 13:17:31 +0900 Subject: [PATCH 04/35] fix: logic test cases --- pool/_TEST_math_logic_test.gnoa | 30 +- ...a => _TEST_pool_multi_lp_fee_api_test.gno} | 64 +--- pool/_TEST_pool_multi_lp_fee_test.gnoa | 101 ++--- pool/_TEST_pool_multi_lp_test.gnoa | 331 ++++++++-------- ...t.gno => _TEST_pool_multi_token_test.gnoa} | 125 +++++- ...est.gnoa => _TEST_pool_router_test.gnoXXX} | 0 ...ST_pool_same_token_pair_diff_fee_test.gnoa | 359 +++++++++--------- pool/_TEST_pool_single_lp_test.gnoa | 267 +++++++------ 8 files changed, 649 insertions(+), 628 deletions(-) rename pool/{_TEST_pool_multi_lp_fee_api_test.gnoa => _TEST_pool_multi_lp_fee_api_test.gno} (69%) rename pool/{_TEST_pool_multi_token_test.gno => _TEST_pool_multi_token_test.gnoa} (68%) rename pool/{_TEST_pool_router_test.gnoa => _TEST_pool_router_test.gnoXXX} (100%) diff --git a/pool/_TEST_math_logic_test.gnoa b/pool/_TEST_math_logic_test.gnoa index 7ea61415..c4ecd645 100644 --- a/pool/_TEST_math_logic_test.gnoa +++ b/pool/_TEST_math_logic_test.gnoa @@ -13,8 +13,8 @@ var ( gsa = testutils.TestAddress("gsa") lp01 = testutils.TestAddress("lp01") - pToken0 = "foo" - pToken1 = "bar" + fooPath = "gno.land/r/foo" // token1 + barPath = "gno.land/r/bar" // token2 pFee uint16 = 500 sqrtPrice bigint = 130621891405341611593710811006 @@ -27,7 +27,7 @@ var ( func init() { std.TestSetOrigCaller(gsa) InitManual() - CreatePool(pToken0, pToken1, pFee, sqrtPrice) + CreatePool(fooPath, barPath, pFee, sqrtPrice) } func TestGetSqrtRatioFromTick(t *testing.T) { @@ -44,19 +44,19 @@ func TestDrySwap_ZeroForOneTrue_AmountSpecified_Positive_16000(t *testing.T) { std.TestSetOrigCaller(lp01) // no mint == no liquidity => swap will fail - shouldPanic(t, func() { DrySwap(pToken0, pToken1, pFee, "_", true, 16000, MIN_PRICE) }) + shouldPanic(t, func() { DrySwap(fooPath, barPath, pFee, "_", true, 16000, MIN_PRICE) }) // not enough mint == swap will fail - pos.Mint(pToken0, pToken1, pFee, tickLower, tickUpper, 1000, 1000, 0, 0, 9999999999) - shouldPanic(t, func() { DrySwap(pToken0, pToken1, pFee, "_", true, 16000, MIN_PRICE) }) + pos.Mint(fooPath, barPath, pFee, tickLower, tickUpper, 1000, 1000, 0, 0, 9999999999) + shouldPanic(t, func() { DrySwap(fooPath, barPath, pFee, "_", true, 16000, MIN_PRICE) }) - pos.Mint(pToken0, pToken1, pFee, tickLower, tickUpper, 100000, 100000, 0, 0, 9999999999) + pos.Mint(fooPath, barPath, pFee, tickLower, tickUpper, 100000, 100000, 0, 0, 9999999999) // zeroForOne true // amountSpecified 16000 input, output := DrySwap( - pToken0, // pToken0 - pToken1, // pToken1 + fooPath, // fooPath + barPath, // barPath pFee, // pFee "_", // recipient true, // zeroForOne @@ -72,8 +72,8 @@ func TestDrySwap_ZeroForOneTrue_AmountSpecified_Negative_16000(t *testing.T) { // amountSpecified -16000 input, output := DrySwap( - pToken0, // pToken0 - pToken1, // pToken1 + fooPath, // fooPath + barPath, // barPath pFee, // pFee "_", // recipient true, // zeroForOne @@ -90,8 +90,8 @@ func TestDrySwap_ZeroForOneFalse_AmountSpecified_Positive_16000(t *testing.T) { // amountSpecified 16000 input, output := DrySwap( - pToken0, // pToken0 - pToken1, // pToken1 + fooPath, // fooPath + barPath, // barPath pFee, // pFee "_", // recipient false, // zeroForOne @@ -107,8 +107,8 @@ func TestDrySwap_ZeroForOneFalse_AmountSpecified_Negative_16000(t *testing.T) { // amountSpecified -16000 input, output := DrySwap( - pToken0, // pToken0 - pToken1, // pToken1 + fooPath, // fooPath + barPath, // barPath pFee, // pFee "_", // recipient false, // zeroForOne diff --git a/pool/_TEST_pool_multi_lp_fee_api_test.gnoa b/pool/_TEST_pool_multi_lp_fee_api_test.gno similarity index 69% rename from pool/_TEST_pool_multi_lp_fee_api_test.gnoa rename to pool/_TEST_pool_multi_lp_fee_api_test.gno index aa3df056..4a060b2e 100644 --- a/pool/_TEST_pool_multi_lp_fee_api_test.gnoa +++ b/pool/_TEST_pool_multi_lp_fee_api_test.gno @@ -6,10 +6,8 @@ import ( "testing" "gno.land/p/demo/testutils" - "gno.land/r/demo/users" - bar "gno.land/r/bar" - foo "gno.land/r/foo" + _ "gno.land/r/grc20_wrapper" g "gno.land/r/gov" ) @@ -19,13 +17,14 @@ var ( lp01 = testutils.TestAddress("lp01") // Liquidity Provider 01 tr01 = testutils.TestAddress("tr01") // Trader 01 - posAddr = std.DerivePkgAddr("gno.land/r/position") + poolAddr = std.DerivePkgAddr("gno.land/r/pool") + posAddr = std.DerivePkgAddr("gno.land/r/position") ) var ( // Common - pToken0 = "foo" - pToken1 = "bar" + fooPath = "gno.land/r/foo" // token1 + barPath = "gno.land/r/bar" // token2 pFee = uint16(500) test_tickLower = int32(9000) @@ -42,7 +41,7 @@ func TestCreatePool(t *testing.T) { jsonStr := gjson.Parse(ApiGetPools()) shouldEQ(t, len(jsonStr.Get("response.data").Array()), 0) - CreatePool("foo", "bar", pFee, 130622891405341611593710811006) + CreatePool(fooPath, barPath, pFee, 130622891405341611593710811006) std.TestSkipHeights(1) // 130621891405341611593710811006 // 9999 // 130622891405341611593710811006 // 10000 @@ -52,48 +51,33 @@ func TestCreatePool(t *testing.T) { // 10000 = 1% // USv3 default jsonStr = gjson.Parse(ApiGetPools()) shouldEQ(t, len(jsonStr.Get("response.data").Array()), 1) - shouldEQ(t, jsonStr.Get("response.data").Array()[0].String(), "bar_foo_500") + shouldEQ(t, jsonStr.Get("response.data").Array()[0].String(), "gno.land/r/bar:gno.land/r/foo:500") - jsonStr = gjson.Parse(ApiGetPool("bar_foo_500")) + jsonStr = gjson.Parse(ApiGetPool("gno.land/r/bar:gno.land/r/foo:500")) shouldEQ(t, jsonStr.Get("response.data.liquidity").Int(), 0) shouldEQ(t, len(jsonStr.Get("response.data.positions").Array()), 0) // sqrtPrice // 130621891405341611593710811006 // tick = 10000 - shouldPanic(t, func() { CreatePool("foo", "bar", 500, 130621891405341611593710811006) }) + shouldPanic(t, func() { CreatePool(fooPath, barPath, 500, 130621891405341611593710811006) }) std.TestSkipHeights(1) - - // Approve - std.TestSetOrigCaller(lp01) - foo.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - bar.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - foo.Approve(users.AddressOrName(lp01), 50000000) - bar.Approve(users.AddressOrName(lp01), 50000000) - std.TestSkipHeights(4) - - std.TestSetOrigCaller(tr01) - foo.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - bar.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - foo.Approve(users.AddressOrName(tr01), 50000000) - bar.Approve(users.AddressOrName(tr01), 50000000) - std.TestSkipHeights(4) } // Swap by tr01, Mint by lp01 ~ 02 func TestSwap(t *testing.T) { - pool := GetPool(pToken0, pToken1, pFee) + pool := GetPool(fooPath, barPath, pFee) // lp01 mint 9000 ~ 11000 tick range std.TestSetPrevRealm("gno.land/r/position") std.TestSetOrigCaller(lp01) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*100000) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*100000) std.TestSkipHeights(1) jsonStr := gjson.Parse(ApiGetPools()) shouldEQ(t, len(jsonStr.Get("response.data").Array()), 1) - shouldEQ(t, jsonStr.Get("response.data").Array()[0].String(), "bar_foo_500") + shouldEQ(t, jsonStr.Get("response.data").Array()[0].String(), "gno.land/r/bar:gno.land/r/foo:500") - jsonStr = gjson.Parse(ApiGetPool("bar_foo_500")) + jsonStr = gjson.Parse(ApiGetPool("gno.land/r/bar:gno.land/r/foo:500")) shouldEQ(t, jsonStr.Get("response.data.token0_balance").Int(), 2957550) shouldEQ(t, jsonStr.Get("response.data.token1_balance").Int(), 8041577) shouldEQ(t, jsonStr.Get("response.data.liquidity").Int(), 100000000) @@ -105,17 +89,17 @@ func TestSwap(t *testing.T) { // SWAP std.TestSetOrigCaller(tr01) - Swap(pToken0, pToken1, pFee, tr01, true, bigint(150000), test_price_01) - jsonStr = gjson.Parse(ApiGetPool("bar_foo_500")) + Swap(fooPath, barPath, pFee, tr01, true, bigint(150000), test_price_01) + jsonStr = gjson.Parse(ApiGetPool("gno.land/r/bar:gno.land/r/foo:500")) shouldEQ(t, jsonStr.Get("response.data.token0_balance").Int(), 3107550) shouldEQ(t, jsonStr.Get("response.data.token1_balance").Int(), 7635058) shouldEQ(t, jsonStr.Get("response.data.liquidity").Int(), 100000000) shouldEQ(t, len(jsonStr.Get("response.data.positions").Array()), 1) - Swap(pToken0, pToken1, pFee, tr01, false, bigint(601851), test_price_10) - // Swap(pToken0, pToken1, pFee, tr01, true, bigint(1500000), test_price_01) // two iteration // s0: 1_500_000 // s1: -3_626_984 // currentTick: 7668 + Swap(fooPath, barPath, pFee, tr01, false, bigint(601851), test_price_10) + // Swap(fooPath, barPath, pFee, tr01, true, bigint(1500000), test_price_01) // two iteration // s0: 1_500_000 // s1: -3_626_984 // currentTick: 7668 std.TestSkipHeights(1) - jsonStr = gjson.Parse(ApiGetPool("bar_foo_500")) + jsonStr = gjson.Parse(ApiGetPool("gno.land/r/bar:gno.land/r/foo:500")) shouldEQ(t, jsonStr.Get("response.data.token0_balance").Int(), 1496418) shouldEQ(t, jsonStr.Get("response.data.token1_balance").Int(), 8236909) shouldEQ(t, jsonStr.Get("response.data.liquidity").Int(), 100000000) @@ -124,13 +108,13 @@ func TestSwap(t *testing.T) { // To collect fee without removing liquidity // burn 0 => collect std.TestSetOrigCaller(lp01) - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, 0) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, 0) std.TestSkipHeights(1) std.TestSetOrigCaller(lp01) - Collect(pToken0, pToken1, pFee, lp01, test_tickLower, test_tickUpper, 100000000, 100000000) + Collect(fooPath, barPath, pFee, lp01, test_tickLower, test_tickUpper, 100000000, 100000000) std.TestSkipHeights(1) - jsonStr = gjson.Parse(ApiGetPool("bar_foo_500")) + jsonStr = gjson.Parse(ApiGetPool("gno.land/r/bar:gno.land/r/foo:500")) shouldEQ(t, jsonStr.Get("response.data.token0_balance").Int(), 1496344) shouldEQ(t, jsonStr.Get("response.data.token1_balance").Int(), 8236609) shouldEQ(t, jsonStr.Get("response.data.liquidity").Int(), 100000000) @@ -161,12 +145,6 @@ func shouldEQ(t *testing.T, got, expected interface{}) { } } -func shouldNEQ(t *testing.T, got, expected interface{}) { - if got == expected { - t.Errorf("got %v, didn't expected %v", got, expected) - } -} - func shouldPanic(t *testing.T, f func()) { defer func() { if r := recover(); r == nil { diff --git a/pool/_TEST_pool_multi_lp_fee_test.gnoa b/pool/_TEST_pool_multi_lp_fee_test.gnoa index d7ff9ed0..ffdee8e1 100644 --- a/pool/_TEST_pool_multi_lp_fee_test.gnoa +++ b/pool/_TEST_pool_multi_lp_fee_test.gnoa @@ -6,10 +6,8 @@ import ( "testing" "gno.land/p/demo/testutils" - "gno.land/r/demo/users" - bar "gno.land/r/bar" - foo "gno.land/r/foo" + _ "gno.land/r/grc20_wrapper" ) var ( @@ -20,13 +18,14 @@ var ( tr01 = testutils.TestAddress("tr01") // Trader 01 - posAddr = std.DerivePkgAddr("gno.land/r/position") + poolAddr = std.DerivePkgAddr("gno.land/r/pool") + posAddr = std.DerivePkgAddr("gno.land/r/position") ) var ( // Common - pToken0 = "foo" - pToken1 = "bar" + fooPath = "gno.land/r/foo" // token1 + barPath = "gno.land/r/bar" // token2 pFee = uint16(500) test_tickLower = int32(9000) @@ -38,7 +37,7 @@ var ( func TestFactoryCreatePool(t *testing.T) { std.TestSetOrigCaller(gsa) InitManual() - CreatePool("foo", "bar", pFee, 130622891405341611593710811006) + CreatePool(fooPath, barPath, pFee, 130622891405341611593710811006) // 130621891405341611593710811006 // 9999 // 130622891405341611593710811006 // 10000 @@ -49,36 +48,17 @@ func TestFactoryCreatePool(t *testing.T) { // sqrtPrice // 130621891405341611593710811006 // tick = 10000 - shouldPanic(t, func() { CreatePool("foo", "bar", 500, 130621891405341611593710811006) }) - - // Approve - std.TestSetOrigCaller(lp01) - foo.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - bar.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - foo.Approve(users.AddressOrName(lp01), 50000000) - bar.Approve(users.AddressOrName(lp01), 50000000) - - std.TestSetOrigCaller(lp02) - foo.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - bar.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - foo.Approve(users.AddressOrName(lp02), 50000000) - bar.Approve(users.AddressOrName(lp02), 50000000) - - std.TestSetOrigCaller(tr01) - foo.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - bar.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - foo.Approve(users.AddressOrName(tr01), 50000000) - bar.Approve(users.AddressOrName(tr01), 50000000) + shouldPanic(t, func() { CreatePool(fooPath, barPath, 500, 130621891405341611593710811006) }) } // Swap by tr01, Mint by lp01 ~ 02 func TestSwap(t *testing.T) { - pool := GetPool(pToken0, pToken1, pFee) + pool := GetPool(fooPath, barPath, pFee) // lp01 mint 9000 ~ 11000 tick range std.TestSetPrevRealm("gno.land/r/position") std.TestSetOrigCaller(lp01) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*10000) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*10000) // lp02 mint (almost) full range // fee 500 will use tickSpacing 10 @@ -86,63 +66,54 @@ func TestSwap(t *testing.T) { // MAX_TICK bigint = 887272 std.TestSetOrigCaller(lp02) - Mint(pToken0, pToken1, pFee, posAddr, MIN_TICK+2, MAX_TICK-2, test_liquidityExpect*10000) - - test_price_01 := bigint(MIN_SQRT_RATIO + 1) // maximum price - test_price_10 := bigint(MAX_SQRT_RATIO - 1) // minimum price + Mint(fooPath, barPath, pFee, posAddr, MIN_TICK+2, MAX_TICK-2, test_liquidityExpect*10000) { // balance before swap - poolOldToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolOldToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolOldToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolOldToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - tr01OldToken0Bal := balanceOf(pool.token0, tr01) - tr01OldToken1Bal := balanceOf(pool.token1, tr01) + userOldToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userOldToken1Bal := balanceOfByRegisterCall(barPath, tr01) - lp01OldToken0Bal := balanceOf(pool.token0, lp01) - lp01OldToken1Bal := balanceOf(pool.token1, lp01) + tr01OldToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + tr01OldToken1Bal := balanceOfByRegisterCall(barPath, tr01) - lp02OldToken0Bal := balanceOf(pool.token0, lp02) - lp02OldToken1Bal := balanceOf(pool.token1, lp02) + lp01OldToken0Bal := balanceOfByRegisterCall(fooPath, lp01) + lp01OldToken1Bal := balanceOfByRegisterCall(barPath, lp01) + + lp02OldToken0Bal := balanceOfByRegisterCall(fooPath, lp02) + lp02OldToken1Bal := balanceOfByRegisterCall(barPath, lp02) // SWAP std.TestSetOrigCaller(tr01) - // s0, s1 := Swap(pToken0, pToken1, pFee, tr01, true, bigint(600000), test_price_01) // 9034 - // s0, s1 := Swap(pToken0, pToken1, pFee, tr01, true, bigint(650000), test_price_01) // 8955 - // println("Current Tick:", pool.GetPoolSlot0Tick()) - - // s0, s1 := Swap(pToken0, pToken1, pFee, tr01, true, bigint(620000), test_price_01) // one iteration - yes fee - s0, s1 := Swap(pToken0, pToken1, pFee, tr01, true, bigint(630000), test_price_01) // one iteration - yes fee(only lp02) - - // s0, s1 := Swap(pToken0, pToken1, pFee, tr01, true, bigint(1400000), test_price_01) // one iteration - no fee - // s0, s1 := Swap(pToken0, pToken1, pFee, tr01, true, bigint(1500000), test_price_01) // two iteration + s0, s1 := Swap(fooPath, barPath, pFee, tr01, true, bigint(630000), MIN_PRICE) // one iteration - yes fee(only lp02) shouldNEQ(t, s0, bigint(0)) shouldNEQ(t, s1, bigint(0)) // balance after swap - poolNewToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolNewToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolNewToken0Bal := balanceOfByRegisterCall(fooPath, GetOrigPkgAddr()) + poolNewToken1Bal := balanceOfByRegisterCall(barPath, GetOrigPkgAddr()) - tr01NewToken0Bal := balanceOf(pool.token0, tr01) - tr01NewToken1Bal := balanceOf(pool.token1, tr01) + tr01NewToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + tr01NewToken1Bal := balanceOfByRegisterCall(barPath, tr01) - lp01NewToken0Bal := balanceOf(pool.token0, lp01) - lp01NewToken1Bal := balanceOf(pool.token1, lp01) + lp01NewToken0Bal := balanceOfByRegisterCall(fooPath, lp01) + lp01NewToken1Bal := balanceOfByRegisterCall(barPath, lp01) - lp02NewToken0Bal := balanceOf(pool.token0, lp02) - lp02NewToken1Bal := balanceOf(pool.token1, lp02) + lp02NewToken0Bal := balanceOfByRegisterCall(fooPath, lp02) + lp02NewToken1Bal := balanceOfByRegisterCall(barPath, lp02) // To collect fee wihout removing liquidity // burn 0 // then collect - std.TestSetOrigCaller(lp01) - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, 0) - Collect(pToken0, pToken1, pFee, lp01, test_tickLower, test_tickUpper, 100000000, 100000000) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, 0) + Collect(fooPath, barPath, pFee, lp01, test_tickLower, test_tickUpper, 100000000, 100000000) std.TestSetOrigCaller(lp02) - Burn(pToken0, pToken1, pFee, MIN_TICK+2, MAX_TICK-2, 0) - Collect(pToken0, pToken1, pFee, lp02, MIN_TICK+2, MAX_TICK-2, 100000000, 100000000) + Burn(fooPath, barPath, pFee, MIN_TICK+2, MAX_TICK-2, 0) + Collect(fooPath, barPath, pFee, lp02, MIN_TICK+2, MAX_TICK-2, 100000000, 100000000) } } @@ -155,11 +126,11 @@ func TestApiGetPools(t *testing.T) { shouldEQ(t, jsonStr.Get("stat.timestamp").Int(), GetTimestamp()) shouldEQ(t, len(jsonStr.Get("response.data").Array()), 1) - shouldEQ(t, jsonStr.Get("response.data").Array()[0].String(), "bar_foo_500") + shouldEQ(t, jsonStr.Get("response.data").Array()[0].String(), "gno.land/r/bar:gno.land/r/foo:500") } func TestApiGetPool(t *testing.T) { - gpl := ApiGetPool("bar_foo_500") + gpl := ApiGetPool("gno.land/r/bar:gno.land/r/foo:500") jsonStr := gjson.Parse(gpl) shouldEQ(t, jsonStr.Get("stat.height").Int(), GetHeight()) diff --git a/pool/_TEST_pool_multi_lp_test.gnoa b/pool/_TEST_pool_multi_lp_test.gnoa index 0bb323dc..c23a5310 100644 --- a/pool/_TEST_pool_multi_lp_test.gnoa +++ b/pool/_TEST_pool_multi_lp_test.gnoa @@ -6,10 +6,8 @@ import ( "testing" "gno.land/p/demo/testutils" - "gno.land/r/demo/users" - bar "gno.land/r/bar" - foo "gno.land/r/foo" + _ "gno.land/r/grc20_wrapper" ) var ( @@ -21,13 +19,14 @@ var ( tr01 = testutils.TestAddress("tr01") // Trader 01 - posAddr = std.DerivePkgAddr("gno.land/r/position") + poolAddr = std.DerivePkgAddr("gno.land/r/pool") + posAddr = std.DerivePkgAddr("gno.land/r/position") ) var ( // Common - pToken0 = "foo" - pToken1 = "bar" + fooPath = "gno.land/r/foo" // token1 + barPath = "gno.land/r/bar" // token2 pFee = uint16(500) test_tickLower = int32(9000) @@ -42,50 +41,21 @@ var ( func TestFactoryCreatePool(t *testing.T) { std.TestSetOrigCaller(gsa) InitManual() - CreatePool("foo", "bar", pFee, 130621891405341611593710811006) - - // fee - // 500 = 0.05% // USv3 default - // 3000 = 0.3% // USv3 default - // 10000 = 1% // USv3 default + CreatePool(fooPath, barPath, pFee, 130621891405341611593710811006) // sqrtPrice // 130621891405341611593710811006 // tick = 10000 - shouldPanic(t, func() { CreatePool("foo", "bar", 500, 130621891405341611593710811006) }) - - // Approve - std.TestSetOrigCaller(lp01) - foo.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - bar.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - foo.Approve(users.AddressOrName(lp01), 50000000) - bar.Approve(users.AddressOrName(lp01), 50000000) - - std.TestSetOrigCaller(lp02) - foo.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - bar.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - foo.Approve(users.AddressOrName(lp02), 50000000) - bar.Approve(users.AddressOrName(lp02), 50000000) - - std.TestSetOrigCaller(lp03) - foo.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - bar.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - foo.Approve(users.AddressOrName(lp03), 50000000) - bar.Approve(users.AddressOrName(lp03), 50000000) - - std.TestSetOrigCaller(tr01) - foo.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - bar.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - foo.Approve(users.AddressOrName(tr01), 50000000) - bar.Approve(users.AddressOrName(tr01), 50000000) + shouldPanic(t, func() { CreatePool(fooPath, barPath, 500, 130621891405341611593710811006) }) } // 2. Mint by lp01 func TestMint(t *testing.T) { std.TestSetPrevRealm("gno.land/r/position") std.TestSetOrigCaller(lp01) + Mint( - pToken0, - pToken1, + fooPath, + barPath, pFee, posAddr, test_tickLower, @@ -93,20 +63,20 @@ func TestMint(t *testing.T) { test_liquidityExpect, ) - pool := GetPool(pToken0, pToken1, pFee) + pool := GetPool(fooPath, barPath, pFee) test_liquidity := pool.GetLiquidity() shouldEQ(t, test_liquidity, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - m81, m82 := Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - m101, m102 := Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + m81, m82 := Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + m101, m102 := Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) shouldNEQ(t, m81, bigint(0)) shouldNEQ(t, m82, bigint(0)) @@ -117,27 +87,27 @@ func TestMint(t *testing.T) { shouldEQ(t, test_liquidity, test_liquidityExpect*10) // tickLower > currentTick == don't add to current liquidity - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower2, test_tickUpper2, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower2, test_tickUpper2, test_liquidityExpect) // tickUpper < current tick == don't add to current liquidity - Mint(pToken0, pToken1, pFee, posAddr, -test_tickUpper2, -test_tickLower2, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, -test_tickUpper2, -test_tickLower2, test_liquidityExpect) // tickUpper < tickLower == don't add to current liquidity - Mint(pToken0, pToken1, pFee, posAddr, -test_tickUpper, -test_tickLower, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, -test_tickUpper, -test_tickLower, test_liquidityExpect) test_liquidity = pool.GetLiquidity() shouldEQ(t, test_liquidity, test_liquidityExpect*10) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) test_liquidity = pool.GetLiquidity() shouldEQ(t, test_liquidity, test_liquidityExpect*20) @@ -148,34 +118,34 @@ func TestBurn(t *testing.T) { std.TestSetPrevRealm("gno.land/r/position") std.TestSetOrigCaller(lp01) - b11, b12 := Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect) - b21, b22 := Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect) + b11, b12 := Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect) + b21, b22 := Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect) shouldEQ(t, b11, b21) shouldEQ(t, b12, b22) - pool := GetPool(pToken0, pToken1, pFee) + pool := GetPool(fooPath, barPath, pFee) test_liquidity := pool.GetLiquidity() shouldEQ(t, test_liquidity, test_liquidityExpect*18) - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*8) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*8) test_liquidity = pool.GetLiquidity() shouldEQ(t, test_liquidity, test_liquidityExpect*10) - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, 1) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, 1) test_liquidity = pool.GetLiquidity() shouldEQ(t, test_liquidity, bigint(9999)) - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, 999) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, 999) test_liquidity = pool.GetLiquidity() shouldEQ(t, test_liquidity, test_liquidityExpect*9) - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*9) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*9) test_liquidity = pool.GetLiquidity() shouldEQ(t, test_liquidity, bigint(0)) // can't burn when liq is 0 - // Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect) - shouldPanic(t, func() { Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect) }) + // Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect) + shouldPanic(t, func() { Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect) }) } // 4. Collect @@ -184,44 +154,44 @@ func TestCollect(t *testing.T) { std.TestSetOrigCaller(lp01) // withdraw all token before test `Collect` - Collect(pToken0, pToken1, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) + Collect(fooPath, barPath, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) // pool should have zero liquidity - pool := GetPool(pToken0, pToken1, pFee) + pool := GetPool(fooPath, barPath, pFee) test_liquidity := pool.GetLiquidity() shouldEQ(t, test_liquidity, bigint(0)) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*15) - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) - c11, c12 := Collect(pToken0, pToken1, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*15) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) + c11, c12 := Collect(fooPath, barPath, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*15) - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) - c21, c22 := Collect(pToken0, pToken1, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*15) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) + c21, c22 := Collect(fooPath, barPath, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) shouldEQ(t, c11, c21) shouldEQ(t, c12, c22) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*15) - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) - c31, c32 := Collect(pToken0, pToken1, pFee, lp01, test_tickLower, test_tickUpper, 100, 100) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*15) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) + c31, c32 := Collect(fooPath, barPath, pFee, lp01, test_tickLower, test_tickUpper, 100, 100) shouldEQ(t, c31, bigint(100)) shouldEQ(t, c32, bigint(100)) - c41, c42 := Collect(pToken0, pToken1, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) + c41, c42 := Collect(fooPath, barPath, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) shouldEQ(t, c41, c21-bigint(100)) shouldEQ(t, c42, c22-bigint(100)) // Mint > No Burn => nothing to collect - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*15) - // Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) - c51, c52 := Collect(pToken0, pToken1, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*15) + // Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) + c51, c52 := Collect(fooPath, barPath, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) shouldEQ(t, c51, bigint(0)) shouldEQ(t, c52, bigint(0)) // Burn Now => something to collect - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) - c61, c62 := Collect(pToken0, pToken1, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) + c61, c62 := Collect(fooPath, barPath, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) shouldNEQ(t, c61, bigint(0)) shouldNEQ(t, c62, bigint(0)) } @@ -229,150 +199,147 @@ func TestCollect(t *testing.T) { // 5. Swap by tr01, Mint by lp02 ~ lp03 func TestSwap(t *testing.T) { std.TestSetPrevRealm("gno.land/r/position") - pool := GetPool(pToken0, pToken1, pFee) + pool := GetPool(fooPath, barPath, pFee) std.TestSetOrigCaller(lp02) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*10000) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*10000) std.TestSetOrigCaller(lp03) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*10000) - - // Swap several times - test_price := bigint(MIN_SQRT_RATIO + 1) // maximum price + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*10000) { - poolOldToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolOldToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolOldToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolOldToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userOldToken0Bal := balanceOf(pool.token0, tr01) - userOldToken1Bal := balanceOf(pool.token1, tr01) + userOldToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userOldToken1Bal := balanceOfByRegisterCall(barPath, tr01) - lp02OldToken0Bal := balanceOf(pool.token0, lp02) - lp02OldToken1Bal := balanceOf(pool.token1, lp02) + lp02OldToken0Bal := balanceOfByRegisterCall(fooPath, lp02) + lp02OldToken1Bal := balanceOfByRegisterCall(barPath, lp02) - lp03OldToken0Bal := balanceOf(pool.token0, lp03) - lp03OldToken1Bal := balanceOf(pool.token1, lp03) + lp03OldToken0Bal := balanceOfByRegisterCall(fooPath, lp03) + lp03OldToken1Bal := balanceOfByRegisterCall(barPath, lp03) std.TestSetOrigCaller(tr01) amount0, amount1 := Swap( - pToken0, - pToken1, + fooPath, + barPath, pFee, tr01, true, bigint(10000), - test_price, + MIN_PRICE, ) shouldNEQ(t, amount0, bigint(0)) shouldNEQ(t, amount1, bigint(0)) - poolNewToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolNewToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolNewToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolNewToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userNewToken0Bal := balanceOf(pool.token0, tr01) - userNewToken1Bal := balanceOf(pool.token1, tr01) + userNewToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userNewToken1Bal := balanceOfByRegisterCall(barPath, tr01) - shouldEQ(t, userOldToken0Bal-amount0, userNewToken0Bal) - shouldEQ(t, userOldToken1Bal-amount1, userNewToken1Bal) - shouldEQ(t, poolOldToken0Bal+amount0, poolNewToken0Bal) - shouldEQ(t, poolOldToken1Bal+amount1, poolNewToken1Bal) + shouldEQ(t, userOldToken0Bal-userNewToken0Bal, int64(amount0)) + shouldEQ(t, userNewToken1Bal-userOldToken1Bal, int64(-amount1)) + shouldEQ(t, poolNewToken0Bal-poolOldToken0Bal, int64(amount0)) + shouldEQ(t, poolOldToken1Bal-poolNewToken1Bal, int64(-amount1)) std.TestSetOrigCaller(lp02) - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*1000) - Collect(pToken0, pToken1, pFee, lp02, test_tickLower, test_tickUpper, 50000000, 50000000) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*1000) + Collect(fooPath, barPath, pFee, lp02, test_tickLower, test_tickUpper, 50000000, 50000000) - lp02NewToken0Bal := balanceOf(pool.token0, lp02) - lp02NewToken1Bal := balanceOf(pool.token1, lp02) + lp02NewToken0Bal := balanceOfByRegisterCall(fooPath, lp02) + lp02NewToken1Bal := balanceOfByRegisterCall(barPath, lp02) std.TestSetOrigCaller(lp03) - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*1000) - Collect(pToken0, pToken1, pFee, lp03, test_tickLower, test_tickUpper, 50000000, 50000000) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*1000) + Collect(fooPath, barPath, pFee, lp03, test_tickLower, test_tickUpper, 50000000, 50000000) - lp03NewToken0Bal := balanceOf(pool.token0, lp03) - lp03NewToken1Bal := balanceOf(pool.token1, lp03) + lp03NewToken0Bal := balanceOfByRegisterCall(fooPath, lp03) + lp03NewToken1Bal := balanceOfByRegisterCall(barPath, lp03) } // Mint again std.TestSetOrigCaller(lp02) - Mint(pToken0, pToken1, pFee, lp02, test_tickLower, test_tickUpper, test_liquidityExpect*1000) + Mint(fooPath, barPath, pFee, lp02, test_tickLower, test_tickUpper, test_liquidityExpect*1000) std.TestSetOrigCaller(lp03) - Mint(pToken0, pToken1, pFee, lp03, test_tickLower, test_tickUpper, test_liquidityExpect*1000) + Mint(fooPath, barPath, pFee, lp03, test_tickLower, test_tickUpper, test_liquidityExpect*1000) { - poolOldToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolOldToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolOldToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolOldToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userOldToken0Bal := balanceOf(pool.token0, tr01) - userOldToken1Bal := balanceOf(pool.token1, tr01) + userOldToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userOldToken1Bal := balanceOfByRegisterCall(barPath, tr01) std.TestSetOrigCaller(tr01) - amount0, amount1 := Swap(pToken0, pToken1, pFee, tr01, true, bigint(5000), test_price) + amount0, amount1 := Swap(fooPath, barPath, pFee, tr01, true, bigint(5000), MIN_PRICE) shouldNEQ(t, amount0, bigint(0)) shouldNEQ(t, amount1, bigint(0)) - poolNewToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolNewToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolNewToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolNewToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userNewToken0Bal := balanceOf(pool.token0, tr01) - userNewToken1Bal := balanceOf(pool.token1, tr01) + userNewToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userNewToken1Bal := balanceOfByRegisterCall(barPath, tr01) - shouldEQ(t, userOldToken0Bal-amount0, userNewToken0Bal) - shouldEQ(t, userOldToken1Bal-amount1, userNewToken1Bal) - shouldEQ(t, poolOldToken0Bal+amount0, poolNewToken0Bal) - shouldEQ(t, poolOldToken1Bal+amount1, poolNewToken1Bal) + shouldEQ(t, userOldToken0Bal-userNewToken0Bal, int64(amount0)) + shouldEQ(t, userNewToken1Bal-userOldToken1Bal, int64(-amount1)) + shouldEQ(t, poolNewToken0Bal-poolOldToken0Bal, int64(amount0)) + shouldEQ(t, poolOldToken1Bal-poolNewToken1Bal, int64(-amount1)) } { - poolOldToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolOldToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolOldToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolOldToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userOldToken0Bal := balanceOf(pool.token0, tr01) - userOldToken1Bal := balanceOf(pool.token1, tr01) + userOldToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userOldToken1Bal := balanceOfByRegisterCall(barPath, tr01) std.TestSetOrigCaller(tr01) - amount0, amount1 := Swap(pToken0, pToken1, pFee, tr01, true, bigint(1000), test_price) + amount0, amount1 := Swap(fooPath, barPath, pFee, tr01, true, bigint(1000), MIN_PRICE) shouldNEQ(t, amount0, bigint(0)) shouldNEQ(t, amount1, bigint(0)) - poolNewToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolNewToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolNewToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolNewToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userNewToken0Bal := balanceOf(pool.token0, tr01) - userNewToken1Bal := balanceOf(pool.token1, tr01) + userNewToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userNewToken1Bal := balanceOfByRegisterCall(barPath, tr01) - shouldEQ(t, userOldToken0Bal-amount0, userNewToken0Bal) - shouldEQ(t, userOldToken1Bal-amount1, userNewToken1Bal) - shouldEQ(t, poolOldToken0Bal+amount0, poolNewToken0Bal) - shouldEQ(t, poolOldToken1Bal+amount1, poolNewToken1Bal) + shouldEQ(t, userOldToken0Bal-userNewToken0Bal, int64(amount0)) + shouldEQ(t, userNewToken1Bal-userOldToken1Bal, int64(-amount1)) + shouldEQ(t, poolNewToken0Bal-poolOldToken0Bal, int64(amount0)) + shouldEQ(t, poolOldToken1Bal-poolNewToken1Bal, int64(-amount1)) } { - poolOldToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolOldToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolOldToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolOldToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userOldToken0Bal := balanceOf(pool.token0, tr01) - userOldToken1Bal := balanceOf(pool.token1, tr01) + userOldToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userOldToken1Bal := balanceOfByRegisterCall(barPath, tr01) std.TestSetOrigCaller(tr01) - amount0, amount1 := Swap(pToken0, pToken1, pFee, tr01, false, bigint(16000), (MAX_SQRT_RATIO - 1)) + amount0, amount1 := Swap(fooPath, barPath, pFee, tr01, false, bigint(16000), (MAX_SQRT_RATIO - 1)) shouldNEQ(t, amount0, bigint(0)) shouldNEQ(t, amount1, bigint(0)) - poolNewToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolNewToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolNewToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolNewToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userNewToken0Bal := balanceOf(pool.token0, tr01) - userNewToken1Bal := balanceOf(pool.token1, tr01) + userNewToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userNewToken1Bal := balanceOfByRegisterCall(barPath, tr01) - shouldEQ(t, userOldToken0Bal-amount0, userNewToken0Bal) - shouldEQ(t, userOldToken1Bal-amount1, userNewToken1Bal) - shouldEQ(t, poolOldToken0Bal+amount0, poolNewToken0Bal) - shouldEQ(t, poolOldToken1Bal+amount1, poolNewToken1Bal) + shouldEQ(t, userOldToken0Bal-userNewToken0Bal, int64(amount0)) + shouldEQ(t, userNewToken1Bal-userOldToken1Bal, int64(-amount1)) + shouldEQ(t, poolNewToken0Bal-poolOldToken0Bal, int64(amount0)) + shouldEQ(t, poolOldToken1Bal-poolNewToken1Bal, int64(-amount1)) } } @@ -394,42 +361,48 @@ func TestCollectProtocol(t *testing.T) { std.TestSetOrigCaller(gsa) SetFeeProtocol(6, 8) - pool := GetPool(pToken0, pToken1, pFee) + pool := GetPool(fooPath, barPath, pFee) test_slot0 := pool.GetSlot0() shouldEQ(t, test_slot0.feeProtocol, bigint(134)) // Make ProtocolFee via Swap by tr01 ( Mint by lp01 ) std.TestSetOrigCaller(lp01) { - gsaOldToken0Bal := balanceOf(pool.token0, gsa) - gsaOldToken1Bal := balanceOf(pool.token1, gsa) + gsaOldToken0Bal := balanceOfByRegisterCall(fooPath, gsa) + gsaOldToken1Bal := balanceOfByRegisterCall(barPath, gsa) std.TestSetOrigCaller(tr01) - Swap(pToken0, pToken1, pFee, tr01, true, 100000, MIN_SQRT_RATIO+1) // swap token0 -> token1 => fee only in token0 - Swap(pToken0, pToken1, pFee, tr01, true, 100000, MIN_SQRT_RATIO+1) // more protocol fee + Swap(fooPath, barPath, pFee, tr01, true, 100000, MIN_PRICE) // swap token0 -> token1 => fee only in token0 + Swap(fooPath, barPath, pFee, tr01, true, 100000, MIN_PRICE) // more protocol fee // Gnoswap Admin std.TestSetOrigCaller(gsa) - amount0, amount1 := CollectProtocol(pToken0, pToken1, pFee, gsa, 100000, 100000) + amount0, amount1 := CollectProtocol(fooPath, barPath, pFee, gsa, 100000, 100000) - gsaNewToken0Bal := balanceOf(pool.token0, gsa) - gsaNewToken1Bal := balanceOf(pool.token1, gsa) + gsaNewToken0Bal := balanceOfByRegisterCall(fooPath, gsa) + gsaNewToken1Bal := balanceOfByRegisterCall(barPath, gsa) + + shouldEQ(t, gsaNewToken0Bal-gsaOldToken0Bal, int64(amount0)) + shouldEQ(t, gsaNewToken1Bal-gsaOldToken1Bal, int64(amount1)) } { - gsaOldToken0Bal := balanceOf(pool.token0, gsa) - gsaOldToken1Bal := balanceOf(pool.token1, gsa) + gsaOldToken0Bal := balanceOfByRegisterCall(fooPath, gsa) + gsaOldToken1Bal := balanceOfByRegisterCall(barPath, gsa) std.TestSetOrigCaller(tr01) - Swap(pToken0, pToken1, pFee, tr01, false, 100000, MAX_SQRT_RATIO-1) // swap token0 -> token1 => fee only in token0 - Swap(pToken0, pToken1, pFee, tr01, false, 100000, MAX_SQRT_RATIO-1) // more protocol fee + Swap(fooPath, barPath, pFee, tr01, false, 100000, MAX_PRICE) // swap token0 -> token1 => fee only in token0 + Swap(fooPath, barPath, pFee, tr01, false, 100000, MAX_PRICE) // more protocol fee // Gnoswap Admin std.TestSetOrigCaller(gsa) - amount0, amount1 := CollectProtocol(pToken0, pToken1, pFee, gsa, 100000, 100000) + amount0, amount1 := CollectProtocol(fooPath, barPath, pFee, gsa, 100000, 100000) + + gsaNewToken0Bal := balanceOfByRegisterCall(fooPath, gsa) + gsaNewToken1Bal := balanceOfByRegisterCall(barPath, gsa) - gsaNewToken0Bal := balanceOf(pool.token0, gsa) - gsaNewToken1Bal := balanceOf(pool.token1, gsa) + shouldEQ(t, gsaNewToken0Bal-gsaOldToken0Bal, int64(amount0)) + shouldEQ(t, gsaNewToken1Bal-gsaOldToken1Bal, int64(amount1)) } } @@ -442,7 +415,7 @@ func TestApiGetPools(t *testing.T) { shouldEQ(t, jsonStr.Get("stat.timestamp").Int(), GetTimestamp()) shouldEQ(t, len(jsonStr.Get("response.data").Array()), 1) - shouldEQ(t, jsonStr.Get("response.data").Array()[0].String(), "bar_foo_500") + shouldEQ(t, jsonStr.Get("response.data").Array()[0].String(), "gno.land/r/bar:gno.land/r/foo:500") } /* HELPER */ diff --git a/pool/_TEST_pool_multi_token_test.gno b/pool/_TEST_pool_multi_token_test.gnoa similarity index 68% rename from pool/_TEST_pool_multi_token_test.gno rename to pool/_TEST_pool_multi_token_test.gnoa index 5c5303e9..b3e9e6f0 100644 --- a/pool/_TEST_pool_multi_token_test.gno +++ b/pool/_TEST_pool_multi_token_test.gnoa @@ -16,15 +16,13 @@ var ( poolAddr = std.DerivePkgAddr("gno.land/r/pool") posAddr = std.DerivePkgAddr("gno.land/r/position") - - poolPath = "gno.land/r/pool" ) var ( // Common - fooPath = "gno.land/r/foo" - barPath = "gno.land/r/bar" - bazPath = "gno.land/r/baz" + fooPath = "gno.land/r/foo" // token1 + barPath = "gno.land/r/bar" // token2 + bazPath = "gno.land/r/baz" // token3 pFee = uint16(500) @@ -56,9 +54,8 @@ func TestCreateBarBazPool(t *testing.T) { // 4. Mint Foo:Bar Liquidity by lp01 func TestMintFooBarLiquidity(t *testing.T) { - std.TestSetOrigCaller(lp01) - std.TestSetPrevRealm("gno.land/r/position") + std.TestSetOrigCaller(lp01) Mint( fooPath, @@ -73,9 +70,8 @@ func TestMintFooBarLiquidity(t *testing.T) { // 5. Mint Bar:Baz Liquidity by lp01 func TestMintBarBazLiquidity(t *testing.T) { - std.TestSetOrigCaller(lp01) - std.TestSetPrevRealm("gno.land/r/position") + std.TestSetOrigCaller(lp01) Mint( barPath, @@ -190,7 +186,45 @@ func TestCollectFooBarFees(t *testing.T) { shouldEQ(t, oldPoolBarBalance-newPoolBarBalance, uint64(c1)) } -// 9. Burn Foo:Bar Liquidity by lp01 +// 9. Collect Bar:Baz Fees by lp01 +func TestCollectBarBazFees(t *testing.T) { + std.TestSetOrigCaller(lp01) + std.TestSetPrevRealm("gno.land/r/position") + + oldLp01BarBalance := balanceOfByRegisterCall(barPath, lp01) + oldLp01BazBalance := balanceOfByRegisterCall(bazPath, lp01) + oldPoolBarBalance := balanceOfByRegisterCall(barPath, poolAddr) + oldPoolBazBalance := balanceOfByRegisterCall(bazPath, poolAddr) + + // burn 0 to collect swap fees + Burn(barPath, bazPath, pFee, test_tickLower, test_tickUpper, 0) + + c0, c1 := Collect( + barPath, + bazPath, + pFee, + lp01, + test_tickLower, + test_tickUpper, + 100_000, + 100_000, + ) + + shouldNEQ(t, c0, bigint(0)) // swap was foo > bar, so only foo has fees + shouldEQ(t, c1, bigint(0)) // swap was foo > bar, so bar has no fees + + newLp01BarBalance := balanceOfByRegisterCall(barPath, lp01) + newLp01BazBalance := balanceOfByRegisterCall(bazPath, lp01) + newPoolBarBalance := balanceOfByRegisterCall(barPath, poolAddr) + newPoolBazBalance := balanceOfByRegisterCall(bazPath, poolAddr) + + shouldEQ(t, newLp01BarBalance-oldLp01BarBalance, uint64(c0)) + shouldEQ(t, newLp01BazBalance-oldLp01BazBalance, uint64(c1)) + shouldEQ(t, oldPoolBarBalance-newPoolBarBalance, uint64(c0)) + shouldEQ(t, oldPoolBazBalance-newPoolBazBalance, uint64(c1)) +} + +// 10. Burn Foo:Bar Liquidity by lp01 func TestBurnFooBarLiquidity(t *testing.T) { std.TestSetOrigCaller(lp01) std.TestSetPrevRealm("gno.land/r/position") @@ -215,7 +249,32 @@ func TestBurnFooBarLiquidity(t *testing.T) { shouldEQ(t, poolOldLiquidity-poolNewLiquidity, test_liquidityExpect) } -// 10. Collect Foo:Bar burned Liquidity by lp01 +// 11. Burn Bar:Baz Liquidity by lp01 +func TestBurnBarBazLiquidity(t *testing.T) { + std.TestSetOrigCaller(lp01) + std.TestSetPrevRealm("gno.land/r/position") + + pool := GetPool(barPath, bazPath, pFee) + poolOldLiquidity := pool.GetLiquidity() + + b0, b1 := Burn( + barPath, + bazPath, + pFee, + test_tickLower, + test_tickUpper, + test_liquidityExpect, + ) + + shouldNEQ(t, b0, bigint(0)) + shouldNEQ(t, b1, bigint(0)) + + poolNewLiquidity := pool.GetLiquidity() + + shouldEQ(t, poolOldLiquidity-poolNewLiquidity, test_liquidityExpect) +} + +// 12. Collect Foo:Bar burned Liquidity by lp01 func TestCollectFooBarLiquidity(t *testing.T) { std.TestSetOrigCaller(lp01) std.TestSetPrevRealm("gno.land/r/position") @@ -250,6 +309,41 @@ func TestCollectFooBarLiquidity(t *testing.T) { shouldEQ(t, oldPoolBarBalance-newPoolBarBalance, uint64(c1)) } +// 13. Collect Bar:Baz burned Liquidity by lp01 +func TestCollectBarBazLiquidity(t *testing.T) { + std.TestSetOrigCaller(lp01) + std.TestSetPrevRealm("gno.land/r/position") + + oldLp01BarBalance := balanceOfByRegisterCall(barPath, lp01) + oldLp01BazBalance := balanceOfByRegisterCall(bazPath, lp01) + oldPoolBarBalance := balanceOfByRegisterCall(barPath, poolAddr) + oldPoolBazBalance := balanceOfByRegisterCall(bazPath, poolAddr) + + c0, c1 := Collect( + barPath, + bazPath, + pFee, + lp01, + test_tickLower, + test_tickUpper, + 100_000, + 100_000, + ) + + shouldNEQ(t, c0, bigint(0)) + shouldNEQ(t, c1, bigint(0)) + + newLp01BarBalance := balanceOfByRegisterCall(barPath, lp01) + newLp01BazBalance := balanceOfByRegisterCall(bazPath, lp01) + newPoolBarBalance := balanceOfByRegisterCall(barPath, poolAddr) + newPoolBazBalance := balanceOfByRegisterCall(bazPath, poolAddr) + + shouldEQ(t, newLp01BarBalance-oldLp01BarBalance, uint64(c0)) + shouldEQ(t, newLp01BazBalance-oldLp01BazBalance, uint64(c1)) + shouldEQ(t, oldPoolBarBalance-newPoolBarBalance, uint64(c0)) + shouldEQ(t, oldPoolBazBalance-newPoolBazBalance, uint64(c1)) +} + /* HELPER */ func shouldEQ(t *testing.T, got, expected interface{}) { if got != expected { @@ -262,12 +356,3 @@ func shouldNEQ(t *testing.T, got, expected interface{}) { t.Errorf("got %v, didn't expected %v", got, expected) } } - -func shouldPanic(t *testing.T, f func()) { - defer func() { - if r := recover(); r == nil { - t.Errorf("expected panic") - } - }() - f() -} diff --git a/pool/_TEST_pool_router_test.gnoa b/pool/_TEST_pool_router_test.gnoXXX similarity index 100% rename from pool/_TEST_pool_router_test.gnoa rename to pool/_TEST_pool_router_test.gnoXXX diff --git a/pool/_TEST_pool_same_token_pair_diff_fee_test.gnoa b/pool/_TEST_pool_same_token_pair_diff_fee_test.gnoa index 5969ddf2..28b87dd5 100644 --- a/pool/_TEST_pool_same_token_pair_diff_fee_test.gnoa +++ b/pool/_TEST_pool_same_token_pair_diff_fee_test.gnoa @@ -6,10 +6,8 @@ import ( "testing" "gno.land/p/demo/testutils" - "gno.land/r/demo/users" - bar "gno.land/r/bar" - foo "gno.land/r/foo" + _ "gno.land/r/grc20_wrapper" ) var ( @@ -17,13 +15,14 @@ var ( lp01 = testutils.TestAddress("lp01") // Liquidity Provider 01 tr01 = testutils.TestAddress("tr01") // Trader 01 - posAddr = std.DerivePkgAddr("gno.land/r/position") + poolAddr = std.DerivePkgAddr("gno.land/r/pool") + posAddr = std.DerivePkgAddr("gno.land/r/position") ) var ( // Common - pToken0 = "foo" - pToken1 = "bar" + fooPath = "gno.land/r/foo" // token1 + barPath = "gno.land/r/bar" // token2 pFee1 = uint16(500) test_tickLower1 = int32(9000) @@ -36,155 +35,169 @@ var ( test_liquidityExpect = bigint(1000) ) -// foo & bar & 500 func TestFirstPool(t *testing.T) { std.TestSetOrigCaller(gsa) InitManual() - CreatePool(pToken0, pToken1, pFee1, 130621891405341611593710811006) + CreatePool(fooPath, barPath, pFee1, 130621891405341611593710811006) - shouldPanic(t, func() { CreatePool(pToken0, pToken1, pFee1, 130621891405341611593710811006) }) + shouldPanic(t, func() { CreatePool(fooPath, barPath, pFee1, 130621891405341611593710811006) }) // Test Only Swap - pool := GetPool(pToken0, pToken1, pFee1) + pool := GetPool(fooPath, barPath, pFee1) test_liquidity := pool.GetLiquidity() shouldEQ(t, test_liquidity, bigint(0)) std.TestSetPrevRealm("gno.land/r/position") std.TestSetOrigCaller(lp01) - Mint(pToken0, pToken1, pFee1, posAddr, test_tickLower1, test_tickUpper1, test_liquidityExpect*20000) + Mint(fooPath, barPath, pFee1, posAddr, test_tickLower1, test_tickUpper1, test_liquidityExpect*100000) + + // clear prev realm std.TestSetPrevRealm("") // Swap several times std.TestSetOrigCaller(tr01) - test_price := bigint(MIN_SQRT_RATIO + 1) // maximum price - { - poolOldToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolOldToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolOldToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolOldToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userOldToken0Bal := balanceOf(pool.token0, tr01) - userOldToken1Bal := balanceOf(pool.token1, tr01) + userOldToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userOldToken1Bal := balanceOfByRegisterCall(barPath, tr01) - amount0, amount1 := Swap(pToken0, pToken1, pFee1, tr01, true, bigint(10000), test_price) + amount0, amount1 := Swap(fooPath, barPath, pFee1, tr01, true, bigint(10000), MIN_PRICE) shouldNEQ(t, amount0, bigint(0)) shouldNEQ(t, amount1, bigint(0)) - poolNewToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolNewToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolNewToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolNewToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userNewToken0Bal := balanceOf(pool.token0, tr01) - userNewToken1Bal := balanceOf(pool.token1, tr01) + userNewToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userNewToken1Bal := balanceOfByRegisterCall(barPath, tr01) - shouldEQ(t, userOldToken0Bal-amount0, userNewToken0Bal) - shouldEQ(t, userOldToken1Bal-amount1, userNewToken1Bal) - shouldEQ(t, poolOldToken0Bal+amount0, poolNewToken0Bal) - shouldEQ(t, poolOldToken1Bal+amount1, poolNewToken1Bal) + shouldEQ(t, userOldToken0Bal-userNewToken0Bal, int64(amount0)) + shouldEQ(t, userNewToken1Bal-userOldToken1Bal, int64(-amount1)) + shouldEQ(t, poolNewToken0Bal-poolOldToken0Bal, int64(amount0)) + shouldEQ(t, poolOldToken1Bal-poolNewToken1Bal, int64(-amount1)) } { - poolOldToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolOldToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolOldToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolOldToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) + + userOldToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userOldToken1Bal := balanceOfByRegisterCall(barPath, tr01) - userOldToken0Bal := balanceOf(pool.token0, tr01) - userOldToken1Bal := balanceOf(pool.token1, tr01) + amount0, amount1 := Swap(fooPath, barPath, pFee1, tr01, true, bigint(5000), MIN_PRICE) - amount0, amount1 := Swap(pToken0, pToken1, pFee1, tr01, true, 5000, test_price) // give enough amount to take fees away + shouldNEQ(t, amount0, bigint(0)) + shouldNEQ(t, amount1, bigint(0)) - poolNewToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolNewToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolNewToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolNewToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userNewToken0Bal := balanceOf(pool.token0, tr01) - userNewToken1Bal := balanceOf(pool.token1, tr01) + userNewToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userNewToken1Bal := balanceOfByRegisterCall(barPath, tr01) - shouldEQ(t, userOldToken0Bal-amount0, userNewToken0Bal) - shouldEQ(t, userOldToken1Bal-amount1, userNewToken1Bal) - shouldEQ(t, poolOldToken0Bal+amount0, poolNewToken0Bal) - shouldEQ(t, poolOldToken1Bal+amount1, poolNewToken1Bal) + shouldEQ(t, userOldToken0Bal-userNewToken0Bal, int64(amount0)) + shouldEQ(t, userNewToken1Bal-userOldToken1Bal, int64(-amount1)) + shouldEQ(t, poolNewToken0Bal-poolOldToken0Bal, int64(amount0)) + shouldEQ(t, poolOldToken1Bal-poolNewToken1Bal, int64(-amount1)) } { - poolOldToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolOldToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolOldToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolOldToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userOldToken0Bal := balanceOf(pool.token0, tr01) - userOldToken1Bal := balanceOf(pool.token1, tr01) + userOldToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userOldToken1Bal := balanceOfByRegisterCall(barPath, tr01) - amount0, amount1 := Swap(pToken0, pToken1, pFee1, tr01, true, 1000, test_price) // give enough amount to take fees away + amount0, amount1 := Swap(fooPath, barPath, pFee1, tr01, true, bigint(1000), MIN_PRICE) - poolNewToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolNewToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + shouldNEQ(t, amount0, bigint(0)) + shouldNEQ(t, amount1, bigint(0)) - userNewToken0Bal := balanceOf(pool.token0, tr01) - userNewToken1Bal := balanceOf(pool.token1, tr01) + poolNewToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolNewToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - shouldEQ(t, userOldToken0Bal-amount0, userNewToken0Bal) - shouldEQ(t, userOldToken1Bal-amount1, userNewToken1Bal) - shouldEQ(t, poolOldToken0Bal+amount0, poolNewToken0Bal) - shouldEQ(t, poolOldToken1Bal+amount1, poolNewToken1Bal) + userNewToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userNewToken1Bal := balanceOfByRegisterCall(barPath, tr01) + + shouldEQ(t, userOldToken0Bal-userNewToken0Bal, int64(amount0)) + shouldEQ(t, userNewToken1Bal-userOldToken1Bal, int64(-amount1)) + shouldEQ(t, poolNewToken0Bal-poolOldToken0Bal, int64(amount0)) + shouldEQ(t, poolOldToken1Bal-poolNewToken1Bal, int64(-amount1)) } - // Swap toek1n -> token0 + // Swap token1 -> token0 { - poolOldToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolOldToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolOldToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolOldToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) + + userOldToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userOldToken1Bal := balanceOfByRegisterCall(barPath, tr01) - userOldToken0Bal := balanceOf(pool.token0, tr01) - userOldToken1Bal := balanceOf(pool.token1, tr01) + amount0, amount1 := Swap(fooPath, barPath, pFee1, tr01, false, bigint(160000), MAX_PRICE) - amount0, amount1 := Swap(pToken0, pToken1, pFee1, tr01, false, 16000, (MAX_SQRT_RATIO - 1)) // give enough amount to take fees away + shouldNEQ(t, amount0, bigint(0)) + shouldNEQ(t, amount1, bigint(0)) - poolNewToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolNewToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolNewToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolNewToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userNewToken0Bal := balanceOf(pool.token0, tr01) - userNewToken1Bal := balanceOf(pool.token1, tr01) + userNewToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userNewToken1Bal := balanceOfByRegisterCall(barPath, tr01) - shouldEQ(t, userOldToken0Bal-amount0, userNewToken0Bal) - shouldEQ(t, userOldToken1Bal-amount1, userNewToken1Bal) - shouldEQ(t, poolOldToken0Bal+amount0, poolNewToken0Bal) - shouldEQ(t, poolOldToken1Bal+amount1, poolNewToken1Bal) + shouldEQ(t, userOldToken0Bal-userNewToken0Bal, int64(amount0)) + shouldEQ(t, userNewToken1Bal-userOldToken1Bal, int64(-amount1)) + shouldEQ(t, poolNewToken0Bal-poolOldToken0Bal, int64(amount0)) + shouldEQ(t, poolOldToken1Bal-poolNewToken1Bal, int64(-amount1)) } // Swap with Protocol std.TestSetOrigCaller(gsa) - SetFeeProtocol(6, 8) + test_slot0 := pool.GetSlot0() shouldEQ(t, test_slot0.feeProtocol, bigint(134)) // Make ProtocolFee via Swap by tr01 ( Mint by lp01 ) std.TestSetOrigCaller(lp01) { - gsaOldToken0Bal := balanceOf(pool.token0, gsa) - gsaOldToken1Bal := balanceOf(pool.token1, gsa) + gsaOldToken0Bal := balanceOfByRegisterCall(fooPath, gsa) + gsaOldToken1Bal := balanceOfByRegisterCall(barPath, gsa) std.TestSetOrigCaller(tr01) - Swap(pToken0, pToken1, pFee1, tr01, true, 100000, MIN_SQRT_RATIO+1) // swap token0 -> token1 => fee only in token0 - Swap(pToken0, pToken1, pFee1, tr01, true, 100000, MIN_SQRT_RATIO+1) // more protocol fee + Swap(fooPath, barPath, pFee1, tr01, true, 100000, MIN_PRICE) // swap token0 -> token1 => fee only in token0 + Swap(fooPath, barPath, pFee1, tr01, true, 100000, MIN_PRICE) // more protocol fee // Gnoswap Admin will collect protocol fee std.TestSetOrigCaller(gsa) - amount0, amount1 := CollectProtocol(pToken0, pToken1, pFee1, gsa, 100000, 100000) + amount0, amount1 := CollectProtocol(fooPath, barPath, pFee1, gsa, 100000, 100000) + + gsaNewToken0Bal := balanceOfByRegisterCall(fooPath, gsa) + gsaNewToken1Bal := balanceOfByRegisterCall(barPath, gsa) - gsaNewToken0Bal := balanceOf(pool.token0, gsa) - gsaNewToken1Bal := balanceOf(pool.token1, gsa) + shouldEQ(t, gsaNewToken0Bal-gsaOldToken0Bal, int64(amount0)) + shouldEQ(t, gsaNewToken1Bal-gsaOldToken1Bal, int64(amount1)) } { - gsaOldToken0Bal := balanceOf(pool.token0, gsa) - gsaOldToken1Bal := balanceOf(pool.token1, gsa) + gsaOldToken0Bal := balanceOfByRegisterCall(fooPath, gsa) + gsaOldToken1Bal := balanceOfByRegisterCall(barPath, gsa) std.TestSetOrigCaller(tr01) - Swap(pToken0, pToken1, pFee1, tr01, false, 100000, MAX_SQRT_RATIO-1) // swap token0 -> token1 => fee only in token0 - Swap(pToken0, pToken1, pFee1, tr01, false, 100000, MAX_SQRT_RATIO-1) // more protocol fee + Swap(fooPath, barPath, pFee1, tr01, false, 100000, MAX_PRICE) // swap token0 -> token1 => fee only in token0 + Swap(fooPath, barPath, pFee1, tr01, false, 100000, MAX_PRICE) // more protocol fee // Gnoswap Admin will collect protocol fee std.TestSetOrigCaller(gsa) - amount0, amount1 := CollectProtocol(pToken0, pToken1, pFee1, gsa, 100000, 100000) + amount0, amount1 := CollectProtocol(fooPath, barPath, pFee1, gsa, 100000, 100000) - gsaNewToken0Bal := balanceOf(pool.token0, gsa) - gsaNewToken1Bal := balanceOf(pool.token1, gsa) + gsaNewToken0Bal := balanceOfByRegisterCall(fooPath, gsa) + gsaNewToken1Bal := balanceOfByRegisterCall(barPath, gsa) + + shouldEQ(t, gsaNewToken0Bal-gsaOldToken0Bal, int64(amount0)) + shouldEQ(t, gsaNewToken1Bal-gsaOldToken1Bal, int64(amount1)) } } @@ -192,164 +205,166 @@ func TestFirstPool(t *testing.T) { func TestSecondPool(t *testing.T) { std.TestSetOrigCaller(gsa) // Init() - CreatePool(pToken0, pToken1, pFee2, 130621891405341611593710811006) - - shouldPanic(t, func() { CreatePool(pToken0, pToken1, pFee2, 130621891405341611593710811006) }) - - // Approve - std.TestSetOrigCaller(lp01) - foo.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - bar.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - foo.Approve(users.AddressOrName(lp01), 50000000) - bar.Approve(users.AddressOrName(lp01), 50000000) + CreatePool(fooPath, barPath, pFee2, 130621891405341611593710811006) - std.TestSetOrigCaller(tr01) - foo.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - bar.Approve(users.AddressOrName(GetOrigPkgAddr()), 50000000) - foo.Approve(users.AddressOrName(tr01), 50000000) - bar.Approve(users.AddressOrName(tr01), 50000000) + shouldPanic(t, func() { CreatePool(fooPath, barPath, pFee2, 130621891405341611593710811006) }) // Test Only Swap - pool := GetPool(pToken0, pToken1, pFee2) + pool := GetPool(fooPath, barPath, pFee2) test_liquidity := pool.GetLiquidity() shouldEQ(t, test_liquidity, bigint(0)) - std.TestSetOrigCaller(lp01) std.TestSetPrevRealm("gno.land/r/position") - m1, m2 := Mint(pToken0, pToken1, pFee2, posAddr, test_tickLower2, test_tickUpper2, test_liquidityExpect*50000) + std.TestSetOrigCaller(lp01) + Mint(fooPath, barPath, pFee2, posAddr, test_tickLower2, test_tickUpper2, test_liquidityExpect*50000) + + // clear prev realm std.TestSetPrevRealm("") // Swap several times std.TestSetOrigCaller(tr01) - test_price := bigint(MIN_SQRT_RATIO + 1) // maximum price - { - poolOldToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolOldToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolOldToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolOldToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userOldToken0Bal := balanceOf(pool.token0, tr01) - userOldToken1Bal := balanceOf(pool.token1, tr01) + userOldToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userOldToken1Bal := balanceOfByRegisterCall(barPath, tr01) - amount0, amount1 := Swap(pToken0, pToken1, pFee2, tr01, true, 50000, test_price) + amount0, amount1 := Swap(fooPath, barPath, pFee2, tr01, true, bigint(10000), MIN_PRICE) shouldNEQ(t, amount0, bigint(0)) shouldNEQ(t, amount1, bigint(0)) - poolNewToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolNewToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolNewToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolNewToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userNewToken0Bal := balanceOf(pool.token0, tr01) - userNewToken1Bal := balanceOf(pool.token1, tr01) + userNewToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userNewToken1Bal := balanceOfByRegisterCall(barPath, tr01) - shouldEQ(t, userOldToken0Bal-amount0, userNewToken0Bal) - shouldEQ(t, userOldToken1Bal-amount1, userNewToken1Bal) - shouldEQ(t, poolOldToken0Bal+amount0, poolNewToken0Bal) - shouldEQ(t, poolOldToken1Bal+amount1, poolNewToken1Bal) + shouldEQ(t, userOldToken0Bal-userNewToken0Bal, int64(amount0)) + shouldEQ(t, userNewToken1Bal-userOldToken1Bal, int64(-amount1)) + shouldEQ(t, poolNewToken0Bal-poolOldToken0Bal, int64(amount0)) + shouldEQ(t, poolOldToken1Bal-poolNewToken1Bal, int64(-amount1)) } { - poolOldToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolOldToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolOldToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolOldToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userOldToken0Bal := balanceOf(pool.token0, tr01) - userOldToken1Bal := balanceOf(pool.token1, tr01) + userOldToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userOldToken1Bal := balanceOfByRegisterCall(barPath, tr01) - amount0, amount1 := Swap(pToken0, pToken1, pFee2, tr01, true, 5000, test_price) // give enough amount to take fees away + amount0, amount1 := Swap(fooPath, barPath, pFee2, tr01, true, bigint(5000), MIN_PRICE) - poolNewToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolNewToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + shouldNEQ(t, amount0, bigint(0)) + shouldNEQ(t, amount1, bigint(0)) - userNewToken0Bal := balanceOf(pool.token0, tr01) - userNewToken1Bal := balanceOf(pool.token1, tr01) + poolNewToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolNewToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - shouldEQ(t, userOldToken0Bal-amount0, userNewToken0Bal) - shouldEQ(t, userOldToken1Bal-amount1, userNewToken1Bal) - shouldEQ(t, poolOldToken0Bal+amount0, poolNewToken0Bal) - shouldEQ(t, poolOldToken1Bal+amount1, poolNewToken1Bal) + userNewToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userNewToken1Bal := balanceOfByRegisterCall(barPath, tr01) + + shouldEQ(t, userOldToken0Bal-userNewToken0Bal, int64(amount0)) + shouldEQ(t, userNewToken1Bal-userOldToken1Bal, int64(-amount1)) + shouldEQ(t, poolNewToken0Bal-poolOldToken0Bal, int64(amount0)) + shouldEQ(t, poolOldToken1Bal-poolNewToken1Bal, int64(-amount1)) } { - poolOldToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolOldToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolOldToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolOldToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) + + userOldToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userOldToken1Bal := balanceOfByRegisterCall(barPath, tr01) - userOldToken0Bal := balanceOf(pool.token0, tr01) - userOldToken1Bal := balanceOf(pool.token1, tr01) + amount0, amount1 := Swap(fooPath, barPath, pFee2, tr01, true, bigint(1000), MIN_PRICE) - amount0, amount1 := Swap(pToken0, pToken1, pFee2, tr01, true, 1000, test_price) // give enough amount to take fees away + shouldNEQ(t, amount0, bigint(0)) + shouldNEQ(t, amount1, bigint(0)) - poolNewToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolNewToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolNewToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolNewToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userNewToken0Bal := balanceOf(pool.token0, tr01) - userNewToken1Bal := balanceOf(pool.token1, tr01) + userNewToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userNewToken1Bal := balanceOfByRegisterCall(barPath, tr01) - shouldEQ(t, userOldToken0Bal-amount0, userNewToken0Bal) - shouldEQ(t, userOldToken1Bal-amount1, userNewToken1Bal) - shouldEQ(t, poolOldToken0Bal+amount0, poolNewToken0Bal) - shouldEQ(t, poolOldToken1Bal+amount1, poolNewToken1Bal) + shouldEQ(t, userOldToken0Bal-userNewToken0Bal, int64(amount0)) + shouldEQ(t, userNewToken1Bal-userOldToken1Bal, int64(-amount1)) + shouldEQ(t, poolNewToken0Bal-poolOldToken0Bal, int64(amount0)) + shouldEQ(t, poolOldToken1Bal-poolNewToken1Bal, int64(-amount1)) } - // Swap toek1n -> token0 + // Swap token1 -> token0 { - poolOldToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolOldToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolOldToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolOldToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) + + userOldToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userOldToken1Bal := balanceOfByRegisterCall(barPath, tr01) - userOldToken0Bal := balanceOf(pool.token0, tr01) - userOldToken1Bal := balanceOf(pool.token1, tr01) + amount0, amount1 := Swap(fooPath, barPath, pFee2, tr01, false, bigint(160000), MAX_PRICE) - amount0, amount1 := Swap(pToken0, pToken1, pFee2, tr01, false, 16000, (MAX_SQRT_RATIO - 1)) // give enough amount to take fees away + shouldNEQ(t, amount0, bigint(0)) + shouldNEQ(t, amount1, bigint(0)) - poolNewToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolNewToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolNewToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolNewToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userNewToken0Bal := balanceOf(pool.token0, tr01) - userNewToken1Bal := balanceOf(pool.token1, tr01) + userNewToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userNewToken1Bal := balanceOfByRegisterCall(barPath, tr01) - shouldEQ(t, userOldToken0Bal-amount0, userNewToken0Bal) - shouldEQ(t, userOldToken1Bal-amount1, userNewToken1Bal) - shouldEQ(t, poolOldToken0Bal+amount0, poolNewToken0Bal) - shouldEQ(t, poolOldToken1Bal+amount1, poolNewToken1Bal) + shouldEQ(t, userOldToken0Bal-userNewToken0Bal, int64(amount0)) + shouldEQ(t, userNewToken1Bal-userOldToken1Bal, int64(-amount1)) + shouldEQ(t, poolNewToken0Bal-poolOldToken0Bal, int64(amount0)) + shouldEQ(t, poolOldToken1Bal-poolNewToken1Bal, int64(-amount1)) } // Swap with Protocol std.TestSetOrigCaller(gsa) - SetFeeProtocol(6, 8) + test_slot0 := pool.GetSlot0() shouldEQ(t, test_slot0.feeProtocol, bigint(134)) // Make ProtocolFee via Swap by tr01 ( Mint by lp01 ) std.TestSetOrigCaller(lp01) { - gsaOldToken0Bal := balanceOf(pool.token0, gsa) - gsaOldToken1Bal := balanceOf(pool.token1, gsa) + gsaOldToken0Bal := balanceOfByRegisterCall(fooPath, gsa) + gsaOldToken1Bal := balanceOfByRegisterCall(barPath, gsa) std.TestSetOrigCaller(tr01) - Swap(pToken0, pToken1, pFee2, tr01, true, 100000, MIN_SQRT_RATIO+1) // swap token0 -> token1 => fee only in token0 - Swap(pToken0, pToken1, pFee2, tr01, true, 100000, MIN_SQRT_RATIO+1) // more protocol fee + Swap(fooPath, barPath, pFee2, tr01, true, 100000, MIN_PRICE) // swap token0 -> token1 => fee only in token0 + Swap(fooPath, barPath, pFee2, tr01, true, 100000, MIN_PRICE) // more protocol fee // Gnoswap Admin will collect protocol fee std.TestSetOrigCaller(gsa) - amount0, amount1 := CollectProtocol(pToken0, pToken1, pFee2, gsa, 100000, 100000) + amount0, amount1 := CollectProtocol(fooPath, barPath, pFee1, gsa, 100000, 100000) - gsaNewToken0Bal := balanceOf(pool.token0, gsa) - gsaNewToken1Bal := balanceOf(pool.token1, gsa) + gsaNewToken0Bal := balanceOfByRegisterCall(fooPath, gsa) + gsaNewToken1Bal := balanceOfByRegisterCall(barPath, gsa) + + shouldEQ(t, gsaNewToken0Bal-gsaOldToken0Bal, int64(amount0)) + shouldEQ(t, gsaNewToken1Bal-gsaOldToken1Bal, int64(amount1)) } { - gsaOldToken0Bal := balanceOf(pool.token0, gsa) - gsaOldToken1Bal := balanceOf(pool.token1, gsa) + gsaOldToken0Bal := balanceOfByRegisterCall(fooPath, gsa) + gsaOldToken1Bal := balanceOfByRegisterCall(barPath, gsa) std.TestSetOrigCaller(tr01) - Swap(pToken0, pToken1, pFee2, tr01, false, 100000, MAX_SQRT_RATIO-1) // swap token0 -> token1 => fee only in token0 - Swap(pToken0, pToken1, pFee2, tr01, false, 100000, MAX_SQRT_RATIO-1) // more protocol fee + Swap(fooPath, barPath, pFee2, tr01, false, 100000, MAX_PRICE) // swap token0 -> token1 => fee only in token0 + Swap(fooPath, barPath, pFee2, tr01, false, 100000, MAX_PRICE) // more protocol fee // Gnoswap Admin will collect protocol fee std.TestSetOrigCaller(gsa) - amount0, amount1 := CollectProtocol(pToken0, pToken1, pFee2, gsa, 100000, 100000) + amount0, amount1 := CollectProtocol(fooPath, barPath, pFee2, gsa, 100000, 100000) + + gsaNewToken0Bal := balanceOfByRegisterCall(fooPath, gsa) + gsaNewToken1Bal := balanceOfByRegisterCall(barPath, gsa) - gsaNewToken0Bal := balanceOf(pool.token0, gsa) - gsaNewToken1Bal := balanceOf(pool.token1, gsa) + shouldEQ(t, gsaNewToken0Bal-gsaOldToken0Bal, int64(amount0)) + shouldEQ(t, gsaNewToken1Bal-gsaOldToken1Bal, int64(amount1)) } } @@ -362,12 +377,12 @@ func TestApiGetPools(t *testing.T) { shouldEQ(t, jsonStr.Get("stat.timestamp").Int(), GetTimestamp()) shouldEQ(t, len(jsonStr.Get("response.data").Array()), 2) - shouldEQ(t, jsonStr.Get("response.data").Array()[0].String(), "bar_foo_500") - shouldEQ(t, jsonStr.Get("response.data").Array()[1].String(), "bar_foo_3000") + shouldEQ(t, jsonStr.Get("response.data").Array()[0].String(), "gno.land/r/bar:gno.land/r/foo:500") + shouldEQ(t, jsonStr.Get("response.data").Array()[1].String(), "gno.land/r/bar:gno.land/r/foo:3000") } func TestApiGetPool(t *testing.T) { - gpl := ApiGetPool("bar_foo_500") + gpl := ApiGetPool("gno.land/r/bar:gno.land/r/foo:500") jsonStr := gjson.Parse(gpl) shouldEQ(t, jsonStr.Get("stat.height").Int(), GetHeight()) diff --git a/pool/_TEST_pool_single_lp_test.gnoa b/pool/_TEST_pool_single_lp_test.gnoa index df8532f9..8547e9e0 100644 --- a/pool/_TEST_pool_single_lp_test.gnoa +++ b/pool/_TEST_pool_single_lp_test.gnoa @@ -6,6 +6,8 @@ import ( "testing" "gno.land/p/demo/testutils" + + _ "gno.land/r/grc20_wrapper" ) var ( @@ -13,15 +15,14 @@ var ( lp01 = testutils.TestAddress("lp01") // Liquidity Provider 01 tr01 = testutils.TestAddress("tr01") // Trader 01 - posAddr = std.DerivePkgAddr("gno.land/r/position") - - poolPath = "gno.land/r/pool" + poolAddr = std.DerivePkgAddr("gno.land/r/pool") + posAddr = std.DerivePkgAddr("gno.land/r/position") ) var ( // Common - pToken0 = "foo" - pToken1 = "bar" + fooPath = "gno.land/r/foo" // token1 + barPath = "gno.land/r/bar" // token2 pFee = uint16(500) test_tickLower = int32(9000) @@ -36,16 +37,11 @@ var ( func TestInitCreatePool(t *testing.T) { std.TestSetOrigCaller(gsa) InitManual() - CreatePool("foo", "bar", pFee, 130621891405341611593710811006) - - // fee - // 500 = 0.05% // USv3 default - // 3000 = 0.3% // USv3 default - // 10000 = 1% // USv3 default + CreatePool(fooPath, barPath, pFee, 130621891405341611593710811006) // sqrtPrice // 130621891405341611593710811006 // tick = 10000 - shouldPanic(t, func() { CreatePool("foo", "bar", 500, 130621891405341611593710811006) }) + shouldPanic(t, func() { CreatePool(fooPath, barPath, 500, 130621891405341611593710811006) }) } // 2. Mint by lp01 @@ -54,8 +50,8 @@ func TestMint(t *testing.T) { std.TestSetOrigCaller(lp01) Mint( - pToken0, - pToken1, + fooPath, + barPath, pFee, posAddr, test_tickLower, @@ -63,20 +59,20 @@ func TestMint(t *testing.T) { test_liquidityExpect, ) - pool := GetPool(pToken0, pToken1, pFee) + pool := GetPool(fooPath, barPath, pFee) test_liquidity := pool.GetLiquidity() shouldEQ(t, test_liquidity, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - m81, m82 := Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - m101, m102 := Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + m81, m82 := Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + m101, m102 := Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) shouldNEQ(t, m81, bigint(0)) shouldNEQ(t, m82, bigint(0)) @@ -87,27 +83,27 @@ func TestMint(t *testing.T) { shouldEQ(t, test_liquidity, test_liquidityExpect*10) // tickLower > currentTick == don't add to current liquidity - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower2, test_tickUpper2, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower2, test_tickUpper2, test_liquidityExpect) // tickUpper < current tick == don't add to current liquidity - Mint(pToken0, pToken1, pFee, posAddr, -test_tickUpper2, -test_tickLower2, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, -test_tickUpper2, -test_tickLower2, test_liquidityExpect) // tickUpper < tickLower == don't add to current liquidity - Mint(pToken0, pToken1, pFee, posAddr, -test_tickUpper, -test_tickLower, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, -test_tickUpper, -test_tickLower, test_liquidityExpect) test_liquidity = pool.GetLiquidity() shouldEQ(t, test_liquidity, test_liquidityExpect*10) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect) test_liquidity = pool.GetLiquidity() shouldEQ(t, test_liquidity, test_liquidityExpect*20) @@ -118,34 +114,34 @@ func TestBurn(t *testing.T) { std.TestSetPrevRealm("gno.land/r/position") std.TestSetOrigCaller(lp01) - b11, b12 := Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect) - b21, b22 := Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect) + b11, b12 := Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect) + b21, b22 := Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect) shouldEQ(t, b11, b21) shouldEQ(t, b12, b22) - pool := GetPool(pToken0, pToken1, pFee) + pool := GetPool(fooPath, barPath, pFee) test_liquidity := pool.GetLiquidity() shouldEQ(t, test_liquidity, test_liquidityExpect*18) - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*8) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*8) test_liquidity = pool.GetLiquidity() shouldEQ(t, test_liquidity, test_liquidityExpect*10) - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, 1) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, 1) test_liquidity = pool.GetLiquidity() shouldEQ(t, test_liquidity, bigint(9999)) - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, 999) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, 999) test_liquidity = pool.GetLiquidity() shouldEQ(t, test_liquidity, test_liquidityExpect*9) - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*9) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*9) test_liquidity = pool.GetLiquidity() shouldEQ(t, test_liquidity, bigint(0)) // can't burn when liq is 0 - // Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect) - shouldPanic(t, func() { Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect) }) + // Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect) + shouldPanic(t, func() { Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect) }) } // 4. Collect @@ -154,72 +150,72 @@ func TestCollect(t *testing.T) { std.TestSetOrigCaller(lp01) // withdraw all token before test `Collect` - Collect(pToken0, pToken1, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) + Collect(fooPath, barPath, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) // pool should have zero liquidity - pool := GetPool(pToken0, pToken1, pFee) + pool := GetPool(fooPath, barPath, pFee) test_liquidity := pool.GetLiquidity() shouldEQ(t, test_liquidity, bigint(0)) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*15) - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) - c11, c12 := Collect(pToken0, pToken1, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*15) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) + c11, c12 := Collect(fooPath, barPath, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*15) - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) - c21, c22 := Collect(pToken0, pToken1, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*15) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) + c21, c22 := Collect(fooPath, barPath, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) shouldEQ(t, c11, c21) shouldEQ(t, c12, c22) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*15) - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) - c31, c32 := Collect(pToken0, pToken1, pFee, lp01, test_tickLower, test_tickUpper, 100, 100) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*15) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) + c31, c32 := Collect(fooPath, barPath, pFee, lp01, test_tickLower, test_tickUpper, 100, 100) shouldEQ(t, c31, bigint(100)) shouldEQ(t, c32, bigint(100)) - c41, c42 := Collect(pToken0, pToken1, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) + c41, c42 := Collect(fooPath, barPath, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) shouldEQ(t, c41, c21-bigint(100)) shouldEQ(t, c42, c22-bigint(100)) // Mint > No Burn => nothing to collect - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*15) - // Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) - c51, c52 := Collect(pToken0, pToken1, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*15) + // Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) + c51, c52 := Collect(fooPath, barPath, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) shouldEQ(t, c51, bigint(0)) shouldEQ(t, c52, bigint(0)) // Burn Now => something to collect - Burn(pToken0, pToken1, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) - c61, c62 := Collect(pToken0, pToken1, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) + Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, test_liquidityExpect*15) + c61, c62 := Collect(fooPath, barPath, pFee, lp01, test_tickLower, test_tickUpper, 50000000, 50000000) shouldNEQ(t, c61, bigint(0)) shouldNEQ(t, c62, bigint(0)) } // 5. Swap by tr01 func TestSwap(t *testing.T) { - pool := GetPool(pToken0, pToken1, pFee) + pool := GetPool(fooPath, barPath, pFee) test_liquidity := pool.GetLiquidity() shouldEQ(t, test_liquidity, bigint(0)) std.TestSetPrevRealm("gno.land/r/position") std.TestSetOrigCaller(lp01) - Mint(pToken0, pToken1, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*20000) + Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*20000) // Swap several times std.TestSetOrigCaller(tr01) - test_price := bigint(MIN_SQRT_RATIO + 1) // maximum price + test_price := bigint(MIN_PRICE) { - poolOldToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolOldToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolOldToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolOldToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userOldToken0Bal := balanceOf(pool.token0, tr01) - userOldToken1Bal := balanceOf(pool.token1, tr01) + userOldToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userOldToken1Bal := balanceOfByRegisterCall(barPath, tr01) amount0, amount1 := Swap( - pToken0, - pToken1, + fooPath, + barPath, pFee, tr01, true, @@ -230,80 +226,80 @@ func TestSwap(t *testing.T) { shouldNEQ(t, amount0, bigint(0)) shouldNEQ(t, amount1, bigint(0)) - poolNewToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolNewToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolNewToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolNewToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userNewToken0Bal := balanceOf(pool.token0, tr01) - userNewToken1Bal := balanceOf(pool.token1, tr01) + userNewToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userNewToken1Bal := balanceOfByRegisterCall(barPath, tr01) - shouldEQ(t, userOldToken0Bal-amount0, userNewToken0Bal) - shouldEQ(t, userOldToken1Bal-amount1, userNewToken1Bal) - shouldEQ(t, poolOldToken0Bal+amount0, poolNewToken0Bal) - shouldEQ(t, poolOldToken1Bal+amount1, poolNewToken1Bal) + shouldEQ(t, userOldToken0Bal-userNewToken0Bal, int64(amount0)) + shouldEQ(t, userNewToken1Bal-userOldToken1Bal, int64(-amount1)) + shouldEQ(t, poolNewToken0Bal-poolOldToken0Bal, int64(amount0)) + shouldEQ(t, poolOldToken1Bal-poolNewToken1Bal, int64(-amount1)) } { - poolOldToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolOldToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolOldToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolOldToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userOldToken0Bal := balanceOf(pool.token0, tr01) - userOldToken1Bal := balanceOf(pool.token1, tr01) + userOldToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userOldToken1Bal := balanceOfByRegisterCall(barPath, tr01) - amount0, amount1 := Swap(pToken0, pToken1, pFee, tr01, true, 5000, test_price) // give enough amount to take fees away + amount0, amount1 := Swap(fooPath, barPath, pFee, tr01, true, 5000, test_price) // give enough amount to take fees away - poolNewToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolNewToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolNewToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolNewToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userNewToken0Bal := balanceOf(pool.token0, tr01) - userNewToken1Bal := balanceOf(pool.token1, tr01) + userNewToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userNewToken1Bal := balanceOfByRegisterCall(barPath, tr01) - shouldEQ(t, userOldToken0Bal-amount0, userNewToken0Bal) - shouldEQ(t, userOldToken1Bal-amount1, userNewToken1Bal) - shouldEQ(t, poolOldToken0Bal+amount0, poolNewToken0Bal) - shouldEQ(t, poolOldToken1Bal+amount1, poolNewToken1Bal) + shouldEQ(t, userOldToken0Bal-userNewToken0Bal, int64(amount0)) + shouldEQ(t, userNewToken1Bal-userOldToken1Bal, int64(-amount1)) + shouldEQ(t, poolNewToken0Bal-poolOldToken0Bal, int64(amount0)) + shouldEQ(t, poolOldToken1Bal-poolNewToken1Bal, int64(-amount1)) } { - poolOldToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolOldToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolOldToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolOldToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userOldToken0Bal := balanceOf(pool.token0, tr01) - userOldToken1Bal := balanceOf(pool.token1, tr01) + userOldToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userOldToken1Bal := balanceOfByRegisterCall(barPath, tr01) - amount0, amount1 := Swap(pToken0, pToken1, pFee, tr01, true, 1000, test_price) // give enough amount to take fees away + amount0, amount1 := Swap(fooPath, barPath, pFee, tr01, true, 1000, test_price) // give enough amount to take fees away - poolNewToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolNewToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolNewToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolNewToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userNewToken0Bal := balanceOf(pool.token0, tr01) - userNewToken1Bal := balanceOf(pool.token1, tr01) + userNewToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userNewToken1Bal := balanceOfByRegisterCall(barPath, tr01) - shouldEQ(t, userOldToken0Bal-amount0, userNewToken0Bal) - shouldEQ(t, userOldToken1Bal-amount1, userNewToken1Bal) - shouldEQ(t, poolOldToken0Bal+amount0, poolNewToken0Bal) - shouldEQ(t, poolOldToken1Bal+amount1, poolNewToken1Bal) + shouldEQ(t, userOldToken0Bal-userNewToken0Bal, int64(amount0)) + shouldEQ(t, userNewToken1Bal-userOldToken1Bal, int64(-amount1)) + shouldEQ(t, poolNewToken0Bal-poolOldToken0Bal, int64(amount0)) + shouldEQ(t, poolOldToken1Bal-poolNewToken1Bal, int64(-amount1)) } // Swap token1 -> token0 { - poolOldToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolOldToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolOldToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolOldToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userOldToken0Bal := balanceOf(pool.token0, tr01) - userOldToken1Bal := balanceOf(pool.token1, tr01) + userOldToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userOldToken1Bal := balanceOfByRegisterCall(barPath, tr01) - amount0, amount1 := Swap(pToken0, pToken1, pFee, tr01, false, 16000, (MAX_SQRT_RATIO - 1)) // give enough amount to take fees away + amount0, amount1 := Swap(fooPath, barPath, pFee, tr01, false, 16000, MAX_PRICE) // give enough amount to take fees away - poolNewToken0Bal := balanceOf(pool.token0, GetOrigPkgAddr()) - poolNewToken1Bal := balanceOf(pool.token1, GetOrigPkgAddr()) + poolNewToken0Bal := balanceOfByRegisterCall(fooPath, poolAddr) + poolNewToken1Bal := balanceOfByRegisterCall(barPath, poolAddr) - userNewToken0Bal := balanceOf(pool.token0, tr01) - userNewToken1Bal := balanceOf(pool.token1, tr01) + userNewToken0Bal := balanceOfByRegisterCall(fooPath, tr01) + userNewToken1Bal := balanceOfByRegisterCall(barPath, tr01) - shouldEQ(t, userOldToken0Bal-amount0, userNewToken0Bal) - shouldEQ(t, userOldToken1Bal-amount1, userNewToken1Bal) - shouldEQ(t, poolOldToken0Bal+amount0, poolNewToken0Bal) - shouldEQ(t, poolOldToken1Bal+amount1, poolNewToken1Bal) + shouldEQ(t, userOldToken0Bal-userNewToken0Bal, int64(amount0)) + shouldEQ(t, userNewToken1Bal-userOldToken1Bal, int64(-amount1)) + shouldEQ(t, poolNewToken0Bal-poolOldToken0Bal, int64(amount0)) + shouldEQ(t, poolOldToken1Bal-poolNewToken1Bal, int64(-amount1)) } } @@ -327,40 +323,43 @@ func TestCollectProtocol(t *testing.T) { std.TestSetOrigCaller(gsa) SetFeeProtocol(6, 8) - pool := GetPool(pToken0, pToken1, pFee) + pool := GetPool(fooPath, barPath, pFee) test_slot0 := pool.GetSlot0() shouldEQ(t, test_slot0.feeProtocol, bigint(134)) // Make ProtocolFee via Swap by tr01 ( Mint by lp01 ) std.TestSetOrigCaller(lp01) { - gsaOldToken0Bal := balanceOf(pool.token0, gsa) - gsaOldToken1Bal := balanceOf(pool.token1, gsa) + gsaOldToken0Bal := balanceOfByRegisterCall(fooPath, gsa) + gsaOldToken1Bal := balanceOfByRegisterCall(barPath, gsa) std.TestSetOrigCaller(tr01) - Swap(pToken0, pToken1, pFee, tr01, true, 200000, MIN_SQRT_RATIO+1) // swap token0 -> token1 => fee only in token0 + Swap(fooPath, barPath, pFee, tr01, true, 200000, MIN_PRICE) // swap token0 -> token1 => fee only in token0 // Gnoswap Admin will collect protocol fee std.TestSetOrigCaller(gsa) - amount0, amount1 := CollectProtocol(pToken0, pToken1, pFee, gsa, 100000, 100000) + amount0, amount1 := CollectProtocol(fooPath, barPath, pFee, gsa, 100000, 100000) + + gsaNewToken0Bal := balanceOfByRegisterCall(fooPath, gsa) + gsaNewToken1Bal := balanceOfByRegisterCall(barPath, gsa) - gsaNewToken0Bal := balanceOf(pool.token0, gsa) - gsaNewToken1Bal := balanceOf(pool.token1, gsa) + shouldEQ(t, gsaNewToken0Bal-gsaOldToken0Bal, int64(amount0)) + shouldEQ(t, gsaNewToken1Bal-gsaOldToken1Bal, int64(amount1)) } { - gsaOldToken0Bal := balanceOf(pool.token0, gsa) - gsaOldToken1Bal := balanceOf(pool.token1, gsa) + gsaOldToken0Bal := balanceOfByRegisterCall(fooPath, gsa) + gsaOldToken1Bal := balanceOfByRegisterCall(fooPath, gsa) std.TestSetOrigCaller(tr01) - Swap(pToken0, pToken1, pFee, tr01, false, 200000, MAX_SQRT_RATIO-1) // swap token0 -> token1 => fee only in token0 + Swap(fooPath, barPath, pFee, tr01, false, 200000, MAX_SQRT_RATIO-1) // swap token0 -> token1 => fee only in token0 // Gnoswap Admin will collect protocol fee std.TestSetOrigCaller(gsa) - amount0, amount1 := CollectProtocol(pToken0, pToken1, pFee, gsa, 100000, 100000) + amount0, amount1 := CollectProtocol(fooPath, barPath, pFee, gsa, 100000, 100000) - gsaNewToken0Bal := balanceOf(pool.token0, gsa) - gsaNewToken1Bal := balanceOf(pool.token1, gsa) + gsaNewToken0Bal := balanceOfByRegisterCall(fooPath, gsa) + gsaNewToken1Bal := balanceOfByRegisterCall(fooPath, gsa) } } @@ -373,11 +372,11 @@ func TestApiGetPools(t *testing.T) { shouldEQ(t, jsonStr.Get("stat.timestamp").Int(), GetTimestamp()) shouldEQ(t, len(jsonStr.Get("response.data").Array()), 1) - shouldEQ(t, jsonStr.Get("response.data").Array()[0].String(), "bar_foo_500") + shouldEQ(t, jsonStr.Get("response.data").Array()[0].String(), "gno.land/r/bar:gno.land/r/foo:500") } func TestApiGetPool(t *testing.T) { - gpl := ApiGetPool("bar_foo_500") + gpl := ApiGetPool("gno.land/r/bar:gno.land/r/foo:500") jsonStr := gjson.Parse(gpl) shouldEQ(t, jsonStr.Get("stat.height").Int(), GetHeight()) From 799e0ceab5e971ea95cede01bdb5b133ae756778 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Fri, 27 Oct 2023 17:18:46 +0900 Subject: [PATCH 05/35] chore: rename grc20 token `gnos` to `gns` --- _setup/gnos/gno.mod | 1 - _setup/gns/gno.mod | 1 + _setup/{gnos/gnos.gno => gns/gns.gno} | 36 +++++++++---------- .../{gnos/gnos_test.gno => gns/gns_test.gno} | 2 +- 4 files changed, 20 insertions(+), 20 deletions(-) delete mode 100644 _setup/gnos/gno.mod create mode 100644 _setup/gns/gno.mod rename _setup/{gnos/gnos.gno => gns/gns.gno} (78%) rename _setup/{gnos/gnos_test.gno => gns/gns_test.gno} (99%) diff --git a/_setup/gnos/gno.mod b/_setup/gnos/gno.mod deleted file mode 100644 index 3cae0054..00000000 --- a/_setup/gnos/gno.mod +++ /dev/null @@ -1 +0,0 @@ -module gno.land/r/gnos \ No newline at end of file diff --git a/_setup/gns/gno.mod b/_setup/gns/gno.mod new file mode 100644 index 00000000..0ad40983 --- /dev/null +++ b/_setup/gns/gno.mod @@ -0,0 +1 @@ +module gno.land/r/gns \ No newline at end of file diff --git a/_setup/gnos/gnos.gno b/_setup/gns/gns.gno similarity index 78% rename from _setup/gnos/gnos.gno rename to _setup/gns/gns.gno index 636c7b1a..4ca4fb8a 100644 --- a/_setup/gnos/gnos.gno +++ b/_setup/gns/gns.gno @@ -1,4 +1,4 @@ -package gnos +package gns import ( "std" @@ -10,12 +10,12 @@ import ( ) var ( - gnos *grc20.AdminToken + gns *grc20.AdminToken admin []string ) func init() { - gnos = grc20.NewAdminToken("Gnoswap Shares", "GNOS", 4) + gns = grc20.NewAdminToken("Gnoswap Shares", "GNOS", 4) // for swap > staker var ( @@ -23,8 +23,8 @@ func init() { poolAddr = std.Address("g1ee305k8yk0pjz443xpwtqdyep522f9g5r7d63w") ira = std.Address("g1d9exzh6lta047h6lta047h6lta047h6l8ylkpa") // INTERNAL REWARD ACCOUNT ) - gnos.Mint(ira, 50000000000) // 50_000_000_000 - gnos.Approve(ira, stakerAddr, 50000000000) // owner, spender, amount + gns.Mint(ira, 50000000000) // 50_000_000_000 + gns.Approve(ira, stakerAddr, 50000000000) // owner, spender, amount admin = append(admin, string(stakerAddr)) } @@ -34,15 +34,15 @@ func init() { // getters. func GetGRC20() *grc20.AdminToken { - return gnos + return gns } func TotalSupply() uint64 { - return gnos.TotalSupply() + return gns.TotalSupply() } func BalanceOf(owner users.AddressOrName) uint64 { - balance, err := gnos.BalanceOf(owner.Resolve()) + balance, err := gns.BalanceOf(owner.Resolve()) if err != nil { panic(err) } @@ -50,7 +50,7 @@ func BalanceOf(owner users.AddressOrName) uint64 { } func Allowance(owner, spender users.AddressOrName) uint64 { - allowance, err := gnos.Allowance(owner.Resolve(), spender.Resolve()) + allowance, err := gns.Allowance(owner.Resolve(), spender.Resolve()) if err != nil { panic(err) } @@ -62,7 +62,7 @@ func Allowance(owner, spender users.AddressOrName) uint64 { func Transfer(to users.AddressOrName, amount uint64) { // caller := std.GetOrigCaller() caller := std.PrevRealm().Addr() - err := gnos.Transfer(caller, to.Resolve(), amount) + err := gns.Transfer(caller, to.Resolve(), amount) if err != nil { panic(err.Error()) } @@ -71,7 +71,7 @@ func Transfer(to users.AddressOrName, amount uint64) { func Approve(spender users.AddressOrName, amount uint64) { // caller := std.GetOrigCaller() caller := std.PrevRealm().Addr() - err := gnos.Approve(caller, spender.Resolve(), amount) + err := gns.Approve(caller, spender.Resolve(), amount) if err != nil { panic(err.Error()) } @@ -80,7 +80,7 @@ func Approve(spender users.AddressOrName, amount uint64) { func TransferFrom(from, to users.AddressOrName, amount uint64) { // caller := std.GetOrigCaller() caller := std.PrevRealm().Addr() - err := gnos.TransferFrom(caller, from.Resolve(), to.Resolve(), amount) + err := gns.TransferFrom(caller, from.Resolve(), to.Resolve(), amount) if err != nil { panic(err.Error()) } @@ -93,7 +93,7 @@ func Faucet() { // FIXME: add payment in gnot? // caller := std.GetOrigCaller() caller := std.PrevRealm().Addr() - gnos.Mint(caller, 1000*10000) // 1k + gns.Mint(caller, 1000*10000) // 1k } func FaucetL() { @@ -101,7 +101,7 @@ func FaucetL() { // FIXME: add payment in gnot? // caller := std.GetOrigCaller() caller := std.PrevRealm().Addr() - gnos.Mint(caller, 50000000000) // 50_000_000_000 + gns.Mint(caller, 50000000000) // 50_000_000_000 } // administration. @@ -110,14 +110,14 @@ func Mint(address users.AddressOrName, amount uint64) { // caller := std.GetOrigCaller() caller := std.PrevRealm().Addr() assertIsAdmin(caller) - gnos.Mint(address.Resolve(), amount) + gns.Mint(address.Resolve(), amount) } func Burn(address users.AddressOrName, amount uint64) { // caller := std.GetOrigCaller() caller := std.PrevRealm().Addr() assertIsAdmin(caller) - gnos.Burn(address.Resolve(), amount) + gns.Burn(address.Resolve(), amount) } func AppendAdmin(address users.AddressOrName) { @@ -148,10 +148,10 @@ func Render(path string) string { switch { case path == "": - return gnos.RenderHome() + return gns.RenderHome() case c == 2 && parts[0] == "balance": owner := users.AddressOrName(parts[1]) - balance, _ := gnos.BalanceOf(owner.Resolve()) + balance, _ := gns.BalanceOf(owner.Resolve()) return ufmt.Sprintf("%d\n", balance) default: return "404\n" diff --git a/_setup/gnos/gnos_test.gno b/_setup/gns/gns_test.gno similarity index 99% rename from _setup/gnos/gnos_test.gno rename to _setup/gns/gns_test.gno index e509ed67..3dd6d3c0 100644 --- a/_setup/gnos/gnos_test.gno +++ b/_setup/gns/gns_test.gno @@ -1,4 +1,4 @@ -package gnos +package gns import ( "std" From 50167d295c31ce8bf015ab51f53478ba85bf9363 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Fri, 27 Oct 2023 17:19:24 +0900 Subject: [PATCH 06/35] fix: typo --- pool/pool.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pool/pool.gno b/pool/pool.gno index 5940ccff..9e9eb158 100644 --- a/pool/pool.gno +++ b/pool/pool.gno @@ -48,7 +48,7 @@ func Mint( ok := transferFromByRegisterCall(pToken0Path, from, to, uint64(amount0)) if !ok { - panic("[POOL] pool.gno__Mint() || transferFromByRegisterCall(pToken0Path, from, to, uint64(amount0)) failed") + panic("[POOL] pool.gno__Mint() || transferFromByRegisterCall(pToken0Path, from, to, uint64(amount0)) failed") } require( From f775e3f1ff7414df75f9676f4c88b60d89b9b691 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Fri, 27 Oct 2023 17:19:43 +0900 Subject: [PATCH 07/35] chore: more util functinos --- pool/utils.gno | 36 ++++++++++++++++++++++-------------- 1 file changed, 22 insertions(+), 14 deletions(-) diff --git a/pool/utils.gno b/pool/utils.gno index 46e7a9f4..ac2b5540 100644 --- a/pool/utils.gno +++ b/pool/utils.gno @@ -1,19 +1,5 @@ package pool -import ( - "std" - - "gno.land/p/demo/grc/grc20" -) - -func balanceOf(token *grc20.AdminToken, addr std.Address) bigint { - balance, err := token.BalanceOf(addr) - if err != nil { - panic(err) - } - return bigint(balance) -} - func requireUnsigned(x bigint, msg string) { if x < 0 { panic(msg) @@ -39,3 +25,25 @@ func abs(x bigint) bigint { } return x } + +func removeDuplicateString(strSlice []string) []string { + // map to store unique keys + keys := make(map[string]bool) + returnSlice := []string{} + for _, item := range strSlice { + if _, value := keys[item]; !value { + keys[item] = true + returnSlice = append(returnSlice, item) + } + } + return returnSlice +} + +func remove(s []string, r string) []string { + for i, v := range s { + if v == r { + return append(s[:i], s[i+1:]...) + } + } + return s +} From fd1268bec210476725336ac366cec6a81eb0d493 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Fri, 27 Oct 2023 17:20:34 +0900 Subject: [PATCH 08/35] feat: router now finds maximum of 3 swap paths --- pool/_TEST_math_logic_test.gnoa | 31 ++- ... => _TEST_pool_multi_lp_fee_api_test.gnoa} | 0 pool/_TEST_pool_router_test.gnoXXX | 161 ------------- pool/_TEST_pool_router_test.gno_WIP | 192 ++++++++++++++++ ...est.gnoa => _TEST_pool_single_lp_test.gno} | 0 pool/pool_router.gno | 214 ++++++++---------- 6 files changed, 308 insertions(+), 290 deletions(-) rename pool/{_TEST_pool_multi_lp_fee_api_test.gno => _TEST_pool_multi_lp_fee_api_test.gnoa} (100%) delete mode 100644 pool/_TEST_pool_router_test.gnoXXX create mode 100644 pool/_TEST_pool_router_test.gno_WIP rename pool/{_TEST_pool_single_lp_test.gnoa => _TEST_pool_single_lp_test.gno} (100%) diff --git a/pool/_TEST_math_logic_test.gnoa b/pool/_TEST_math_logic_test.gnoa index c4ecd645..4b1230b3 100644 --- a/pool/_TEST_math_logic_test.gnoa +++ b/pool/_TEST_math_logic_test.gnoa @@ -6,22 +6,28 @@ import ( "gno.land/p/demo/testutils" - pos "gno.land/r/position" + _ "gno.land/r/grc20_wrapper" ) var ( gsa = testutils.TestAddress("gsa") lp01 = testutils.TestAddress("lp01") + poolAddr = std.DerivePkgAddr("gno.land/r/pool") + posAddr = std.DerivePkgAddr("gno.land/r/position") +) + +var ( fooPath = "gno.land/r/foo" // token1 barPath = "gno.land/r/bar" // token2 - pFee uint16 = 500 + pFee = uint16(500) sqrtPrice bigint = 130621891405341611593710811006 - tickLower int32 = 9000 - tickUpper int32 = 11000 - liquidityAmount bigint = 123456789 - currentTick int32 = 10000 + tickLower = int32(9000) + tickUpper = int32(11000) + liquidityExpect = bigint(100_000_000) + + currentTick = int32(10000) ) func init() { @@ -41,16 +47,17 @@ func TestGetTickFromSqrtRatio(t *testing.T) { } func TestDrySwap_ZeroForOneTrue_AmountSpecified_Positive_16000(t *testing.T) { + std.TestSetPrevRealm("gno.land/r/position") std.TestSetOrigCaller(lp01) // no mint == no liquidity => swap will fail shouldPanic(t, func() { DrySwap(fooPath, barPath, pFee, "_", true, 16000, MIN_PRICE) }) // not enough mint == swap will fail - pos.Mint(fooPath, barPath, pFee, tickLower, tickUpper, 1000, 1000, 0, 0, 9999999999) + Mint(fooPath, barPath, pFee, posAddr, tickLower, tickUpper, 1000) shouldPanic(t, func() { DrySwap(fooPath, barPath, pFee, "_", true, 16000, MIN_PRICE) }) - pos.Mint(fooPath, barPath, pFee, tickLower, tickUpper, 100000, 100000, 0, 0, 9999999999) + Mint(fooPath, barPath, pFee, posAddr, tickLower, tickUpper, liquidityExpect) // zeroForOne true // amountSpecified 16000 @@ -64,7 +71,7 @@ func TestDrySwap_ZeroForOneTrue_AmountSpecified_Positive_16000(t *testing.T) { MIN_PRICE, // sqrtPriceLimitX96 ) shouldEQ(t, input, bigint(16000)) - shouldEQ(t, output, bigint(-42574)) + shouldEQ(t, output, bigint(-43457)) } func TestDrySwap_ZeroForOneTrue_AmountSpecified_Negative_16000(t *testing.T) { @@ -81,7 +88,7 @@ func TestDrySwap_ZeroForOneTrue_AmountSpecified_Negative_16000(t *testing.T) { MIN_SQRT_RATIO+1, // sqrtPriceLimitX96 ) - shouldEQ(t, input, bigint(5934)) + shouldEQ(t, input, bigint(5888)) shouldEQ(t, output, bigint(-15999)) } @@ -98,7 +105,7 @@ func TestDrySwap_ZeroForOneFalse_AmountSpecified_Positive_16000(t *testing.T) { 16000, // amountSpecified MAX_SQRT_RATIO-1, // sqrtPriceLimitX96 ) - shouldEQ(t, input, bigint(-42574)) + shouldEQ(t, input, bigint(-43457)) shouldEQ(t, output, bigint(16000)) } @@ -116,7 +123,7 @@ func TestDrySwap_ZeroForOneFalse_AmountSpecified_Negative_16000(t *testing.T) { MAX_SQRT_RATIO-1, // sqrtPriceLimitX96 ) shouldEQ(t, input, bigint(-15999)) - shouldEQ(t, output, bigint(5934)) + shouldEQ(t, output, bigint(5888)) } /* HELPER */ diff --git a/pool/_TEST_pool_multi_lp_fee_api_test.gno b/pool/_TEST_pool_multi_lp_fee_api_test.gnoa similarity index 100% rename from pool/_TEST_pool_multi_lp_fee_api_test.gno rename to pool/_TEST_pool_multi_lp_fee_api_test.gnoa diff --git a/pool/_TEST_pool_router_test.gnoXXX b/pool/_TEST_pool_router_test.gnoXXX deleted file mode 100644 index 1a036894..00000000 --- a/pool/_TEST_pool_router_test.gnoXXX +++ /dev/null @@ -1,161 +0,0 @@ -package pool - -import ( - "std" - "testing" - - "encoding/gjson" - - "gno.land/p/demo/testutils" - "gno.land/r/demo/users" - - bar "gno.land/r/bar" - foo "gno.land/r/foo" - - pos "gno.land/r/position" -) - -var ( - gsa = testutils.TestAddress("gsa") // Gnoswap Admin - lp01 = testutils.TestAddress("lp01") // Liquidity Provider 01 - - poolAddr = std.DerivePkgAddr("gno.land/r/pool") -) - -// 1. Init -func TestInitManual(t *testing.T) { - std.TestSetOrigCaller(gsa) - InitManual() - std.TestSkipHeights(1) -} - -func TestCreatePool(t *testing.T) { - CreatePool("foo", "bar", uint16(500), 130621891405341611593710811006) // 2.7181459268 - CreatePool("foo", "bar", uint16(3000), 130621891405341611593710811006) // 2.7181459268 - std.TestSkipHeights(1) -} - -func TestPositionMint500(t *testing.T) { - std.TestSetOrigCaller(lp01) - - _, _, m0, m1 := pos.Mint( - "foo", // token0 - "bar", // token1 - uint16(500), // fee 3000 ~= tickSpacing 60 - 6600, // tickLower - 10200, // tickUpper - 100, // amount0Desired - 100, // amount1Desired - 0, // amount0Min - 0, // amount1Min - 9999999999, // deadline - ) - shouldEQ(t, m0, bigint(2)) - shouldEQ(t, m1, bigint(99)) -} - -func TestPositionMint3000(t *testing.T) { - std.TestSetOrigCaller(lp01) - - _, _, m0, m1 := pos.Mint( - "foo", // token0 - "bar", // token1 - uint16(3000), // fee 3000 ~= tickSpacing 60 - 6600, // tickLower - 10200, // tickUpper - 1000, // amount0Desired - 1000, // amount1Desired - 0, // amount0Min - 0, // amount1Min - 9999999999, // deadline - ) - shouldEQ(t, m0, bigint(23)) - shouldEQ(t, m1, bigint(999)) -} - -func TestFindBestPoolTruePositive(t *testing.T) { - bestPoolPathDetail := FindBestPool( - "foo", // tokenA - "bar", // tokenB - true, // zeroForOne - 200, // amountSpecified - ) - jsonStr := gjson.Parse(bestPoolPathDetail) - shouldEQ(t, jsonStr.Get("response.data.pool_path").String(), "bar_foo_3000") - - // should return empty if not enough balance - { - bestPoolPathDetail := FindBestPool( - "foo", // tokenA - "bar", // tokenB - true, // zeroForOne - 438, // amountSpecified - ) - - jsonStr := gjson.Parse(bestPoolPathDetail) - shouldEQ(t, jsonStr.Get("response.data.pool_path").String(), "") - } -} - -func TestFindBestPoolTrueNegative(t *testing.T) { - bestPoolPathDetail := FindBestPool( - "foo", // tokenA - "bar", // tokenB - true, // zeroForOne - -888, // amountSpecified - ) - jsonStr := gjson.Parse(bestPoolPathDetail) - shouldEQ(t, jsonStr.Get("response.data.pool_path").String(), "bar_foo_3000") -} - -func TestFindBestPoolFalsePositive(t *testing.T) { - bestPoolPathDetail := FindBestPool( - "foo", // tokenA - "bar", // tokenB - false, // zeroForOne - 5, // amountSpecified - ) - jsonStr := gjson.Parse(bestPoolPathDetail) - shouldEQ(t, jsonStr.Get("response.data.pool_path").String(), "bar_foo_3000") -} - -func TestFindBestPoolFalseNegative(t *testing.T) { - bestPoolPathDetail := FindBestPool( - "foo", // tokenA - "bar", // tokenB - false, // zeroForOne - -11, // amountSpecified - ) - jsonStr := gjson.Parse(bestPoolPathDetail) - shouldEQ(t, jsonStr.Get("response.data.pool_path").String(), "bar_foo_3000") -} - -func TestFindBestPoolWrong(t *testing.T) { - shouldPanic(t, func() { FindBestPool("foo", "bar", true, 0) }) -} - -/* HELPER */ -func shouldEQ(t *testing.T, got, expected interface{}) { - if got != expected { - t.Errorf("got %v, expected %v", got, expected) - } -} - -func shouldPanic(t *testing.T, f func()) { - defer func() { - if r := recover(); r == nil { - t.Errorf("expected panic") - } - }() - f() -} - -func fooBalance(addr std.Address) uint64 { - user := users.AddressOrName(addr) - return foo.BalanceOf(user) -} - -func barBalance(addr std.Address) uint64 { - user := users.AddressOrName(addr) - return bar.BalanceOf(user) -} diff --git a/pool/_TEST_pool_router_test.gno_WIP b/pool/_TEST_pool_router_test.gno_WIP new file mode 100644 index 00000000..4ce078ba --- /dev/null +++ b/pool/_TEST_pool_router_test.gno_WIP @@ -0,0 +1,192 @@ +package pool + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + + _ "gno.land/r/grc20_wrapper" +) + +var ( + gsa = testutils.TestAddress("gsa") // Gnoswap Admin + lp01 = testutils.TestAddress("lp01") // Liquidity Provider 01 + + poolAddr = std.DerivePkgAddr("gno.land/r/pool") + posAddr = std.DerivePkgAddr("gno.land/r/position") +) + +var ( + // Common + fooPath = "gno.land/r/foo" // token1 + barPath = "gno.land/r/bar" // token2 + bazPath = "gno.land/r/baz" // token3 + quxPath = "gno.land/r/qux" // token4 +) + +// 1. Init +func TestInitManual(t *testing.T) { + std.TestSetOrigCaller(gsa) + InitManual() + std.TestSkipHeights(1) +} + +func TestCreatePool(t *testing.T) { + CreatePool(fooPath, barPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + CreatePool(fooPath, barPath, uint16(500), 215353707227994575755767921544) // tick = 20_000, ratio = 7.388317279516561 + + CreatePool(fooPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + + CreatePool(barPath, bazPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + CreatePool(barPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + + CreatePool(bazPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + + // std.TestSkipHeights(6) + // shouldEQ(t, len(pools), 6) +} + +func TestMintFooBar100(t *testing.T) { + std.TestSetPrevRealm("gno.land/r/position") + std.TestSetOrigCaller(lp01) + + m0, m1 := Mint( + fooPath, + barPath, + uint16(100), + posAddr, + int32(9000), + int32(11000), + bigint(100000000), + ) + + shouldEQ(t, m0, bigint(2958014)) + shouldEQ(t, m1, bigint(8040315)) +} + +func TestMintFooBar500(t *testing.T) { + std.TestSetPrevRealm("gno.land/r/position") + std.TestSetOrigCaller(lp01) + + m0, m1 := Mint( + fooPath, + barPath, + uint16(500), + posAddr, + int32(19000), + int32(21000), + bigint(100000000), + ) + + shouldEQ(t, m0, bigint(1794171)) + shouldEQ(t, m1, bigint(13255907)) +} + +func TestMintBarBaz100(t *testing.T) { + std.TestSetPrevRealm("gno.land/r/position") + std.TestSetOrigCaller(lp01) + + m0, m1 := Mint( + barPath, + bazPath, + uint16(100), + posAddr, + int32(9000), + int32(11000), + bigint(100000000), + ) + + shouldEQ(t, m0, bigint(2958014)) + shouldEQ(t, m1, bigint(8040315)) +} + +func TestFindAllPoolPath(t *testing.T) { + // r3v4_xx: for now max hop is 3 + allPaths := FindAllPoolPath(fooPath, quxPath, 3) + + shouldEQ(t, len(allPaths), 5) +} + +func TestFindBestPath(t *testing.T) { + // for _, path := range allPaths { + // println(path) + // } + + // bestPoolPathDetail := FindBestPool( + // fooPath, // tokenA + // barPath, // tokenB + // true, // zeroForOne + // 200, // amountSpecified + // ) + // jsonStr := gjson.Parse(bestPoolPathDetail) + // shouldEQ(t, jsonStr.Get("response.data.pool_path").String(), foo_3000") + + // // should return empty if not enough balance + // { + // bestPoolPathDetail := FindBestPool( + // fooPath, // tokenA + // barPath, // tokenB + // true, // zeroForOne + // 438, // amountSpecified + // ) + + // jsonStr := gjson.Parse(bestPoolPathDetail) + // shouldEQ(t, jsonStr.Get("response.data.pool_path").String(), "") + // } + // } + + // func TestFindBestPoolTrueNegative(t *testing.T) { + // bestPoolPathDetail := FindBestPool( + // fooPath, // tokenA + // barPath, // tokenB + // true, // zeroForOne + // -888, // amountSpecified + // ) + // jsonStr := gjson.Parse(bestPoolPathDetail) + // shouldEQ(t, jsonStr.Get("response.data.pool_path").String(), barPathfoo_3000") + // } + + // func TestFindBestPoolFalsePositive(t *testing.T) { + // bestPoolPathDetail := FindBestPool( + // fooPath, // tokenA + // barPath, // tokenB + // false, // zeroForOne + // 5, // amountSpecified + // ) + // jsonStr := gjson.Parse(bestPoolPathDetail) + // shouldEQ(t, jsonStr.Get("response.data.pool_path").String(), barPathfoo_3000") + // } + + // func TestFindBestPoolFalseNegative(t *testing.T) { + // bestPoolPathDetail := FindBestPool( + // fooPath, // tokenA + // barPath, // tokenB + // false, // zeroForOne + // -11, // amountSpecified + // ) + // jsonStr := gjson.Parse(bestPoolPathDetail) + // shouldEQ(t, jsonStr.Get("response.data.pool_path").String(), barPathfoo_3000") +} + +/* HELPER */ +func shouldEQ(t *testing.T, got, expected interface{}) { + if got != expected { + t.Errorf("got %v, expected %v", got, expected) + } +} + +func shouldNEQ(t *testing.T, got, expected interface{}) { + if got == expected { + t.Errorf("got %v, expected %v", got, expected) + } +} + +func shouldPanic(t *testing.T, f func()) { + defer func() { + if r := recover(); r == nil { + t.Errorf("expected panic") + } + }() + f() +} diff --git a/pool/_TEST_pool_single_lp_test.gnoa b/pool/_TEST_pool_single_lp_test.gno similarity index 100% rename from pool/_TEST_pool_single_lp_test.gnoa rename to pool/_TEST_pool_single_lp_test.gno diff --git a/pool/pool_router.gno b/pool/pool_router.gno index bce84494..6f755060 100644 --- a/pool/pool_router.gno +++ b/pool/pool_router.gno @@ -7,149 +7,129 @@ import ( "gno.land/p/demo/ufmt" ) -func FindBestPool( - tokenA string, - tokenB string, - zeroForOne bool, - amountSpecified bigint, -) string { - if tokenA == tokenB { - panic("token pair cannot be the same") - } +type POSSIBLE_PATHS map[int][]string - if tokenA > tokenB { - tokenA, tokenB = tokenB, tokenA - } +func FindAllPoolPath( + inputTokenPath string, + outputTokenPath string, + maxDepths int, +) POSSIBLE_PATHS { - partialPath := tokenA + "_" + tokenB + "_" - foundPool := []string{} + tokenPairs := make(map[string][]string) for poolPath, _ := range pools { - if strings.HasPrefix(poolPath, partialPath) { - foundPool = append(foundPool, poolPath) - } + token0Path, token1Path, pFee := poolPathWithFeeDivide(poolPath) + + k := token0Path + v := token1Path + ":" + strconv.Itoa(int(pFee)) + + tokenPairs[k] = append(tokenPairs[k], v) } - // check if amount is enough - firstSelectedPool := []string{} - if false { - continue - } else if zeroForOne == true && amountSpecified > 0 { - for _, singlePool := range foundPool { - func() { - defer func() { - if r := recover(); r != nil { - continue - } - }() - - _, _, fee := poolPathDivide(singlePool) - es0, es1 := DrySwap( - tokenA, - tokenB, - fee, - "", - true, - amountSpecified, - MIN_PRICE, - ) - - pool := GetPoolFromPoolKey(singlePool) - if pool.balances.token1 > (-1 * es1) { // must be bigger (can't be equal due to fee) - firstSelectedPool = append(firstSelectedPool, singlePool) - } - }() - } + // r3v4_xx: pass max_depth + possiblePaths := getSwapPaths(tokenPairs, inputTokenPath, outputTokenPath) - } else if zeroForOne == true && amountSpecified < 0 { - for _, singlePool := range foundPool { - pool := GetPoolFromPoolKey(singlePool) + return possiblePaths +} - if pool.balances.token1 > (-1 * amountSpecified) { // must be bigger (can't be equal due to fee) - firstSelectedPool = append(firstSelectedPool, singlePool) +func getSwapPaths( + tokenPairs map[string][]string, + inputTokenPath string, + outputTokenPath string, +) POSSIBLE_PATHS { + // r3v4_xx: handle max_depth + + possiblePaths := make(POSSIBLE_PATHS, 0) + + for firstPath, secondPaths := range tokenPairs { + if firstPath == inputTokenPath { // find if direct path exists + for _, secondPath := range secondPaths { + if strings.HasPrefix(secondPath, outputTokenPath) { + fullPath := inputTokenPath + " > " + secondPath + possiblePaths[len(possiblePaths)] = []string{fullPath} + } } - } - - } else if zeroForOne == false && amountSpecified > 0 { - for _, singlePool := range foundPool { - func() { - defer func() { - if r := recover(); r != nil { - continue + } else { // no direct path + for _, secondPathWithFee := range secondPaths { + secondPath, sFee := singlePoolPathWithFeeDivide(secondPathWithFee) + + if secondPath == inputTokenPath { + // l2 go + for i, thirdPathWithFee := range tokenPairs[secondPath] { + thirdPath, tFee := singlePoolPathWithFeeDivide(thirdPathWithFee) + + if strings.HasPrefix(thirdPathWithFee, outputTokenPath) { + fullPath := inputTokenPath + " > " + firstPath + ":" + strconv.Itoa(int(sFee)) + " > " + thirdPathWithFee + possiblePaths[len(possiblePaths)] = []string{fullPath} + } + } + } else { + // l3 go + for i, secondPathWithFee := range tokenPairs[firstPath] { + secondPath, sFee := singlePoolPathWithFeeDivide(secondPathWithFee) + + if strings.HasPrefix(secondPathWithFee, inputTokenPath) { + partialPath := "" + partialPath += inputTokenPath + " > " + firstPath + ":" + strconv.Itoa(int(sFee)) + // println("F PARTIAL:", partialPath) + + for _, thirdPathWithFee := range tokenPairs[firstPath] { + thirdPath, tFee := singlePoolPathWithFeeDivide(thirdPathWithFee) + + if thirdPath != inputTokenPath && thirdPath != outputTokenPath { + partialPath += " > " + thirdPathWithFee + + for i, fourthPathWithFee := range tokenPairs[thirdPath] { + if strings.HasPrefix(fourthPathWithFee, outputTokenPath) { + fourthPath, _ := singlePoolPathWithFeeDivide(fourthPathWithFee) + + // THIS IS FULL PATH + partialPath += " > " + fourthPathWithFee + possiblePaths[len(possiblePaths)] = []string{partialPath} + + firstToSecond := tokenPairs[firstPath] + toDel := secondPath + ":" + strconv.Itoa(int(sFee)) + remove(firstToSecond, toDel) + } + } + } + } + } } - }() - - _, _, fee := poolPathDivide(singlePool) - es0, es1 := DrySwap( - tokenA, - tokenB, - fee, - "", - false, - amountSpecified, - MAX_PRICE, - ) - - pool := GetPoolFromPoolKey(singlePool) - if pool.balances.token1 > (-1 * es0) { // must be bigger (can't be equal due to fee) - firstSelectedPool = append(firstSelectedPool, singlePool) } - }() - } - - } else if zeroForOne == false && amountSpecified < 0 { - for _, singlePool := range foundPool { - pool := GetPoolFromPoolKey(singlePool) - if pool.balances.token0 > (-1 * amountSpecified) { // must be bigger (can't be equal due to fee) - firstSelectedPool = append(firstSelectedPool, singlePool) } } - } else { - panic(ufmt.Sprintf("[POOL] pool_router.gno__FindBestPool() || unknown swap condition, zeroForOne: %t, amountSpecified: %d", zeroForOne, amountSpecified)) } - // check tick and return - var poolWithTick map[int32]string = make(map[int32]string) - - minTick := int32(887272) - maxTick := int32(-887272) - for _, singlePool := range firstSelectedPool { - // save tick with poolPath - pool := GetPoolFromPoolKey(singlePool) - poolTick := pool.slot0.tick - - poolWithTick[poolTick] = singlePool + return possiblePaths +} - // find min - if poolTick < minTick { - minTick = poolTick - } +func poolPathWithFeeDivide(poolPath string) (string, string, uint16) { + poolPathSplit := strings.Split(poolPath, ":") - // find max - if poolTick > maxTick { - maxTick = poolTick - } + if len(poolPathSplit) != 3 { + panic(ufmt.Sprintf("[POOL] pool_router.gno__poolPathWithFeeDivide() || len(poolPathSplit) != 3, poolPath: %s", poolPath)) } - if zeroForOne == true { // if token0 is being sold to buy token1, then we want to find the pool with the largest tick (more token1 can be bought) - return ApiGetPool(poolWithTick[maxTick]) - - } else { // if token1 is being sold to buy token0, then we want to find the pool with the smallest tick (more token0 can be bought) - return ApiGetPool(poolWithTick[minTick]) + feeInt, err := strconv.Atoi(poolPathSplit[2]) + if err != nil { + panic(ufmt.Sprintf("[POOL] pool_router.gno__poolPathWithFeeDivide() || cannot convert fee(%s) to uint16", poolPathSplit[2])) } + + return poolPathSplit[0], poolPathSplit[1], uint16(feeInt) } -func poolPathDivide(poolPath string) (string, string, uint16) { - poolPathSplit := strings.Split(poolPath, "_") +func singlePoolPathWithFeeDivide(poolPath string) (string, uint16) { + singlePoolPathSplit := strings.Split(poolPath, ":") - if len(poolPathSplit) != 3 { - panic(ufmt.Sprintf("[POOL] pool_router.gno__poolPathDivide() || len(poolPathSplit) != 3, poolPath: %s", poolPath)) + if len(singlePoolPathSplit) != 2 { + panic(ufmt.Sprintf("[POOL] pool_router.gno__singlePoolPathWithFeeDivide || len(singlePoolPathSplit) != 2, poolPath: %s", poolPath)) } - feeInt, err := strconv.Atoi(poolPathSplit[2]) + feeInt, err := strconv.Atoi(singlePoolPathSplit[1]) if err != nil { - panic(ufmt.Sprintf("[POOL] pool_router.gno__poolPathDivide() || cannot convert fee(%s) to uint16", poolPathSplit[2])) + panic(ufmt.Sprintf("[POOL] pool_router.gno__singlePoolPathWithFeeDivide() || cannot convert fee(%s) to uint16", singlePoolPathSplit[1])) } - return poolPathSplit[0], poolPathSplit[1], uint16(feeInt) + return singlePoolPathSplit[0], uint16(feeInt) } From 01ac8802e81224c8c7a2a22a1c8e8e1c195d85ab Mon Sep 17 00:00:00 2001 From: n3wbie Date: Fri, 27 Oct 2023 17:34:40 +0900 Subject: [PATCH 09/35] feat: registered interface for position --- position/position_test.gno | 44 ++++++++++++++++++++------------------ position/test_helper.gno | 4 ++-- position/util.gno | 2 +- 3 files changed, 26 insertions(+), 24 deletions(-) diff --git a/position/position_test.gno b/position/position_test.gno index 16dd5f25..acd823ed 100644 --- a/position/position_test.gno +++ b/position/position_test.gno @@ -1,15 +1,17 @@ package position import ( - "encoding/gjson" "std" "testing" - "gno.land/p/demo/testutils" + "encoding/gjson" - p "gno.land/r/pool" + "gno.land/p/demo/testutils" nft "gno.land/r/gnft" + + _ "gno.land/r/grc20_wrapper" + p "gno.land/r/pool" ) var ( @@ -24,8 +26,8 @@ var ( var ( // Common - pToken0 = "foo" - pToken1 = "bar" + fooPath = "gno.land/r/foo" + barPath = "gno.land/r/bar" pFee = uint16(500) test_tickLower = int32(9000) @@ -37,7 +39,7 @@ var ( func TestPoolInitCreatePool(t *testing.T) { std.TestSetOrigCaller(own) p.InitManual() - p.CreatePool("foo", "bar", pFee, 130621891405341611593710811006) + p.CreatePool(fooPath, barPath, pFee, 130621891405341611593710811006) // fee // 500 = 0.05% // USv3 default @@ -46,7 +48,7 @@ func TestPoolInitCreatePool(t *testing.T) { // sqrtPrice // 130621891405341611593710811006 // tick = 10000 - shouldPanic(t, func() { p.CreatePool("foo", "bar", 500, 130621891405341611593710811006) }) + shouldPanic(t, func() { p.CreatePool(fooPath, barPath, 500, 130621891405341611593710811006) }) } // 2. Mint LP and Get GNFT @@ -60,8 +62,8 @@ func TestMint(t *testing.T) { poolOldToken1Bal := Token1Bal(PoolAddr) tTokenId, tLiquidity, tAmount0, tAmount1 := Mint( - pToken0, - pToken1, + fooPath, + barPath, pFee, test_tickLower, test_tickUpper, @@ -75,6 +77,7 @@ func TestMint(t *testing.T) { isOwner(t, tTokenId, lp01) shouldEQ(t, tTokenId, 1) shouldEQ(t, getNextId(), 2) + shouldEQ(t, Token0Bal(PoolAddr), poolOldToken0Bal+tAmount0) shouldEQ(t, Token1Bal(PoolAddr), poolOldToken1Bal+tAmount1) } @@ -88,8 +91,8 @@ func TestMint(t *testing.T) { poolOldToken1Bal := Token1Bal(PoolAddr) tTokenId, tLiquidity, tAmount0, tAmount1 := Mint( - pToken0, - pToken1, + fooPath, + barPath, pFee, test_tickLower, test_tickUpper, @@ -115,8 +118,8 @@ func TestMint(t *testing.T) { poolOldToken1Bal := Token1Bal(PoolAddr) tTokenId, tLiquidity, tAmount0, tAmount1 := Mint( - pToken0, - pToken1, + fooPath, + barPath, pFee, int32(14000), int32(18000), @@ -144,8 +147,8 @@ func TestMint(t *testing.T) { poolOldToken1Bal := Token1Bal(PoolAddr) tTokenId, tLiquidity, tAmount0, tAmount1 := Mint( - pToken0, - pToken1, + fooPath, + barPath, pFee, int32(7000), int32(9000), @@ -210,7 +213,7 @@ func TestDecreaseLiquidity(t *testing.T) { // lp01 decreases liquidity at tid 1 position ( in range ) { std.TestSetOrigCaller(lp01) - pool := p.GetPool(pToken0, pToken1, pFee) + pool := p.GetPool(fooPath, barPath, pFee) tTargetLiquidity := bigint(1234) @@ -273,7 +276,7 @@ func TestCollect(t *testing.T) { // lp01 did decrease some liquidity => there are some to collect { std.TestSetOrigCaller(lp01) - pool := p.GetPool(pToken0, pToken1, pFee) + pool := p.GetPool(fooPath, barPath, pFee) poolOldLiquidity := pool.GetLiquidity() poolOldToken0Bal := Token0Bal(PoolAddr) @@ -308,7 +311,7 @@ func TestCollect(t *testing.T) { // lp01 collect all { std.TestSetOrigCaller(lp01) - pool := p.GetPool(pToken0, pToken1, pFee) + pool := p.GetPool(fooPath, barPath, pFee) poolOldLiquidity := pool.GetLiquidity() poolOldToken0Bal := Token0Bal(PoolAddr) @@ -342,7 +345,7 @@ func TestCollect(t *testing.T) { // lp02 didn't decrease any liquidity => nothing to collect { std.TestSetOrigCaller(lp02) - pool := p.GetPool(pToken0, pToken1, pFee) + pool := p.GetPool(fooPath, barPath, pFee) poolOldLiquidity := pool.GetLiquidity() poolOldToken0Bal := Token0Bal(PoolAddr) @@ -468,7 +471,7 @@ func TestApiGetPositionByUser(t *testing.T) { shouldEQ(t, len(jsonStr.Get("response.data").Array()), 3) shouldEQ(t, jsonStr.Get("response.data.0.token_id").Int(), 2) - shouldEQ(t, jsonStr.Get("response.data.0.pool_key").String(), "bar_foo_500") + shouldEQ(t, jsonStr.Get("response.data.0.pool_key").String(), "gno.land/r/bar:gno.land/r/foo:500") shouldEQ(t, jsonStr.Get("response.data.0.tick_lower").Int(), 9000) shouldEQ(t, jsonStr.Get("response.data.0.tick_upper").Int(), 11000) shouldEQ(t, jsonStr.Get("response.data.0.liquidity").Int(), 24874) @@ -482,7 +485,6 @@ func TestApiGetPositionByUser(t *testing.T) { shouldEQ(t, jsonStr.Get("response.data.2.tick_lower").Int(), 7000) shouldEQ(t, jsonStr.Get("response.data.2.tick_upper").Int(), 9000) shouldEQ(t, jsonStr.Get("response.data.2.liquidity").Int(), 6700) - } /* HELPER */ diff --git a/position/test_helper.gno b/position/test_helper.gno index 7ec463ef..bb7566b9 100644 --- a/position/test_helper.gno +++ b/position/test_helper.gno @@ -37,9 +37,9 @@ func tid(tokenId interface{}) grc721.TokenID { } func Token0Bal(addr std.Address) bigint { - return bigint(foo.BalanceOf(a2u(addr))) + return bigint(bar.BalanceOf(a2u(addr))) } func Token1Bal(addr std.Address) bigint { - return bigint(bar.BalanceOf(a2u(addr))) + return bigint(foo.BalanceOf(a2u(addr))) } diff --git a/position/util.gno b/position/util.gno index 289fcc01..56111cfa 100644 --- a/position/util.gno +++ b/position/util.gno @@ -25,7 +25,7 @@ func a2u(addr std.Address) users.AddressOrName { } func poolKeyDivide(poolKey string) (string, string, uint16) { - res := strings.Split(poolKey, "_") + res := strings.Split(poolKey, ":") if len(res) != 3 { panic(ufmt.Sprintf("[POSITION] util.gno__poolKeyDivide() || invalid poolKey(%s)", poolKey)) } From 574ed1c7d48505a81c2e2e7d63298799c00c6aaf Mon Sep 17 00:00:00 2001 From: n3wbie Date: Fri, 27 Oct 2023 17:59:50 +0900 Subject: [PATCH 10/35] chore --- pool/pool_register.gno | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/pool/pool_register.gno b/pool/pool_register.gno index 009774d9..265d9227 100644 --- a/pool/pool_register.gno +++ b/pool/pool_register.gno @@ -56,10 +56,9 @@ func RegisterGRC20Interface(pkgPath string, igrc20 GRC20Interface) { // } _, found := findGRC20(pkgPath) - if found { + if !found { + appendGRC20Interface(pkgPath, igrc20) } - - appendGRC20Interface(pkgPath, igrc20) } func UnregisterGRC20Interface(pkgPath string) { From fec6afeeb9a34c6e02d11b4221f40b577da7b96f Mon Sep 17 00:00:00 2001 From: n3wbie Date: Fri, 27 Oct 2023 18:00:33 +0900 Subject: [PATCH 11/35] chore: rename --- staker/getter_api.gno | 8 ++--- staker/reward_math.gno | 24 +++++++------- staker/staker.gno | 10 +++--- staker/staker_test.gno | 74 ++++++++++++++++++++---------------------- staker/utils.gno | 11 ++++--- 5 files changed, 64 insertions(+), 63 deletions(-) diff --git a/staker/getter_api.gno b/staker/getter_api.gno index 738e10c5..b3c32f94 100644 --- a/staker/getter_api.gno +++ b/staker/getter_api.gno @@ -33,12 +33,12 @@ func ApiGetRewardByAddress(address std.Address) string { if deposit.owner == address { poolPath := deposit.targetPoolPath - // get internal gnos reward - internalGNOS := rewardMathComputeInternalRewardAmount(tokenId, deposit) + // get internal gns reward + internalGNS := rewardMathComputeInternalRewardAmount(tokenId, deposit) rewardList = append(rewardList, ApiRewardByAddress{ Type: "Internal", - Token: "GNOS", - Reward: internalGNOS, + Token: "GNS", + Reward: internalGNS, }) // find all external reward list for this pool diff --git a/staker/reward_math.gno b/staker/reward_math.gno index f6f9ae92..0e8e2488 100644 --- a/staker/reward_math.gno +++ b/staker/reward_math.gno @@ -1,24 +1,24 @@ package staker import ( - s "gno.land/r/position" - "gno.land/p/demo/ufmt" + + pos "gno.land/r/position" ) func rewardMathComputeInternalRewardAmount(tokenId uint64, deposit Deposit) uint64 { - // r3v4_xxx: calculate amount of `GNOS` to be minted by every block + // r3v4_xxx: calculate amount of `GNS` to be minted by every block // 1. get block creation time (5s for now) - // 2. calculate amount of `GNOS` to be minted by every block (10_000 for now) - // 3. above `GNOS amount`` is supposed to be minted to a separate wallet specified by onbloc - // 4. this logic is supposed to be included in GNOS token contract, not staker contract + // 2. calculate amount of `GNS` to be minted by every block (10_000 for now) + // 3. above `GNS amount`` is supposed to be minted to a separate wallet specified by onbloc + // 4. this logic is supposed to be included in GNS token contract, not staker contract blockRewardInit := 10_000 - // because of `~ 10 days` staking duration, certain block GNOS amount won't be distribute 100% as reward) - blockRewardLeft := blockRewardInit / 10 // gnos.BalanceOf(INTERNAL_REWARD_ACCOUNT) - blockRewardInit + // because of `~ 10 days` staking duration, certain block GNS amount won't be distribute 100% as reward) + blockRewardLeft := blockRewardInit / 10 // gns.BalanceOf(INTERNAL_REWARD_ACCOUNT) - blockRewardInit blockReward := blockRewardInit + blockRewardLeft // get pool tier and ratio - poolPath := s.PositionGetPositionPoolKey(tokenId) + poolPath := pos.PositionGetPositionPoolKey(tokenId) _, poolRatioX96 := getPoolTierAndRatio(poolPath) // get pool reward per block @@ -66,7 +66,7 @@ func rewardMathComputeExternalRewardAmount(tokenId uint64, deposit Deposit, ince // 1 second reward == total reward amount / reward duration monthlyReward = incentive.rewardAmount / uint64(incentiveDuration) * uint64(TIMESTAMP_30DAYS) default: - panic(ufmt.Sprintf("[STAKER] reward_math.gno || incentiveDuration(%s) at least 30 days", incentiveDuration)) + panic(ufmt.Sprintf("[STAKER] reward_math.gno || incentiveDuration(%d) at least 30 days", incentiveDuration)) } // calculate reward amount per block @@ -112,7 +112,7 @@ func getPoolTotalStakedLiquidity(poolPath string) bigint { // get all staked liquidity for tokenId, deposit := range deposits { // key is tokenId // used in this range loop only if deposit.targetPoolPath == poolPath { - tokenLiquidity := s.PositionGetPositionLiquidity(tokenId) + tokenLiquidity := pos.PositionGetPositionLiquidity(tokenId) poolStakedLiquidity += tokenLiquidity } } @@ -124,7 +124,7 @@ func getMyLiquidityRatio(poolPath string, tokenId uint64) bigint { poolStakedLiquidity := getPoolTotalStakedLiquidity(poolPath) // my(current tokenId) liquidity - myLiquidity := s.PositionGetPositionLiquidity(tokenId) + myLiquidity := pos.PositionGetPositionLiquidity(tokenId) // my liquidity ratio liqRatioX96 := (myLiquidity * Q96 / poolStakedLiquidity * Q96) // 2 times Q96 because of Q96 / Q96 diff --git a/staker/staker.gno b/staker/staker.gno index 2841964e..b92228d3 100644 --- a/staker/staker.gno +++ b/staker/staker.gno @@ -6,7 +6,7 @@ import ( "gno.land/p/demo/ufmt" gnft "gno.land/r/gnft" // GNFT, Gnoswap NFT - gnos "gno.land/r/gnos" // GNOS, INTERNAL Reward Token + gns "gno.land/r/gns" // GNS, INTERNAL Reward Token obl "gno.land/r/obl" // OBL, EXTERNAL Reward Token s "gno.land/r/position" @@ -30,8 +30,8 @@ func init() { poolTiers["BAR/FOO_500"] = 1 // DEV // tier 2 - poolTiers["GNOS/USDT_500"] = 2 - poolTiers["ATOM/GNOS_500"] = 2 + poolTiers["GNS/USDT_500"] = 2 + poolTiers["ATOM/GNS_500"] = 2 // tier 3 poolTiers["ATOM/GNOT_500"] = 3 @@ -142,10 +142,10 @@ func UnstakeToken( } // default `Internal` reward - internalGNOS := rewardMathComputeInternalRewardAmount(tokenId, deposit) + internalGNS := rewardMathComputeInternalRewardAmount(tokenId, deposit) // transfer it - gnos.TransferFrom(a2u(INTERNAL_REWARD_ACCOUNT), a2u(deposit.owner), uint64(internalGNOS)) + gns.TransferFrom(a2u(INTERNAL_REWARD_ACCOUNT), a2u(deposit.owner), uint64(internalGNS)) // unstaked status delete(deposits, tokenId) diff --git a/staker/staker_test.gno b/staker/staker_test.gno index e48fd908..71a95847 100644 --- a/staker/staker_test.gno +++ b/staker/staker_test.gno @@ -1,19 +1,20 @@ package staker import ( - "encoding/gjson" "std" "testing" + "encoding/gjson" + "gno.land/p/demo/testutils" - g "gno.land/r/gov" p "gno.land/r/pool" - s "gno.land/r/position" + pos "gno.land/r/position" gnft "gno.land/r/gnft" // GNFT, Gnoswap NFT - gnos "gno.land/r/gnos" // GNOS, INTERNAL Reward Token - obl "gno.land/r/obl" // OBL, EXTERNAL Reward Token + gns "gno.land/r/gns" // GNS, Gnoswap Share + + _ "gno.land/r/grc20_wrapper" ) var ( @@ -25,45 +26,42 @@ var ( ira = testutils.TestAddress("ira") // Internal Reward Account ) +var ( + fooPath = "gno.land/r/foo" + barPath = "gno.land/r/bar" +) + func init() { // init pool tiers // tier 1 - poolTiers["BAR/FOO_500"] = 1 // DEV + poolTiers["gno.land/r/bar:gno.land/r/foo:500"] = 1 // DEV // tier 2 - poolTiers["GNOS/USDT_500"] = 2 - poolTiers["ATOM/GNOS_500"] = 2 + poolTiers["GNS/USDT_500"] = 2 + poolTiers["ATOM/GNS_500"] = 2 // tier 3 poolTiers["ATOM/GNOT_500"] = 3 poolTiers["ATOM/USDT_500"] = 3 poolTiers["ATOM/WETH_500"] = 3 - - // debug - print addr - // println(pc01, "// pc01") - // println(ci01, "// ci01") - // println(lp01, "// lp01") - // println(lp02, "// lp02") - // println(ira, "// internal_reward_account") - // println(p.GetOrigPkgAddr(), "// pool") - // println(s.GetOrigPkgAddr(), "// position") - // println(GetOrigPkgAddr(), "// staker") } func TestPoolInitCreatePool(t *testing.T) { std.TestSetOrigCaller(pc01) + p.InitManual() std.TestSkipHeights(1) - p.CreatePool("foo", "bar", 500, 130621891405341611593710811006) + + p.CreatePool(fooPath, barPath, 500, 130621891405341611593710811006) std.TestSkipHeights(1) } func TestPositionMint(t *testing.T) { { std.TestSetOrigCaller(lp01) - tPosTokenId, tPosLiquidity, tPosAmount0, tPosAmount1 := s.Mint( - std.Address("foo"), // token0 - std.Address("bar"), // token1 + tPosTokenId, tPosLiquidity, tPosAmount0, tPosAmount1 := pos.Mint( + fooPath, // token0 + barPath, // token1 uint16(500), // fee int32(9000), // tickLower int32(11000), // tickUpper @@ -86,9 +84,9 @@ func TestPositionMint(t *testing.T) { { std.TestSetOrigCaller(lp02) - tPosTokenId, tPosLiquidity, tPosAmount0, tPosAmount1 := s.Mint( - std.Address("foo"), // token0 - std.Address("bar"), // token1 + tPosTokenId, tPosLiquidity, tPosAmount0, tPosAmount1 := pos.Mint( + fooPath, // token0 + barPath, // token1 uint16(500), // fee int32(9100), // tickLower int32(12000), // tickUpper @@ -114,16 +112,16 @@ func TestCreateExternalIncentive(t *testing.T) { std.TestSetOrigCaller(ci01) CreateExternalIncentive( - "bar_foo_500", // targetPoolPath - "OBL", // rewardToken - 10_000_000_000, // rewardAmount - GetTimestamp(), // startTimestamp + "gno.land/r/bar:gno.land/r/foo:500", // targetPoolPath + "OBL", // rewardToken + 10_000_000_000, // rewardAmount + GetTimestamp(), // startTimestamp GetTimestamp()+TIMESTAMP_30DAYS+TIMESTAMP_30DAYS, // endTimestamp ) std.TestSkipHeights(5) shouldPanic(t, func() { - CreateExternalIncentive("bar_foo_500", "OBL", 10_000_000_000, GetTimestamp(), GetTimestamp()+TIMESTAMP_30DAYS+TIMESTAMP_30DAYS) + CreateExternalIncentive("gno.land/r/bar:gno.land/r/foo:500", "OBL", 10_000_000_000, GetTimestamp(), GetTimestamp()+TIMESTAMP_30DAYS+TIMESTAMP_30DAYS) }) } @@ -153,8 +151,8 @@ func TestApiGetRewardsByAddress(t *testing.T) { gra := ApiGetRewardByAddress(lp01) jsonStr := gjson.Parse(gra) shouldEQ(t, jsonStr.Get("response.data.0.type").String(), "Internal") - shouldEQ(t, jsonStr.Get("response.data.0.token").String(), "GNOS") - shouldEQ(t, jsonStr.Get("response.data.0.reward").Int(), 252) + shouldEQ(t, jsonStr.Get("response.data.0.token").String(), "GNS") + shouldEQ(t, jsonStr.Get("response.data.0.reward").Int(), 126) shouldEQ(t, jsonStr.Get("response.data.1.type").String(), "External") shouldEQ(t, jsonStr.Get("response.data.1.token").String(), "OBL") shouldEQ(t, jsonStr.Get("response.data.1.reward").Int(), 486) @@ -165,8 +163,8 @@ func TestApiGetRewardsByAddress(t *testing.T) { gra := ApiGetRewardByAddress(lp02) jsonStr := gjson.Parse(gra) shouldEQ(t, jsonStr.Get("response.data.0.type").String(), "Internal") - shouldEQ(t, jsonStr.Get("response.data.0.token").String(), "GNOS") - shouldEQ(t, jsonStr.Get("response.data.0.reward").Int(), 1397) + shouldEQ(t, jsonStr.Get("response.data.0.token").String(), "GNS") + shouldEQ(t, jsonStr.Get("response.data.0.reward").Int(), 698) shouldEQ(t, jsonStr.Get("response.data.1.type").String(), "External") shouldEQ(t, jsonStr.Get("response.data.1.token").String(), "OBL") shouldEQ(t, jsonStr.Get("response.data.1.reward").Int(), 2696) @@ -182,8 +180,8 @@ func TestUnstakeToken(t *testing.T) { shouldEQ(t, gnft.OwnerOf(tid(1)), lp01) // lp01 // check reward - shouldEQ(t, gnos.BalanceOf(a2u(lp01)), 252) // internal - shouldEQ(t, obl.BalanceOf(a2u(lp01)), 486) // external + shouldEQ(t, gns.BalanceOf(a2u(lp01)), 252) // internal + shouldEQ(t, obl.BalanceOf(a2u(lp01)), 486) // external } { @@ -194,8 +192,8 @@ func TestUnstakeToken(t *testing.T) { shouldEQ(t, gnft.OwnerOf(tid(2)), lp02) // lp02 // check reward - shouldEQ(t, gnos.BalanceOf(a2u(lp02)), 1650) // internal - shouldEQ(t, obl.BalanceOf(a2u(lp02)), 3182) // external + shouldEQ(t, gns.BalanceOf(a2u(lp02)), 1650) // internal + shouldEQ(t, obl.BalanceOf(a2u(lp02)), 3182) // external } } diff --git a/staker/utils.gno b/staker/utils.gno index ff06ca93..b66cc74a 100644 --- a/staker/utils.gno +++ b/staker/utils.gno @@ -12,18 +12,21 @@ import ( ) func poolKeyDivide(poolKey string) string { - res := strings.Split(poolKey, "_") + + res := strings.Split(poolKey, ":") if len(res) != 3 { - panic(ufmt.Sprintf("[STAKER] staker.gno__poolKeyDivide() || invalid poolKey(%s)", poolKey)) + panic(ufmt.Sprintf("[STAKER] utils.gno__poolKeyDivide() || invalid poolKey(%s)", poolKey)) } pToken0, pToken1, fee := res[0], res[1], res[2] if pToken0 < pToken1 { - return ufmt.Sprintf("%s/%s_%s", strings.ToUpper(pToken0), strings.ToUpper(pToken1), fee) + zz := ufmt.Sprintf("%s:%s:%s", pToken0, pToken1, fee) + return ufmt.Sprintf("%s:%s:%s", pToken0, pToken1, fee) } - return ufmt.Sprintf("%s/%s_%s", strings.ToUpper(pToken1), strings.ToUpper(pToken0), fee) + zz := ufmt.Sprintf("%s:%s:%s", pToken1, pToken0, fee) + return ufmt.Sprintf("%s:%s:%s", pToken1, pToken0, fee) } func require(condition bool, message string) { From f177e6c195df7b73c76dc33f7cb211b0a67cdcbd Mon Sep 17 00:00:00 2001 From: n3wbie Date: Tue, 31 Oct 2023 18:14:55 +0900 Subject: [PATCH 12/35] chore: fourth token to test router --- _setup/qux/gno.mod | 1 + _setup/qux/qux.gno | 154 ++++++++++++++++++++++++++++++++++++++++ _setup/qux/qux_test.gno | 73 +++++++++++++++++++ 3 files changed, 228 insertions(+) create mode 100644 _setup/qux/gno.mod create mode 100644 _setup/qux/qux.gno create mode 100644 _setup/qux/qux_test.gno diff --git a/_setup/qux/gno.mod b/_setup/qux/gno.mod new file mode 100644 index 00000000..7af24dac --- /dev/null +++ b/_setup/qux/gno.mod @@ -0,0 +1 @@ +module gno.land/r/qux \ No newline at end of file diff --git a/_setup/qux/qux.gno b/_setup/qux/qux.gno new file mode 100644 index 00000000..0fd41cc0 --- /dev/null +++ b/_setup/qux/qux.gno @@ -0,0 +1,154 @@ +package qux + +import ( + "std" + "strings" + + "gno.land/p/demo/grc/grc20" + "gno.land/p/demo/ufmt" + "gno.land/r/demo/users" + + // for swap + "gno.land/p/demo/testutils" +) + +var ( + qux *grc20.AdminToken + admin std.Address = "g1jg8mtutu9khhfwc4nxmuhcpftf0pajdhfvsqf5" // TODO: helper to change admin +) + +func init() { + qux = grc20.NewAdminToken("Qux", "QUX", 4) + qux.Mint(admin, 1000000*10000) // @administrator (1M) + + // for swap > pool + var ( + lp01 = testutils.TestAddress("lp01") // Liquidity Provider 01 + lp02 = testutils.TestAddress("lp02") // Liquidity Provider 02 + lp03 = testutils.TestAddress("lp03") // Liquidity Provider 03 + tr01 = testutils.TestAddress("tr01") // Trader 01 + poolAddr = std.DerivePkgAddr("gno.land/r/pool") + posAddr = std.DerivePkgAddr("gno.land/r/position") + ) + + qux.Mint(lp01, 50000000000) + qux.Mint(lp02, 50000000000) + qux.Mint(lp03, 50000000000) + qux.Mint(tr01, 50000000000) + + qux.Approve(lp01, poolAddr, 50000000000) + qux.Approve(lp02, poolAddr, 50000000000) + qux.Approve(lp03, poolAddr, 50000000000) + qux.Approve(tr01, poolAddr, 50000000000) + + // qux.Approve(posAddr, poolAddr, 50000000000) +} + +// method proxies as public functions. +// + +// getters. +func GetGRC20() *grc20.AdminToken { + return qux +} + +func TotalSupply() uint64 { + return qux.TotalSupply() +} + +func BalanceOf(owner users.AddressOrName) uint64 { + balance, err := qux.BalanceOf(owner.Resolve()) + if err != nil { + panic(err) + } + return balance +} + +func Allowance(owner, spender users.AddressOrName) uint64 { + allowance, err := qux.Allowance(owner.Resolve(), spender.Resolve()) + if err != nil { + panic(err) + } + return allowance +} + +// setters. + +func Transfer(to users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + err := qux.Transfer(caller, to.Resolve(), amount) + if err != nil { + panic(err.Error()) + } +} + +func Approve(spender users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + err := qux.Approve(caller, spender.Resolve(), amount) + if err != nil { + panic(err.Error()) + } +} + +func TransferFrom(from, to users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + err := qux.TransferFrom(caller, from.Resolve(), to.Resolve(), amount) + if err != nil { + panic(err.Error()) + } +} + +// faucet. + +func Faucet() { + // FIXME: add limits? + // FIXME: add payment in gnot? + caller := std.PrevRealm().Addr() + qux.Mint(caller, 1000*10000) // 1k +} + +func FaucetL() { + // FIXME: add limits? + // FIXME: add payment in gnot? + caller := std.PrevRealm().Addr() + qux.Mint(caller, 50000000000000) // 50_000_000_000 +} + +// administration. + +func Mint(address users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + qux.Mint(address.Resolve(), amount) +} + +func Burn(address users.AddressOrName, amount uint64) { + caller := std.PrevRealm().Addr() + assertIsAdmin(caller) + qux.Burn(address.Resolve(), amount) +} + +// render. +// + +func Render(path string) string { + parts := strings.Split(path, "/") + c := len(parts) + + switch { + case path == "": + return qux.RenderHome() + case c == 2 && parts[0] == "balance": + owner := users.AddressOrName(parts[1]) + balance, _ := qux.BalanceOf(owner.Resolve()) + return ufmt.Sprintf("%d\n", balance) + default: + return "404\n" + } +} + +func assertIsAdmin(address std.Address) { + if address != admin { + panic("restricted access") + } +} diff --git a/_setup/qux/qux_test.gno b/_setup/qux/qux_test.gno new file mode 100644 index 00000000..e1ab5dbe --- /dev/null +++ b/_setup/qux/qux_test.gno @@ -0,0 +1,73 @@ +package qux + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + + "gno.land/r/demo/users" +) + +var ( + a1 = testutils.TestAddress("a1") + a2 = testutils.TestAddress("a2") + a3 = testutils.TestAddress("a3") + a4 = testutils.TestAddress("a4") +) + +func init() { + std.TestSetOrigCaller(a1) + Faucet() +} + +func TestTransfer(t *testing.T) { + std.TestSetOrigCaller(a1) + Transfer(a2u(a2), 100) + + std.TestSetOrigCaller(a2) + Transfer(a2u(a3), 95) + + shouldPanicWithMsg(t, func() { Transfer(a2u(a3), 10) }, "insufficient balance") +} + +func TestApprove(t *testing.T) { + std.TestSetOrigCaller(std.Address("")) + shouldPanicWithMsg(t, func() { Approve(a2u(a2), 1000) }, "invalid address") + + std.TestSetOrigCaller(a1) + shouldPanicWithMsg(t, func() { Approve(a2u(std.Address("")), 1000) }, "invalid address") +} + +func TestTransferFrom(t *testing.T) { + std.TestSetOrigCaller(a1) + Approve(a2u(a2), 1000) + + std.TestSetOrigCaller(a2) + TransferFrom(a2u(a1), a2u(a3), 100) + + // not enough allowance + shouldPanicWithMsg(t, func() { TransferFrom(a2u(a1), a2u(a3), 901) }, "insufficient allowance") + + // didn't approve + std.TestSetOrigCaller(a3) + shouldPanicWithMsg(t, func() { TransferFrom(a2u(a1), a2u(a4), 100) }, "insufficient allowance") + +} + +func a2u(addr std.Address) users.AddressOrName { + return users.AddressOrName(addr) +} + +func shouldPanicWithMsg(t *testing.T, f func(), msg string) { + defer func() { + if r := recover(); r == nil { + t.Errorf("The code did not panic") + } else { + if r != msg { + t.Errorf("excepted panic(%v), got(%v)", msg, r) + } + } + }() + f() +} From 5912fbe2b8abc3091e1c460837ae952a6bc3ccad Mon Sep 17 00:00:00 2001 From: n3wbie Date: Tue, 31 Oct 2023 18:15:11 +0900 Subject: [PATCH 13/35] chore: increase mint amount --- _setup/bar/bar.gno | 20 ++++++++++---------- _setup/baz/baz.gno | 20 ++++++++++---------- _setup/foo/foo.gno | 18 +++++++++--------- 3 files changed, 29 insertions(+), 29 deletions(-) diff --git a/_setup/bar/bar.gno b/_setup/bar/bar.gno index d38d9d1c..c3d263d5 100644 --- a/_setup/bar/bar.gno +++ b/_setup/bar/bar.gno @@ -31,17 +31,17 @@ func init() { posAddr = std.DerivePkgAddr("gno.land/r/position") ) - bar.Mint(lp01, 50000000) - bar.Mint(lp02, 50000000) - bar.Mint(lp03, 50000000) - bar.Mint(tr01, 50000000) + bar.Mint(lp01, 50000000000) + bar.Mint(lp02, 50000000000) + bar.Mint(lp03, 50000000000) + bar.Mint(tr01, 50000000000) - bar.Approve(lp01, poolAddr, 50000000) - bar.Approve(lp02, poolAddr, 50000000) - bar.Approve(lp03, poolAddr, 50000000) - bar.Approve(tr01, poolAddr, 50000000) + bar.Approve(lp01, poolAddr, 50000000000) + bar.Approve(lp02, poolAddr, 50000000000) + bar.Approve(lp03, poolAddr, 50000000000) + bar.Approve(tr01, poolAddr, 50000000000) - // bar.Approve(poolAddr, registryAddr, 50000000) + // bar.Approve(poolAddr, registryAddr, 50000000000) } // method proxies as public functions. @@ -112,7 +112,7 @@ func FaucetL() { // FIXME: add limits? // FIXME: add payment in gnot? caller := std.PrevRealm().Addr() - bar.Mint(caller, 50000000000) // 50_000_000_000 + bar.Mint(caller, 50000000000000) // 50_000_000_000 } // administration. diff --git a/_setup/baz/baz.gno b/_setup/baz/baz.gno index 2b52206b..f2491329 100644 --- a/_setup/baz/baz.gno +++ b/_setup/baz/baz.gno @@ -31,17 +31,17 @@ func init() { posAddr = std.DerivePkgAddr("gno.land/r/position") ) - baz.Mint(lp01, 50000000) - baz.Mint(lp02, 50000000) - baz.Mint(lp03, 50000000) - baz.Mint(tr01, 50000000) + baz.Mint(lp01, 50000000000) + baz.Mint(lp02, 50000000000) + baz.Mint(lp03, 50000000000) + baz.Mint(tr01, 50000000000) - baz.Approve(lp01, poolAddr, 50000000) - baz.Approve(lp02, poolAddr, 50000000) - baz.Approve(lp03, poolAddr, 50000000) - baz.Approve(tr01, poolAddr, 50000000) + baz.Approve(lp01, poolAddr, 50000000000) + baz.Approve(lp02, poolAddr, 50000000000) + baz.Approve(lp03, poolAddr, 50000000000) + baz.Approve(tr01, poolAddr, 50000000000) - baz.Approve(posAddr, poolAddr, 50000000) + baz.Approve(posAddr, poolAddr, 50000000000) } // method proxies as public functions. @@ -116,7 +116,7 @@ func FaucetL() { // FIXME: add payment in gnot? // caller := std.GetOrigCaller() caller := std.PrevRealm().Addr() - baz.Mint(caller, 50000000000) // 50_000_000_000 + baz.Mint(caller, 50000000000000) // 50_000_000_000 } // administration. diff --git a/_setup/foo/foo.gno b/_setup/foo/foo.gno index 066ba7ba..9f3dca40 100644 --- a/_setup/foo/foo.gno +++ b/_setup/foo/foo.gno @@ -32,17 +32,17 @@ func init() { posAddr = std.DerivePkgAddr("gno.land/r/position") ) - foo.Mint(lp01, 50000000) - foo.Mint(lp02, 50000000) - foo.Mint(lp03, 50000000) - foo.Mint(tr01, 50000000) + foo.Mint(lp01, 50000000000) + foo.Mint(lp02, 50000000000) + foo.Mint(lp03, 50000000000) + foo.Mint(tr01, 50000000000) - foo.Approve(lp01, poolAddr, 50000000) - foo.Approve(lp02, poolAddr, 50000000) - foo.Approve(lp03, poolAddr, 50000000) - foo.Approve(tr01, poolAddr, 50000000) + foo.Approve(lp01, poolAddr, 50000000000) + foo.Approve(lp02, poolAddr, 50000000000) + foo.Approve(lp03, poolAddr, 50000000000) + foo.Approve(tr01, poolAddr, 50000000000) - // foo.Approve(posAddr, poolAddr, 50000000) + // foo.Approve(posAddr, poolAddr, 50000000000) } // method proxies as public functions. From 771a0ff21016c670d65c59be0d7a67dbfee7e9be Mon Sep 17 00:00:00 2001 From: n3wbie Date: Tue, 31 Oct 2023 18:18:02 +0900 Subject: [PATCH 14/35] feat: Dryswap return bool instead of panic --- pool/math_logic.gno | 43 ++++++++++++++++++++++++------------------- 1 file changed, 24 insertions(+), 19 deletions(-) diff --git a/pool/math_logic.gno b/pool/math_logic.gno index dd8da390..08797f18 100644 --- a/pool/math_logic.gno +++ b/pool/math_logic.gno @@ -2,8 +2,6 @@ package pool import ( "std" - - "gno.land/p/demo/ufmt" ) // get sqrtX96 from tick @@ -28,26 +26,26 @@ func DrySwap( zeroForOne bool, amountSpecified bigint, sqrtPriceLimitX96 bigint, -) (bigint, bigint) { - require(amountSpecified != 0, "[POOL] math_logic.gno__DrySwap() || amountSpecified can't be zero") +) (bigint, bigint, bool) { + if !(amountSpecified != 0) { + return 0, 0, false + } pool := GetPool(pToken0, pToken1, pFee) - require(pool.liquidity > 0, ufmt.Sprintf("[POOL] math_logic.gno__DrySwap() || pool.liquidity(%d) must be > 0", pool.liquidity)) + if !(pool.liquidity > 0) { + return 0, 0, false + } slot0Start := pool.slot0 if zeroForOne { - require( - sqrtPriceLimitX96 < slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 > MIN_SQRT_RATIO, - ufmt.Sprintf("[POOL] math_logic.gno__DrySwap() || SPL-zeroForOne(T)__sqrtPriceLimitX96(%s) < slot0Start.sqrtPriceX96(%s) && sqrtPriceLimitX96(%s) > MIN_SQRT_RATIO(%s)", - sqrtPriceLimitX96, slot0Start.sqrtPriceX96, sqrtPriceLimitX96, MIN_SQRT_RATIO), - ) + if !(sqrtPriceLimitX96 < slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 > MIN_SQRT_RATIO) { + return 0, 0, false + } } else { - require( - sqrtPriceLimitX96 > slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 < MAX_SQRT_RATIO, - ufmt.Sprintf("[POOL] math_logic.gno__DrySwap() || SPL-zeroForOne(F)__sqrtPriceLimitX96(%s) > slot0Start.sqrtPriceX96(%s) && sqrtPriceLimitX96(%s) < MAX_SQRT_RATIO(%s)", - sqrtPriceLimitX96, slot0Start.sqrtPriceX96, sqrtPriceLimitX96, MAX_SQRT_RATIO), - ) + if !(sqrtPriceLimitX96 > slot0Start.sqrtPriceX96 && sqrtPriceLimitX96 < MAX_SQRT_RATIO) { + return 0, 0, false + } } cache := SwapCache{ @@ -122,7 +120,9 @@ func DrySwap( uint32(pool.fee), ) - require(step.amountIn != 0 && step.amountOut != 0, ufmt.Sprintf("[POOL] math_logic.gno__DrySwap() || step.amountIn(%d) != 0 && step.amountOut(%d) != 0", step.amountIn, step.amountOut)) + if !(step.amountIn != 0 && step.amountOut != 0) { + return 0, 0, false + } if exactInput { state.amountSpecifiedRemaining -= step.amountIn + step.feeAmount @@ -144,10 +144,15 @@ func DrySwap( } if zeroForOne { - require(pool.balances.token1 > (-1*amount1), ufmt.Sprintf("[POOL] math_logic.gno__DrySwap()_ZFO_T || pool.balances.token1(%s) > amount1(%s)", pool.balances.token1, (-1*amount1))) + if !(pool.balances.token1 > (-1 * amount1)) { + return 0, 0, false + } + } else { - require(pool.balances.token0 > (-1*amount0), ufmt.Sprintf("[POOL] math_logic.gno__DrySwap()_ZFO_F || pool.balances.token0(%s) > amount0(%s)", pool.balances.token0, (-1*amount0))) + if !(pool.balances.token0 > (-1 * amount0)) { + return 0, 0, false + } } - return amount0, amount1 + return amount0, amount1, true } From 2251f5958531de2897b3dd39b29862bbec3f25a2 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Tue, 31 Oct 2023 18:18:30 +0900 Subject: [PATCH 15/35] fix: use pool's token pair, not parameter --- pool/pool.gno | 92 +++++++++++++++------------------------------------ 1 file changed, 26 insertions(+), 66 deletions(-) diff --git a/pool/pool.gno b/pool/pool.gno index 9e9eb158..761a33c0 100644 --- a/pool/pool.gno +++ b/pool/pool.gno @@ -3,9 +3,7 @@ package pool import ( "std" - "gno.land/p/demo/grc/grc20" "gno.land/p/demo/ufmt" - "gno.land/r/demo/users" g "gno.land/r/gov" ) @@ -97,7 +95,7 @@ func Burn( ) (bigint, bigint) { require(PrevRealmPath() == "gno.land/r/position", ufmt.Sprintf("[POOL] pool.gno__Burn() || caller(%s) must be position contract", PrevRealmPath())) - requireUnsigned(amount, ufmt.Sprintf("[POOL] pool.gno__Burn() || amount(%s) >= 0", amount)) + requireUnsigned(amount, ufmt.Sprintf("[POOL] pool.gno__Burn() || amount(%d) >= 0", amount)) pool := GetPool(pToken0Path, pToken1Path, pFee) @@ -111,8 +109,8 @@ func Burn( ) amount0, amount1 := -amount0Int, -amount1Int - requireUnsigned(amount0, ufmt.Sprintf("pool.gno__Burn() || amount0(%s) >= 0", amount0)) - requireUnsigned(amount1, ufmt.Sprintf("pool.gno__Burn() || amount1(%s) >= 0", amount1)) + requireUnsigned(amount0, ufmt.Sprintf("pool.gno__Burn() || amount0(%d) >= 0", amount0)) + requireUnsigned(amount1, ufmt.Sprintf("pool.gno__Burn() || amount1(%d) >= 0", amount1)) if amount0 > 0 || amount1 > 0 { position.tokensOwed0 += amount0 @@ -183,6 +181,7 @@ func Swap( amountSpecified bigint, sqrtPriceLimitX96 bigint, ) (bigint, bigint) { + // r3v4_xxx: ONLY ROUTER CAN CALL THIS require(amountSpecified != 0, "[POOL] pool.gno__Swap() || amountSpecified can't be zero") pool := GetPool(pToken0Path, pToken1Path, pFee) @@ -378,29 +377,29 @@ func Swap( if amount1 < 0 { require(pool.balances.token1 > (-amount1), ufmt.Sprintf("[POOL] pool.gno__Swap() || pool.balances.token1(%s) > (-1 * amount1)(%s)", pool.balances.token1, (-amount1))) - ok := transferByRegisterCall(pToken1Path, recipient, uint64(-amount1)) + ok := transferByRegisterCall(pool.token1Path, recipient, uint64(-amount1)) if !ok { - panic("[POOL] pool.gno__Swap() || transferByRegisterCall(pToken1Path, recipient, uint64(-amount1)) failed") + panic("[POOL] pool.gno__Swap() || transferByRegisterCall(pool.token1Path, recipient, uint64(-amount1)) failed") } pool.balances.token1 += amount1 } - balance0Before := bigint(balanceOfByRegisterCall(pToken0Path, GetOrigPkgAddr())) + balance0Before := bigint(balanceOfByRegisterCall(pool.token0Path, GetOrigPkgAddr())) txOrigin := GetOrigCaller() // token should be transferred from actual user(GetOrigCaller), not from the realm(PrevRealm) poolPkg := GetOrigPkgAddr() - ok := transferFromByRegisterCall(pToken0Path, txOrigin, poolPkg, uint64(amount0)) + ok := transferFromByRegisterCall(pool.token0Path, txOrigin, poolPkg, uint64(amount0)) if !ok { - panic("[POOL] pool.gno__Swap() || transferFromByRegisterCall(pToken0Path, from, to, uint64(amount0)) failed") + panic("[POOL] pool.gno__Swap() || transferFromByRegisterCall(pool.token0Path, from, to, uint64(amount0)) failed") } require( - balance0Before+amount0 <= bigint(balanceOfByRegisterCall(pToken0Path, GetOrigPkgAddr())), + balance0Before+amount0 <= bigint(balanceOfByRegisterCall(pool.token0Path, GetOrigPkgAddr())), ufmt.Sprintf( - "[POOL] pool.gno__Swap() || balance0Before(%d) + amount0(%d) <= balanceOfByRegisterCall(pToken0Path, GetOrigPkgAddr())(%d)", - balance0Before, amount0, balanceOfByRegisterCall(pToken0Path, GetOrigPkgAddr()), + "[POOL] pool.gno__Swap() || balance0Before(%d) + amount0(%d) <= balanceOfByRegisterCall(pool.token0Path, GetOrigPkgAddr())(%d)", + balance0Before, amount0, balanceOfByRegisterCall(pool.token0Path, GetOrigPkgAddr()), ), ) @@ -412,29 +411,29 @@ func Swap( if amount0 < 0 { require(pool.balances.token0 > (-amount0), ufmt.Sprintf("[POOL] pool.gno__Swap() || pool.balances.token0(%s) > (-1 * amount0)(%s)", pool.balances.token0, (-amount0))) - ok := transferByRegisterCall(pToken0Path, recipient, uint64(-amount0)) + ok := transferByRegisterCall(pool.token0Path, recipient, uint64(-amount0)) if !ok { - panic("[POOL] pool.gno__Swap() || transferByRegisterCall(pToken0Path, recipient, uint64(-amount0)) failed") + panic("[POOL] pool.gno__Swap() || transferByRegisterCall(pool.token0Path, recipient, uint64(-amount0)) failed") } pool.balances.token0 += amount0 } - balance1Before := bigint(balanceOfByRegisterCall(pToken1Path, GetOrigPkgAddr())) + balance1Before := bigint(balanceOfByRegisterCall(pool.token1Path, GetOrigPkgAddr())) txOrigin := GetOrigCaller() // token should be transferred from actual user(GetOrigCaller), not from the realm(PrevRealm) poolPkg := GetOrigPkgAddr() - ok := transferFromByRegisterCall(pToken1Path, txOrigin, poolPkg, uint64(amount1)) + ok := transferFromByRegisterCall(pool.token1Path, txOrigin, poolPkg, uint64(amount1)) if !ok { - panic("[POOL] pool.gno__Swap() || transferFromByRegisterCall(pToken1Path, from, to, uint64(amount1)) failed") + panic("[POOL] pool.gno__Swap() || transferFromByRegisterCall(pool.token1Path, from, to, uint64(amount1)) failed") } require( - balance1Before+amount1 <= bigint(balanceOfByRegisterCall(pToken1Path, GetOrigPkgAddr())), + balance1Before+amount1 <= bigint(balanceOfByRegisterCall(pool.token1Path, GetOrigPkgAddr())), ufmt.Sprintf( - "[POOL] pool.gno__Mint() || balance1Before(%d) + amount1(%d) <= balanceOfByRegisterCall(pToken1Path, GetOrigPkgAddr())(%d)", - balance1Before, amount1, balanceOfByRegisterCall(pToken1Path, GetOrigPkgAddr()), + "[POOL] pool.gno__Mint() || balance1Before(%d) + amount1(%d) <= balanceOfByRegisterCall(pool.token1Path, GetOrigPkgAddr())(%d)", + balance1Before, amount1, balanceOfByRegisterCall(pool.token1Path, GetOrigPkgAddr()), ), ) @@ -493,14 +492,14 @@ func CollectProtocol( // without procotol fee amount0, amount1 = pool.saveProtocolFees(amount0, amount1) - ok := transferByRegisterCall(pToken0Path, recipient, uint64(amount0)) + ok := transferByRegisterCall(pool.token0Path, recipient, uint64(amount0)) if !ok { - panic("[POOL] pool.gno__CollectProtocol() || transferByRegisterCall(pToken0Path, recipient, uint64(amount0)) failed") + panic("[POOL] pool.gno__CollectProtocol() || transferByRegisterCall(pool.token0Path, recipient, uint64(amount0)) failed") } - ok = transferByRegisterCall(pToken1Path, recipient, uint64(amount1)) + ok = transferByRegisterCall(pool.token1Path, recipient, uint64(amount1)) if !ok { - panic("[POOL] pool.gno__CollectProtocol() || transferByRegisterCall(pToken1Path, recipient, uint64(amount1)) failed") + panic("[POOL] pool.gno__CollectProtocol() || transferByRegisterCall(pool.token1Path, recipient, uint64(amount1)) failed") } return amount0, amount1 @@ -594,6 +593,8 @@ func (pool *Pool) updatePosition( } } + // NO LIQ, ONLY BURN 0 + feeGrowthInside0X128, feeGrowthInside1X128 := pool.tickGetFeeGrowthInside( tickLower, tickUpper, @@ -623,37 +624,6 @@ func (pool *Pool) updatePosition( return position } -// r3v4_xxx -// unused for now -func transfer(token *grc20.AdminToken, amount bigint) { - balanceBefore, err := token.BalanceOf(GetOrigPkgAddr()) - if err != nil { - balanceBefore = 0 - } - from := GetOrigCaller() // token should be transferred from actual user(GetOrigCaller), not from the realm(PrevRealm) - to := GetOrigPkgAddr() - - token.TransferFrom(GetOrigPkgAddr(), from, to, uint64(amount)) - - balancAfter, err := token.BalanceOf(GetOrigPkgAddr()) - if err != nil { - balancAfter = 0 - } - require( - balanceBefore+uint64(amount) <= balancAfter, - ufmt.Sprintf( - "[POOL] pool.gno__mint() || balanceBefore(%s) + amount(%s) <= balancAfter(%s)", - ), - ) -} - -// r3v4_xxx -// unused for now -func (pool *Pool) collect(token *grc20.AdminToken, poolBalance, requestAmount bigint, recipient std.Address) { - require(poolBalance >= requestAmount, ufmt.Sprintf("[POOL] pool.gno__collect() || poolBalance(%s) >= requestAmount(%s)", poolBalance, requestAmount)) - token.Transfer(GetOrigPkgAddr(), recipient, uint64(requestAmount)) -} - func (pool *Pool) saveProtocolFees(amount0, amount1 bigint) (bigint, bigint) { if amount0 > 0 && amount0 == pool.protocolFees.token0 { amount0-- @@ -669,13 +639,3 @@ func (pool *Pool) saveProtocolFees(amount0, amount1 bigint) (bigint, bigint) { // return rest fee return amount0, amount1 } - -func checkTicks(tickLower, tickUpper bigint) { - require(tickLower < tickUpper, ufmt.Sprintf("[POOL] pool.gno__checkTicks() || tickLower(%s) < tickUpper(%s)", tickLower, tickUpper)) - require(tickLower >= MIN_TICK, ufmt.Sprintf("[POOL] pool.gno__checkTicks() || tickLower(%s) >= MIN_TICK(%s)", tickLower, MIN_TICK)) - require(tickUpper <= MAX_TICK, ufmt.Sprintf("[POOL] pool.gno__checkTicks() || tickUpper(%s) <= MAX_TICK(%s)", tickUpper, MAX_TICK)) -} - -func a2u(addr std.Address) users.AddressOrName { - return users.AddressOrName(addr) -} From 231cd5f4b04d63c40ae84d287b713b065d341668 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Tue, 31 Oct 2023 18:18:45 +0900 Subject: [PATCH 16/35] chore --- ...t.gnoa => _TEST_pool_multi_lp_fee_test.gno} | 15 ++++++++------- ...test.gno => _TEST_pool_single_lp_test.gnoa} | 0 pool/consts.gno | 2 +- pool/getter_pool.gno | 6 +++++- pool/getter_position.gno | 16 ++++++++-------- pool/utils.gno | 18 ++++++++++++++++++ 6 files changed, 40 insertions(+), 17 deletions(-) rename pool/{_TEST_pool_multi_lp_fee_test.gnoa => _TEST_pool_multi_lp_fee_test.gno} (87%) rename pool/{_TEST_pool_single_lp_test.gno => _TEST_pool_single_lp_test.gnoa} (100%) diff --git a/pool/_TEST_pool_multi_lp_fee_test.gnoa b/pool/_TEST_pool_multi_lp_fee_test.gno similarity index 87% rename from pool/_TEST_pool_multi_lp_fee_test.gnoa rename to pool/_TEST_pool_multi_lp_fee_test.gno index ffdee8e1..83ea0ce4 100644 --- a/pool/_TEST_pool_multi_lp_fee_test.gnoa +++ b/pool/_TEST_pool_multi_lp_fee_test.gno @@ -58,7 +58,7 @@ func TestSwap(t *testing.T) { // lp01 mint 9000 ~ 11000 tick range std.TestSetPrevRealm("gno.land/r/position") std.TestSetOrigCaller(lp01) - Mint(fooPath, barPath, pFee, posAddr, test_tickLower, test_tickUpper, test_liquidityExpect*10000) + Mint(fooPath, barPath, pFee, posAddr, int32(-887270), int32(887270), test_liquidityExpect*10000) // lp02 mint (almost) full range // fee 500 will use tickSpacing 10 @@ -66,7 +66,7 @@ func TestSwap(t *testing.T) { // MAX_TICK bigint = 887272 std.TestSetOrigCaller(lp02) - Mint(fooPath, barPath, pFee, posAddr, MIN_TICK+2, MAX_TICK-2, test_liquidityExpect*10000) + Mint(fooPath, barPath, pFee, posAddr, int32(9990), int32(10010), test_liquidityExpect*10000) { // balance before swap @@ -86,8 +86,9 @@ func TestSwap(t *testing.T) { lp02OldToken1Bal := balanceOfByRegisterCall(barPath, lp02) // SWAP + std.TestSetOrigCaller(tr01) - s0, s1 := Swap(fooPath, barPath, pFee, tr01, true, bigint(630000), MIN_PRICE) // one iteration - yes fee(only lp02) + s0, s1 := Swap(fooPath, barPath, pFee, tr01, true, bigint(100000), MIN_PRICE) // one iteration - yes fee(only lp02) shouldNEQ(t, s0, bigint(0)) shouldNEQ(t, s1, bigint(0)) @@ -108,12 +109,12 @@ func TestSwap(t *testing.T) { // burn 0 // then collect std.TestSetOrigCaller(lp01) - Burn(fooPath, barPath, pFee, test_tickLower, test_tickUpper, 0) - Collect(fooPath, barPath, pFee, lp01, test_tickLower, test_tickUpper, 100000000, 100000000) + Burn(fooPath, barPath, pFee, int32(-887270), int32(887270), 0) + lp01f0, lp01f1 := Collect(fooPath, barPath, pFee, lp01, int32(-887270), int32(887270), 100000000, 100000000) std.TestSetOrigCaller(lp02) - Burn(fooPath, barPath, pFee, MIN_TICK+2, MAX_TICK-2, 0) - Collect(fooPath, barPath, pFee, lp02, MIN_TICK+2, MAX_TICK-2, 100000000, 100000000) + Burn(fooPath, barPath, pFee, int32(9990), int32(10010), 0) + lp02f0, lp02f1 := Collect(fooPath, barPath, pFee, lp02, int32(9990), int32(10010), 100000000, 100000000) } } diff --git a/pool/_TEST_pool_single_lp_test.gno b/pool/_TEST_pool_single_lp_test.gnoa similarity index 100% rename from pool/_TEST_pool_single_lp_test.gno rename to pool/_TEST_pool_single_lp_test.gnoa diff --git a/pool/consts.gno b/pool/consts.gno index 23af38fc..d76a519e 100644 --- a/pool/consts.gno +++ b/pool/consts.gno @@ -33,7 +33,7 @@ const ( MAX_INT256 bigint = 57896044618658097711785492504343953926634992332820282019728792003956564819967 MAX_UINT256 bigint = 115792089237316195423570985008687907853269984665640564039457584007913129639935 - // TICK Related + // Tick Related MIN_TICK int32 = -887272 MAX_TICK int32 = 887272 diff --git a/pool/getter_pool.gno b/pool/getter_pool.gno index d5f5e6d4..5b04557a 100644 --- a/pool/getter_pool.gno +++ b/pool/getter_pool.gno @@ -81,6 +81,10 @@ func (pool *Pool) GetTickBitmaps() TickBitmaps { return pool.tickBitmaps } -func (pool *Pool) GetSqrtPriceX96() bigint { +func (pool *Pool) GetSlotSqrtPriceX96() bigint { return pool.slot0.sqrtPriceX96 } + +func (pool *Pool) GetSlotTick() int32 { + return pool.slot0.tick +} diff --git a/pool/getter_position.gno b/pool/getter_position.gno index 512a166f..c3dcbfb8 100644 --- a/pool/getter_position.gno +++ b/pool/getter_position.gno @@ -1,9 +1,14 @@ // POSITION CONTRACT USES BELOW FUNCTIONS IN ITS LOGIC package pool -import ( - "std" -) +func GetPoolList() []string { + poolPaths := []string{} + for poolPath, _ := range pools { + poolPaths = append(poolPaths, poolPath) + } + + return poolPaths +} func (pool *Pool) GetPoolSlot0SqrtPriceX96() bigint { return pool.slot0.sqrtPriceX96 @@ -29,11 +34,6 @@ func (pool *Pool) GetPoolPositionFeeGrowthInside1LastX128(key string) bigint { return pool.positions[key].feeGrowthInside1LastX128 } -func GetPoolAddress() std.Address { - return std.DerivePkgAddr("gno.land/r/pool") - // XXX return std.GetOrigPkgAddr() -} - func (pool *Pool) GetPoolSlot0Tick() int32 { return pool.slot0.tick } diff --git a/pool/utils.gno b/pool/utils.gno index ac2b5540..094cd1cd 100644 --- a/pool/utils.gno +++ b/pool/utils.gno @@ -1,5 +1,23 @@ package pool +import ( + "std" + + "gno.land/r/demo/users" + + "gno.land/p/demo/ufmt" +) + +func checkTicks(tickLower, tickUpper bigint) { + require(tickLower < tickUpper, ufmt.Sprintf("[POOL] pool.gno__checkTicks() || tickLower(%s) < tickUpper(%s)", tickLower, tickUpper)) + require(tickLower >= MIN_TICK, ufmt.Sprintf("[POOL] pool.gno__checkTicks() || tickLower(%s) >= MIN_TICK(%s)", tickLower, MIN_TICK)) + require(tickUpper <= MAX_TICK, ufmt.Sprintf("[POOL] pool.gno__checkTicks() || tickUpper(%s) <= MAX_TICK(%s)", tickUpper, MAX_TICK)) +} + +func a2u(addr std.Address) users.AddressOrName { + return users.AddressOrName(addr) +} + func requireUnsigned(x bigint, msg string) { if x < 0 { panic(msg) From f4486f85c043e95f1c39a2d9623d65052fce152e Mon Sep 17 00:00:00 2001 From: n3wbie Date: Tue, 31 Oct 2023 18:19:44 +0900 Subject: [PATCH 17/35] test: --- position/consts.gno | 10 ++++++++++ position/{math_logic_test.gno => math_logic_test.gnoa} | 0 2 files changed, 10 insertions(+) rename position/{math_logic_test.gno => math_logic_test.gnoa} (100%) diff --git a/position/consts.gno b/position/consts.gno index 2e9f85a4..8d198a60 100644 --- a/position/consts.gno +++ b/position/consts.gno @@ -16,4 +16,14 @@ const ( Q128 bigint = 340282366920938463463374607431768211456 // 2 ** 128 MAX_UINT160 bigint = 1461501637330902918203684832716283019655932542975 + + // Tick Related + MIN_TICK int32 = -887272 + MAX_TICK int32 = 887272 + + MIN_SQRT_RATIO bigint = 4295128739 // same as TickMathGetSqrtRatioAtTick(MIN_TICK) + MAX_SQRT_RATIO bigint = 1461446703485210103287273052203988822378723970342 // same as TickMathGetSqrtRatioAtTick(MAX_TICK) + + MIN_PRICE bigint = 4295128740 // MIN_SQRT_RATIO + 1 + MAX_PRICE bigint = 1461446703485210103287273052203988822378723970341 // MAX_SQRT_RATIO - 1 ) diff --git a/position/math_logic_test.gno b/position/math_logic_test.gnoa similarity index 100% rename from position/math_logic_test.gno rename to position/math_logic_test.gnoa From d2cec36c6c0e00f598e52f90c7f8aedcb73e05ba Mon Sep 17 00:00:00 2001 From: n3wbie Date: Tue, 31 Oct 2023 18:20:28 +0900 Subject: [PATCH 18/35] feat: router contract - entireSwapAmount --- router/consts.gno | 45 +---- router/find_direct_indirect_path.gno | 59 +++++++ router/gno.mod | 1 + router/router.gno | 119 +++++++++++++ router/router_test.gno | 149 +++++++++++++++++ router/single_multi_swap.gno | 140 ++++++++++++++++ router/swap_router.gno | 239 --------------------------- router/type.gno | 38 +++++ router/util.gno | 89 ++++++++++ 9 files changed, 597 insertions(+), 282 deletions(-) create mode 100644 router/find_direct_indirect_path.gno create mode 100644 router/gno.mod create mode 100644 router/router.gno create mode 100644 router/router_test.gno create mode 100644 router/single_multi_swap.gno delete mode 100644 router/swap_router.gno create mode 100644 router/type.gno create mode 100644 router/util.gno diff --git a/router/consts.gno b/router/consts.gno index 6d894993..ae23250a 100644 --- a/router/consts.gno +++ b/router/consts.gno @@ -1,47 +1,6 @@ package router -import ( - "std" -) - const ( - zeroAddress = std.Address("") - - // some numbers - // calculated by https://mathiasbynens.be/demo/integer-range - MIN_INT8 bigint = -128 - MAX_INT8 bigint = 127 - MAX_UINT8 bigint = 255 - - MIN_INT16 bigint = -32768 - MAX_INT16 bigint = 32767 - MAX_UINT16 bigint = 65535 - - MIN_INT32 bigint = -2147483648 - MAX_INT32 bigint = 2147483647 - MAX_UINT32 bigint = 4294967295 - - MIN_INT64 bigint = -9223372036854775808 - MAX_INT64 bigint = 9223372036854775807 - MAX_UINT64 bigint = 18446744073709551615 - - MIN_INT128 bigint = -170141183460469231731687303715884105728 - MAX_INT128 bigint = 170141183460469231731687303715884105727 - MAX_UINT128 bigint = 340282366920938463463374607431768211455 - - MIN_INT256 bigint = -57896044618658097711785492504343953926634992332820282019728792003956564819968 - MAX_INT256 bigint = 57896044618658097711785492504343953926634992332820282019728792003956564819967 - MAX_UINT256 bigint = 115792089237316195423570985008687907853269984665640564039457584007913129639935 - - // ETC - Q96 bigint = 79228162514264337593543950336 // 2 ** 96 - - // Extra - MIN_INT24 bigint = -8388608 - MAX_INT24 bigint = 8388607 - MAX_UINT24 bigint = 16777215 - - MIN_INT96 bigint = -39614081257132168796771975168 - MAX_INT96 bigint = 39614081257132168796771975167 - MAX_UINT96 bigint = 79228162514264337593543950335 + MIN_PRICE bigint = 4295128740 // MIN_SQRT_RATIO + 1 + MAX_PRICE bigint = 1461446703485210103287273052203988822378723970341 // MAX_SQRT_RATIO - 1 ) diff --git a/router/find_direct_indirect_path.gno b/router/find_direct_indirect_path.gno new file mode 100644 index 00000000..09b85015 --- /dev/null +++ b/router/find_direct_indirect_path.gno @@ -0,0 +1,59 @@ +package router + +import "strings" + +func findTwicePath( + tokenPairs TokenPairs, + inputTokenPath string, + outputTokenPath string, + swapPaths SwapPaths, +) SwapPaths { + for _, second := range tokenPairs[inputTokenPath] { + secondPath, secondFee := singlePoolPathWithFeeDivide(second) + tokenPairs[inputTokenPath] = removeItemFromStringArray(tokenPairs[inputTokenPath], second) + tokenPairs[secondPath] = removeItemFromStringArray(tokenPairs[secondPath], (inputTokenPath + ":" + secondFee)) + + for _, third := range tokenPairs[secondPath] { + thirdPath, thirdFee := singlePoolPathWithFeeDivide(third) + tokenPairs[secondPath] = removeItemFromStringArray(tokenPairs[secondPath], third) + tokenPairs[thirdPath] = removeItemFromStringArray(tokenPairs[thirdPath], (secondPath + ":" + thirdFee)) + + if strings.HasPrefix(third, outputTokenPath) { + // twice + nestedPath := inputTokenPath + "," + secondFee + "," + secondPath + "," + secondFee + "," + outputTokenPath + + swapPaths[len(swapPaths)] = nestedPath + } + } + } + + return swapPaths +} + +func findThreeTimePath( + tokenPairs TokenPairs, + inputTokenPath string, + outputTokenPath string, + swapPaths SwapPaths, +) SwapPaths { + for _, second := range tokenPairs[inputTokenPath] { + secondPath, secondFee := singlePoolPathWithFeeDivide(second) + tokenPairs[secondPath] = removeItemFromStringArray(tokenPairs[secondPath], (inputTokenPath + ":" + secondFee)) + + for _, third := range tokenPairs[secondPath] { // bar > bz + thirdPath, thirdFee := singlePoolPathWithFeeDivide(third) + tokenPairs[secondPath] = removeItemFromStringArray(tokenPairs[thirdPath], (secondPath + ":" + thirdFee)) + + for _, fourth := range tokenPairs[thirdPath] { + fourthPath, fourthFee := singlePoolPathWithFeeDivide(fourth) + + if strings.HasPrefix(fourth, outputTokenPath) { + // three times + nestedPath := inputTokenPath + "," + secondFee + "," + secondPath + "," + secondFee + "," + thirdPath + "," + thirdFee + "," + fourthPath + swapPaths[len(swapPaths)] = nestedPath + } + } + } + } + return swapPaths +} diff --git a/router/gno.mod b/router/gno.mod new file mode 100644 index 00000000..3a953446 --- /dev/null +++ b/router/gno.mod @@ -0,0 +1 @@ +module gno.land/r/router \ No newline at end of file diff --git a/router/router.gno b/router/router.gno new file mode 100644 index 00000000..b9621d73 --- /dev/null +++ b/router/router.gno @@ -0,0 +1,119 @@ +package router + +import ( + "std" + "strconv" + "strings" + + p "gno.land/r/pool" +) + +func SwapRoute( + inputTokenPath string, + outputTokenPath string, + + amountIn bigint, + recipient std.Address, + sqrtPriceLimitX96 bigint, +) (amountOut bigint) { + numSwaps := findSwapPaths(inputTokenPath, outputTokenPath) + + for i, swapPath := range numSwaps { + numPools := strings.Count(swapPath, ",") / 2 + + for j := i; j < numPools; j++ { + if numPools < 1 { + panic("CAN NOT FIND SWAP PATH") + } else if numPools == 1 { + // single Swap + input, output, fee := getSwapData(swapPath, j) + + singleParams := SingleSwapParams{ + tokenIn: input, + tokenOut: output, + fee: fee, + amountIn: amountIn, + sqrtPriceLimitX96: sqrtPriceLimitX96, + } + + amountOut = singleSwap(singleParams) + } else { + // multiple Swap + input, output, fee := getSwapData(swapPath, j-1) + swapParams := SwapParams{ + tokenIn: input, + tokenOut: output, + fee: fee, + + recipient: std.GetOrigCaller(), + amountIn: amountIn, + minAmountOut: amountIn * 5, // r3v4_xxx: need usd price I think + } + + amountOut = multiSwap(swapParams, i, numPools, swapPath) + } + } + } + + return amountOut +} + +func findSwapPaths( + inputTokenPath string, + outputTokenPath string, +) (swapPaths SwapPaths) { + tokenPairs := TokenPairs{} + poolList := p.GetPoolList() + + for i, poolPath := range poolList { + token0Path, token1Path, pFee := poolPathWithFeeDivide(poolPath) + + { + k := token0Path + v := token1Path + ":" + strconv.Itoa(pFee) + + tokenPairs[k] = append(tokenPairs[k], v) + } + + { + k := token1Path + v := token0Path + ":" + strconv.Itoa(pFee) + + tokenPairs[k] = append(tokenPairs[k], v) + } + } + + swapPaths = getSwapPaths(tokenPairs, inputTokenPath, outputTokenPath) + return swapPaths +} + +func getSwapPaths( + tokenPairs TokenPairs, + inputTokenPath string, + outputTokenPath string, +) (swapPaths SwapPaths) { + swapPaths = make(SwapPaths, 0) + + // check if there is path that starts with input + if len(tokenPairs[inputTokenPath]) == 0 { + panic("NO POOL") + } + + // find direct path + for _, output := range tokenPairs[inputTokenPath] { + if strings.HasPrefix(output, outputTokenPath) { + outputPath, outputFee := singlePoolPathWithFeeDivide(output) + directPath := inputTokenPath + "," + outputFee + "," + outputPath + swapPaths[len(swapPaths)] = directPath + + tokenPairs[inputTokenPath] = removeItemFromStringArray(tokenPairs[inputTokenPath], output) + } + } + + // find nested path + // r3v4_xx: handle more than three time swap ?? + swapPaths = findTwicePath(tokenPairs, inputTokenPath, outputTokenPath, swapPaths) + swapPaths = findThreeTimePath(tokenPairs, inputTokenPath, outputTokenPath, swapPaths) + + return +} diff --git a/router/router_test.gno b/router/router_test.gno new file mode 100644 index 00000000..5195f80a --- /dev/null +++ b/router/router_test.gno @@ -0,0 +1,149 @@ +package router + +import ( + "std" + "testing" + + "gno.land/p/demo/testutils" + + _ "gno.land/r/grc20_wrapper" + p "gno.land/r/pool" + pos "gno.land/r/position" +) + +var ( + gsa = testutils.TestAddress("gsa") // Gnoswap Admin + lp01 = testutils.TestAddress("lp01") // Liquidity Provider 01 + + poolAddr = std.DerivePkgAddr("gno.land/r/pool") + posAddr = std.DerivePkgAddr("gno.land/r/position") + routerAddr = std.DerivePkgAddr("gno.land/r/router") +) + +var ( + // Common + fooPath = "gno.land/r/foo" // token1 + barPath = "gno.land/r/bar" // token2 + bazPath = "gno.land/r/baz" // token3 + quxPath = "gno.land/r/qux" // token4 + + test_tickLower = int32(9000) + test_tickUpper = int32(11000) + test_liquidityExpect = bigint(1000) +) + +func init() { + println(gsa, "// gsa") + println(lp01, "// lp01") + println(poolAddr, "// poolAddr") + println(posAddr, "// posAddr") + println(routerAddr, "// routerAddr") +} + +func TestInitManual(t *testing.T) { + std.TestSetOrigCaller(gsa) + p.InitManual() + std.TestSkipHeights(1) +} + +func TestCreatePool(t *testing.T) { + std.TestSetOrigCaller(gsa) + + // p.CreatePool(fooPath, barPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + // p.CreatePool(fooPath, barPath, uint16(500), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + // p.CreatePool(fooPath, bazPath, uint16(500), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + // p.CreatePool(barPath, bazPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + // findSwapPaths("gno.land/r/foo", "gno.land/r/baz") // 3 paths + // // foo_bar_100 > bar_baz_100 + // // foo_bar_500 > bar_baz_100 + // // foo_baz_500 + + // p.CreatePool(fooPath, barPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + // p.CreatePool(fooPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + // p.CreatePool(barPath, bazPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + // p.CreatePool(barPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + // p.CreatePool(bazPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + // findSwapPaths("gno.land/r/foo", "gno.land/r/qux") // 3 paths + // // foo_qux_100 + // // foo_bar_100 > bar_qux_100 + // // foo_bar_100 > bar_baz_100 > baz_qux_100 + + // p.CreatePool(fooPath, barPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + // p.CreatePool(fooPath, barPath, uint16(500), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + // p.CreatePool(fooPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + // p.CreatePool(barPath, bazPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + // p.CreatePool(barPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + // p.CreatePool(bazPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + // findSwapPaths("gno.land/r/foo", "gno.land/r/qux") // 5 paths + // foo_qux_100 + // foo_bar_100 > bar_qux_100 + // foo_bar_500 > bar_qux_100 + // foo_bar_100 > bar_baz_100 > baz_qux_100 + // foo_bar_500 > bar_baz_100 > baz_qux_100 + + p.CreatePool(fooPath, barPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + p.CreatePool(fooPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + p.CreatePool(barPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + // findSwapPaths("gno.land/r/foo", "gno.land/r/qux") // 2 paths + // foo_bar_100 > bar_qux_100 + // foo_qux_100 +} + +func TestPositionMint(t *testing.T) { + std.TestSetOrigCaller(lp01) + + pos.Mint( + fooPath, + barPath, + uint16(100), + test_tickLower, + test_tickUpper, + bigint(10000000), + bigint(10000000), + bigint(1), + bigint(1), + bigint(9999999999), + ) + + pos.Mint( + fooPath, + quxPath, + uint16(100), + test_tickLower, + test_tickUpper, + bigint(10000), + bigint(10000), + bigint(1), + bigint(1), + bigint(9999999999), + ) + + pos.Mint( + barPath, + quxPath, + uint16(100), + test_tickLower, + test_tickUpper, + bigint(10000000), + bigint(10000000), + bigint(1), + bigint(1), + bigint(9999999999), + ) +} + +func TestFuncs(t *testing.T) { + // sPath := findSwapPaths("gno.land/r/foo", "gno.land/r/qux") // 2 paths + + outputAmount := SwapRoute( + "gno.land/r/foo", // inputTokenPath + "gno.land/r/qux", // outputTokenPath + + 10_000, // amountIn + std.Address("g123recv"), // recipient + 0, // sqrtPriceLimitX96 + ) + + println("YOU GOT QUX OF", outputAmount) + +} diff --git a/router/single_multi_swap.gno b/router/single_multi_swap.gno new file mode 100644 index 00000000..a16f0a75 --- /dev/null +++ b/router/single_multi_swap.gno @@ -0,0 +1,140 @@ +package router + +import ( + "std" + + p "gno.land/r/pool" +) + +type SingleSwapParams struct { + tokenIn string + tokenOut string + fee uint16 + amountIn bigint + sqrtPriceLimitX96 bigint +} + +func singleSwap(params SingleSwapParams) (amountOut bigint) { + amountOut = _swap( + params.amountIn, + std.PrevRealm().Addr(), // msg.sender + params.sqrtPriceLimitX96, + SwapCallbackData{ + params.tokenIn, + params.tokenOut, + params.fee, + std.PrevRealm().Addr(), // msg.sender, + }, + ) + + return amountOut +} + +type SwapParams struct { + tokenIn string + tokenOut string + fee uint16 + + recipient std.Address + amountIn bigint + minAmountOut bigint +} + +func multiSwap(params SwapParams, currentPoolIndex, numPool int, swapPath string) (amountOut bigint) { + payer := std.GetOrigCaller() // user + var hasMultiplePools bool + + for { + hasMultiplePools = currentPoolIndex < numPool + + var recipient std.Address + if hasMultiplePools { + recipient = std.DerivePkgAddr("gno.land/r/router") + } else { + recipient = params.recipient // user ~= std.GetOrigCaller() + } + + params.amountIn = _swap( + params.amountIn, // amountIn + recipient, // recipient + 0, // sqrtPriceLimitX96 + SwapCallbackData{ + params.tokenIn, // tokenIn + params.tokenOut, // tokenOut + params.fee, // fee + payer, // payer + }, + ) + + if hasMultiplePools { + payer = std.DerivePkgAddr("gno.land/r/router") + + nextInput, nextOutput, nextFee := getSwapData(swapPath, currentPoolIndex) + params.tokenIn = nextInput + params.tokenOut = nextOutput + params.fee = nextFee + + currentPoolIndex++ + } else { + amountOut = params.amountIn + return amountOut + } + } + + if amountOut < params.minAmountOut { + panic("Too few receive") + } +} + +func _swap( + amountIn bigint, + recipient std.Address, + sqrtPriceLimitX96 bigint, + data SwapCallbackData, +) (amountOut bigint) { + // prepare + zeroForOne := data.tokenIn < data.tokenOut + + if sqrtPriceLimitX96 == 0 { + if zeroForOne { + sqrtPriceLimitX96 = MIN_PRICE + } else { + sqrtPriceLimitX96 = MAX_PRICE + } + } + + // check possible + _, _, ok := p.DrySwap( + data.tokenIn, + data.tokenOut, + data.fee, + + recipient, + zeroForOne, + amountIn, + sqrtPriceLimitX96, + ) + if !ok { + return 0 + } + + amount0, amount1 := p.Swap( + data.tokenIn, + data.tokenOut, + data.fee, + + recipient, + zeroForOne, + amountIn, + sqrtPriceLimitX96, + ) + + // if success + if zeroForOne { + amountOut = -amount1 + } else { + amountOut = -amount0 + } + + return amountOut +} diff --git a/router/swap_router.gno b/router/swap_router.gno deleted file mode 100644 index 7fb1912d..00000000 --- a/router/swap_router.gno +++ /dev/null @@ -1,239 +0,0 @@ -package router - -import ( - "std" - "math" - - "gno.land/p/demo/grc/grc20" - - pool "gno.land/p/demo/pool" // XXX -) - -var ( - token0 *grc20.AdminToken - token1 *grc20.AdminToken - DEFAULT_AMOUNT_IN_CACHED = MAX_UINT32 // 32 or 64 ? - amountCached = DEFAULT_AMOUNT_IN_CACHED -) - -type ExactInputSingleParams struct { - TokenIn std.Address - TokenOut std.Address - Fee bigint - Recipient std.Address - Deadline bigint - AmountIn bigint - AmountOutMinimum bigint - SqrtPriceLimit bigint - ZeroForOne bool -} - -type ExactInputParams struct { - Recipient std.Address - Deadline bigint - AmountIn bigint - AmountOutMinimum bigint - ZeroForOne bool -} - -type ExactOutputSingleParams struct { - ZeroForOne bool - TokenIn std.Address - TokenOut std.Address - Fee bigint - Recipient std.Address - Deadline bigint - AmountOut bigint - AmountInMaximum bigint - SqrtPriceLimit bigint -} - -type ExactOutputParams struct { - ZeroForOne bool - Recipient std.Address - Deadline bigint - AmountOut bigint - AmountInMaximum bigint -} - -func Init() { - // testtoken1.Init() - // testtoken1.Faucet() - // testtoken2.Init() - // testtoken2.Faucet() - - token0 = _.GetGRC20() - token1 = _.GetGRC20() - - routerAddress := std.GetOrigPkgAddr() -} - - -func getPool( - tokenA std.Address, - tokenB std.Address, - fee bigint, -) std.Address { - /// GetPoolAddresss - return pool.GetPoolAddress(tokenA, tokenB, fee) -} - - -func exactInputInternal( - amountIn bigint, - recipient std.Address, - sqrtPriceLimit bigint, - poolAddress std.Address -) (amountOut bigint) { - if (recipient == ZeroAddress) { - recipient = routerAddress - } - - // XXX - tokenIn, tokenOut std.Address, fee bigint = poolAddress.DecodeFirstPool() - var zeroForOne bool = tokenIn < tokenOut - - // XXX - var amount0, amount1 bigint = getPool(tokenIn, tokenOut, fee) - - if sqrtPriceLimit == 0 { - if zeeroForOne { - sqrtPriceLimit = TickMath.MIN_SQRT_RATIO + 1 - } else { - sqrtPriceLimit = TickMath.MAX_SQRT_RATIO - 1 - } - } - - poolAddress.swap( - recipient, - zeroForOne, - bigint(amountIn), - sqrtPriceLimit - ) - - if zeroForOne { - return bigint(-amount1) - } else { - return bigint(-amount0) - } -} - - -func exactInputSingle(params ExactInputSingleParams) (amountOut bigint) { - checkDeadline(params.Deadline) - - amountOut = exactInputInternal(e, - params.amountIn, - params.recipient, - params.sqrtPriceLimit, - ) - require(amountOut >= params.AmountOutMinimum, "Too little received") - - return amountOut -} - - -func exactInput(params ExactInputParams) (amountOut bigint) { - checkDeadline(params.Deadline) - - payer := GetOrigCaller() - - - // XX chaining // eth path - params.AmountIn = exactInputInternal( - params.ZeroForOne, - params.AmountIn, - params.Recipient, // for intermediate swaps, this contract custodies - 0, - ) - - // decide whether to continue or terminate - amountOut = params.AmountIn - - require(amountOut >= params.AmountOutMinimum, "Too little received") - - return amountOut -} - - -func exactOutputInternal( - amountOut bigint, - recipient std.Address, - sqrtPriceLimit bigint, -) (amountIn bigint) { - // allow swapping to the router address with address 0 - if recipient == zeroAddress { - recipient = routerAddress - } - - if sqrtPriceLimit == 0 { - if zeroForOne { - sqrtPriceLimit = MIN_SQRT_RATIO + 1 - } else { - sqrtPriceLimit = MAX_SQRT_RATIO - 1 - } - } - amount0Delta, amount1Delta := pool.Swap( - std.GetOrigCaller(), - recipient, - zeroForOne, - - int (amountOut), - sqrtPriceLimit, - ) - - var amountOutReceived bigint - if zeroForOne { - amountIn = bigint (amount0Delta) - amountOutReceived = bigint (-amount1Delta) - } else { - amountIn = bigint (amount1Delta) - amountOutReceived = bigint (-amount0Delta) - } - - if sqrtPriceLimit == 0 { - require(amountOutReceived == amountOut, "amountOutReceived must equal amountOut") - } - - return amountIn -} - -func exactOutputSingle(params ExactOutputSingleParams) (amountIn uint) { - checkDeadline(params.Deadline) - amountIn = exactOutputInternal( - params.ZeroForOne, - params.AmountOut, - params.Recipient, - params.SqrtPriceLimit, - ) - - require(amountIn <= params.AmountInMaximum, "Too much requested") - amountInCached = DEFAULT_AMOUNT_IN_CACHED - return amountIn -} - -func exactOutput(params ExactOutputParams) (amountIn uint) { - checkDeadline(params.Deadline) - amountIn = exactOutputInternal( - params.ZeroForOne, - params.AmountOut, - params.Recipient, - 0, - ) - - amountIn = amountInCached - require(amountIn <= params.AmountInMaximum, "Too much requested") - amountInCached = DEFAULT_AMOUNT_IN_CACHED - return amountIn -} - - -// helpers -func require(ok bool, msg string) { - if !ok { - panic(msg) - } -} - -func checkDeadline(deadlint uint64) { - require(BlockTimestamp() <= deadline, "Expired") -} \ No newline at end of file diff --git a/router/type.gno b/router/type.gno new file mode 100644 index 00000000..eff04d46 --- /dev/null +++ b/router/type.gno @@ -0,0 +1,38 @@ +package router + +import ( + "std" +) + +/* NOT USED CURRENTLY +type SwapRoutes []SwapRoute // RETURN THIS + +type SwapRoute struct { + inputTokenPath string + outputTokenPath string + swapAmount bigint + swapAmountRatio int // 최소 5% + pools []Pool +} + +type Pool struct { + poolPath string + token0Path string + token1Path string + fee int + currentPriceX96 bigint + currentTick int32 +} +*/ + +type SwapCallbackData struct { + // poolPath string + tokenIn string + tokenOut string + fee uint16 + + payer std.Address +} + +type SwapPaths map[int]string +type TokenPairs map[string][]string diff --git a/router/util.gno b/router/util.gno new file mode 100644 index 00000000..4850ba99 --- /dev/null +++ b/router/util.gno @@ -0,0 +1,89 @@ +package router + +import ( + "strconv" + "strings" + + "gno.land/p/demo/ufmt" +) + +/* UTIL */ +func removeItemFromStringArray(s []string, r string) []string { + for i, v := range s { + if v == r { + return append(s[:i], s[i+1:]...) + } + } + return s +} + +func poolPathWithFeeDivide(poolPath string) (string, string, int) { + poolPathSplit := strings.Split(poolPath, ":") + + if len(poolPathSplit) != 3 { + panic(ufmt.Sprintf("[POOL] pool_router.gno__poolPathWithFeeDivide() || len(poolPathSplit) != 3, poolPath: %s", poolPath)) + } + + feeInt, err := strconv.Atoi(poolPathSplit[2]) + if err != nil { + panic(ufmt.Sprintf("[POOL] pool_router.gno__poolPathWithFeeDivide() || cannot convert fee(%s) to int", poolPathSplit[2])) + } + + return poolPathSplit[0], poolPathSplit[1], feeInt +} + +func singlePoolPathWithFeeDivide(poolPath string) (string, string) { + singlePoolPathSplit := strings.Split(poolPath, ":") + + if len(singlePoolPathSplit) != 2 { + panic(ufmt.Sprintf("[POOL] pool_router.gno__singlePoolPathWithFeeDivide || len(singlePoolPathSplit) != 2, poolPath: %s", poolPath)) + } + return singlePoolPathSplit[0], singlePoolPathSplit[1] +} + +func getNumPoolInPath(path string) int { + count := strings.Count(path, ",") + return count / 2 +} + +func getPoolKey(path string, poolIdx int) string { + datas := strings.Split(path, ",") + + switch poolIdx { + case 0: + return sortItems(datas[0], datas[2], datas[1]) + case 1: + return sortItems(datas[2], datas[4], datas[3]) + case 2: + return sortItems(datas[4], datas[6], datas[5]) + default: + panic("NOT SUPPORTED #1") + } +} + +func getSwapData(path string, poolIdx int) (string, string, uint16) { + // inputToken, outputToken, fee + datas := strings.Split(path, ",") + + switch poolIdx { + case 0: + fee, _ := strconv.Atoi(datas[1]) + return datas[0], datas[2], uint16(fee) + case 1: + fee, _ := strconv.Atoi(datas[3]) + return datas[2], datas[4], uint16(fee) + case 2: + fee, _ := strconv.Atoi(datas[5]) + return datas[4], datas[6], uint16(fee) + default: + panic("NOT SUPPORTED #2") + } +} + +func sortItems(tokenAPath, tokenBPath, fee string) string { + if tokenAPath < tokenBPath { + return tokenAPath + ":" + tokenBPath + ":" + fee + } else { + return tokenBPath + ":" + tokenAPath + ":" + fee + } +} From 68886f10d159f845f84eddc6f49684ade375afcc Mon Sep 17 00:00:00 2001 From: n3wbie Date: Thu, 2 Nov 2023 18:14:34 +0900 Subject: [PATCH 19/35] GSW-527 feat: find best swap paths --- router/find_path.gno | 123 +++++++++++++++++++++++++++++++++++++ router/quotation.gno | 107 ++++++++++++++++++++++++++++++++ router/router_register.gno | 121 ++++++++++++++++++++++++++++++++++++ router/type.gno | 35 +++++------ router/util.gno | 2 +- 5 files changed, 366 insertions(+), 22 deletions(-) create mode 100644 router/find_path.gno create mode 100644 router/quotation.gno create mode 100644 router/router_register.gno diff --git a/router/find_path.gno b/router/find_path.gno new file mode 100644 index 00000000..40de51e8 --- /dev/null +++ b/router/find_path.gno @@ -0,0 +1,123 @@ +package router + +import ( + "strconv" + "strings" + + p "gno.land/r/pool" +) + +func findSwapPaths( + inputTokenPath string, + outputTokenPath string, +) (swapPaths SwapPaths) { + tokenPairs := TokenPairs{} + poolList := p.GetPoolList() + + for i, poolPath := range poolList { + token0Path, token1Path, pFee := poolPathWithFeeDivide(poolPath) + + { + k := token0Path + v := token1Path + ":" + strconv.Itoa(pFee) + + tokenPairs[k] = append(tokenPairs[k], v) + } + + { + k := token1Path + v := token0Path + ":" + strconv.Itoa(pFee) + + tokenPairs[k] = append(tokenPairs[k], v) + } + } + + swapPaths = getSwapPaths(tokenPairs, inputTokenPath, outputTokenPath) + return swapPaths +} + +func getSwapPaths( + tokenPairs TokenPairs, + inputTokenPath string, + outputTokenPath string, +) (swapPaths SwapPaths) { + swapPaths = make(SwapPaths, 0) + + // check if there is path that starts with input + if len(tokenPairs[inputTokenPath]) == 0 { + panic("NO POOL") + } + + // find direct path + for _, output := range tokenPairs[inputTokenPath] { + if strings.HasPrefix(output, outputTokenPath) { + outputPath, outputFee := singlePoolPathWithFeeDivide(output) + directPath := inputTokenPath + "," + outputFee + "," + outputPath + swapPaths[len(swapPaths)] = directPath + + tokenPairs[inputTokenPath] = removeItemFromStringArray(tokenPairs[inputTokenPath], output) + } + } + + // find nested path + // r3v4_xx: handle more than three time swap ?? + swapPaths = findTwicePath(tokenPairs, inputTokenPath, outputTokenPath, swapPaths) + swapPaths = findThreeTimePath(tokenPairs, inputTokenPath, outputTokenPath, swapPaths) + + return swapPaths +} + +func findTwicePath( + tokenPairs TokenPairs, + inputTokenPath string, + outputTokenPath string, + swapPaths SwapPaths, +) SwapPaths { + for _, second := range tokenPairs[inputTokenPath] { + secondPath, secondFee := singlePoolPathWithFeeDivide(second) + // tokenPairs[inputTokenPath] = removeItemFromStringArray(tokenPairs[inputTokenPath], second) + // tokenPairs[secondPath] = removeItemFromStringArray(tokenPairs[secondPath], (inputTokenPath + ":" + secondFee)) + + for _, third := range tokenPairs[secondPath] { + thirdPath, thirdFee := singlePoolPathWithFeeDivide(third) + // tokenPairs[secondPath] = removeItemFromStringArray(tokenPairs[secondPath], third) + // tokenPairs[thirdPath] = removeItemFromStringArray(tokenPairs[thirdPath], (secondPath + ":" + thirdFee)) + + if strings.HasPrefix(third, outputTokenPath) { + // twice + nestedPath := inputTokenPath + "," + secondFee + "," + secondPath + "," + thirdFee + "," + outputTokenPath + swapPaths[len(swapPaths)] = nestedPath + } + } + } + + return swapPaths +} + +func findThreeTimePath( + tokenPairs TokenPairs, + inputTokenPath string, + outputTokenPath string, + swapPaths SwapPaths, +) SwapPaths { + for _, second := range tokenPairs[inputTokenPath] { + secondPath, secondFee := singlePoolPathWithFeeDivide(second) + // tokenPairs[secondPath] = removeItemFromStringArray(tokenPairs[secondPath], (inputTokenPath + ":" + secondFee)) + + for _, third := range tokenPairs[secondPath] { // bar > bz + thirdPath, thirdFee := singlePoolPathWithFeeDivide(third) + // tokenPairs[secondPath] = removeItemFromStringArray(tokenPairs[thirdPath], (secondPath + ":" + thirdFee)) + + for _, fourth := range tokenPairs[thirdPath] { + fourthPath, fourthFee := singlePoolPathWithFeeDivide(fourth) + + if strings.HasPrefix(fourth, outputTokenPath) { + // three times + nestedPath := inputTokenPath + "," + secondFee + "," + secondPath + "," + thirdFee + "," + thirdPath + "," + fourthFee + "," + fourthPath + swapPaths[len(swapPaths)] = nestedPath + } + } + } + } + return swapPaths +} diff --git a/router/quotation.gno b/router/quotation.gno new file mode 100644 index 00000000..65b4d99f --- /dev/null +++ b/router/quotation.gno @@ -0,0 +1,107 @@ +package router + +import ( + "sort" + "std" + "strings" +) + +func quoteForAllPath( + inputTokenPath string, + outputTokenPath string, + + amountIn bigint, + sqrtPriceLimitX96 bigint, +) (quoterTargets []QuoterTarget) { + swapPaths := findSwapPaths(inputTokenPath, outputTokenPath) + swapPcts := calculatePercentages(amountIn) // 5% - 100, 10% - 200, ...., 100% - 2000 + + for _, swapPath := range swapPaths { + for pct, pctAmount := range swapPcts { + quoterTarget := QuoterTarget{ + pct: pct, + pctAmount: pctAmount, + targetPath: swapPath, + outputRatioX96: bigint(0), // will update later + } + quoterTargets = append(quoterTargets, quoterTarget) + } + } + + // DrySwap to calculate + for i, quoterTarget := range quoterTargets { + numPools := strings.Count(quoterTarget.targetPath, ",") / 2 + + if numPools < 1 { + panic("quotation.gno__CAN NOT FIND SWAP PATH") + } + + if numPools > 3 { + panic("quotation.gno__TOO MANY SWAP PATH") + } + + if numPools == 1 { + input, output, fee := getSwapData(quoterTarget.targetPath, 0) + + singleParams := SingleSwapParams{ + tokenIn: input, + tokenOut: output, + fee: fee, + amountIn: quoterTarget.pctAmount, // 5%, 10%, ... 100% + sqrtPriceLimitX96: sqrtPriceLimitX96, + } + + estimatedOut := singleSwapDry(singleParams) + if estimatedOut > 0 { + outputRatioX96 := estimatedOut * Q96 / quoterTarget.pctAmount * Q96 / Q96 // divide this value by Q96 => will get float ratio + quoterTarget.outputRatioX96 = outputRatioX96 + quoterTargets[i] = quoterTarget + } + } + + if numPools > 1 && numPools <= 3 { + input, output, fee := getSwapData(quoterTarget.targetPath, 0) + + swapParams := SwapParams{ + tokenIn: input, + tokenOut: output, + fee: fee, + + recipient: std.GetOrigCaller(), + amountIn: quoterTarget.pctAmount, // 5%, 10%, ... 100% + minAmountOut: 1, // r3v4_xx: sqrtPriceLimitX96 + } + + estimatedOut := multiSwapDry(swapParams, 0, numPools, quoterTarget.targetPath) // will iterate here to cover multi pools + if estimatedOut > 0 { + outputRatioX96 := estimatedOut * Q96 / quoterTarget.pctAmount * Q96 / Q96 + quoterTarget.outputRatioX96 = outputRatioX96 + quoterTargets[i] = quoterTarget + } + } + } + + // it contains outputRatio with 0 which is impossible path + // remove path with 0 ratio + var finalTargets []QuoterTarget + for _, quote := range quoterTargets { + if quote.outputRatioX96 > 0 { + finalTargets = append(finalTargets, quote) + } + } + + // sort this struct descending by ratio then return + sort.Sort(ByOutputRatioDesc(finalTargets)) + return finalTargets +} + +func calculatePercentages(amount bigint) map[int]bigint { // map[pct][value] + percentages := make(map[int]bigint) + + for i := 5; i <= 100; i += 5 { + percentage := (bigint(i) * amount) / 100 + percentages[i] = percentage + } + + return percentages +} diff --git a/router/router_register.gno b/router/router_register.gno new file mode 100644 index 00000000..5ecd1dca --- /dev/null +++ b/router/router_register.gno @@ -0,0 +1,121 @@ +package router + +import ( + "std" + + "gno.land/r/demo/users" +) + +const APPROVED_CALLER = "g12l9splsyngcgefrwa52x5a7scc29e9v086m6p4" // gsa + +var registered = []GRC20Pair{} + +type GRC20Interface interface { + Transfer() func(to users.AddressOrName, amount uint64) + TransferFrom() func(from, to users.AddressOrName, amount uint64) + BalanceOf() func(owner users.AddressOrName) uint64 + Approve() func(spender users.AddressOrName, amount uint64) +} + +type GRC20Pair struct { + pkgPath string + igrc20 GRC20Interface +} + +func findGRC20(pkgPath string) (int, bool) { + for i, pair := range registered { + if pair.pkgPath == pkgPath { + return i, true + } + } + + return -1, false +} + +func appendGRC20Interface(pkgPath string, igrc20 GRC20Interface) { + registered = append(registered, GRC20Pair{pkgPath: pkgPath, igrc20: igrc20}) +} + +func removeGRC20Interface(pkgPath string) { + i, found := findGRC20(pkgPath) + if !found { + return + } + + registered = append(registered[:i], registered[i+1:]...) +} + +func RegisterGRC20Interface(pkgPath string, igrc20 GRC20Interface) { + // only admin can register + // r3v4_xx: below logic can't be used in test case + // r3v4_xx: however must be used in production + + // caller := std.GetOrigCaller() + // if caller != APPROVED_CALLER { + // panic("unauthorized address to register") + // } + + _, found := findGRC20(pkgPath) + if !found { + appendGRC20Interface(pkgPath, igrc20) + } +} + +func UnregisterGRC20Interface(pkgPath string) { + // do not allow realm to unregister + std.AssertOriginCall() + + // only admin can unregister + caller := std.GetOrigCaller() + if caller != APPROVED_CALLER { + panic("unauthorized address to unregister") + } + + _, found := findGRC20(pkgPath) + if found { + removeGRC20Interface(pkgPath) + } +} + +func transferByRegisterCall(pkgPath string, to std.Address, amount uint64) bool { + i, found := findGRC20(pkgPath) + if !found { + return false + } + + registered[i].igrc20.Transfer()(users.AddressOrName(to), amount) + + return true +} + +func transferFromByRegisterCall(pkgPath string, from, to std.Address, amount uint64) bool { + i, found := findGRC20(pkgPath) + if !found { + return false + } + + registered[i].igrc20.TransferFrom()(users.AddressOrName(from), users.AddressOrName(to), amount) + + return true +} + +func balanceOfByRegisterCall(pkgPath string, owner std.Address) uint64 { + i, found := findGRC20(pkgPath) + if !found { + return 0 + } + + balance := registered[i].igrc20.BalanceOf()(users.AddressOrName(owner)) + return balance +} + +func approveByRegisterCall(pkgPath string, spender std.Address, amount uint64) bool { + i, found := findGRC20(pkgPath) + if !found { + return false + } + + registered[i].igrc20.Approve()(users.AddressOrName(spender), amount) + + return true +} diff --git a/router/type.gno b/router/type.gno index eff04d46..f54e83f8 100644 --- a/router/type.gno +++ b/router/type.gno @@ -4,27 +4,6 @@ import ( "std" ) -/* NOT USED CURRENTLY -type SwapRoutes []SwapRoute // RETURN THIS - -type SwapRoute struct { - inputTokenPath string - outputTokenPath string - swapAmount bigint - swapAmountRatio int // 최소 5% - pools []Pool -} - -type Pool struct { - poolPath string - token0Path string - token1Path string - fee int - currentPriceX96 bigint - currentTick int32 -} -*/ - type SwapCallbackData struct { // poolPath string tokenIn string @@ -36,3 +15,17 @@ type SwapCallbackData struct { type SwapPaths map[int]string type TokenPairs map[string][]string + +type QuoterTarget struct { + // totalAmount int // 100 // neee this? + pct int // 5% + pctAmount bigint // 5% + targetPath string // gno.land/r/foo,500,gno.land/r/qux + outputRatioX96 bigint // 121324894654 +} + +type ByOutputRatioDesc []QuoterTarget + +func (a ByOutputRatioDesc) Len() int { return len(a) } +func (a ByOutputRatioDesc) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByOutputRatioDesc) Less(i, j int) bool { return a[i].outputRatioX96 > a[j].outputRatioX96 } diff --git a/router/util.gno b/router/util.gno index 4850ba99..904e4863 100644 --- a/router/util.gno +++ b/router/util.gno @@ -76,7 +76,7 @@ func getSwapData(path string, poolIdx int) (string, string, uint16) { fee, _ := strconv.Atoi(datas[5]) return datas[4], datas[6], uint16(fee) default: - panic("NOT SUPPORTED #2") + return "", "", 0 } } From 919323a3defd6daf483eecd895c5e315529571f7 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Thu, 2 Nov 2023 18:15:25 +0900 Subject: [PATCH 20/35] GSW-528 feat: swap multi path with multi pools --- pool/consts.gno | 3 + pool/math_logic.gno | 5 +- pool/pool.gno | 19 ++-- pool/swap_math.gno | 5 +- router/consts.gno | 6 + router/find_direct_indirect_path.gno | 59 ---------- router/router.gno | 160 ++++++++++++--------------- router/router_test.gno | 146 +++++++++++------------- router/single_multi_swap.gno | 140 ----------------------- router/swap_inner.gno | 112 +++++++++++++++++++ router/swap_multi.gno | 101 +++++++++++++++++ router/swap_single.gno | 43 +++++++ 12 files changed, 416 insertions(+), 383 deletions(-) delete mode 100644 router/find_direct_indirect_path.gno delete mode 100644 router/single_multi_swap.gno create mode 100644 router/swap_inner.gno create mode 100644 router/swap_multi.gno create mode 100644 router/swap_single.gno diff --git a/pool/consts.gno b/pool/consts.gno index d76a519e..db66afe7 100644 --- a/pool/consts.gno +++ b/pool/consts.gno @@ -57,4 +57,7 @@ const ( MAX_UINT96 bigint = 79228162514264337593543950335 MAX_UINT160 bigint = 1461501637330902918203684832716283019655932542975 + + ADDR_ROUTER std.Address = std.DerivePkgAddr("gno.land/r/router") + ADDR_POOL std.Address = std.DerivePkgAddr("gno.land/r/pool") ) diff --git a/pool/math_logic.gno b/pool/math_logic.gno index 08797f18..c31e7de7 100644 --- a/pool/math_logic.gno +++ b/pool/math_logic.gno @@ -121,11 +121,12 @@ func DrySwap( ) if !(step.amountIn != 0 && step.amountOut != 0) { + // NOT ENOUGH MINTED return 0, 0, false } if exactInput { - state.amountSpecifiedRemaining -= step.amountIn + step.feeAmount + state.amountSpecifiedRemaining -= (step.amountIn + step.feeAmount) state.amountCalculated -= step.amountOut } else { state.amountSpecifiedRemaining += step.amountOut @@ -145,11 +146,13 @@ func DrySwap( if zeroForOne { if !(pool.balances.token1 > (-1 * amount1)) { + // NOT ENOUGH BALANCE for token1 return 0, 0, false } } else { if !(pool.balances.token0 > (-1 * amount0)) { + // NOT ENOUGH BALANCE for token0 return 0, 0, false } } diff --git a/pool/pool.gno b/pool/pool.gno index 761a33c0..f4718b77 100644 --- a/pool/pool.gno +++ b/pool/pool.gno @@ -180,6 +180,7 @@ func Swap( zeroForOne bool, amountSpecified bigint, sqrtPriceLimitX96 bigint, + payer std.Address, // router ) (bigint, bigint) { // r3v4_xxx: ONLY ROUTER CAN CALL THIS require(amountSpecified != 0, "[POOL] pool.gno__Swap() || amountSpecified can't be zero") @@ -373,6 +374,7 @@ func Swap( amount1 = amountSpecified - state.amountSpecifiedRemaining } + // TOKEN TRANFSER if zeroForOne { if amount1 < 0 { require(pool.balances.token1 > (-amount1), ufmt.Sprintf("[POOL] pool.gno__Swap() || pool.balances.token1(%s) > (-1 * amount1)(%s)", pool.balances.token1, (-amount1))) @@ -387,12 +389,9 @@ func Swap( balance0Before := bigint(balanceOfByRegisterCall(pool.token0Path, GetOrigPkgAddr())) - txOrigin := GetOrigCaller() // token should be transferred from actual user(GetOrigCaller), not from the realm(PrevRealm) - poolPkg := GetOrigPkgAddr() - - ok := transferFromByRegisterCall(pool.token0Path, txOrigin, poolPkg, uint64(amount0)) + ok := transferFromByRegisterCall(pool.token0Path, payer, ADDR_POOL, uint64(amount0)) if !ok { - panic("[POOL] pool.gno__Swap() || transferFromByRegisterCall(pool.token0Path, from, to, uint64(amount0)) failed") + panic("[POOL] pool.gno__Swap() || transferFromByRegisterCall(pool.token0Path, payer, ADDR_POOL, uint64(amount0)) failed") } require( @@ -406,7 +405,6 @@ func Swap( pool.balances.token0 += amount0 require(pool.balances.token0 >= 0, ufmt.Sprintf("[POOL] pool.gno__Swap() || pool.balances.token0(%s) >= 0__#1", pool.balances.token0)) require(pool.balances.token1 >= 0, ufmt.Sprintf("[POOL] pool.gno__Swap() || pool.balances.token1(%s) >= 0__#1", pool.balances.token1)) - } else { if amount0 < 0 { require(pool.balances.token0 > (-amount0), ufmt.Sprintf("[POOL] pool.gno__Swap() || pool.balances.token0(%s) > (-1 * amount0)(%s)", pool.balances.token0, (-amount0))) @@ -421,18 +419,15 @@ func Swap( balance1Before := bigint(balanceOfByRegisterCall(pool.token1Path, GetOrigPkgAddr())) - txOrigin := GetOrigCaller() // token should be transferred from actual user(GetOrigCaller), not from the realm(PrevRealm) - poolPkg := GetOrigPkgAddr() - - ok := transferFromByRegisterCall(pool.token1Path, txOrigin, poolPkg, uint64(amount1)) + ok := transferFromByRegisterCall(pool.token1Path, payer, ADDR_POOL, uint64(amount1)) if !ok { - panic("[POOL] pool.gno__Swap() || transferFromByRegisterCall(pool.token1Path, from, to, uint64(amount1)) failed") + panic("[POOL] pool.gno__Swap() || transferFromByRegisterCall(pool.token1Path, payer, ADDR_POOL, uint64(amount1)) failed") } require( balance1Before+amount1 <= bigint(balanceOfByRegisterCall(pool.token1Path, GetOrigPkgAddr())), ufmt.Sprintf( - "[POOL] pool.gno__Mint() || balance1Before(%d) + amount1(%d) <= balanceOfByRegisterCall(pool.token1Path, GetOrigPkgAddr())(%d)", + "[POOL] pool.gno__Swap() || balance1Before(%d) + amount1(%d) <= balanceOfByRegisterCall(pool.token1Path, GetOrigPkgAddr())(%d)", balance1Before, amount1, balanceOfByRegisterCall(pool.token1Path, GetOrigPkgAddr()), ), ) diff --git a/pool/swap_math.gno b/pool/swap_math.gno index e38f8f0f..5701a3fe 100644 --- a/pool/swap_math.gno +++ b/pool/swap_math.gno @@ -10,11 +10,10 @@ func swapMathComputeSwapStep( liquidity bigint, amountRemaining bigint, feePips uint32, -) (bigint, bigint, bigint, bigint) { +) (sqrtRatioNextX96, amountIn, amountOut, feeAmount bigint) { requireUnsigned(sqrtRatioCurrentX96, ufmt.Sprintf("[POOL] swap_math.gno__swapMathComputeSwapStep() || sqrtRatioCurrentX96(%s) >= 0", sqrtRatioCurrentX96)) requireUnsigned(sqrtRatioTargetX96, ufmt.Sprintf("[POOL] swap_math.gno__swapMathComputeSwapStep() || sqrtRatioTargetX96(%s) >= 0", sqrtRatioTargetX96)) requireUnsigned(liquidity, ufmt.Sprintf("[POOL] swap_math.gno__swapMathComputeSwapStep() || liquidity(%s) >= 0", liquidity)) - var sqrtRatioNextX96, amountIn, amountOut, feeAmount bigint zeroForOne := sqrtRatioCurrentX96 >= sqrtRatioTargetX96 exactIn := amountRemaining >= 0 @@ -94,7 +93,7 @@ func swapMathComputeSwapStep( requireUnsigned(amountOut, ufmt.Sprintf("[POOL] swap_math.gno__swapMathComputeSwapStep() || amountOut(%s) >= 0__#2", amountOut)) } - if !exactIn && amountOut > -amountRemaining { + if !exactIn && amountOut > amountRemaining { amountOut = -amountRemaining requireUnsigned(amountOut, ufmt.Sprintf("[POOL] swap_math.gno__swapMathComputeSwapStep() || amountOut(%s) >= 0__#3", amountOut)) } diff --git a/router/consts.gno b/router/consts.gno index ae23250a..42a59ffb 100644 --- a/router/consts.gno +++ b/router/consts.gno @@ -1,6 +1,12 @@ package router +import "std" + const ( MIN_PRICE bigint = 4295128740 // MIN_SQRT_RATIO + 1 MAX_PRICE bigint = 1461446703485210103287273052203988822378723970341 // MAX_SQRT_RATIO - 1 + + Q96 bigint = 79228162514264337593543950336 // 2 ** 96 + + ADDR_POOL std.Address = std.DerivePkgAddr("gno.land/r/pool") ) diff --git a/router/find_direct_indirect_path.gno b/router/find_direct_indirect_path.gno deleted file mode 100644 index 09b85015..00000000 --- a/router/find_direct_indirect_path.gno +++ /dev/null @@ -1,59 +0,0 @@ -package router - -import "strings" - -func findTwicePath( - tokenPairs TokenPairs, - inputTokenPath string, - outputTokenPath string, - swapPaths SwapPaths, -) SwapPaths { - for _, second := range tokenPairs[inputTokenPath] { - secondPath, secondFee := singlePoolPathWithFeeDivide(second) - tokenPairs[inputTokenPath] = removeItemFromStringArray(tokenPairs[inputTokenPath], second) - tokenPairs[secondPath] = removeItemFromStringArray(tokenPairs[secondPath], (inputTokenPath + ":" + secondFee)) - - for _, third := range tokenPairs[secondPath] { - thirdPath, thirdFee := singlePoolPathWithFeeDivide(third) - tokenPairs[secondPath] = removeItemFromStringArray(tokenPairs[secondPath], third) - tokenPairs[thirdPath] = removeItemFromStringArray(tokenPairs[thirdPath], (secondPath + ":" + thirdFee)) - - if strings.HasPrefix(third, outputTokenPath) { - // twice - nestedPath := inputTokenPath + "," + secondFee + "," + secondPath + "," + secondFee + "," + outputTokenPath - - swapPaths[len(swapPaths)] = nestedPath - } - } - } - - return swapPaths -} - -func findThreeTimePath( - tokenPairs TokenPairs, - inputTokenPath string, - outputTokenPath string, - swapPaths SwapPaths, -) SwapPaths { - for _, second := range tokenPairs[inputTokenPath] { - secondPath, secondFee := singlePoolPathWithFeeDivide(second) - tokenPairs[secondPath] = removeItemFromStringArray(tokenPairs[secondPath], (inputTokenPath + ":" + secondFee)) - - for _, third := range tokenPairs[secondPath] { // bar > bz - thirdPath, thirdFee := singlePoolPathWithFeeDivide(third) - tokenPairs[secondPath] = removeItemFromStringArray(tokenPairs[thirdPath], (secondPath + ":" + thirdFee)) - - for _, fourth := range tokenPairs[thirdPath] { - fourthPath, fourthFee := singlePoolPathWithFeeDivide(fourth) - - if strings.HasPrefix(fourth, outputTokenPath) { - // three times - nestedPath := inputTokenPath + "," + secondFee + "," + secondPath + "," + secondFee + "," + thirdPath + "," + thirdFee + "," + fourthPath - swapPaths[len(swapPaths)] = nestedPath - } - } - } - } - return swapPaths -} diff --git a/router/router.gno b/router/router.gno index b9621d73..7db2523d 100644 --- a/router/router.gno +++ b/router/router.gno @@ -2,118 +2,100 @@ package router import ( "std" - "strconv" "strings" - - p "gno.land/r/pool" ) -func SwapRoute( - inputTokenPath string, +func BestSwap( + inputToken string, outputTokenPath string, amountIn bigint, - recipient std.Address, sqrtPriceLimitX96 bigint, -) (amountOut bigint) { - numSwaps := findSwapPaths(inputTokenPath, outputTokenPath) - - for i, swapPath := range numSwaps { - numPools := strings.Count(swapPath, ",") / 2 - - for j := i; j < numPools; j++ { - if numPools < 1 { - panic("CAN NOT FIND SWAP PATH") - } else if numPools == 1 { - // single Swap - input, output, fee := getSwapData(swapPath, j) - - singleParams := SingleSwapParams{ - tokenIn: input, - tokenOut: output, - fee: fee, - amountIn: amountIn, - sqrtPriceLimitX96: sqrtPriceLimitX96, - } - - amountOut = singleSwap(singleParams) - } else { - // multiple Swap - input, output, fee := getSwapData(swapPath, j-1) - swapParams := SwapParams{ - tokenIn: input, - tokenOut: output, - fee: fee, - - recipient: std.GetOrigCaller(), - amountIn: amountIn, - minAmountOut: amountIn * 5, // r3v4_xxx: need usd price I think - } - - amountOut = multiSwap(swapParams, i, numPools, swapPath) - } +) { + // get quotes + quotes := quoteForAllPath( // sored by Ratio DESC + inputToken, // inputToken + outputTokenPath, // outputToken + amountIn, // amountIn + sqrtPriceLimitX96, // sqrtPriceLimitX96 + ) + if len(quotes) == 0 { + panic("router.gno__THERE IS NO QUOTE") + } + + bestSwaps := []QuoterTarget{} + totalPct := 100 + + for i, quote := range quotes { + quotePct := quote.pct + outputRatioX96 := quote.outputRatioX96 + + totalPct -= quotePct + bestSwaps = append(bestSwaps, quote) + + if totalPct <= 0 { + break } } - return amountOut -} + if len(bestSwaps) == 0 { + panic("router.gno__CAN'T MAKE BestSwapRoute") + } -func findSwapPaths( - inputTokenPath string, - outputTokenPath string, -) (swapPaths SwapPaths) { - tokenPairs := TokenPairs{} - poolList := p.GetPoolList() + // DEBUG + println("DEBUG router.gno__len(bestSwaps):", len(bestSwaps)) - for i, poolPath := range poolList { - token0Path, token1Path, pFee := poolPathWithFeeDivide(poolPath) + remainingAmount := amountIn + for i, bestSwap := range bestSwaps { + numPools := strings.Count(bestSwap.targetPath, ",") / 2 - { - k := token0Path - v := token1Path + ":" + strconv.Itoa(pFee) + if numPools < 1 { + panic("router.gno__CAN NOT FIND SWAP PATH") + } - tokenPairs[k] = append(tokenPairs[k], v) + if numPools > 3 { + panic("router.gno__TOO MANY SWAP PATH") } - { - k := token1Path - v := token0Path + ":" + strconv.Itoa(pFee) + // SINGLE + if numPools == 1 { + input, output, fee := getSwapData(bestSwap.targetPath, 0) - tokenPairs[k] = append(tokenPairs[k], v) + singleParams := SingleSwapParams{ + tokenIn: input, + tokenOut: output, + fee: fee, + amountIn: bestSwap.pctAmount, + sqrtPriceLimitX96: sqrtPriceLimitX96, + } + amountOut := singleSwap(singleParams) + remainingAmount -= bestSwap.pctAmount } - } - swapPaths = getSwapPaths(tokenPairs, inputTokenPath, outputTokenPath) - return swapPaths -} + // MULTI + if numPools > 1 && numPools <= 3 { + toSwap := bigint(0) -func getSwapPaths( - tokenPairs TokenPairs, - inputTokenPath string, - outputTokenPath string, -) (swapPaths SwapPaths) { - swapPaths = make(SwapPaths, 0) + // isFinal + if i == len(bestSwaps)-1 { // last swap routes + toSwap = remainingAmount + } else { + remainingAmount -= bestSwap.pctAmount + toSwap = bestSwap.pctAmount + } - // check if there is path that starts with input - if len(tokenPairs[inputTokenPath]) == 0 { - panic("NO POOL") - } + input, output, fee := getSwapData(bestSwap.targetPath, 0) - // find direct path - for _, output := range tokenPairs[inputTokenPath] { - if strings.HasPrefix(output, outputTokenPath) { - outputPath, outputFee := singlePoolPathWithFeeDivide(output) - directPath := inputTokenPath + "," + outputFee + "," + outputPath - swapPaths[len(swapPaths)] = directPath + swapParams := SwapParams{ + tokenIn: input, + tokenOut: output, + fee: fee, - tokenPairs[inputTokenPath] = removeItemFromStringArray(tokenPairs[inputTokenPath], output) + recipient: std.GetOrigCaller(), + amountIn: toSwap, + minAmountOut: 1, // r3v4_xx: sqrtPriceLimitX96 + } + amountOut := multiSwap(swapParams, 0, numPools, bestSwap.targetPath) // iterate here } } - - // find nested path - // r3v4_xx: handle more than three time swap ?? - swapPaths = findTwicePath(tokenPairs, inputTokenPath, outputTokenPath, swapPaths) - swapPaths = findThreeTimePath(tokenPairs, inputTokenPath, outputTokenPath, swapPaths) - - return } diff --git a/router/router_test.gno b/router/router_test.gno index 5195f80a..ba312325 100644 --- a/router/router_test.gno +++ b/router/router_test.gno @@ -6,14 +6,19 @@ import ( "gno.land/p/demo/testutils" + "gno.land/r/demo/users" _ "gno.land/r/grc20_wrapper" p "gno.land/r/pool" pos "gno.land/r/position" + + foo "gno.land/r/foo" + qux "gno.land/r/qux" ) var ( gsa = testutils.TestAddress("gsa") // Gnoswap Admin lp01 = testutils.TestAddress("lp01") // Liquidity Provider 01 + tr01 = testutils.TestAddress("tr01") // Trader 01 poolAddr = std.DerivePkgAddr("gno.land/r/pool") posAddr = std.DerivePkgAddr("gno.land/r/position") @@ -27,14 +32,16 @@ var ( bazPath = "gno.land/r/baz" // token3 quxPath = "gno.land/r/qux" // token4 - test_tickLower = int32(9000) - test_tickUpper = int32(11000) - test_liquidityExpect = bigint(1000) + test_fee100 = uint16(100) + test_fee500 = uint16(500) + + max_timeout = bigint(9999999999) ) func init() { println(gsa, "// gsa") println(lp01, "// lp01") + println(tr01, "// tr01") println(poolAddr, "// poolAddr") println(posAddr, "// posAddr") println(routerAddr, "// routerAddr") @@ -49,101 +56,82 @@ func TestInitManual(t *testing.T) { func TestCreatePool(t *testing.T) { std.TestSetOrigCaller(gsa) - // p.CreatePool(fooPath, barPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - // p.CreatePool(fooPath, barPath, uint16(500), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - // p.CreatePool(fooPath, bazPath, uint16(500), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - // p.CreatePool(barPath, bazPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - // findSwapPaths("gno.land/r/foo", "gno.land/r/baz") // 3 paths - // // foo_bar_100 > bar_baz_100 - // // foo_bar_500 > bar_baz_100 - // // foo_baz_500 - - // p.CreatePool(fooPath, barPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - // p.CreatePool(fooPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - // p.CreatePool(barPath, bazPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - // p.CreatePool(barPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - // p.CreatePool(bazPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - // findSwapPaths("gno.land/r/foo", "gno.land/r/qux") // 3 paths - // // foo_qux_100 - // // foo_bar_100 > bar_qux_100 - // // foo_bar_100 > bar_baz_100 > baz_qux_100 - - // p.CreatePool(fooPath, barPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - // p.CreatePool(fooPath, barPath, uint16(500), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - // p.CreatePool(fooPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - // p.CreatePool(barPath, bazPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - // p.CreatePool(barPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - // p.CreatePool(bazPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + p.CreatePool(fooPath, barPath, test_fee100, 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + p.CreatePool(fooPath, barPath, test_fee500, 167719732170287102368011632179) // tick = 15_000, ratio = 4.481352978667308 + p.CreatePool(fooPath, quxPath, test_fee100, 965075977353221155028623082916) // tick = 50_000, ratio = 148.37606292307464 + p.CreatePool(barPath, bazPath, test_fee100, 124251697196845557112282348179) // tick = 9_000, ratio = 2.4594924388851824 + p.CreatePool(barPath, quxPath, test_fee100, 101729702841318637793976746270) // tick = 5_000, ratio = 1.648680055931176 + p.CreatePool(bazPath, quxPath, test_fee100, 92049301871182272007977902845) // tick = 3_000, ratio = 1.3498385611954853 // findSwapPaths("gno.land/r/foo", "gno.land/r/qux") // 5 paths // foo_qux_100 // foo_bar_100 > bar_qux_100 // foo_bar_500 > bar_qux_100 // foo_bar_100 > bar_baz_100 > baz_qux_100 // foo_bar_500 > bar_baz_100 > baz_qux_100 - - p.CreatePool(fooPath, barPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - p.CreatePool(fooPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - p.CreatePool(barPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - // findSwapPaths("gno.land/r/foo", "gno.land/r/qux") // 2 paths - // foo_bar_100 > bar_qux_100 - // foo_qux_100 } func TestPositionMint(t *testing.T) { std.TestSetOrigCaller(lp01) + pos.Mint(fooPath, barPath, test_fee100, int32(9000), int32(11000), bigint(100000000), bigint(100000000), 0, 0, max_timeout) + pos.Mint(fooPath, barPath, test_fee500, int32(14000), int32(16000), bigint(100000000), bigint(100000000), 0, 0, max_timeout) + pos.Mint(barPath, bazPath, test_fee100, int32(8000), int32(10000), bigint(100000000), bigint(100000000), 0, 0, max_timeout) + pos.Mint(barPath, quxPath, test_fee100, int32(4000), int32(6000), bigint(100000000), bigint(100000000), 0, 0, max_timeout) + pos.Mint(bazPath, quxPath, test_fee100, int32(2000), int32(4000), bigint(100000000), bigint(100000000), 0, 0, max_timeout) - pos.Mint( - fooPath, - barPath, - uint16(100), - test_tickLower, - test_tickUpper, - bigint(10000000), - bigint(10000000), - bigint(1), - bigint(1), - bigint(9999999999), - ) - - pos.Mint( - fooPath, - quxPath, - uint16(100), - test_tickLower, - test_tickUpper, - bigint(10000), - bigint(10000), - bigint(1), - bigint(1), - bigint(9999999999), - ) + // direct & expensive + pos.Mint(fooPath, quxPath, test_fee100, int32(48000), int32(52000), bigint(100000000), bigint(100000000), 0, 0, max_timeout) - pos.Mint( - barPath, - quxPath, - uint16(100), - test_tickLower, - test_tickUpper, - bigint(10000000), - bigint(10000000), - bigint(1), - bigint(1), - bigint(9999999999), - ) } func TestFuncs(t *testing.T) { - // sPath := findSwapPaths("gno.land/r/foo", "gno.land/r/qux") // 2 paths + std.TestSetOrigCaller(tr01) + + tr01FooOldBal := foo.BalanceOf(a2u(tr01)) + tr01QuxOldBal := qux.BalanceOf(a2u(tr01)) + RoQuxOldBal := qux.BalanceOf(a2u(routerAddr)) + PoQuxOldBal := qux.BalanceOf(a2u(poolAddr)) - outputAmount := SwapRoute( + BestSwap( "gno.land/r/foo", // inputTokenPath "gno.land/r/qux", // outputTokenPath - - 10_000, // amountIn - std.Address("g123recv"), // recipient - 0, // sqrtPriceLimitX96 + 100, // amountIn + 0, // sqrtPriceLimitX96 ) - println("YOU GOT QUX OF", outputAmount) + tr01FooNewBal := foo.BalanceOf(a2u(tr01)) + tr01QuxNewBal := qux.BalanceOf(a2u(tr01)) + RoQuxNewBal := qux.BalanceOf(a2u(routerAddr)) + PoQuxNewBal := qux.BalanceOf(a2u(poolAddr)) + + shouldEQ(t, RoQuxOldBal, RoQuxNewBal) + + println("Send Foo:", tr01FooOldBal-tr01FooNewBal) + println("Recv Qux:", tr01QuxNewBal-tr01QuxOldBal) + println() +} + +func a2u(addr std.Address) users.AddressOrName { + return users.AddressOrName(addr) +} + +/* HELPER */ +func shouldEQ(t *testing.T, got, expected interface{}) { + if got != expected { + t.Errorf("got %v, expected %v", got, expected) + } +} + +func shouldNEQ(t *testing.T, got, expected interface{}) { + if got == expected { + t.Errorf("got %v, expected %v", got, expected) + } +} +func shouldPanic(t *testing.T, f func()) { + defer func() { + if r := recover(); r == nil { + t.Errorf("expected panic") + } + }() + f() } diff --git a/router/single_multi_swap.gno b/router/single_multi_swap.gno deleted file mode 100644 index a16f0a75..00000000 --- a/router/single_multi_swap.gno +++ /dev/null @@ -1,140 +0,0 @@ -package router - -import ( - "std" - - p "gno.land/r/pool" -) - -type SingleSwapParams struct { - tokenIn string - tokenOut string - fee uint16 - amountIn bigint - sqrtPriceLimitX96 bigint -} - -func singleSwap(params SingleSwapParams) (amountOut bigint) { - amountOut = _swap( - params.amountIn, - std.PrevRealm().Addr(), // msg.sender - params.sqrtPriceLimitX96, - SwapCallbackData{ - params.tokenIn, - params.tokenOut, - params.fee, - std.PrevRealm().Addr(), // msg.sender, - }, - ) - - return amountOut -} - -type SwapParams struct { - tokenIn string - tokenOut string - fee uint16 - - recipient std.Address - amountIn bigint - minAmountOut bigint -} - -func multiSwap(params SwapParams, currentPoolIndex, numPool int, swapPath string) (amountOut bigint) { - payer := std.GetOrigCaller() // user - var hasMultiplePools bool - - for { - hasMultiplePools = currentPoolIndex < numPool - - var recipient std.Address - if hasMultiplePools { - recipient = std.DerivePkgAddr("gno.land/r/router") - } else { - recipient = params.recipient // user ~= std.GetOrigCaller() - } - - params.amountIn = _swap( - params.amountIn, // amountIn - recipient, // recipient - 0, // sqrtPriceLimitX96 - SwapCallbackData{ - params.tokenIn, // tokenIn - params.tokenOut, // tokenOut - params.fee, // fee - payer, // payer - }, - ) - - if hasMultiplePools { - payer = std.DerivePkgAddr("gno.land/r/router") - - nextInput, nextOutput, nextFee := getSwapData(swapPath, currentPoolIndex) - params.tokenIn = nextInput - params.tokenOut = nextOutput - params.fee = nextFee - - currentPoolIndex++ - } else { - amountOut = params.amountIn - return amountOut - } - } - - if amountOut < params.minAmountOut { - panic("Too few receive") - } -} - -func _swap( - amountIn bigint, - recipient std.Address, - sqrtPriceLimitX96 bigint, - data SwapCallbackData, -) (amountOut bigint) { - // prepare - zeroForOne := data.tokenIn < data.tokenOut - - if sqrtPriceLimitX96 == 0 { - if zeroForOne { - sqrtPriceLimitX96 = MIN_PRICE - } else { - sqrtPriceLimitX96 = MAX_PRICE - } - } - - // check possible - _, _, ok := p.DrySwap( - data.tokenIn, - data.tokenOut, - data.fee, - - recipient, - zeroForOne, - amountIn, - sqrtPriceLimitX96, - ) - if !ok { - return 0 - } - - amount0, amount1 := p.Swap( - data.tokenIn, - data.tokenOut, - data.fee, - - recipient, - zeroForOne, - amountIn, - sqrtPriceLimitX96, - ) - - // if success - if zeroForOne { - amountOut = -amount1 - } else { - amountOut = -amount0 - } - - return amountOut -} diff --git a/router/swap_inner.gno b/router/swap_inner.gno new file mode 100644 index 00000000..8c536987 --- /dev/null +++ b/router/swap_inner.gno @@ -0,0 +1,112 @@ +package router + +import ( + "std" + + p "gno.land/r/pool" +) + +func _swap( + amountIn bigint, + recipient std.Address, + sqrtPriceLimitX96 bigint, + data SwapCallbackData, +) (amountOut bigint) { + // prepare + zeroForOne := data.tokenIn < data.tokenOut + + if sqrtPriceLimitX96 == 0 { + if zeroForOne { + sqrtPriceLimitX96 = MIN_PRICE + } else { + sqrtPriceLimitX96 = MAX_PRICE + } + } + + // dry swap -> esteimate amount -> approve exact amount + // r3v4_xx: or little bit more + approveAmount0, approveAmount1, _ := p.DrySwap( + data.tokenIn, + data.tokenOut, + data.fee, + + recipient, + zeroForOne, + amountIn, + sqrtPriceLimitX96, + ) + + // ROUTER approves POOL as spender + approveByRegisterCall(data.tokenIn, ADDR_POOL, abs(approveAmount0)) // r3v4_xxx + approveByRegisterCall(data.tokenOut, ADDR_POOL, abs(approveAmount1)) // r3v4_xxx + + amount0, amount1 := p.Swap( + data.tokenIn, + data.tokenOut, + data.fee, + + recipient, + zeroForOne, + amountIn, + sqrtPriceLimitX96, + + data.payer, + ) + + if zeroForOne { + amountOut = -amount1 + } else { + amountOut = -amount0 + } + + return amountOut +} + +func _swapDry( + amountIn bigint, + recipient std.Address, + sqrtPriceLimitX96 bigint, + data SwapCallbackData, +) (amountOut bigint) { + zeroForOne := data.tokenIn < data.tokenOut + + if sqrtPriceLimitX96 == 0 { + if zeroForOne { + sqrtPriceLimitX96 = MIN_PRICE + } else { + sqrtPriceLimitX96 = MAX_PRICE + } + } + + // check possible + amount0, amount1, ok := p.DrySwap( + data.tokenIn, + data.tokenOut, + data.fee, + + recipient, + zeroForOne, + amountIn, + sqrtPriceLimitX96, + ) + if !ok { + return 0 + } + + // if success + if zeroForOne { + amountOut = -amount1 + } else { + amountOut = -amount0 + } + + return amountOut +} + +func abs(x bigint) uint64 { + if x < 0 { + return uint64(-x) + } + + return uint64(x) +} diff --git a/router/swap_multi.gno b/router/swap_multi.gno new file mode 100644 index 00000000..5f055707 --- /dev/null +++ b/router/swap_multi.gno @@ -0,0 +1,101 @@ +package router + +import ( + "std" +) + +type SwapParams struct { + tokenIn string + tokenOut string + fee uint16 + + recipient std.Address + amountIn bigint + minAmountOut bigint +} + +func multiSwap(params SwapParams, currentPoolIndex, numPool int, swapPath string) (amountOut bigint) { + payer := std.GetOrigCaller() // user + + for { + var recipient std.Address + currentPoolIndex++ + + if currentPoolIndex < numPool { + recipient = std.DerivePkgAddr("gno.land/r/router") + } else { + recipient = params.recipient // user ~= std.GetOrigCaller() + } + + params.amountIn = _swap( + params.amountIn, // amountIn + recipient, // recipient + 0, // sqrtPriceLimitX96 + SwapCallbackData{ + params.tokenIn, // tokenIn + params.tokenOut, // tokenOut + params.fee, // fee + payer, // payer + }, + ) + + if currentPoolIndex < numPool { + payer = std.DerivePkgAddr("gno.land/r/router") + + nextInput, nextOutput, nextFee := getSwapData(swapPath, currentPoolIndex) + params.tokenIn = nextInput + params.tokenOut = nextOutput + params.fee = nextFee + } else { + amountOut = params.amountIn + return amountOut + } + } + + if amountOut < params.minAmountOut { + panic("Too few receive") +} +} + +func multiSwapDry(params SwapParams, currentPoolIndex, numPool int, swapPath string) (amountOut bigint) { + payer := std.GetOrigCaller() // user + + for { + var recipient std.Address + currentPoolIndex++ + + if currentPoolIndex < numPool { + recipient = std.DerivePkgAddr("gno.land/r/router") + } else { + recipient = params.recipient // user ~= std.GetOrigCaller() + } + + params.amountIn = _swapDry( + params.amountIn, // amountIn + recipient, // recipient + 0, // sqrtPriceLimitX96 + SwapCallbackData{ + params.tokenIn, // tokenIn + params.tokenOut, // tokenOut + params.fee, // fee + payer, // payer + }, + ) + + if currentPoolIndex < numPool { + payer = std.DerivePkgAddr("gno.land/r/router") + + nextInput, nextOutput, nextFee := getSwapData(swapPath, currentPoolIndex) + params.tokenIn = nextInput + params.tokenOut = nextOutput + params.fee = nextFee + } else { + amountOut = params.amountIn + return amountOut + } + } + + if amountOut < params.minAmountOut { + panic("Too few receive") + } +} diff --git a/router/swap_single.gno b/router/swap_single.gno new file mode 100644 index 00000000..95c3484d --- /dev/null +++ b/router/swap_single.gno @@ -0,0 +1,43 @@ +package router + +import ( + "std" +) + +type SingleSwapParams struct { + tokenIn string + tokenOut string + fee uint16 + amountIn bigint + sqrtPriceLimitX96 bigint +} + +func singleSwap(params SingleSwapParams) (amountOut bigint) { + amountOut = _swap( + params.amountIn, + std.GetOrigCaller(), // if single swap => user will recieve + params.sqrtPriceLimitX96, + SwapCallbackData{ + params.tokenIn, + params.tokenOut, + params.fee, + std.PrevRealm().Addr(), // payer ==> msg.sender, + }, + ) + return amountOut +} + +func singleSwapDry(params SingleSwapParams) (amountOut bigint) { + amountOut = _swapDry( + params.amountIn, + std.GetOrigCaller(), // if single swap => user will recieve + params.sqrtPriceLimitX96, + SwapCallbackData{ + params.tokenIn, + params.tokenOut, + params.fee, + std.PrevRealm().Addr(), // payer ==> msg.sender, + }, + ) + return amountOut +} From 8c3fcb70588cbb7c4db46a9d44185b0b0697628f Mon Sep 17 00:00:00 2001 From: n3wbie Date: Fri, 3 Nov 2023 16:43:32 +0900 Subject: [PATCH 21/35] chore: remove dummy testcase --- pool/_TEST_pool_router_test.gno_WIP | 192 ---------------------------- 1 file changed, 192 deletions(-) delete mode 100644 pool/_TEST_pool_router_test.gno_WIP diff --git a/pool/_TEST_pool_router_test.gno_WIP b/pool/_TEST_pool_router_test.gno_WIP deleted file mode 100644 index 4ce078ba..00000000 --- a/pool/_TEST_pool_router_test.gno_WIP +++ /dev/null @@ -1,192 +0,0 @@ -package pool - -import ( - "std" - "testing" - - "gno.land/p/demo/testutils" - - _ "gno.land/r/grc20_wrapper" -) - -var ( - gsa = testutils.TestAddress("gsa") // Gnoswap Admin - lp01 = testutils.TestAddress("lp01") // Liquidity Provider 01 - - poolAddr = std.DerivePkgAddr("gno.land/r/pool") - posAddr = std.DerivePkgAddr("gno.land/r/position") -) - -var ( - // Common - fooPath = "gno.land/r/foo" // token1 - barPath = "gno.land/r/bar" // token2 - bazPath = "gno.land/r/baz" // token3 - quxPath = "gno.land/r/qux" // token4 -) - -// 1. Init -func TestInitManual(t *testing.T) { - std.TestSetOrigCaller(gsa) - InitManual() - std.TestSkipHeights(1) -} - -func TestCreatePool(t *testing.T) { - CreatePool(fooPath, barPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - CreatePool(fooPath, barPath, uint16(500), 215353707227994575755767921544) // tick = 20_000, ratio = 7.388317279516561 - - CreatePool(fooPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - - CreatePool(barPath, bazPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - CreatePool(barPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - - CreatePool(bazPath, quxPath, uint16(100), 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - - // std.TestSkipHeights(6) - // shouldEQ(t, len(pools), 6) -} - -func TestMintFooBar100(t *testing.T) { - std.TestSetPrevRealm("gno.land/r/position") - std.TestSetOrigCaller(lp01) - - m0, m1 := Mint( - fooPath, - barPath, - uint16(100), - posAddr, - int32(9000), - int32(11000), - bigint(100000000), - ) - - shouldEQ(t, m0, bigint(2958014)) - shouldEQ(t, m1, bigint(8040315)) -} - -func TestMintFooBar500(t *testing.T) { - std.TestSetPrevRealm("gno.land/r/position") - std.TestSetOrigCaller(lp01) - - m0, m1 := Mint( - fooPath, - barPath, - uint16(500), - posAddr, - int32(19000), - int32(21000), - bigint(100000000), - ) - - shouldEQ(t, m0, bigint(1794171)) - shouldEQ(t, m1, bigint(13255907)) -} - -func TestMintBarBaz100(t *testing.T) { - std.TestSetPrevRealm("gno.land/r/position") - std.TestSetOrigCaller(lp01) - - m0, m1 := Mint( - barPath, - bazPath, - uint16(100), - posAddr, - int32(9000), - int32(11000), - bigint(100000000), - ) - - shouldEQ(t, m0, bigint(2958014)) - shouldEQ(t, m1, bigint(8040315)) -} - -func TestFindAllPoolPath(t *testing.T) { - // r3v4_xx: for now max hop is 3 - allPaths := FindAllPoolPath(fooPath, quxPath, 3) - - shouldEQ(t, len(allPaths), 5) -} - -func TestFindBestPath(t *testing.T) { - // for _, path := range allPaths { - // println(path) - // } - - // bestPoolPathDetail := FindBestPool( - // fooPath, // tokenA - // barPath, // tokenB - // true, // zeroForOne - // 200, // amountSpecified - // ) - // jsonStr := gjson.Parse(bestPoolPathDetail) - // shouldEQ(t, jsonStr.Get("response.data.pool_path").String(), foo_3000") - - // // should return empty if not enough balance - // { - // bestPoolPathDetail := FindBestPool( - // fooPath, // tokenA - // barPath, // tokenB - // true, // zeroForOne - // 438, // amountSpecified - // ) - - // jsonStr := gjson.Parse(bestPoolPathDetail) - // shouldEQ(t, jsonStr.Get("response.data.pool_path").String(), "") - // } - // } - - // func TestFindBestPoolTrueNegative(t *testing.T) { - // bestPoolPathDetail := FindBestPool( - // fooPath, // tokenA - // barPath, // tokenB - // true, // zeroForOne - // -888, // amountSpecified - // ) - // jsonStr := gjson.Parse(bestPoolPathDetail) - // shouldEQ(t, jsonStr.Get("response.data.pool_path").String(), barPathfoo_3000") - // } - - // func TestFindBestPoolFalsePositive(t *testing.T) { - // bestPoolPathDetail := FindBestPool( - // fooPath, // tokenA - // barPath, // tokenB - // false, // zeroForOne - // 5, // amountSpecified - // ) - // jsonStr := gjson.Parse(bestPoolPathDetail) - // shouldEQ(t, jsonStr.Get("response.data.pool_path").String(), barPathfoo_3000") - // } - - // func TestFindBestPoolFalseNegative(t *testing.T) { - // bestPoolPathDetail := FindBestPool( - // fooPath, // tokenA - // barPath, // tokenB - // false, // zeroForOne - // -11, // amountSpecified - // ) - // jsonStr := gjson.Parse(bestPoolPathDetail) - // shouldEQ(t, jsonStr.Get("response.data.pool_path").String(), barPathfoo_3000") -} - -/* HELPER */ -func shouldEQ(t *testing.T, got, expected interface{}) { - if got != expected { - t.Errorf("got %v, expected %v", got, expected) - } -} - -func shouldNEQ(t *testing.T, got, expected interface{}) { - if got == expected { - t.Errorf("got %v, expected %v", got, expected) - } -} - -func shouldPanic(t *testing.T, f func()) { - defer func() { - if r := recover(); r == nil { - t.Errorf("expected panic") - } - }() - f() -} From 649da4f72308183eb59e729432164ee345eb6a5c Mon Sep 17 00:00:00 2001 From: n3wbie Date: Fri, 3 Nov 2023 16:45:04 +0900 Subject: [PATCH 22/35] GSW-528 feat: swap multi path with multi pools --- router/quotation.gno | 58 ++++++++++++++++++++++ router/router.gno | 31 +++--------- router/router_test.gno | 48 +++++++++++------- router/rpc_call.gno | 108 +++++++++++++++++++++++++++++++++++++++++ router/type.gno | 3 +- router/util.gno | 6 +++ 6 files changed, 210 insertions(+), 44 deletions(-) create mode 100644 router/rpc_call.gno diff --git a/router/quotation.gno b/router/quotation.gno index 65b4d99f..e76b3e6c 100644 --- a/router/quotation.gno +++ b/router/quotation.gno @@ -105,3 +105,61 @@ func calculatePercentages(amount bigint) map[int]bigint { // map[pct][value] return percentages } + +func findBestPaths(quotes []QuoterTarget) (bestSwaps []QuoterTarget) { + bestSwaps = make([]QuoterTarget, 0) + + totalPct := 100 + for _, quote := range quotes { + quotePct := quote.pct + outputRatioX96 := quote.outputRatioX96 + + totalPct -= quotePct + + bestSwaps = append(bestSwaps, quote) + + if totalPct <= 0 { + break + } + } + + return bestSwaps +} + +func removeDuplication(bestSwaps []QuoterTarget) (finalSwaps []QuoterTarget) { + finalSwaps = make([]QuoterTarget, 0) // return + insertedPath := []string{} // tmp + pctToSwap := 0 + for _, bestSwap := range bestSwaps { + pctToSwap += bestSwap.pct + if !containsString(insertedPath, bestSwap.targetPath) { + insertedPath = append(insertedPath, bestSwap.targetPath) + finalSwaps = append(finalSwaps, bestSwap) + } else { + for i, finalSwap := range finalSwaps { + if finalSwap.targetPath == bestSwap.targetPath { + finalSwap.pctAmount += bestSwap.pctAmount // can be exceed + + if pctToSwap > 100 { + finalSwap.pct += (bestSwap.pct - (pctToSwap - 100)) + } else { + finalSwap.pct += bestSwap.pct + } + + finalSwaps[i] = finalSwap + } + } + } + } + + return finalSwaps +} + +func containsString(slice []string, target string) bool { + for _, s := range slice { + if s == target { + return true + } + } + return false +} diff --git a/router/router.gno b/router/router.gno index 7db2523d..069455ef 100644 --- a/router/router.gno +++ b/router/router.gno @@ -13,38 +13,21 @@ func BestSwap( sqrtPriceLimitX96 bigint, ) { // get quotes - quotes := quoteForAllPath( // sored by Ratio DESC - inputToken, // inputToken - outputTokenPath, // outputToken - amountIn, // amountIn - sqrtPriceLimitX96, // sqrtPriceLimitX96 + quotes := quoteForAllPath( // sorted by ratio DESC + inputToken, + outputTokenPath, + amountIn, + sqrtPriceLimitX96, ) if len(quotes) == 0 { panic("router.gno__THERE IS NO QUOTE") } - bestSwaps := []QuoterTarget{} - totalPct := 100 - - for i, quote := range quotes { - quotePct := quote.pct - outputRatioX96 := quote.outputRatioX96 - - totalPct -= quotePct - bestSwaps = append(bestSwaps, quote) - - if totalPct <= 0 { - break - } - } - + bestSwaps := findBestPaths(quotes) if len(bestSwaps) == 0 { panic("router.gno__CAN'T MAKE BestSwapRoute") } - // DEBUG - println("DEBUG router.gno__len(bestSwaps):", len(bestSwaps)) - remainingAmount := amountIn for i, bestSwap := range bestSwaps { numPools := strings.Count(bestSwap.targetPath, ",") / 2 @@ -93,7 +76,7 @@ func BestSwap( recipient: std.GetOrigCaller(), amountIn: toSwap, - minAmountOut: 1, // r3v4_xx: sqrtPriceLimitX96 + minAmountOut: 1, } amountOut := multiSwap(swapParams, 0, numPools, bestSwap.targetPath) // iterate here } diff --git a/router/router_test.gno b/router/router_test.gno index ba312325..73b5eb66 100644 --- a/router/router_test.gno +++ b/router/router_test.gno @@ -1,16 +1,18 @@ package router import ( + "encoding/gjson" "std" "testing" "gno.land/p/demo/testutils" - "gno.land/r/demo/users" _ "gno.land/r/grc20_wrapper" p "gno.land/r/pool" pos "gno.land/r/position" + _ "gno.land/r/bar" + _ "gno.land/r/baz" foo "gno.land/r/foo" qux "gno.land/r/qux" ) @@ -58,10 +60,10 @@ func TestCreatePool(t *testing.T) { p.CreatePool(fooPath, barPath, test_fee100, 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 p.CreatePool(fooPath, barPath, test_fee500, 167719732170287102368011632179) // tick = 15_000, ratio = 4.481352978667308 - p.CreatePool(fooPath, quxPath, test_fee100, 965075977353221155028623082916) // tick = 50_000, ratio = 148.37606292307464 p.CreatePool(barPath, bazPath, test_fee100, 124251697196845557112282348179) // tick = 9_000, ratio = 2.4594924388851824 p.CreatePool(barPath, quxPath, test_fee100, 101729702841318637793976746270) // tick = 5_000, ratio = 1.648680055931176 p.CreatePool(bazPath, quxPath, test_fee100, 92049301871182272007977902845) // tick = 3_000, ratio = 1.3498385611954853 + p.CreatePool(fooPath, quxPath, test_fee100, 965075977353221155028623082916) // tick = 50_000, ratio = 148.37606292307464 // findSwapPaths("gno.land/r/foo", "gno.land/r/qux") // 5 paths // foo_qux_100 // foo_bar_100 > bar_qux_100 @@ -72,18 +74,36 @@ func TestCreatePool(t *testing.T) { func TestPositionMint(t *testing.T) { std.TestSetOrigCaller(lp01) - pos.Mint(fooPath, barPath, test_fee100, int32(9000), int32(11000), bigint(100000000), bigint(100000000), 0, 0, max_timeout) - pos.Mint(fooPath, barPath, test_fee500, int32(14000), int32(16000), bigint(100000000), bigint(100000000), 0, 0, max_timeout) - pos.Mint(barPath, bazPath, test_fee100, int32(8000), int32(10000), bigint(100000000), bigint(100000000), 0, 0, max_timeout) - pos.Mint(barPath, quxPath, test_fee100, int32(4000), int32(6000), bigint(100000000), bigint(100000000), 0, 0, max_timeout) - pos.Mint(bazPath, quxPath, test_fee100, int32(2000), int32(4000), bigint(100000000), bigint(100000000), 0, 0, max_timeout) + pos.Mint(fooPath, barPath, test_fee100, int32(9000), int32(11000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) + pos.Mint(fooPath, barPath, test_fee500, int32(14000), int32(16000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) + pos.Mint(barPath, bazPath, test_fee100, int32(6000), int32(12000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) + pos.Mint(barPath, quxPath, test_fee100, int32(4000), int32(6000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) + pos.Mint(bazPath, quxPath, test_fee100, int32(2000), int32(4000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) // direct & expensive - pos.Mint(fooPath, quxPath, test_fee100, int32(48000), int32(52000), bigint(100000000), bigint(100000000), 0, 0, max_timeout) + pos.Mint(fooPath, quxPath, test_fee100, int32(48000), int32(52000), bigint(1000000), bigint(1000000), 0, 0, max_timeout) +} + +func TestBestSwapDry(t *testing.T) { + std.TestSetOrigCaller(tr01) + + tr01FooOldBal := foo.BalanceOf(a2u(tr01)) + tr01QuxOldBal := qux.BalanceOf(a2u(tr01)) + RoQuxOldBal := qux.BalanceOf(a2u(routerAddr)) + PoQuxOldBal := qux.BalanceOf(a2u(poolAddr)) + jsonOutput := BestSwapDry( + "gno.land/r/foo", // inputTokenPath + "gno.land/r/qux", // outputTokenPath + 123_456, // amountIn + 0, // sqrtPriceLimitX96 + ) + jsonStr := gjson.Parse(jsonOutput) + + shouldEQ(t, jsonStr.Get("totalRoutes").String(), "2") } -func TestFuncs(t *testing.T) { +func TestBestSwap(t *testing.T) { std.TestSetOrigCaller(tr01) tr01FooOldBal := foo.BalanceOf(a2u(tr01)) @@ -94,7 +114,7 @@ func TestFuncs(t *testing.T) { BestSwap( "gno.land/r/foo", // inputTokenPath "gno.land/r/qux", // outputTokenPath - 100, // amountIn + 123_456, // amountIn 0, // sqrtPriceLimitX96 ) @@ -104,14 +124,6 @@ func TestFuncs(t *testing.T) { PoQuxNewBal := qux.BalanceOf(a2u(poolAddr)) shouldEQ(t, RoQuxOldBal, RoQuxNewBal) - - println("Send Foo:", tr01FooOldBal-tr01FooNewBal) - println("Recv Qux:", tr01QuxNewBal-tr01QuxOldBal) - println() -} - -func a2u(addr std.Address) users.AddressOrName { - return users.AddressOrName(addr) } /* HELPER */ diff --git a/router/rpc_call.gno b/router/rpc_call.gno new file mode 100644 index 00000000..98633526 --- /dev/null +++ b/router/rpc_call.gno @@ -0,0 +1,108 @@ +package router + +import ( + "encoding/json" + "strings" + + "gno.land/p/demo/ufmt" +) + +func BestSwapDry( + inputToken string, + outputToken string, + + amountIn bigint, + sqrtPriceLimitX96 bigint, +) string { + // get quotes + quotes := quoteForAllPath( // sored by Ratio DESC + inputToken, // inputToken + outputToken, // outputToken + amountIn, // amountIn + sqrtPriceLimitX96, // sqrtPriceLimitX96 + ) + if len(quotes) == 0 { + panic("router.gno__THERE IS NO QUOTE") + } + + bestSwaps := findBestPaths(quotes) + if len(bestSwaps) == 0 { + panic("router.gno__CAN'T MAKE BestSwapRoute") + } + + finalSwaps := removeDuplication(bestSwaps) + if len(bestSwaps) == 0 { + panic("router.gno__CAN'T MAKE FinalSwapRoute") + } + + rpcReturnBestSwap := RpcReturnBestSwap{} + rpcReturnBestSwap.TotalRoutes = len(finalSwaps) + + rpcRoutes := []RpcRoute{} + + for i, finalSwap := range finalSwaps { + numSwaps := strings.Count(finalSwap.targetPath, ",") / 2 + splitPaths := multiTargetPathToList(finalSwap.targetPath) + + rpcRoute := RpcRoute{ + SwapPct: finalSwap.pct, + NumSwaps: numSwaps, + SwapPaths: splitPaths, + } + rpcRoutes = append(rpcRoutes, rpcRoute) + + rpcReturnBestSwap.TotalPct += finalSwap.pct + + if rpcReturnBestSwap.TotalPct == 100 { + rpcReturnBestSwap.Possible = true + } + } + rpcReturnBestSwap.RpcRoutes = rpcRoutes + + rr, err := json.Marshal(rpcReturnBestSwap) + if err != nil { + panic(ufmt.Sprintf("[ROUTER] rpc_call.gno__BestSwapDry() || %v", err)) + } + + return string(rr) +} + +type RpcRoute struct { + SwapPct int `json:"swapPct"` + NumSwaps int `json:"numSwaps"` + SwapPaths []string `json:"swapPaths"` +} + +type RpcReturnBestSwap struct { + TotalRoutes int `json:"totalRoutes"` + TotalPct int `json:"totalPct"` + Possible bool `json:"possible"` + AmountIn bigint `json:'amountIn"` + AmountOut bigint `json:'amountOut"` + RpcRoutes []RpcRoute `json:"rpcRoutes"` +} + +func multiTargetPathToList(path string) []string { + numSwaps := strings.Count(path, ",") / 2 + splitData := strings.Split(path, ",") + + fullPaths := []string{} + for i := 0; i < numSwaps; i++ { + j := 0 + if i == 0 { + j = i + } else { + j = i * 2 + } + + token0Path := splitData[j] // 0 > 2 > 4 + fee := splitData[j+1] // 1 > 3 > 5 + token1Path := splitData[j+2] // 2 > 4 > 6 + + fullPath := token0Path + ":" + token1Path + ":" + fee + + fullPaths = append(fullPaths, fullPath) + } + + return fullPaths +} diff --git a/router/type.gno b/router/type.gno index f54e83f8..18b05dcb 100644 --- a/router/type.gno +++ b/router/type.gno @@ -17,9 +17,8 @@ type SwapPaths map[int]string type TokenPairs map[string][]string type QuoterTarget struct { - // totalAmount int // 100 // neee this? pct int // 5% - pctAmount bigint // 5% + pctAmount bigint // targetPath string // gno.land/r/foo,500,gno.land/r/qux outputRatioX96 bigint // 121324894654 } diff --git a/router/util.gno b/router/util.gno index 904e4863..bede21be 100644 --- a/router/util.gno +++ b/router/util.gno @@ -1,10 +1,12 @@ package router import ( + "std" "strconv" "strings" "gno.land/p/demo/ufmt" + "gno.land/r/demo/users" ) /* UTIL */ @@ -87,3 +89,7 @@ func sortItems(tokenAPath, tokenBPath, fee string) string { return tokenBPath + ":" + tokenAPath + ":" + fee } } + +func a2u(addr std.Address) users.AddressOrName { + return users.AddressOrName(addr) +} From dae26079a7a54a132c6ed778a1b096cfca2ec271 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Fri, 3 Nov 2023 18:24:51 +0900 Subject: [PATCH 23/35] GSW-547 feat: estimate & swap singlePath when amountSpecified is negative --- router/find_path.gno | 2 +- router/quotation.gno | 54 ++++++++++++++++++++++-------------- router/router.gno | 19 ++++++++----- router/router_test.gno | 63 ++++++++++-------------------------------- router/rpc_call.gno | 14 +++++----- router/swap_inner.gno | 52 +++++++++++++++++++++------------- router/swap_multi.gno | 28 +++++++++---------- router/swap_single.gno | 6 ++-- router/type.gno | 13 ++++++--- 9 files changed, 128 insertions(+), 123 deletions(-) diff --git a/router/find_path.gno b/router/find_path.gno index 40de51e8..bf40e164 100644 --- a/router/find_path.gno +++ b/router/find_path.gno @@ -45,7 +45,7 @@ func getSwapPaths( // check if there is path that starts with input if len(tokenPairs[inputTokenPath]) == 0 { - panic("NO POOL") + panic("find_path.gno__NO POOL") } // find direct path diff --git a/router/quotation.gno b/router/quotation.gno index e76b3e6c..2218a1ff 100644 --- a/router/quotation.gno +++ b/router/quotation.gno @@ -10,11 +10,11 @@ func quoteForAllPath( inputTokenPath string, outputTokenPath string, - amountIn bigint, + amountSpecified bigint, sqrtPriceLimitX96 bigint, ) (quoterTargets []QuoterTarget) { swapPaths := findSwapPaths(inputTokenPath, outputTokenPath) - swapPcts := calculatePercentages(amountIn) // 5% - 100, 10% - 200, ...., 100% - 2000 + swapPcts := calculatePercentages(amountSpecified) // 5% - 100, 10% - 200, ...., 100% - 2000 for _, swapPath := range swapPaths { for pct, pctAmount := range swapPcts { @@ -22,7 +22,7 @@ func quoteForAllPath( pct: pct, pctAmount: pctAmount, targetPath: swapPath, - outputRatioX96: bigint(0), // will update later + resultRatioX96: bigint(0), // will update later } quoterTargets = append(quoterTargets, quoterTarget) } @@ -47,15 +47,23 @@ func quoteForAllPath( tokenIn: input, tokenOut: output, fee: fee, - amountIn: quoterTarget.pctAmount, // 5%, 10%, ... 100% + amountSpecified: quoterTarget.pctAmount, sqrtPriceLimitX96: sqrtPriceLimitX96, } - estimatedOut := singleSwapDry(singleParams) - if estimatedOut > 0 { - outputRatioX96 := estimatedOut * Q96 / quoterTarget.pctAmount * Q96 / Q96 // divide this value by Q96 => will get float ratio - quoterTarget.outputRatioX96 = outputRatioX96 - quoterTargets[i] = quoterTarget + estimatedResult := singleSwapDry(singleParams) + if estimatedResult > 0 { + if amountSpecified > 0 { + resultRatioX96 := estimatedResult * Q96 / quoterTarget.pctAmount * Q96 / Q96 // divide this value by Q96 => will get float ratio + quoterTarget.resultRatioX96 = resultRatioX96 + quoterTargets[i] = quoterTarget + } else { + resultRatioX96 := -quoterTarget.pctAmount * Q96 / estimatedResult * Q96 / Q96 // divide this value by Q96 => will get float ratio + quoterTarget.resultRatioX96 = resultRatioX96 + quoterTargets[i] = quoterTarget + } + } else { + panic("quotation.gno__CAN NOT HAPPEN") } } @@ -67,15 +75,15 @@ func quoteForAllPath( tokenOut: output, fee: fee, - recipient: std.GetOrigCaller(), - amountIn: quoterTarget.pctAmount, // 5%, 10%, ... 100% - minAmountOut: 1, // r3v4_xx: sqrtPriceLimitX96 + recipient: std.GetOrigCaller(), + amountSpecified: quoterTarget.pctAmount, + minAmountOut: 1, // r3v4_xx: sqrtPriceLimitX96 } - estimatedOut := multiSwapDry(swapParams, 0, numPools, quoterTarget.targetPath) // will iterate here to cover multi pools - if estimatedOut > 0 { - outputRatioX96 := estimatedOut * Q96 / quoterTarget.pctAmount * Q96 / Q96 - quoterTarget.outputRatioX96 = outputRatioX96 + estimatedResult := multiSwapDry(swapParams, 0, numPools, quoterTarget.targetPath) // will iterate here to cover multi pools + if estimatedResult > 0 { + resultRatioX96 := estimatedResult * Q96 / quoterTarget.pctAmount * Q96 / Q96 + quoterTarget.resultRatioX96 = resultRatioX96 quoterTargets[i] = quoterTarget } } @@ -83,15 +91,21 @@ func quoteForAllPath( // it contains outputRatio with 0 which is impossible path // remove path with 0 ratio + // positive ratio => amountSpecified > 0 + // negative ratio => amountSpecified < 0 var finalTargets []QuoterTarget for _, quote := range quoterTargets { - if quote.outputRatioX96 > 0 { + if quote.resultRatioX96 != 0 { finalTargets = append(finalTargets, quote) } } - // sort this struct descending by ratio then return - sort.Sort(ByOutputRatioDesc(finalTargets)) + if amountSpecified > 0 { + sort.Sort(ByOutputRatioDesc(finalTargets)) + } else { + sort.Sort(ByOutputRatioAsc(finalTargets)) + } + return finalTargets } @@ -112,7 +126,7 @@ func findBestPaths(quotes []QuoterTarget) (bestSwaps []QuoterTarget) { totalPct := 100 for _, quote := range quotes { quotePct := quote.pct - outputRatioX96 := quote.outputRatioX96 + resultRatioX96 := quote.resultRatioX96 totalPct -= quotePct diff --git a/router/router.gno b/router/router.gno index 069455ef..fb0bd626 100644 --- a/router/router.gno +++ b/router/router.gno @@ -9,14 +9,14 @@ func BestSwap( inputToken string, outputTokenPath string, - amountIn bigint, + amountSpecified bigint, sqrtPriceLimitX96 bigint, ) { // get quotes quotes := quoteForAllPath( // sorted by ratio DESC inputToken, outputTokenPath, - amountIn, + amountSpecified, sqrtPriceLimitX96, ) if len(quotes) == 0 { @@ -28,7 +28,12 @@ func BestSwap( panic("router.gno__CAN'T MAKE BestSwapRoute") } - remainingAmount := amountIn + finalSwaps := removeDuplication(bestSwaps) + if len(bestSwaps) == 0 { + panic("router.gno__CAN'T MAKE FinalSwapRoute") + } + + remainingAmount := amountSpecified for i, bestSwap := range bestSwaps { numPools := strings.Count(bestSwap.targetPath, ",") / 2 @@ -48,7 +53,7 @@ func BestSwap( tokenIn: input, tokenOut: output, fee: fee, - amountIn: bestSwap.pctAmount, + amountSpecified: bestSwap.pctAmount, sqrtPriceLimitX96: sqrtPriceLimitX96, } amountOut := singleSwap(singleParams) @@ -74,9 +79,9 @@ func BestSwap( tokenOut: output, fee: fee, - recipient: std.GetOrigCaller(), - amountIn: toSwap, - minAmountOut: 1, + recipient: std.GetOrigCaller(), + amountSpecified: toSwap, + minAmountOut: 1, } amountOut := multiSwap(swapParams, 0, numPools, bestSwap.targetPath) // iterate here } diff --git a/router/router_test.gno b/router/router_test.gno index 73b5eb66..963deb13 100644 --- a/router/router_test.gno +++ b/router/router_test.gno @@ -58,33 +58,19 @@ func TestInitManual(t *testing.T) { func TestCreatePool(t *testing.T) { std.TestSetOrigCaller(gsa) - p.CreatePool(fooPath, barPath, test_fee100, 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 - p.CreatePool(fooPath, barPath, test_fee500, 167719732170287102368011632179) // tick = 15_000, ratio = 4.481352978667308 - p.CreatePool(barPath, bazPath, test_fee100, 124251697196845557112282348179) // tick = 9_000, ratio = 2.4594924388851824 - p.CreatePool(barPath, quxPath, test_fee100, 101729702841318637793976746270) // tick = 5_000, ratio = 1.648680055931176 - p.CreatePool(bazPath, quxPath, test_fee100, 92049301871182272007977902845) // tick = 3_000, ratio = 1.3498385611954853 - p.CreatePool(fooPath, quxPath, test_fee100, 965075977353221155028623082916) // tick = 50_000, ratio = 148.37606292307464 - // findSwapPaths("gno.land/r/foo", "gno.land/r/qux") // 5 paths + p.CreatePool(fooPath, quxPath, test_fee100, 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + p.CreatePool(fooPath, quxPath, test_fee500, 101729702841318637793976746270) // tick = 5_000, ratio = 1.648680055931176 + // findSwapPaths("gno.land/r/foo", "gno.land/r/qux") // 2 paths // foo_qux_100 - // foo_bar_100 > bar_qux_100 - // foo_bar_500 > bar_qux_100 - // foo_bar_100 > bar_baz_100 > baz_qux_100 - // foo_bar_500 > bar_baz_100 > baz_qux_100 } func TestPositionMint(t *testing.T) { std.TestSetOrigCaller(lp01) - pos.Mint(fooPath, barPath, test_fee100, int32(9000), int32(11000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) - pos.Mint(fooPath, barPath, test_fee500, int32(14000), int32(16000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) - pos.Mint(barPath, bazPath, test_fee100, int32(6000), int32(12000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) - pos.Mint(barPath, quxPath, test_fee100, int32(4000), int32(6000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) - pos.Mint(bazPath, quxPath, test_fee100, int32(2000), int32(4000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) - - // direct & expensive - pos.Mint(fooPath, quxPath, test_fee100, int32(48000), int32(52000), bigint(1000000), bigint(1000000), 0, 0, max_timeout) + pos.Mint(fooPath, quxPath, test_fee100, int32(9000), int32(11000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) + pos.Mint(fooPath, quxPath, test_fee500, int32(4000), int32(6000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) } -func TestBestSwapDry(t *testing.T) { +func TestBestSwapDryPositiveSpend(t *testing.T) { std.TestSetOrigCaller(tr01) tr01FooOldBal := foo.BalanceOf(a2u(tr01)) @@ -95,15 +81,15 @@ func TestBestSwapDry(t *testing.T) { jsonOutput := BestSwapDry( "gno.land/r/foo", // inputTokenPath "gno.land/r/qux", // outputTokenPath - 123_456, // amountIn + 123_456, // amountSpecified 0, // sqrtPriceLimitX96 ) jsonStr := gjson.Parse(jsonOutput) - - shouldEQ(t, jsonStr.Get("totalRoutes").String(), "2") + shouldEQ(t, jsonStr.Get("totalRoutes").String(), "1") + shouldEQ(t, jsonStr.Get("rpcRoutes.0.swapPaths.0").String(), "gno.land/r/foo:gno.land/r/qux:100") } -func TestBestSwap(t *testing.T) { +func TestBestSwapDryNegative(t *testing.T) { std.TestSetOrigCaller(tr01) tr01FooOldBal := foo.BalanceOf(a2u(tr01)) @@ -111,19 +97,15 @@ func TestBestSwap(t *testing.T) { RoQuxOldBal := qux.BalanceOf(a2u(routerAddr)) PoQuxOldBal := qux.BalanceOf(a2u(poolAddr)) - BestSwap( + jsonOutput := BestSwapDry( "gno.land/r/foo", // inputTokenPath "gno.land/r/qux", // outputTokenPath - 123_456, // amountIn + -123_456, // amountSpecified 0, // sqrtPriceLimitX96 ) - - tr01FooNewBal := foo.BalanceOf(a2u(tr01)) - tr01QuxNewBal := qux.BalanceOf(a2u(tr01)) - RoQuxNewBal := qux.BalanceOf(a2u(routerAddr)) - PoQuxNewBal := qux.BalanceOf(a2u(poolAddr)) - - shouldEQ(t, RoQuxOldBal, RoQuxNewBal) + jsonStr := gjson.Parse(jsonOutput) + shouldEQ(t, jsonStr.Get("totalRoutes").String(), "1") + shouldEQ(t, jsonStr.Get("rpcRoutes.0.swapPaths.0").String(), "gno.land/r/foo:gno.land/r/qux:500") } /* HELPER */ @@ -132,18 +114,3 @@ func shouldEQ(t *testing.T, got, expected interface{}) { t.Errorf("got %v, expected %v", got, expected) } } - -func shouldNEQ(t *testing.T, got, expected interface{}) { - if got == expected { - t.Errorf("got %v, expected %v", got, expected) - } -} - -func shouldPanic(t *testing.T, f func()) { - defer func() { - if r := recover(); r == nil { - t.Errorf("expected panic") - } - }() - f() -} diff --git a/router/rpc_call.gno b/router/rpc_call.gno index 98633526..78d1283c 100644 --- a/router/rpc_call.gno +++ b/router/rpc_call.gno @@ -11,28 +11,28 @@ func BestSwapDry( inputToken string, outputToken string, - amountIn bigint, + amountSpecified bigint, sqrtPriceLimitX96 bigint, ) string { // get quotes quotes := quoteForAllPath( // sored by Ratio DESC inputToken, // inputToken outputToken, // outputToken - amountIn, // amountIn + amountSpecified, // amountIn sqrtPriceLimitX96, // sqrtPriceLimitX96 ) if len(quotes) == 0 { - panic("router.gno__THERE IS NO QUOTE") + panic("rpc_call.gno__THERE IS NO QUOTE") } bestSwaps := findBestPaths(quotes) if len(bestSwaps) == 0 { - panic("router.gno__CAN'T MAKE BestSwapRoute") + panic("rpc_call.gno__CAN'T MAKE BestSwapRoute") } finalSwaps := removeDuplication(bestSwaps) if len(bestSwaps) == 0 { - panic("router.gno__CAN'T MAKE FinalSwapRoute") + panic("rpc_call.gno__CAN'T MAKE FinalSwapRoute") } rpcReturnBestSwap := RpcReturnBestSwap{} @@ -77,8 +77,8 @@ type RpcReturnBestSwap struct { TotalRoutes int `json:"totalRoutes"` TotalPct int `json:"totalPct"` Possible bool `json:"possible"` - AmountIn bigint `json:'amountIn"` - AmountOut bigint `json:'amountOut"` + AmountIn bigint `json:"amountIn"` + AmountOut bigint `json:"amountOut"` RpcRoutes []RpcRoute `json:"rpcRoutes"` } diff --git a/router/swap_inner.gno b/router/swap_inner.gno index 8c536987..b9c03dd2 100644 --- a/router/swap_inner.gno +++ b/router/swap_inner.gno @@ -7,11 +7,11 @@ import ( ) func _swap( - amountIn bigint, + amountSpecified bigint, recipient std.Address, sqrtPriceLimitX96 bigint, data SwapCallbackData, -) (amountOut bigint) { +) (amountResult bigint) { // prepare zeroForOne := data.tokenIn < data.tokenOut @@ -24,7 +24,6 @@ func _swap( } // dry swap -> esteimate amount -> approve exact amount - // r3v4_xx: or little bit more approveAmount0, approveAmount1, _ := p.DrySwap( data.tokenIn, data.tokenOut, @@ -32,13 +31,13 @@ func _swap( recipient, zeroForOne, - amountIn, + amountSpecified, sqrtPriceLimitX96, ) // ROUTER approves POOL as spender - approveByRegisterCall(data.tokenIn, ADDR_POOL, abs(approveAmount0)) // r3v4_xxx - approveByRegisterCall(data.tokenOut, ADDR_POOL, abs(approveAmount1)) // r3v4_xxx + approveByRegisterCall(data.tokenIn, ADDR_POOL, abs(approveAmount0)) + approveByRegisterCall(data.tokenOut, ADDR_POOL, abs(approveAmount1)) amount0, amount1 := p.Swap( data.tokenIn, @@ -47,27 +46,35 @@ func _swap( recipient, zeroForOne, - amountIn, + amountSpecified, sqrtPriceLimitX96, data.payer, ) - if zeroForOne { - amountOut = -amount1 + if amountSpecified > 0 { + if zeroForOne { + amountResult = -amount1 + } else { + amountResult = -amount0 + } } else { - amountOut = -amount0 + if zeroForOne { + amountResult = amount0 + } else { + amountResult = amount1 + } } - return amountOut + return amountResult } func _swapDry( - amountIn bigint, + amountSpecified bigint, recipient std.Address, sqrtPriceLimitX96 bigint, data SwapCallbackData, -) (amountOut bigint) { +) (amountResult bigint) { zeroForOne := data.tokenIn < data.tokenOut if sqrtPriceLimitX96 == 0 { @@ -86,21 +93,28 @@ func _swapDry( recipient, zeroForOne, - amountIn, + amountSpecified, sqrtPriceLimitX96, ) if !ok { return 0 } - // if success - if zeroForOne { - amountOut = -amount1 + if amountSpecified > 0 { + if zeroForOne { + amountResult = -amount1 + } else { + amountResult = -amount0 + } } else { - amountOut = -amount0 + if zeroForOne { + amountResult = amount0 + } else { + amountResult = amount1 + } } - return amountOut + return amountResult } func abs(x bigint) uint64 { diff --git a/router/swap_multi.gno b/router/swap_multi.gno index 5f055707..4f0a15c2 100644 --- a/router/swap_multi.gno +++ b/router/swap_multi.gno @@ -9,9 +9,9 @@ type SwapParams struct { tokenOut string fee uint16 - recipient std.Address - amountIn bigint - minAmountOut bigint + recipient std.Address + amountSpecified bigint + minAmountOut bigint } func multiSwap(params SwapParams, currentPoolIndex, numPool int, swapPath string) (amountOut bigint) { @@ -27,10 +27,10 @@ func multiSwap(params SwapParams, currentPoolIndex, numPool int, swapPath string recipient = params.recipient // user ~= std.GetOrigCaller() } - params.amountIn = _swap( - params.amountIn, // amountIn - recipient, // recipient - 0, // sqrtPriceLimitX96 + params.amountSpecified = _swap( + params.amountSpecified, // amountSpecified + recipient, // recipient + 0, // sqrtPriceLimitX96 SwapCallbackData{ params.tokenIn, // tokenIn params.tokenOut, // tokenOut @@ -47,14 +47,14 @@ func multiSwap(params SwapParams, currentPoolIndex, numPool int, swapPath string params.tokenOut = nextOutput params.fee = nextFee } else { - amountOut = params.amountIn + amountOut = params.amountSpecified return amountOut } } if amountOut < params.minAmountOut { panic("Too few receive") -} + } } func multiSwapDry(params SwapParams, currentPoolIndex, numPool int, swapPath string) (amountOut bigint) { @@ -70,10 +70,10 @@ func multiSwapDry(params SwapParams, currentPoolIndex, numPool int, swapPath str recipient = params.recipient // user ~= std.GetOrigCaller() } - params.amountIn = _swapDry( - params.amountIn, // amountIn - recipient, // recipient - 0, // sqrtPriceLimitX96 + params.amountSpecified = _swapDry( + params.amountSpecified, // amountSpecified + recipient, // recipient + 0, // sqrtPriceLimitX96 SwapCallbackData{ params.tokenIn, // tokenIn params.tokenOut, // tokenOut @@ -90,7 +90,7 @@ func multiSwapDry(params SwapParams, currentPoolIndex, numPool int, swapPath str params.tokenOut = nextOutput params.fee = nextFee } else { - amountOut = params.amountIn + amountOut = params.amountSpecified return amountOut } } diff --git a/router/swap_single.gno b/router/swap_single.gno index 95c3484d..31799241 100644 --- a/router/swap_single.gno +++ b/router/swap_single.gno @@ -8,13 +8,13 @@ type SingleSwapParams struct { tokenIn string tokenOut string fee uint16 - amountIn bigint + amountSpecified bigint sqrtPriceLimitX96 bigint } func singleSwap(params SingleSwapParams) (amountOut bigint) { amountOut = _swap( - params.amountIn, + params.amountSpecified, std.GetOrigCaller(), // if single swap => user will recieve params.sqrtPriceLimitX96, SwapCallbackData{ @@ -29,7 +29,7 @@ func singleSwap(params SingleSwapParams) (amountOut bigint) { func singleSwapDry(params SingleSwapParams) (amountOut bigint) { amountOut = _swapDry( - params.amountIn, + params.amountSpecified, std.GetOrigCaller(), // if single swap => user will recieve params.sqrtPriceLimitX96, SwapCallbackData{ diff --git a/router/type.gno b/router/type.gno index 18b05dcb..1791e13e 100644 --- a/router/type.gno +++ b/router/type.gno @@ -5,7 +5,6 @@ import ( ) type SwapCallbackData struct { - // poolPath string tokenIn string tokenOut string fee uint16 @@ -18,13 +17,19 @@ type TokenPairs map[string][]string type QuoterTarget struct { pct int // 5% - pctAmount bigint // + pctAmount bigint // targetPath string // gno.land/r/foo,500,gno.land/r/qux - outputRatioX96 bigint // 121324894654 + resultRatioX96 bigint // } type ByOutputRatioDesc []QuoterTarget func (a ByOutputRatioDesc) Len() int { return len(a) } func (a ByOutputRatioDesc) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a ByOutputRatioDesc) Less(i, j int) bool { return a[i].outputRatioX96 > a[j].outputRatioX96 } +func (a ByOutputRatioDesc) Less(i, j int) bool { return a[i].resultRatioX96 > a[j].resultRatioX96 } + +type ByOutputRatioAsc []QuoterTarget + +func (a ByOutputRatioAsc) Len() int { return len(a) } +func (a ByOutputRatioAsc) Swap(i, j int) { a[i], a[j] = a[j], a[i] } +func (a ByOutputRatioAsc) Less(i, j int) bool { return a[i].resultRatioX96 < a[j].resultRatioX96 } From 55e3f237b4746e1fdf2b9365290a708bb9a87251 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Mon, 6 Nov 2023 11:48:52 +0900 Subject: [PATCH 24/35] GSW-548 feat: estimate & swap multiPath when amountSpecified is negative --- router/swap_multi.gno | 49 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/router/swap_multi.gno b/router/swap_multi.gno index 4f0a15c2..ae276f36 100644 --- a/router/swap_multi.gno +++ b/router/swap_multi.gno @@ -99,3 +99,52 @@ func multiSwapDry(params SwapParams, currentPoolIndex, numPool int, swapPath str panic("Too few receive") } } + +func multiSwapNegativeDry(params SwapParams, currentPoolIndex int, swapPath string) (amountOut bigint) { + payer := std.DerivePkgAddr("gno.land/r/router") + + params.tokenIn, params.tokenOut, params.fee = getSwapData(swapPath, currentPoolIndex) + + numPools := currentPoolIndex + for { + var recipient std.Address + currentPoolIndex-- + + if currentPoolIndex == numPools-1 { + recipient = params.recipient + } else { + recipient = std.DerivePkgAddr("gno.land/r/router") + } + + params.amountSpecified = _swapDry( + params.amountSpecified, // amountSpecified + recipient, // recipient + 0, // sqrtPriceLimitX96 + SwapCallbackData{ + params.tokenIn, // tokenIn + params.tokenOut, // tokenOut + params.fee, // fee + payer, // payer + }, + ) + + if currentPoolIndex != -1 { + nextInput, nextOutput, nextFee := getSwapData(swapPath, currentPoolIndex) + params.tokenIn = nextInput + params.tokenOut = nextOutput + params.fee = nextFee + + // input amounts derived in the last stage must be used again as the n-1th output amounts + // >> must be negative + params.amountSpecified = -params.amountSpecified + + } else { + amountOut = params.amountSpecified + return amountOut + } + } + + if amountOut < params.minAmountOut { + panic("Too few receive") + } +} From 92a7ef9a44ae57cc291a749de0e4f250d478dbe3 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Mon, 6 Nov 2023 11:50:33 +0900 Subject: [PATCH 25/35] feat: swap multi path with multi pools --- router/_TEST_router_multi_path_dry_test.gnoa | 109 ++++++++ router/_TEST_router_multi_path_swap_test.gno | 243 ++++++++++++++++++ ...=> _TEST_router_single_path_dry_test.gnoa} | 19 +- .../_TEST_router_single_path_swap_test.gnoa | 205 +++++++++++++++ router/router.gno | 63 +++-- 5 files changed, 588 insertions(+), 51 deletions(-) create mode 100644 router/_TEST_router_multi_path_dry_test.gnoa create mode 100644 router/_TEST_router_multi_path_swap_test.gno rename router/{router_test.gno => _TEST_router_single_path_dry_test.gnoa} (84%) create mode 100644 router/_TEST_router_single_path_swap_test.gnoa diff --git a/router/_TEST_router_multi_path_dry_test.gnoa b/router/_TEST_router_multi_path_dry_test.gnoa new file mode 100644 index 00000000..9a1abcdc --- /dev/null +++ b/router/_TEST_router_multi_path_dry_test.gnoa @@ -0,0 +1,109 @@ +package router + +import ( + "encoding/gjson" + "std" + "testing" + + "gno.land/p/demo/testutils" + + _ "gno.land/r/grc20_wrapper" + p "gno.land/r/pool" + pos "gno.land/r/position" +) + +var ( + gsa = testutils.TestAddress("gsa") // Gnoswap Admin + lp01 = testutils.TestAddress("lp01") // Liquidity Provider 01 + tr01 = testutils.TestAddress("tr01") // Trader 01 + + poolAddr = std.DerivePkgAddr("gno.land/r/pool") + posAddr = std.DerivePkgAddr("gno.land/r/position") + routerAddr = std.DerivePkgAddr("gno.land/r/router") +) + +var ( + // Common + fooPath = "gno.land/r/foo" // token1 + barPath = "gno.land/r/bar" // token2 + bazPath = "gno.land/r/baz" // token3 + quxPath = "gno.land/r/qux" // token4 + + test_fee100 = uint16(100) + test_fee500 = uint16(500) + + max_timeout = bigint(9999999999) +) + +// func init() { +// println(gsa, "// gsa") +// println(lp01, "// lp01") +// println(tr01, "// tr01") +// println(poolAddr, "// poolAddr") +// println(posAddr, "// posAddr") +// println(routerAddr, "// routerAddr") +// } + +func TestInitManual(t *testing.T) { + std.TestSetOrigCaller(gsa) + p.InitManual() + std.TestSkipHeights(1) +} + +func TestCreatePool(t *testing.T) { + std.TestSetOrigCaller(gsa) + + p.CreatePool(fooPath, barPath, test_fee100, 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + p.CreatePool(barPath, quxPath, test_fee100, 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + + p.CreatePool(fooPath, barPath, test_fee500, 101729702841318637793976746270) // tick = 5_000, ratio = 1.648680055931176 + p.CreatePool(barPath, quxPath, test_fee500, 101729702841318637793976746270) // tick = 5_000, ratio = 1.648680055931176 + // findSwapPaths("gno.land/r/foo", "gno.land/r/qux") // 2 paths + // foo_qux_100 +} + +func TestPositionMint(t *testing.T) { + std.TestSetOrigCaller(lp01) + pos.Mint(fooPath, barPath, test_fee100, int32(9000), int32(11000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) + pos.Mint(barPath, quxPath, test_fee100, int32(9000), int32(11000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) + + pos.Mint(fooPath, barPath, test_fee500, int32(4000), int32(6000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) + pos.Mint(barPath, quxPath, test_fee500, int32(4000), int32(6000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) +} + +func TestBestSwapDryPositive(t *testing.T) { + std.TestSetOrigCaller(tr01) + + jsonOutput := BestSwapDry( + "gno.land/r/foo", // inputTokenPath + "gno.land/r/qux", // outputTokenPath + 123_456, // amountSpecified + 0, // sqrtPriceLimitX96 + ) + jsonStr := gjson.Parse(jsonOutput) + shouldEQ(t, jsonStr.Get("rpcRoutes.0.numSwaps").String(), "2") + shouldEQ(t, jsonStr.Get("rpcRoutes.0.swapPaths.0").String(), "gno.land/r/foo:gno.land/r/bar:100") + shouldEQ(t, jsonStr.Get("rpcRoutes.0.swapPaths.1").String(), "gno.land/r/bar:gno.land/r/qux:100") +} + +func TestBestSwapDryNegative(t *testing.T) { + std.TestSetOrigCaller(tr01) + + jsonOutput := BestSwapDry( + "gno.land/r/foo", // inputTokenPath + "gno.land/r/qux", // outputTokenPath + -123_456, // amountSpecified + 0, // sqrtPriceLimitX96 + ) + jsonStr := gjson.Parse(jsonOutput) + shouldEQ(t, jsonStr.Get("rpcRoutes.0.numSwaps").String(), "2") + shouldEQ(t, jsonStr.Get("rpcRoutes.0.swapPaths.0").String(), "gno.land/r/foo:gno.land/r/bar:100") + shouldEQ(t, jsonStr.Get("rpcRoutes.0.swapPaths.1").String(), "gno.land/r/bar:gno.land/r/qux:100") +} + +/* HELPER */ +func shouldEQ(t *testing.T, got, expected interface{}) { + if got != expected { + t.Errorf("got %v, expected %v", got, expected) + } +} diff --git a/router/_TEST_router_multi_path_swap_test.gno b/router/_TEST_router_multi_path_swap_test.gno new file mode 100644 index 00000000..5d235f87 --- /dev/null +++ b/router/_TEST_router_multi_path_swap_test.gno @@ -0,0 +1,243 @@ +package router + +import ( + "encoding/gjson" + "std" + "testing" + + "gno.land/p/demo/testutils" + + _ "gno.land/r/grc20_wrapper" + p "gno.land/r/pool" + pos "gno.land/r/position" + + "gno.land/r/bar" + "gno.land/r/foo" + "gno.land/r/qux" +) + +var ( + gsa = testutils.TestAddress("gsa") // Gnoswap Admin + lp01 = testutils.TestAddress("lp01") // Liquidity Provider 01 + tr01 = testutils.TestAddress("tr01") // Trader 01 + + poolAddr = std.DerivePkgAddr("gno.land/r/pool") + posAddr = std.DerivePkgAddr("gno.land/r/position") + routerAddr = std.DerivePkgAddr("gno.land/r/router") +) + +var ( + // Common + fooPath = "gno.land/r/foo" // token1 + barPath = "gno.land/r/bar" // token2 + bazPath = "gno.land/r/baz" // token3 + quxPath = "gno.land/r/qux" // token4 + + test_fee100 = uint16(100) + test_fee500 = uint16(500) + + max_timeout = bigint(9999999999) +) + +func init() { + println(gsa, "// gsa") + println(lp01, "// lp01") + println(tr01, "// tr01") + println(poolAddr, "// poolAddr") + println(posAddr, "// posAddr") + println(routerAddr, "// routerAddr") +} + +func TestInitManual(t *testing.T) { + std.TestSetOrigCaller(gsa) + p.InitManual() + std.TestSkipHeights(1) +} + +func TestCreatePool(t *testing.T) { + std.TestSetOrigCaller(gsa) + + p.CreatePool(fooPath, barPath, test_fee100, 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + p.CreatePool(barPath, quxPath, test_fee100, 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + + p.CreatePool(fooPath, barPath, test_fee500, 101729702841318637793976746270) // tick = 5_000, ratio = 1.648680055931176 + p.CreatePool(barPath, quxPath, test_fee500, 101729702841318637793976746270) // tick = 5_000, ratio = 1.648680055931176 + // findSwapPaths("gno.land/r/foo", "gno.land/r/qux") // 2 paths + // foo_qux_100 + + jsonOutput := p.ApiGetPools() + jsonStr := gjson.Parse(jsonOutput) + shouldEQ(t, len(jsonStr.Get("response.data").Array()), 4) +} + +func TestPositionMint(t *testing.T) { + std.TestSetOrigCaller(lp01) + { + oldFooBalance := foo.BalanceOf(a2u(poolAddr)) + oldBarBalance := bar.BalanceOf(a2u(poolAddr)) + shouldEQ(t, oldFooBalance, 0) + shouldEQ(t, oldBarBalance, 0) + + tokenId, liquidity, amount0, amount1 := pos.Mint(fooPath, barPath, test_fee100, int32(9000), int32(11000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) + shouldEQ(t, tokenId, 1) + shouldNEQ(t, liquidity, bigint(0)) + shouldNEQ(t, amount0, bigint(0)) + shouldNEQ(t, amount1, bigint(0)) + + newFooBalance := foo.BalanceOf(a2u(poolAddr)) + newBarBalance := bar.BalanceOf(a2u(poolAddr)) + shouldEQ(t, newBarBalance, uint64(amount0)) // 3678978 + shouldEQ(t, newFooBalance, uint64(amount1)) // 9999999 + } + + { + oldBarBalance := bar.BalanceOf(a2u(poolAddr)) + oldQuxBalance := qux.BalanceOf(a2u(poolAddr)) + shouldEQ(t, oldBarBalance, 3678978) + shouldEQ(t, oldQuxBalance, 0) + + tokenId, liquidity, amount0, amount1 := pos.Mint(barPath, quxPath, test_fee100, int32(9000), int32(11000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) + shouldEQ(t, tokenId, 2) + shouldNEQ(t, liquidity, bigint(0)) + shouldNEQ(t, amount0, bigint(0)) + shouldNEQ(t, amount1, bigint(0)) + + newBarBalance := bar.BalanceOf(a2u(poolAddr)) + newQuxBalance := qux.BalanceOf(a2u(poolAddr)) + shouldEQ(t, newBarBalance, uint64(amount0)+3678978) // 7357956 + shouldEQ(t, newQuxBalance, uint64(amount1)) // 9999999 + } + + { + oldFooBalance := foo.BalanceOf(a2u(poolAddr)) + oldBarBalance := bar.BalanceOf(a2u(poolAddr)) + shouldEQ(t, oldFooBalance, 9999999) + shouldEQ(t, oldBarBalance, 7357956) + + tokenId, liquidity, amount0, amount1 := pos.Mint(fooPath, barPath, test_fee500, int32(4000), int32(6000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) + shouldEQ(t, tokenId, 3) + shouldNEQ(t, liquidity, bigint(0)) + shouldNEQ(t, amount0, bigint(0)) + shouldNEQ(t, amount1, bigint(0)) + + newFooBalance := foo.BalanceOf(a2u(poolAddr)) + newBarBalance := bar.BalanceOf(a2u(poolAddr)) + shouldEQ(t, newBarBalance, uint64(amount0)+7357956) // 13423414 + shouldEQ(t, newFooBalance, uint64(amount1)+9999999) // 19999998 + } + + { + oldBarBalance := bar.BalanceOf(a2u(poolAddr)) + oldQuxBalance := qux.BalanceOf(a2u(poolAddr)) + shouldEQ(t, oldBarBalance, 13423414) + shouldEQ(t, oldQuxBalance, 9999999) + + tokenId, liquidity, amount0, amount1 := pos.Mint(barPath, quxPath, test_fee500, int32(4000), int32(6000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) + shouldEQ(t, tokenId, 4) + shouldNEQ(t, liquidity, bigint(0)) + shouldNEQ(t, amount0, bigint(0)) + shouldNEQ(t, amount1, bigint(0)) + + newBarBalance := bar.BalanceOf(a2u(poolAddr)) + newQuxBalance := qux.BalanceOf(a2u(poolAddr)) + shouldEQ(t, newBarBalance, uint64(amount0)+13423414) // 19488872 + shouldEQ(t, newQuxBalance, uint64(amount1)+9999999) // 19999998 + } +} + +func TestBestSwapPositivieMultiPath(t *testing.T) { + std.TestSetOrigCaller(tr01) + + userOldFooBalance := foo.BalanceOf(a2u(tr01)) + userOldQuxBalance := qux.BalanceOf(a2u(tr01)) + poolOldFooBalance := foo.BalanceOf(a2u(poolAddr)) + poolOldQuxBalance := qux.BalanceOf(a2u(poolAddr)) + + swapAmount := 123_456 + BestSwap( + fooPath, // inputToken + quxPath, // outputToken + bigint(swapAmount), // amountSpecified + 0, // sqrtPriceLimitX96 + ) + + userNewFooBalance := foo.BalanceOf(a2u(tr01)) + userNewQuxBalance := qux.BalanceOf(a2u(tr01)) + poolNewFooBalance := foo.BalanceOf(a2u(poolAddr)) + poolNewQuxBalance := qux.BalanceOf(a2u(poolAddr)) + + shouldGT(t, userOldFooBalance, userNewFooBalance) + shouldGT(t, userNewQuxBalance, userOldQuxBalance) + + shouldGT(t, poolNewFooBalance, poolOldFooBalance) + shouldGT(t, poolOldQuxBalance, poolNewQuxBalance) + + shouldLTE(t, userOldFooBalance-userNewFooBalance, swapAmount) +} + +func TestBestSwapNegativeMultiPath(t *testing.T) { + std.TestSetOrigCaller(tr01) + + userOldFooBalance := foo.BalanceOf(a2u(tr01)) + userOldQuxBalance := qux.BalanceOf(a2u(tr01)) + poolOldFooBalance := foo.BalanceOf(a2u(poolAddr)) + poolOldQuxBalance := qux.BalanceOf(a2u(poolAddr)) + + swapAmount := -987_654 + BestSwap( + fooPath, // inputToken + quxPath, // outputToken + bigint(swapAmount), // amountSpecified + 0, // sqrtPriceLimitX96 + ) + + userNewFooBalance := foo.BalanceOf(a2u(tr01)) + userNewQuxBalance := qux.BalanceOf(a2u(tr01)) + poolNewFooBalance := foo.BalanceOf(a2u(poolAddr)) + poolNewQuxBalance := qux.BalanceOf(a2u(poolAddr)) + + shouldGT(t, userOldFooBalance, userNewFooBalance) + shouldGT(t, userNewQuxBalance, userOldQuxBalance) + + shouldGT(t, poolNewFooBalance, poolOldFooBalance) + shouldGT(t, poolOldQuxBalance, poolNewQuxBalance) + + shouldLTE(t, userNewQuxBalance-userOldQuxBalance, -swapAmount) +} + +/* HELPER */ +func shouldEQ(t *testing.T, got, expected interface{}) { + if got != expected { + t.Errorf("got %v, expected %v", got, expected) + } +} + +func shouldNEQ(t *testing.T, got, expected interface{}) { + if got == expected { + t.Errorf("got %v, didn't expected %v", got, expected) + } +} + +func shouldGT(t *testing.T, l, r interface{}) { + if !(l > r) { + t.Errorf("expected %v > %v", l, r) + } +} + +func shouldGTE(t *testing.T, l, r interface{}) { + if !(l >= r) { + t.Errorf("expected %v > %v", l, r) + } +} + +func shouldLT(t *testing.T, l, r interface{}) { + if !(l < r) { + t.Errorf("expected %v < %v", l, r) + } +} + +func shouldLTE(t *testing.T, l, r interface{}) { + if !(l <= r) { + t.Errorf("expected %v < %v", l, r) + } +} diff --git a/router/router_test.gno b/router/_TEST_router_single_path_dry_test.gnoa similarity index 84% rename from router/router_test.gno rename to router/_TEST_router_single_path_dry_test.gnoa index 963deb13..8b4a5aac 100644 --- a/router/router_test.gno +++ b/router/_TEST_router_single_path_dry_test.gnoa @@ -10,11 +10,6 @@ import ( _ "gno.land/r/grc20_wrapper" p "gno.land/r/pool" pos "gno.land/r/position" - - _ "gno.land/r/bar" - _ "gno.land/r/baz" - foo "gno.land/r/foo" - qux "gno.land/r/qux" ) var ( @@ -70,14 +65,9 @@ func TestPositionMint(t *testing.T) { pos.Mint(fooPath, quxPath, test_fee500, int32(4000), int32(6000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) } -func TestBestSwapDryPositiveSpend(t *testing.T) { +func TestBestSwapDryPositive(t *testing.T) { std.TestSetOrigCaller(tr01) - tr01FooOldBal := foo.BalanceOf(a2u(tr01)) - tr01QuxOldBal := qux.BalanceOf(a2u(tr01)) - RoQuxOldBal := qux.BalanceOf(a2u(routerAddr)) - PoQuxOldBal := qux.BalanceOf(a2u(poolAddr)) - jsonOutput := BestSwapDry( "gno.land/r/foo", // inputTokenPath "gno.land/r/qux", // outputTokenPath @@ -92,11 +82,6 @@ func TestBestSwapDryPositiveSpend(t *testing.T) { func TestBestSwapDryNegative(t *testing.T) { std.TestSetOrigCaller(tr01) - tr01FooOldBal := foo.BalanceOf(a2u(tr01)) - tr01QuxOldBal := qux.BalanceOf(a2u(tr01)) - RoQuxOldBal := qux.BalanceOf(a2u(routerAddr)) - PoQuxOldBal := qux.BalanceOf(a2u(poolAddr)) - jsonOutput := BestSwapDry( "gno.land/r/foo", // inputTokenPath "gno.land/r/qux", // outputTokenPath @@ -105,7 +90,7 @@ func TestBestSwapDryNegative(t *testing.T) { ) jsonStr := gjson.Parse(jsonOutput) shouldEQ(t, jsonStr.Get("totalRoutes").String(), "1") - shouldEQ(t, jsonStr.Get("rpcRoutes.0.swapPaths.0").String(), "gno.land/r/foo:gno.land/r/qux:500") + shouldEQ(t, jsonStr.Get("rpcRoutes.0.swapPaths.0").String(), "gno.land/r/foo:gno.land/r/qux:100") } /* HELPER */ diff --git a/router/_TEST_router_single_path_swap_test.gnoa b/router/_TEST_router_single_path_swap_test.gnoa new file mode 100644 index 00000000..e4c14756 --- /dev/null +++ b/router/_TEST_router_single_path_swap_test.gnoa @@ -0,0 +1,205 @@ +package router + +import ( + "encoding/gjson" + "std" + "testing" + + "gno.land/p/demo/testutils" + + _ "gno.land/r/grc20_wrapper" + p "gno.land/r/pool" + pos "gno.land/r/position" + + "gno.land/r/foo" + "gno.land/r/qux" +) + +var ( + gsa = testutils.TestAddress("gsa") // Gnoswap Admin + lp01 = testutils.TestAddress("lp01") // Liquidity Provider 01 + tr01 = testutils.TestAddress("tr01") // Trader 01 + + poolAddr = std.DerivePkgAddr("gno.land/r/pool") + posAddr = std.DerivePkgAddr("gno.land/r/position") + routerAddr = std.DerivePkgAddr("gno.land/r/router") +) + +var ( + // Common + fooPath = "gno.land/r/foo" // token1 + barPath = "gno.land/r/bar" // token2 + bazPath = "gno.land/r/baz" // token3 + quxPath = "gno.land/r/qux" // token4 + + test_fee100 = uint16(100) + test_fee500 = uint16(500) + + max_timeout = bigint(9999999999) +) + +func init() { + println(gsa, "// gsa") + println(lp01, "// lp01") + println(tr01, "// tr01") + println(poolAddr, "// poolAddr") + println(posAddr, "// posAddr") + println(routerAddr, "// routerAddr") +} + +func TestInitManual(t *testing.T) { + std.TestSetOrigCaller(gsa) + p.InitManual() + std.TestSkipHeights(1) +} + +func TestCreatePool(t *testing.T) { + std.TestSetOrigCaller(gsa) + + p.CreatePool(fooPath, quxPath, test_fee100, 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 + p.CreatePool(fooPath, quxPath, test_fee500, 101729702841318637793976746270) // tick = 5_000, ratio = 1.648680055931176 + // findSwapPaths("gno.land/r/foo", "gno.land/r/qux") // 2 paths + // foo_qux_100 + + jsonOutput := p.ApiGetPools() + jsonStr := gjson.Parse(jsonOutput) + shouldEQ(t, len(jsonStr.Get("response.data").Array()), 2) +} + +func TestPositionMint(t *testing.T) { + std.TestSetOrigCaller(lp01) + { + oldT0Balance := foo.BalanceOf(a2u(poolAddr)) + oldT1Balance := qux.BalanceOf(a2u(poolAddr)) + shouldEQ(t, oldT0Balance, 0) + shouldEQ(t, oldT1Balance, 0) + + tokenId, liquidity, amount0, amount1 := pos.Mint(fooPath, quxPath, test_fee100, int32(9000), int32(11000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) + + shouldEQ(t, tokenId, 1) + shouldNEQ(t, liquidity, bigint(0)) + shouldNEQ(t, amount0, bigint(0)) + shouldNEQ(t, amount1, bigint(0)) + + newT0Balance := foo.BalanceOf(a2u(poolAddr)) + newT1Balance := qux.BalanceOf(a2u(poolAddr)) + shouldEQ(t, newT0Balance, uint64(amount0)) // 3678978 + shouldEQ(t, newT1Balance, uint64(amount1)) // 9999999 + } + + { + oldT0Balance := foo.BalanceOf(a2u(poolAddr)) + oldT1Balance := qux.BalanceOf(a2u(poolAddr)) + shouldEQ(t, oldT0Balance, 3678978) + shouldEQ(t, oldT1Balance, 9999999) + + tokenId, liquidity, amount0, amount1 := pos.Mint(fooPath, quxPath, test_fee500, int32(4000), int32(6000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) + + shouldEQ(t, tokenId, 2) + shouldNEQ(t, liquidity, bigint(0)) + shouldNEQ(t, amount0, bigint(0)) + shouldNEQ(t, amount1, bigint(0)) + + newT0Balance := foo.BalanceOf(a2u(poolAddr)) + newT1Balance := qux.BalanceOf(a2u(poolAddr)) + shouldEQ(t, newT0Balance, uint64(amount0)+3678978) + shouldEQ(t, newT1Balance, uint64(amount1)+9999999) + } +} + +func TestBestSwapPositivieSinglePath(t *testing.T) { + std.TestSetOrigCaller(tr01) + + userOldFooBalance := foo.BalanceOf(a2u(tr01)) + userOldQuxBalance := qux.BalanceOf(a2u(tr01)) + poolOldFooBalance := foo.BalanceOf(a2u(poolAddr)) + poolOldQuxBalance := qux.BalanceOf(a2u(poolAddr)) + + swapAmount := 123_456 + BestSwap( + fooPath, // inputToken + quxPath, // outputToken + bigint(swapAmount), // amountSpecified + 0, // sqrtPriceLimitX96 + ) + + userNewFooBalance := foo.BalanceOf(a2u(tr01)) + userNewQuxBalance := qux.BalanceOf(a2u(tr01)) + poolNewFooBalance := foo.BalanceOf(a2u(poolAddr)) + poolNewQuxBalance := qux.BalanceOf(a2u(poolAddr)) + + shouldGT(t, userOldFooBalance, userNewFooBalance) + shouldGT(t, userNewQuxBalance, userOldQuxBalance) + + shouldGT(t, poolNewFooBalance, poolOldFooBalance) + shouldGT(t, poolOldQuxBalance, poolNewQuxBalance) + + shouldLTE(t, userOldFooBalance-userNewFooBalance, swapAmount) +} + +func TestBestSwapNegativeSinglePath(t *testing.T) { + std.TestSetOrigCaller(tr01) + + userOldFooBalance := foo.BalanceOf(a2u(tr01)) + userOldQuxBalance := qux.BalanceOf(a2u(tr01)) + poolOldFooBalance := foo.BalanceOf(a2u(poolAddr)) + poolOldQuxBalance := qux.BalanceOf(a2u(poolAddr)) + + swapAmount := -987_654 + BestSwap( + fooPath, // inputToken + quxPath, // outputToken + bigint(swapAmount), // amountSpecified + 0, // sqrtPriceLimitX96 + ) + + userNewFooBalance := foo.BalanceOf(a2u(tr01)) + userNewQuxBalance := qux.BalanceOf(a2u(tr01)) + poolNewFooBalance := foo.BalanceOf(a2u(poolAddr)) + poolNewQuxBalance := qux.BalanceOf(a2u(poolAddr)) + + shouldGT(t, userOldFooBalance, userNewFooBalance) + shouldGT(t, userNewQuxBalance, userOldQuxBalance) + + shouldGT(t, poolNewFooBalance, poolOldFooBalance) + shouldGT(t, poolOldQuxBalance, poolNewQuxBalance) + + shouldLTE(t, userNewQuxBalance-userOldQuxBalance, -swapAmount) +} + +/* HELPER */ +func shouldEQ(t *testing.T, got, expected interface{}) { + if got != expected { + t.Errorf("got %v, expected %v", got, expected) + } +} + +func shouldNEQ(t *testing.T, got, expected interface{}) { + if got == expected { + t.Errorf("got %v, didn't expected %v", got, expected) + } +} + +func shouldGT(t *testing.T, l, r interface{}) { + if !(l > r) { + t.Errorf("expected %v > %v", l, r) + } +} + +func shouldGTE(t *testing.T, l, r interface{}) { + if !(l >= r) { + t.Errorf("expected %v > %v", l, r) + } +} + +func shouldLT(t *testing.T, l, r interface{}) { + if !(l < r) { + t.Errorf("expected %v < %v", l, r) + } +} + +func shouldLTE(t *testing.T, l, r interface{}) { + if !(l <= r) { + t.Errorf("expected %v < %v", l, r) + } +} diff --git a/router/router.gno b/router/router.gno index fb0bd626..3a3cbb4e 100644 --- a/router/router.gno +++ b/router/router.gno @@ -3,11 +3,13 @@ package router import ( "std" "strings" + + "gno.land/p/demo/ufmt" ) func BestSwap( inputToken string, - outputTokenPath string, + outputToken string, amountSpecified bigint, sqrtPriceLimitX96 bigint, @@ -15,49 +17,44 @@ func BestSwap( // get quotes quotes := quoteForAllPath( // sorted by ratio DESC inputToken, - outputTokenPath, + outputToken, amountSpecified, sqrtPriceLimitX96, ) - if len(quotes) == 0 { - panic("router.gno__THERE IS NO QUOTE") - } + require(len(quotes) != 0, ufmt.Sprintf("[ROUTER] router.gno__BestSwap() || len(quotes) == 0, inputToken:%s, outputToken:%s", inputToken, outputToken)) bestSwaps := findBestPaths(quotes) - if len(bestSwaps) == 0 { - panic("router.gno__CAN'T MAKE BestSwapRoute") - } + require(len(bestSwaps) != 0, "[ROUTER] router.gno__BestSwap() || len(bestSwaps) == 0") finalSwaps := removeDuplication(bestSwaps) - if len(bestSwaps) == 0 { - panic("router.gno__CAN'T MAKE FinalSwapRoute") - } + require(len(finalSwaps) != 0, "[ROUTER] router.gno__BestSwap() || len(finalSwaps) == 0") remainingAmount := amountSpecified - for i, bestSwap := range bestSwaps { - numPools := strings.Count(bestSwap.targetPath, ",") / 2 - - if numPools < 1 { - panic("router.gno__CAN NOT FIND SWAP PATH") - } - - if numPools > 3 { - panic("router.gno__TOO MANY SWAP PATH") - } + for i, finalSwap := range finalSwaps { + numPools := strings.Count(finalSwap.targetPath, ",") / 2 + require(numPools > 1 && numPools < 3, ufmt.Sprintf("[ROUTER] router.gno__BestSwap() || numPools should 1 ~ 3, but found %d", numPools)) // SINGLE if numPools == 1 { - input, output, fee := getSwapData(bestSwap.targetPath, 0) + toSwap := bigint(0) + + // isFinal + if i == len(finalSwaps)-1 { // last swap routes + toSwap = remainingAmount + } else { + remainingAmount -= finalSwap.pctAmount + toSwap = finalSwap.pctAmount + } + input, output, fee := getSwapData(finalSwap.targetPath, 0) singleParams := SingleSwapParams{ tokenIn: input, tokenOut: output, fee: fee, - amountSpecified: bestSwap.pctAmount, + amountSpecified: toSwap, sqrtPriceLimitX96: sqrtPriceLimitX96, } amountOut := singleSwap(singleParams) - remainingAmount -= bestSwap.pctAmount } // MULTI @@ -65,25 +62,23 @@ func BestSwap( toSwap := bigint(0) // isFinal - if i == len(bestSwaps)-1 { // last swap routes + if i == len(finalSwaps)-1 { // last swap routes toSwap = remainingAmount } else { - remainingAmount -= bestSwap.pctAmount - toSwap = bestSwap.pctAmount + remainingAmount -= finalSwap.pctAmount + toSwap = finalSwap.pctAmount } - input, output, fee := getSwapData(bestSwap.targetPath, 0) - + input, output, fee := getSwapData(finalSwap.targetPath, 0) swapParams := SwapParams{ - tokenIn: input, - tokenOut: output, - fee: fee, - + tokenIn: input, + tokenOut: output, + fee: fee, recipient: std.GetOrigCaller(), amountSpecified: toSwap, minAmountOut: 1, } - amountOut := multiSwap(swapParams, 0, numPools, bestSwap.targetPath) // iterate here + amountOut := multiSwap(swapParams, 0, numPools, finalSwap.targetPath) // iterate here } } } From 0336b8f2d5ce895470fd943369a482f8c3c307b7 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Mon, 6 Nov 2023 11:52:11 +0900 Subject: [PATCH 26/35] chore: better error handling, remove unused codes --- router/consts.gno | 3 ++- router/find_path.gno | 14 +++------- router/quotation.gno | 55 +++++++++++++++++++++----------------- router/router_register.gno | 4 +-- router/swap_inner.gno | 8 +++--- router/type.gno | 18 ++++--------- router/util.gno | 38 +++++++------------------- 7 files changed, 54 insertions(+), 86 deletions(-) diff --git a/router/consts.gno b/router/consts.gno index 42a59ffb..c1f0e57e 100644 --- a/router/consts.gno +++ b/router/consts.gno @@ -8,5 +8,6 @@ const ( Q96 bigint = 79228162514264337593543950336 // 2 ** 96 - ADDR_POOL std.Address = std.DerivePkgAddr("gno.land/r/pool") + ADDR_POOL std.Address = std.DerivePkgAddr("gno.land/r/pool") + ADDR_ROUTER std.Address = std.DerivePkgAddr("gno.land/r/router") ) diff --git a/router/find_path.gno b/router/find_path.gno index bf40e164..5427b489 100644 --- a/router/find_path.gno +++ b/router/find_path.gno @@ -5,6 +5,8 @@ import ( "strings" p "gno.land/r/pool" + + "gno.land/p/demo/ufmt" ) func findSwapPaths( @@ -44,9 +46,7 @@ func getSwapPaths( swapPaths = make(SwapPaths, 0) // check if there is path that starts with input - if len(tokenPairs[inputTokenPath]) == 0 { - panic("find_path.gno__NO POOL") - } + require(len(tokenPairs[inputTokenPath]) != 0, ufmt.Sprintf("[ROUTER] find_path.gno__getSwapPaths() || len(tokenPairs[inputTokenPath]) == 0, inputTokenPath: %s", inputTokenPath)) // find direct path for _, output := range tokenPairs[inputTokenPath] { @@ -60,7 +60,6 @@ func getSwapPaths( } // find nested path - // r3v4_xx: handle more than three time swap ?? swapPaths = findTwicePath(tokenPairs, inputTokenPath, outputTokenPath, swapPaths) swapPaths = findThreeTimePath(tokenPairs, inputTokenPath, outputTokenPath, swapPaths) @@ -75,16 +74,11 @@ func findTwicePath( ) SwapPaths { for _, second := range tokenPairs[inputTokenPath] { secondPath, secondFee := singlePoolPathWithFeeDivide(second) - // tokenPairs[inputTokenPath] = removeItemFromStringArray(tokenPairs[inputTokenPath], second) - // tokenPairs[secondPath] = removeItemFromStringArray(tokenPairs[secondPath], (inputTokenPath + ":" + secondFee)) for _, third := range tokenPairs[secondPath] { thirdPath, thirdFee := singlePoolPathWithFeeDivide(third) - // tokenPairs[secondPath] = removeItemFromStringArray(tokenPairs[secondPath], third) - // tokenPairs[thirdPath] = removeItemFromStringArray(tokenPairs[thirdPath], (secondPath + ":" + thirdFee)) if strings.HasPrefix(third, outputTokenPath) { - // twice nestedPath := inputTokenPath + "," + secondFee + "," + secondPath + "," + thirdFee + "," + outputTokenPath swapPaths[len(swapPaths)] = nestedPath } @@ -102,11 +96,9 @@ func findThreeTimePath( ) SwapPaths { for _, second := range tokenPairs[inputTokenPath] { secondPath, secondFee := singlePoolPathWithFeeDivide(second) - // tokenPairs[secondPath] = removeItemFromStringArray(tokenPairs[secondPath], (inputTokenPath + ":" + secondFee)) for _, third := range tokenPairs[secondPath] { // bar > bz thirdPath, thirdFee := singlePoolPathWithFeeDivide(third) - // tokenPairs[secondPath] = removeItemFromStringArray(tokenPairs[thirdPath], (secondPath + ":" + thirdFee)) for _, fourth := range tokenPairs[thirdPath] { fourthPath, fourthFee := singlePoolPathWithFeeDivide(fourth) diff --git a/router/quotation.gno b/router/quotation.gno index 2218a1ff..f11b2bd8 100644 --- a/router/quotation.gno +++ b/router/quotation.gno @@ -4,6 +4,8 @@ import ( "sort" "std" "strings" + + "gno.land/p/demo/ufmt" ) func quoteForAllPath( @@ -31,14 +33,7 @@ func quoteForAllPath( // DrySwap to calculate for i, quoterTarget := range quoterTargets { numPools := strings.Count(quoterTarget.targetPath, ",") / 2 - - if numPools < 1 { - panic("quotation.gno__CAN NOT FIND SWAP PATH") - } - - if numPools > 3 { - panic("quotation.gno__TOO MANY SWAP PATH") - } + require(numPools > 1 && numPools < 3, ufmt.Sprintf("[ROUTER] router.gno__quoteForAllPath() || numPools should 1 ~ 3, but found %d", numPools)) if numPools == 1 { input, output, fee := getSwapData(quoterTarget.targetPath, 0) @@ -54,16 +49,16 @@ func quoteForAllPath( estimatedResult := singleSwapDry(singleParams) if estimatedResult > 0 { if amountSpecified > 0 { - resultRatioX96 := estimatedResult * Q96 / quoterTarget.pctAmount * Q96 / Q96 // divide this value by Q96 => will get float ratio + resultRatioX96 := estimatedResult * Q96 / quoterTarget.pctAmount * Q96 / Q96 quoterTarget.resultRatioX96 = resultRatioX96 quoterTargets[i] = quoterTarget } else { - resultRatioX96 := -quoterTarget.pctAmount * Q96 / estimatedResult * Q96 / Q96 // divide this value by Q96 => will get float ratio + resultRatioX96 := -quoterTarget.pctAmount * Q96 / estimatedResult * Q96 / Q96 quoterTarget.resultRatioX96 = resultRatioX96 quoterTargets[i] = quoterTarget } } else { - panic("quotation.gno__CAN NOT HAPPEN") + panic("[ROUTER] quotation.gno__quoteForAllPath() || SINGLE__estimateResult < 0") } } @@ -80,19 +75,34 @@ func quoteForAllPath( minAmountOut: 1, // r3v4_xx: sqrtPriceLimitX96 } - estimatedResult := multiSwapDry(swapParams, 0, numPools, quoterTarget.targetPath) // will iterate here to cover multi pools - if estimatedResult > 0 { - resultRatioX96 := estimatedResult * Q96 / quoterTarget.pctAmount * Q96 / Q96 - quoterTarget.resultRatioX96 = resultRatioX96 - quoterTargets[i] = quoterTarget + // if amountSpecified > 0, proceed forward + // if not, proceed with backward + if amountSpecified > 0 { + estimatedResult := multiSwapDry(swapParams, 0, numPools, quoterTarget.targetPath) // will iterate here to cover multi pools + if estimatedResult > 0 { + resultRatioX96 := estimatedResult * Q96 / quoterTarget.pctAmount * Q96 / Q96 + quoterTarget.resultRatioX96 = resultRatioX96 + quoterTargets[i] = quoterTarget + } else { + panic("[ROUTER] quotation.gno__quoteForAllPath() || MULTI__PositiveAmountSpecified__estimateResult < 0") + } + } else { + estimatedResult := multiSwapNegativeDry(swapParams, numPools-1, quoterTarget.targetPath) // will iterate here to cover multi pools + if estimatedResult > 0 { + resultRatioX96 := -quoterTarget.pctAmount * Q96 / estimatedResult * Q96 / Q96 + + quoterTarget.resultRatioX96 = resultRatioX96 + quoterTargets[i] = quoterTarget + } else { + panic("[ROUTER] quotation.gno__quoteForAllPath() || MULTI__NegativeAmountSpecified__estimateResult < 0") + } } + } } // it contains outputRatio with 0 which is impossible path - // remove path with 0 ratio - // positive ratio => amountSpecified > 0 - // negative ratio => amountSpecified < 0 + // > remove path with 0 ratio var finalTargets []QuoterTarget for _, quote := range quoterTargets { if quote.resultRatioX96 != 0 { @@ -100,12 +110,7 @@ func quoteForAllPath( } } - if amountSpecified > 0 { - sort.Sort(ByOutputRatioDesc(finalTargets)) - } else { - sort.Sort(ByOutputRatioAsc(finalTargets)) - } - + sort.Sort(ByOutputRatioDesc(finalTargets)) return finalTargets } diff --git a/router/router_register.gno b/router/router_register.gno index 5ecd1dca..f7650b6d 100644 --- a/router/router_register.gno +++ b/router/router_register.gno @@ -67,9 +67,7 @@ func UnregisterGRC20Interface(pkgPath string) { // only admin can unregister caller := std.GetOrigCaller() - if caller != APPROVED_CALLER { - panic("unauthorized address to unregister") - } + require(caller == APPROVED_CALLER, "[ROUTER] router_register.gno__UnregisterGRC20Interface() || nauthorized address to unregister") _, found := findGRC20(pkgPath) if found { diff --git a/router/swap_inner.gno b/router/swap_inner.gno index b9c03dd2..12a58f94 100644 --- a/router/swap_inner.gno +++ b/router/swap_inner.gno @@ -53,13 +53,13 @@ func _swap( ) if amountSpecified > 0 { - if zeroForOne { + if zeroForOne { // return amount, user recvs ~= pool sends amountResult = -amount1 } else { amountResult = -amount0 } } else { - if zeroForOne { + if zeroForOne { // return amount, user sends ~= pool recvs amountResult = amount0 } else { amountResult = amount1 @@ -101,12 +101,12 @@ func _swapDry( } if amountSpecified > 0 { - if zeroForOne { + if zeroForOne { // return amount, user recvs ~= pool sends amountResult = -amount1 } else { amountResult = -amount0 } - } else { + } else { // return amount, user sends ~= pool recvs if zeroForOne { amountResult = amount0 } else { diff --git a/router/type.gno b/router/type.gno index 1791e13e..3fae9d05 100644 --- a/router/type.gno +++ b/router/type.gno @@ -1,8 +1,6 @@ package router -import ( - "std" -) +import "std" type SwapCallbackData struct { tokenIn string @@ -16,10 +14,10 @@ type SwapPaths map[int]string type TokenPairs map[string][]string type QuoterTarget struct { - pct int // 5% - pctAmount bigint // - targetPath string // gno.land/r/foo,500,gno.land/r/qux - resultRatioX96 bigint // + pct int + pctAmount bigint + targetPath string + resultRatioX96 bigint } type ByOutputRatioDesc []QuoterTarget @@ -27,9 +25,3 @@ type ByOutputRatioDesc []QuoterTarget func (a ByOutputRatioDesc) Len() int { return len(a) } func (a ByOutputRatioDesc) Swap(i, j int) { a[i], a[j] = a[j], a[i] } func (a ByOutputRatioDesc) Less(i, j int) bool { return a[i].resultRatioX96 > a[j].resultRatioX96 } - -type ByOutputRatioAsc []QuoterTarget - -func (a ByOutputRatioAsc) Len() int { return len(a) } -func (a ByOutputRatioAsc) Swap(i, j int) { a[i], a[j] = a[j], a[i] } -func (a ByOutputRatioAsc) Less(i, j int) bool { return a[i].resultRatioX96 < a[j].resultRatioX96 } diff --git a/router/util.gno b/router/util.gno index bede21be..a1a38434 100644 --- a/router/util.gno +++ b/router/util.gno @@ -9,7 +9,6 @@ import ( "gno.land/r/demo/users" ) -/* UTIL */ func removeItemFromStringArray(s []string, r string) []string { for i, v := range s { if v == r { @@ -21,14 +20,11 @@ func removeItemFromStringArray(s []string, r string) []string { func poolPathWithFeeDivide(poolPath string) (string, string, int) { poolPathSplit := strings.Split(poolPath, ":") - - if len(poolPathSplit) != 3 { - panic(ufmt.Sprintf("[POOL] pool_router.gno__poolPathWithFeeDivide() || len(poolPathSplit) != 3, poolPath: %s", poolPath)) - } + require(len(poolPathSplit) == 3, ufmt.Sprintf("[ROUTER] util.gno__poolPathWithFeeDivide() || len(poolPathSplit) != 3, poolPath: %s", poolPath)) feeInt, err := strconv.Atoi(poolPathSplit[2]) if err != nil { - panic(ufmt.Sprintf("[POOL] pool_router.gno__poolPathWithFeeDivide() || cannot convert fee(%s) to int", poolPathSplit[2])) + panic(ufmt.Sprintf("[ROUTER] util.gno__poolPathWithFeeDivide() || cannot convert fee(%s) to int", poolPathSplit[2])) } return poolPathSplit[0], poolPathSplit[1], feeInt @@ -36,33 +32,11 @@ func poolPathWithFeeDivide(poolPath string) (string, string, int) { func singlePoolPathWithFeeDivide(poolPath string) (string, string) { singlePoolPathSplit := strings.Split(poolPath, ":") + require(len(singlePoolPathSplit) == 2, ufmt.Sprintf("[ROUTER] util.gno__singlePoolPathWithFeeDivide || len(singlePoolPathSplit) != 2, poolPath: %s", poolPath)) - if len(singlePoolPathSplit) != 2 { - panic(ufmt.Sprintf("[POOL] pool_router.gno__singlePoolPathWithFeeDivide || len(singlePoolPathSplit) != 2, poolPath: %s", poolPath)) - } return singlePoolPathSplit[0], singlePoolPathSplit[1] } -func getNumPoolInPath(path string) int { - count := strings.Count(path, ",") - return count / 2 -} - -func getPoolKey(path string, poolIdx int) string { - datas := strings.Split(path, ",") - - switch poolIdx { - case 0: - return sortItems(datas[0], datas[2], datas[1]) - case 1: - return sortItems(datas[2], datas[4], datas[3]) - case 2: - return sortItems(datas[4], datas[6], datas[5]) - default: - panic("NOT SUPPORTED #1") - } -} - func getSwapData(path string, poolIdx int) (string, string, uint16) { // inputToken, outputToken, fee datas := strings.Split(path, ",") @@ -93,3 +67,9 @@ func sortItems(tokenAPath, tokenBPath, fee string) string { func a2u(addr std.Address) users.AddressOrName { return users.AddressOrName(addr) } + +func require(cond bool, msg string) { + if !cond { + panic(msg) + } +} From 8a4592c467f61fd7ee15a73d72eac69b7d43c1ac Mon Sep 17 00:00:00 2001 From: n3wbie Date: Mon, 6 Nov 2023 19:45:35 +0900 Subject: [PATCH 27/35] GSW-57 feat: implement router getter for api --- router/_TEST_router_getter_api_test.gno | 81 +++++ router/_TEST_router_multi_path_dry_test.gnoa | 21 +- ...=> _TEST_router_multi_path_swap_test.gnoa} | 8 +- router/_TEST_router_single_path_dry_test.gnoa | 8 +- .../_TEST_router_single_path_swap_test.gnoa | 8 +- router/getter_api.gno | 291 ++++++++++++++++++ router/gno_helper.gno | 14 + router/quotation.gno | 2 +- router/router.gno | 16 +- router/rpc_call.gno | 109 ++++++- 10 files changed, 514 insertions(+), 44 deletions(-) create mode 100644 router/_TEST_router_getter_api_test.gno rename router/{_TEST_router_multi_path_swap_test.gno => _TEST_router_multi_path_swap_test.gnoa} (97%) create mode 100644 router/getter_api.gno create mode 100644 router/gno_helper.gno diff --git a/router/_TEST_router_getter_api_test.gno b/router/_TEST_router_getter_api_test.gno new file mode 100644 index 00000000..933bff14 --- /dev/null +++ b/router/_TEST_router_getter_api_test.gno @@ -0,0 +1,81 @@ +// EXTERNAL API +package router + +import ( + "encoding/gjson" + "std" + "testing" + + p "gno.land/r/pool" + pos "gno.land/r/position" + + "gno.land/p/demo/testutils" + + _ "gno.land/r/grc20_wrapper" +) + +var ( + gsa = testutils.TestAddress("gsa") // Gnoswap Admin + lp01 = testutils.TestAddress("lp01") // Liquidity Provider 01 + + // Common + fooPath = "gno.land/r/foo" + barPath = "gno.land/r/bar" + bazPath = "gno.land/r/baz" + quxPath = "gno.land/r/qux" + + test_fee100 = uint16(100) + + max_timeout = bigint(9999999999) +) + +func TestInitManual(t *testing.T) { + std.TestSetOrigCaller(gsa) + p.InitManual() +} + +func TestCreatePool(t *testing.T) { + std.TestSetOrigCaller(gsa) + + p.CreatePool(fooPath, barPath, test_fee100, 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 // 1 bar == 2.7 foo + p.CreatePool(barPath, bazPath, test_fee100, 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 // 1 bar == 2.7 baz + p.CreatePool(bazPath, quxPath, test_fee100, 130621891405341611593710811006) // tick = 10_000, ratio = 2.7181459268252253 // 1 baz == 2.7 qux +} + +func TestPositionMint(t *testing.T) { + std.TestSetOrigCaller(lp01) + + pos.Mint(fooPath, barPath, test_fee100, int32(9000), int32(11000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) + pos.Mint(barPath, bazPath, test_fee100, int32(9000), int32(11000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) + pos.Mint(bazPath, quxPath, test_fee100, int32(9000), int32(11000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) +} + +func TestApiGetRatiosFromBase(t *testing.T) { + jsonStr := ApiGetRatiosFromBase() + jsonOutput := gjson.Parse(jsonStr) + + shouldEQ(t, len(jsonOutput.Get("response.data").Array()), 4) + shouldEQ(t, jsonOutput.Get("response.data.1").String(), "{\"gno.land/r/bar\":29147869410676662479573841822}") + /* + len(tokenPrice): 4 + + token: gno.land/r/foo + price: 79228162514264337593543950336 ~= 1 + + token: gno.land/r/bar + price: 29147869410676662479573841822 ~= 0.3678978344 + + token: gno.land/r/baz + price: 79228162514264337593543950333 ~= 1 + + token: gno.land/r/qux + price: 215353707227994575755767921538 ~= 2.7181459268 + */ +} + +/* HELPER */ +func shouldEQ(t *testing.T, got, expected interface{}) { + if got != expected { + t.Errorf("got %v, expected %v", got, expected) + } +} diff --git a/router/_TEST_router_multi_path_dry_test.gnoa b/router/_TEST_router_multi_path_dry_test.gnoa index 9a1abcdc..7d56925d 100644 --- a/router/_TEST_router_multi_path_dry_test.gnoa +++ b/router/_TEST_router_multi_path_dry_test.gnoa @@ -35,15 +35,6 @@ var ( max_timeout = bigint(9999999999) ) -// func init() { -// println(gsa, "// gsa") -// println(lp01, "// lp01") -// println(tr01, "// tr01") -// println(poolAddr, "// poolAddr") -// println(posAddr, "// posAddr") -// println(routerAddr, "// routerAddr") -// } - func TestInitManual(t *testing.T) { std.TestSetOrigCaller(gsa) p.InitManual() @@ -71,32 +62,34 @@ func TestPositionMint(t *testing.T) { pos.Mint(barPath, quxPath, test_fee500, int32(4000), int32(6000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) } -func TestBestSwapDryPositive(t *testing.T) { +func TestBestSwapDryExactIn(t *testing.T) { std.TestSetOrigCaller(tr01) jsonOutput := BestSwapDry( "gno.land/r/foo", // inputTokenPath "gno.land/r/qux", // outputTokenPath + "EXACT_IN", // swapType 123_456, // amountSpecified 0, // sqrtPriceLimitX96 ) jsonStr := gjson.Parse(jsonOutput) - shouldEQ(t, jsonStr.Get("rpcRoutes.0.numSwaps").String(), "2") + shouldEQ(t, jsonStr.Get("rpcRoutes.0.numPools").String(), "2") shouldEQ(t, jsonStr.Get("rpcRoutes.0.swapPaths.0").String(), "gno.land/r/foo:gno.land/r/bar:100") shouldEQ(t, jsonStr.Get("rpcRoutes.0.swapPaths.1").String(), "gno.land/r/bar:gno.land/r/qux:100") } -func TestBestSwapDryNegative(t *testing.T) { +func TestBestSwapDryExactOut(t *testing.T) { std.TestSetOrigCaller(tr01) jsonOutput := BestSwapDry( "gno.land/r/foo", // inputTokenPath "gno.land/r/qux", // outputTokenPath - -123_456, // amountSpecified + "EXACT_OUT", // swapType + 123_456, // amountSpecified 0, // sqrtPriceLimitX96 ) jsonStr := gjson.Parse(jsonOutput) - shouldEQ(t, jsonStr.Get("rpcRoutes.0.numSwaps").String(), "2") + shouldEQ(t, jsonStr.Get("rpcRoutes.0.numPools").String(), "2") shouldEQ(t, jsonStr.Get("rpcRoutes.0.swapPaths.0").String(), "gno.land/r/foo:gno.land/r/bar:100") shouldEQ(t, jsonStr.Get("rpcRoutes.0.swapPaths.1").String(), "gno.land/r/bar:gno.land/r/qux:100") } diff --git a/router/_TEST_router_multi_path_swap_test.gno b/router/_TEST_router_multi_path_swap_test.gnoa similarity index 97% rename from router/_TEST_router_multi_path_swap_test.gno rename to router/_TEST_router_multi_path_swap_test.gnoa index 5d235f87..875d69b7 100644 --- a/router/_TEST_router_multi_path_swap_test.gno +++ b/router/_TEST_router_multi_path_swap_test.gnoa @@ -145,7 +145,7 @@ func TestPositionMint(t *testing.T) { } } -func TestBestSwapPositivieMultiPath(t *testing.T) { +func TestBestSwapExactInMultiPath(t *testing.T) { std.TestSetOrigCaller(tr01) userOldFooBalance := foo.BalanceOf(a2u(tr01)) @@ -157,6 +157,7 @@ func TestBestSwapPositivieMultiPath(t *testing.T) { BestSwap( fooPath, // inputToken quxPath, // outputToken + "EXACT_IN", // swapType bigint(swapAmount), // amountSpecified 0, // sqrtPriceLimitX96 ) @@ -175,7 +176,7 @@ func TestBestSwapPositivieMultiPath(t *testing.T) { shouldLTE(t, userOldFooBalance-userNewFooBalance, swapAmount) } -func TestBestSwapNegativeMultiPath(t *testing.T) { +func TestBestSwapExactOutMultiPath(t *testing.T) { std.TestSetOrigCaller(tr01) userOldFooBalance := foo.BalanceOf(a2u(tr01)) @@ -183,10 +184,11 @@ func TestBestSwapNegativeMultiPath(t *testing.T) { poolOldFooBalance := foo.BalanceOf(a2u(poolAddr)) poolOldQuxBalance := qux.BalanceOf(a2u(poolAddr)) - swapAmount := -987_654 + swapAmount := 987_654 BestSwap( fooPath, // inputToken quxPath, // outputToken + "EXACT_OUT", // swapType bigint(swapAmount), // amountSpecified 0, // sqrtPriceLimitX96 ) diff --git a/router/_TEST_router_single_path_dry_test.gnoa b/router/_TEST_router_single_path_dry_test.gnoa index 8b4a5aac..448ac928 100644 --- a/router/_TEST_router_single_path_dry_test.gnoa +++ b/router/_TEST_router_single_path_dry_test.gnoa @@ -65,12 +65,13 @@ func TestPositionMint(t *testing.T) { pos.Mint(fooPath, quxPath, test_fee500, int32(4000), int32(6000), bigint(10000000), bigint(10000000), 0, 0, max_timeout) } -func TestBestSwapDryPositive(t *testing.T) { +func TestBestSwapDryExactIn(t *testing.T) { std.TestSetOrigCaller(tr01) jsonOutput := BestSwapDry( "gno.land/r/foo", // inputTokenPath "gno.land/r/qux", // outputTokenPath + "EXACT_IN", // swapType 123_456, // amountSpecified 0, // sqrtPriceLimitX96 ) @@ -79,13 +80,14 @@ func TestBestSwapDryPositive(t *testing.T) { shouldEQ(t, jsonStr.Get("rpcRoutes.0.swapPaths.0").String(), "gno.land/r/foo:gno.land/r/qux:100") } -func TestBestSwapDryNegative(t *testing.T) { +func TestBestSwapDryExactOut(t *testing.T) { std.TestSetOrigCaller(tr01) jsonOutput := BestSwapDry( "gno.land/r/foo", // inputTokenPath "gno.land/r/qux", // outputTokenPath - -123_456, // amountSpecified + "EXACT_OUT", // swapType + 123_456, // amountSpecified 0, // sqrtPriceLimitX96 ) jsonStr := gjson.Parse(jsonOutput) diff --git a/router/_TEST_router_single_path_swap_test.gnoa b/router/_TEST_router_single_path_swap_test.gnoa index e4c14756..743ea8ce 100644 --- a/router/_TEST_router_single_path_swap_test.gnoa +++ b/router/_TEST_router_single_path_swap_test.gnoa @@ -107,7 +107,7 @@ func TestPositionMint(t *testing.T) { } } -func TestBestSwapPositivieSinglePath(t *testing.T) { +func TestBestSwapExactInSinglePath(t *testing.T) { std.TestSetOrigCaller(tr01) userOldFooBalance := foo.BalanceOf(a2u(tr01)) @@ -119,6 +119,7 @@ func TestBestSwapPositivieSinglePath(t *testing.T) { BestSwap( fooPath, // inputToken quxPath, // outputToken + "EXACT_IN", // swapType bigint(swapAmount), // amountSpecified 0, // sqrtPriceLimitX96 ) @@ -137,7 +138,7 @@ func TestBestSwapPositivieSinglePath(t *testing.T) { shouldLTE(t, userOldFooBalance-userNewFooBalance, swapAmount) } -func TestBestSwapNegativeSinglePath(t *testing.T) { +func TestBestSwapExactOutSinglePath(t *testing.T) { std.TestSetOrigCaller(tr01) userOldFooBalance := foo.BalanceOf(a2u(tr01)) @@ -145,10 +146,11 @@ func TestBestSwapNegativeSinglePath(t *testing.T) { poolOldFooBalance := foo.BalanceOf(a2u(poolAddr)) poolOldQuxBalance := qux.BalanceOf(a2u(poolAddr)) - swapAmount := -987_654 + swapAmount := 987_654 BestSwap( fooPath, // inputToken quxPath, // outputToken + "EXACT_OUT", // swapType bigint(swapAmount), // amountSpecified 0, // sqrtPriceLimitX96 ) diff --git a/router/getter_api.gno b/router/getter_api.gno new file mode 100644 index 00000000..bfd21609 --- /dev/null +++ b/router/getter_api.gno @@ -0,0 +1,291 @@ +// EXTERNAL API +package router + +import ( + "encoding/json" + "strings" + + p "gno.land/r/pool" + + "gno.land/p/demo/ufmt" +) + +const BASE_TOKEN = "gno.land/r/foo" + +type ApiQueryBase struct { + Height int64 `json:"height"` + Timestamp int64 `json:"timestamp"` +} + +type ResponseGetRatiosFromBase struct { + Stat ApiQueryBase `json:"stat"` + Response struct { + Data []map[string]bigint `json:"data"` + } `json:"response"` +} + +func ApiGetRatiosFromBase() string { + qb := ApiQueryBase{ + Height: GetHeight(), + Timestamp: GetTimestamp(), + } + + ratios := getRatiosFromBase() + r := ResponseGetRatiosFromBase{ + Stat: qb, + Response: struct { + Data []map[string]bigint `json:"data"` + }{ + Data: ratios, + }, + } + + rr, err := json.Marshal(r) + if err != nil { + panic(ufmt.Sprintf("[ROUTER] getter_api.gno()__ApiGetRatioFromBase || json.Marshal error with %v", err)) + } + + return string(rr) +} + +func getRatiosFromBase() []map[string]bigint { + tokenPrice := make(map[string]bigint, 0) + + // BASE + tokenPrice[BASE_TOKEN] = Q96 // ~= 1 + + // ELSE + tokenList := getTokenList() + for _, token := range tokenList { + if token != BASE_TOKEN { + swapPaths := findSwapPaths(token, BASE_TOKEN) + numSwapPaths := len(swapPaths) + + thisTokenPriceX96 := bigint(0) + if numSwapPaths < 1 { + tokenPrice[token] = 0 + } else { + for _, swapPath := range swapPaths { + numPools := strings.Count(swapPath, ",") / 2 + + switch numPools { + case 0: + thisTokenPriceX96 = 0 + case 1: + priceRatio := calculateSinglePoolPrice(token, swapPath) + thisTokenPriceX96 += priceRatio + case 2: + priceRatio := calculateTwoPoolPrice(token, swapPath, numPools) + thisTokenPriceX96 += priceRatio + case 3: + priceRatio := calculateThreePoolPrice(token, swapPath, numPools) + thisTokenPriceX96 += priceRatio + default: + thisTokenPriceX96 = 0 + } + } + avgPriceX96 := thisTokenPriceX96 / bigint(numSwapPaths) + tokenPrice[token] = avgPriceX96 + } + } + // TOKEN ENDS + } + + var tokenPrices []map[string]bigint + for token, price := range tokenPrice { + tokenPrices = append(tokenPrices, map[string]bigint{token: price}) + // DEBUG + // println("token:", token) + // println("price:", price) + // println() + } + + return tokenPrices +} + +func getTokenList() []string { + tokenList := []string{} + poolList := p.GetPoolList() + + for _, poolPath := range poolList { + token0Path, token1Path, fee := poolPathWithFeeDivide(poolPath) + tokenList = append(tokenList, token0Path) + tokenList = append(tokenList, token1Path) + } + + uniqueTokenList := removeDuplicates(tokenList) + return uniqueTokenList +} + +func removeDuplicates(input []string) []string { + seen := make(map[string]struct{}) + result := []string{} + + for _, item := range input { + if _, ok := seen[item]; !ok { + seen[item] = struct{}{} + result = append(result, item) + } + } + + return result +} + +func makePoolPath(poolPath string, poolIndex int) string { + poolDatas := strings.Split(poolPath, ",") + + switch poolIndex { + case 0: + token0Path := poolDatas[0] + token1Path := poolDatas[2] + fee := poolDatas[1] + if token0Path < token1Path { + return token0Path + ":" + token1Path + ":" + fee + } else { + return token1Path + ":" + token0Path + ":" + fee + } + case 1: + token0Path := poolDatas[2] + token1Path := poolDatas[4] + fee := poolDatas[3] + if token0Path < token1Path { + return token0Path + ":" + token1Path + ":" + fee + } else { + return token1Path + ":" + token0Path + ":" + fee + } + case 2: + token0Path := poolDatas[4] + token1Path := poolDatas[6] + fee := poolDatas[5] + if token0Path < token1Path { + return token0Path + ":" + token1Path + ":" + fee + } else { + return token1Path + ":" + token0Path + ":" + fee + } + default: + panic(ufmt.Sprintf("[ROUTER] getter_api.gno__makePoolPath() || unknown pool index: %d", poolIndex)) + } +} + +func calculateSinglePoolPrice(token, swapPath string) bigint { + poolPathKey := makePoolPath(swapPath, 0) + pool := p.GetPoolFromPoolKey(poolPathKey) + + token0 := pool.GetToken0Path() + token1 := pool.GetToken1Path() + sqrtPriceX96 := pool.GetSlotSqrtPriceX96() + + if token0 == token && BASE_TOKEN == token1 { // token0 is token + ratio := Q96 * Q96 / (sqrtPriceX96 * sqrtPriceX96 / Q96) + return ratio + } else if token1 == token && BASE_TOKEN == token0 { // token1 is token + return sqrtPriceX96 * sqrtPriceX96 / Q96 + } else { + panic("[ROUTER] getter_api.gno__calculateSinglePoolPrice() || wrong condition") + } +} + +func calculateTwoPoolPrice(token, swapPath string, numPools int) bigint { + // first + firstPoolPathKey := makePoolPath(swapPath, 0) + firstPool := p.GetPoolFromPoolKey(firstPoolPathKey) + + firstToken0 := firstPool.GetToken0Path() + firstToken1 := firstPool.GetToken1Path() + firstSqrtPriceX96 := firstPool.GetSlotSqrtPriceX96() + + firstPrice := bigint(0) + useNext := "" + if firstToken0 == token { + firstPrice = Q96 * Q96 / (firstSqrtPriceX96 * firstSqrtPriceX96 / Q96) + useNext = firstToken1 + } else if firstToken1 == token { + firstPrice = firstSqrtPriceX96 * firstSqrtPriceX96 / Q96 + useNext = firstToken0 + } else { + panic("[ROUTER] getter_api.gno__calculateTwoPoolPrice() || wrong condition #1") + } + + // second && last + secondPoolPathKey := makePoolPath(swapPath, 1) + secondPool := p.GetPoolFromPoolKey(secondPoolPathKey) + + secondToken0 := secondPool.GetToken0Path() + secondToken1 := secondPool.GetToken1Path() + secondSqrtPriceX96 := secondPool.GetSlotSqrtPriceX96() + + secondPrice := bigint(0) + if secondToken0 == useNext && BASE_TOKEN == secondToken1 { + secondPrice = Q96 * Q96 / (secondSqrtPriceX96 * secondSqrtPriceX96 / Q96) + } else if secondToken1 == useNext && BASE_TOKEN == secondToken0 { + secondPrice = secondSqrtPriceX96 * secondSqrtPriceX96 / Q96 + } else { + panic("[ROUTER] getter_api.gno__calculateTwoPoolPrice() || wrong condition #2") + } + + twoNestedPrice := firstPrice * secondPrice + return twoNestedPrice / Q96 +} + +func calculateThreePoolPrice(token, swapPath string, numPools int) bigint { + // first + firstPoolPathKey := makePoolPath(swapPath, 0) + firstPool := p.GetPoolFromPoolKey(firstPoolPathKey) + + firstToken0 := firstPool.GetToken0Path() + firstToken1 := firstPool.GetToken1Path() + firstSqrtPriceX96 := firstPool.GetSlotSqrtPriceX96() + + firstPrice := bigint(0) + restFirst := "" + if firstToken0 == token { + firstPrice = Q96 * Q96 / (firstSqrtPriceX96 * firstSqrtPriceX96 / Q96) + restFirst = firstToken1 + } else if firstToken1 == token { + firstPrice = firstSqrtPriceX96 * firstSqrtPriceX96 / Q96 + restFirst = firstToken0 + } else { + panic("[ROUTER] getter_api.gno__calculateThreePoolPrice() || wrong condition #1") + } + + // second + secondPoolPathKey := makePoolPath(swapPath, 1) + secondPool := p.GetPoolFromPoolKey(secondPoolPathKey) + + secondToken0 := secondPool.GetToken0Path() + secondToken1 := secondPool.GetToken1Path() + secondSqrtPriceX96 := secondPool.GetSlotSqrtPriceX96() + + secondPrice := bigint(0) + restSecond := "" + if secondToken0 == restFirst { + secondPrice = Q96 * Q96 / (secondSqrtPriceX96 * secondSqrtPriceX96 / Q96) + restSecond = secondToken1 + } else if secondToken1 == restFirst { + secondPrice = secondSqrtPriceX96 * secondSqrtPriceX96 / Q96 + restSecond = secondToken0 + } else { + panic("[ROUTER] getter_api.gno__calculateThreePoolPrice() || wrong condition #2") + } + + // third && last + thirdPoolPathKey := makePoolPath(swapPath, 2) + thirdPool := p.GetPoolFromPoolKey(thirdPoolPathKey) + + thirdToken0 := thirdPool.GetToken0Path() + thirdToken1 := thirdPool.GetToken1Path() + thirdSqrtPriceX96 := thirdPool.GetSlotSqrtPriceX96() + + thirdPrice := bigint(0) + + if thirdToken0 == restSecond && BASE_TOKEN == thirdToken1 { + thirdPrice = Q96 * Q96 / (thirdSqrtPriceX96 * thirdSqrtPriceX96 / Q96) + } else if thirdToken1 == restSecond && BASE_TOKEN == thirdToken0 { + thirdPrice = thirdSqrtPriceX96 * thirdSqrtPriceX96 / Q96 + } else { + panic("[ROUTER] getter_api.gno__calculateThreePoolPrice() || wrong condition #3") + } + + threeNestedPrice := firstPrice * secondPrice * thirdPrice + return threeNestedPrice / Q96 / Q96 +} diff --git a/router/gno_helper.gno b/router/gno_helper.gno new file mode 100644 index 00000000..e3f24bde --- /dev/null +++ b/router/gno_helper.gno @@ -0,0 +1,14 @@ +package router + +import ( + "std" + "time" +) + +func GetHeight() int64 { + return std.GetHeight() +} + +func GetTimestamp() int64 { + return time.Now().Unix() +} diff --git a/router/quotation.gno b/router/quotation.gno index f11b2bd8..09309efc 100644 --- a/router/quotation.gno +++ b/router/quotation.gno @@ -33,7 +33,7 @@ func quoteForAllPath( // DrySwap to calculate for i, quoterTarget := range quoterTargets { numPools := strings.Count(quoterTarget.targetPath, ",") / 2 - require(numPools > 1 && numPools < 3, ufmt.Sprintf("[ROUTER] router.gno__quoteForAllPath() || numPools should 1 ~ 3, but found %d", numPools)) + require(numPools >= 1 && numPools <= 3, ufmt.Sprintf("[ROUTER] quotation.gno__quoteForAllPath() || numPools should 1 ~ 3, but found %d", numPools)) if numPools == 1 { input, output, fee := getSwapData(quoterTarget.targetPath, 0) diff --git a/router/router.gno b/router/router.gno index 3a3cbb4e..185c24dc 100644 --- a/router/router.gno +++ b/router/router.gno @@ -10,10 +10,19 @@ import ( func BestSwap( inputToken string, outputToken string, - + swapType string, amountSpecified bigint, sqrtPriceLimitX96 bigint, ) { + switch swapType { + case "EXACT_IN": + amountSpecified = amountSpecified + case "EXACT_OUT": + amountSpecified = -amountSpecified + default: + panic("UNKNOWN TYPE") + } + // get quotes quotes := quoteForAllPath( // sorted by ratio DESC inputToken, @@ -30,9 +39,10 @@ func BestSwap( require(len(finalSwaps) != 0, "[ROUTER] router.gno__BestSwap() || len(finalSwaps) == 0") remainingAmount := amountSpecified + for i, finalSwap := range finalSwaps { numPools := strings.Count(finalSwap.targetPath, ",") / 2 - require(numPools > 1 && numPools < 3, ufmt.Sprintf("[ROUTER] router.gno__BestSwap() || numPools should 1 ~ 3, but found %d", numPools)) + require(numPools >= 1 && numPools < 3, ufmt.Sprintf("[ROUTER] router.gno__BestSwap() || numPools should 1 ~ 3, but found %d", numPools)) // SINGLE if numPools == 1 { @@ -58,7 +68,7 @@ func BestSwap( } // MULTI - if numPools > 1 && numPools <= 3 { + if numPools >= 2 && numPools <= 3 { toSwap := bigint(0) // isFinal diff --git a/router/rpc_call.gno b/router/rpc_call.gno index 78d1283c..abfa955f 100644 --- a/router/rpc_call.gno +++ b/router/rpc_call.gno @@ -2,6 +2,7 @@ package router import ( "encoding/json" + "std" "strings" "gno.land/p/demo/ufmt" @@ -10,16 +11,26 @@ import ( func BestSwapDry( inputToken string, outputToken string, - + swapType string, amountSpecified bigint, sqrtPriceLimitX96 bigint, ) string { + + switch swapType { + case "EXACT_IN": + amountSpecified = amountSpecified + case "EXACT_OUT": + amountSpecified = -amountSpecified + default: + panic("UNKNOWN TYPE") + } + // get quotes quotes := quoteForAllPath( // sored by Ratio DESC - inputToken, // inputToken - outputToken, // outputToken - amountSpecified, // amountIn - sqrtPriceLimitX96, // sqrtPriceLimitX96 + inputToken, + outputToken, + amountSpecified, + sqrtPriceLimitX96, ) if len(quotes) == 0 { panic("rpc_call.gno__THERE IS NO QUOTE") @@ -36,17 +47,20 @@ func BestSwapDry( } rpcReturnBestSwap := RpcReturnBestSwap{} + rpcReturnBestSwap.SwapType = swapType rpcReturnBestSwap.TotalRoutes = len(finalSwaps) rpcRoutes := []RpcRoute{} + remainingAmount := amountSpecified + outputEstimated := bigint(0) for i, finalSwap := range finalSwaps { - numSwaps := strings.Count(finalSwap.targetPath, ",") / 2 + numPools := strings.Count(finalSwap.targetPath, ",") / 2 splitPaths := multiTargetPathToList(finalSwap.targetPath) rpcRoute := RpcRoute{ SwapPct: finalSwap.pct, - NumSwaps: numSwaps, + NumPools: numPools, SwapPaths: splitPaths, } rpcRoutes = append(rpcRoutes, rpcRoute) @@ -56,12 +70,72 @@ func BestSwapDry( if rpcReturnBestSwap.TotalPct == 100 { rpcReturnBestSwap.Possible = true } + + // ESTIMATE Amount + // SINGLE + if numPools == 1 { + toSwap := bigint(0) + + // isFinal + if i == len(finalSwaps)-1 { // last swap routes + toSwap = remainingAmount + } else { + remainingAmount -= finalSwap.pctAmount + toSwap = finalSwap.pctAmount + } + + input, output, fee := getSwapData(finalSwap.targetPath, 0) + singleParams := SingleSwapParams{ + tokenIn: input, + tokenOut: output, + fee: fee, + amountSpecified: toSwap, + sqrtPriceLimitX96: sqrtPriceLimitX96, + } + amountOut := singleSwap(singleParams) + outputEstimated += amountOut + } + + // MULTI + if numPools > 1 && numPools <= 3 { + toSwap := bigint(0) + + // isFinal + if i == len(finalSwaps)-1 { // last swap routes + toSwap = remainingAmount + } else { + remainingAmount -= finalSwap.pctAmount + toSwap = finalSwap.pctAmount + } + + input, output, fee := getSwapData(finalSwap.targetPath, 0) + swapParams := SwapParams{ + tokenIn: input, + tokenOut: output, + fee: fee, + recipient: std.GetOrigCaller(), + amountSpecified: toSwap, + minAmountOut: 1, + } + amountOut := multiSwap(swapParams, 0, numPools, finalSwap.targetPath) // iterate here + outputEstimated += amountOut + } } rpcReturnBestSwap.RpcRoutes = rpcRoutes + if rpcReturnBestSwap.Possible == true { + rpcReturnBestSwap.AmountResult = outputEstimated + + if rpcReturnBestSwap.SwapType == "EXACT_IN" { + rpcReturnBestSwap.AmountSpecified = amountSpecified + } else { + rpcReturnBestSwap.AmountSpecified = -amountSpecified + } + } + rr, err := json.Marshal(rpcReturnBestSwap) if err != nil { - panic(ufmt.Sprintf("[ROUTER] rpc_call.gno__BestSwapDry() || %v", err)) + panic(ufmt.Sprintf("[ROUTER] rpc_call.gno__BestSwapDry() || json.Marshal error with %v", err)) } return string(rr) @@ -69,25 +143,26 @@ func BestSwapDry( type RpcRoute struct { SwapPct int `json:"swapPct"` - NumSwaps int `json:"numSwaps"` + NumPools int `json:"numPools"` SwapPaths []string `json:"swapPaths"` } type RpcReturnBestSwap struct { - TotalRoutes int `json:"totalRoutes"` - TotalPct int `json:"totalPct"` - Possible bool `json:"possible"` - AmountIn bigint `json:"amountIn"` - AmountOut bigint `json:"amountOut"` - RpcRoutes []RpcRoute `json:"rpcRoutes"` + TotalRoutes int `json:"totalRoutes"` + TotalPct int `json:"totalPct"` + Possible bool `json:"possible"` + SwapType string `json:"swapType"` + AmountSpecified bigint `json:"amountSpecified"` + AmountResult bigint `json:"amountResult"` + RpcRoutes []RpcRoute `json:"rpcRoutes"` } func multiTargetPathToList(path string) []string { - numSwaps := strings.Count(path, ",") / 2 + numPools := strings.Count(path, ",") / 2 splitData := strings.Split(path, ",") fullPaths := []string{} - for i := 0; i < numSwaps; i++ { + for i := 0; i < numPools; i++ { j := 0 if i == 0 { j = i From 6e8b46adf974c8f74b2ea778cf0ae0d016622aa1 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Mon, 6 Nov 2023 20:11:21 +0900 Subject: [PATCH 28/35] refactor: router contract handles pool router --- pool/pool_router.gno | 135 ------------------------------------------- 1 file changed, 135 deletions(-) delete mode 100644 pool/pool_router.gno diff --git a/pool/pool_router.gno b/pool/pool_router.gno deleted file mode 100644 index 6f755060..00000000 --- a/pool/pool_router.gno +++ /dev/null @@ -1,135 +0,0 @@ -package pool - -import ( - "strconv" - "strings" - - "gno.land/p/demo/ufmt" -) - -type POSSIBLE_PATHS map[int][]string - -func FindAllPoolPath( - inputTokenPath string, - outputTokenPath string, - maxDepths int, -) POSSIBLE_PATHS { - - tokenPairs := make(map[string][]string) - - for poolPath, _ := range pools { - token0Path, token1Path, pFee := poolPathWithFeeDivide(poolPath) - - k := token0Path - v := token1Path + ":" + strconv.Itoa(int(pFee)) - - tokenPairs[k] = append(tokenPairs[k], v) - } - - // r3v4_xx: pass max_depth - possiblePaths := getSwapPaths(tokenPairs, inputTokenPath, outputTokenPath) - - return possiblePaths -} - -func getSwapPaths( - tokenPairs map[string][]string, - inputTokenPath string, - outputTokenPath string, -) POSSIBLE_PATHS { - // r3v4_xx: handle max_depth - - possiblePaths := make(POSSIBLE_PATHS, 0) - - for firstPath, secondPaths := range tokenPairs { - if firstPath == inputTokenPath { // find if direct path exists - for _, secondPath := range secondPaths { - if strings.HasPrefix(secondPath, outputTokenPath) { - fullPath := inputTokenPath + " > " + secondPath - possiblePaths[len(possiblePaths)] = []string{fullPath} - } - } - } else { // no direct path - for _, secondPathWithFee := range secondPaths { - secondPath, sFee := singlePoolPathWithFeeDivide(secondPathWithFee) - - if secondPath == inputTokenPath { - // l2 go - for i, thirdPathWithFee := range tokenPairs[secondPath] { - thirdPath, tFee := singlePoolPathWithFeeDivide(thirdPathWithFee) - - if strings.HasPrefix(thirdPathWithFee, outputTokenPath) { - fullPath := inputTokenPath + " > " + firstPath + ":" + strconv.Itoa(int(sFee)) + " > " + thirdPathWithFee - possiblePaths[len(possiblePaths)] = []string{fullPath} - } - } - } else { - // l3 go - for i, secondPathWithFee := range tokenPairs[firstPath] { - secondPath, sFee := singlePoolPathWithFeeDivide(secondPathWithFee) - - if strings.HasPrefix(secondPathWithFee, inputTokenPath) { - partialPath := "" - partialPath += inputTokenPath + " > " + firstPath + ":" + strconv.Itoa(int(sFee)) - // println("F PARTIAL:", partialPath) - - for _, thirdPathWithFee := range tokenPairs[firstPath] { - thirdPath, tFee := singlePoolPathWithFeeDivide(thirdPathWithFee) - - if thirdPath != inputTokenPath && thirdPath != outputTokenPath { - partialPath += " > " + thirdPathWithFee - - for i, fourthPathWithFee := range tokenPairs[thirdPath] { - if strings.HasPrefix(fourthPathWithFee, outputTokenPath) { - fourthPath, _ := singlePoolPathWithFeeDivide(fourthPathWithFee) - - // THIS IS FULL PATH - partialPath += " > " + fourthPathWithFee - possiblePaths[len(possiblePaths)] = []string{partialPath} - - firstToSecond := tokenPairs[firstPath] - toDel := secondPath + ":" + strconv.Itoa(int(sFee)) - remove(firstToSecond, toDel) - } - } - } - } - } - } - } - } - } - } - - return possiblePaths -} - -func poolPathWithFeeDivide(poolPath string) (string, string, uint16) { - poolPathSplit := strings.Split(poolPath, ":") - - if len(poolPathSplit) != 3 { - panic(ufmt.Sprintf("[POOL] pool_router.gno__poolPathWithFeeDivide() || len(poolPathSplit) != 3, poolPath: %s", poolPath)) - } - - feeInt, err := strconv.Atoi(poolPathSplit[2]) - if err != nil { - panic(ufmt.Sprintf("[POOL] pool_router.gno__poolPathWithFeeDivide() || cannot convert fee(%s) to uint16", poolPathSplit[2])) - } - - return poolPathSplit[0], poolPathSplit[1], uint16(feeInt) -} - -func singlePoolPathWithFeeDivide(poolPath string) (string, uint16) { - singlePoolPathSplit := strings.Split(poolPath, ":") - - if len(singlePoolPathSplit) != 2 { - panic(ufmt.Sprintf("[POOL] pool_router.gno__singlePoolPathWithFeeDivide || len(singlePoolPathSplit) != 2, poolPath: %s", poolPath)) - } - - feeInt, err := strconv.Atoi(singlePoolPathSplit[1]) - if err != nil { - panic(ufmt.Sprintf("[POOL] pool_router.gno__singlePoolPathWithFeeDivide() || cannot convert fee(%s) to uint16", singlePoolPathSplit[1])) - } - - return singlePoolPathSplit[0], uint16(feeInt) -} From f802161315c4a3d0400f4aa1108df7dae2e97850 Mon Sep 17 00:00:00 2001 From: Blake <104744707+r3v4s@users.noreply.github.com> Date: Tue, 7 Nov 2023 09:55:37 +0900 Subject: [PATCH 29/35] Update router/quotation.gno Co-authored-by: Lee ByeongJun --- router/quotation.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/quotation.gno b/router/quotation.gno index 09309efc..f3b59101 100644 --- a/router/quotation.gno +++ b/router/quotation.gno @@ -80,7 +80,7 @@ func quoteForAllPath( if amountSpecified > 0 { estimatedResult := multiSwapDry(swapParams, 0, numPools, quoterTarget.targetPath) // will iterate here to cover multi pools if estimatedResult > 0 { - resultRatioX96 := estimatedResult * Q96 / quoterTarget.pctAmount * Q96 / Q96 + resultRatioX96 := estimatedResult * Q96 / quoterTarget.pctAmount quoterTarget.resultRatioX96 = resultRatioX96 quoterTargets[i] = quoterTarget } else { From cf7f56fb57df3af0588406af4ba91a8a16aaa913 Mon Sep 17 00:00:00 2001 From: Blake <104744707+r3v4s@users.noreply.github.com> Date: Tue, 7 Nov 2023 09:55:44 +0900 Subject: [PATCH 30/35] Update router/getter_api.gno Co-authored-by: Lee ByeongJun --- router/getter_api.gno | 13 ------------- 1 file changed, 13 deletions(-) diff --git a/router/getter_api.gno b/router/getter_api.gno index bfd21609..7de1a264 100644 --- a/router/getter_api.gno +++ b/router/getter_api.gno @@ -117,19 +117,6 @@ func getTokenList() []string { return uniqueTokenList } -func removeDuplicates(input []string) []string { - seen := make(map[string]struct{}) - result := []string{} - - for _, item := range input { - if _, ok := seen[item]; !ok { - seen[item] = struct{}{} - result = append(result, item) - } - } - - return result -} func makePoolPath(poolPath string, poolIndex int) string { poolDatas := strings.Split(poolPath, ",") From d318425f21a5cd7388480285dd68d8c91a9951a2 Mon Sep 17 00:00:00 2001 From: Blake <104744707+r3v4s@users.noreply.github.com> Date: Tue, 7 Nov 2023 09:56:18 +0900 Subject: [PATCH 31/35] Update router/getter_api.gno Co-authored-by: Lee ByeongJun --- router/getter_api.gno | 28 +++++++++++++++++----------- 1 file changed, 17 insertions(+), 11 deletions(-) diff --git a/router/getter_api.gno b/router/getter_api.gno index 7de1a264..f03ec600 100644 --- a/router/getter_api.gno +++ b/router/getter_api.gno @@ -104,17 +104,23 @@ func getRatiosFromBase() []map[string]bigint { } func getTokenList() []string { - tokenList := []string{} - poolList := p.GetPoolList() - - for _, poolPath := range poolList { - token0Path, token1Path, fee := poolPathWithFeeDivide(poolPath) - tokenList = append(tokenList, token0Path) - tokenList = append(tokenList, token1Path) - } - - uniqueTokenList := removeDuplicates(tokenList) - return uniqueTokenList + seen := make(map[string]bool) + uniqueTokenList := []string{} + poolList := p.GetPoolList() + + for _, poolPath := range poolList { + token0Path, token1Path, _ := poolPathWithFeeDivide(poolPath) + if _, exists := seen[token0Path]; !exists { + seen[token0Path] = true + uniqueTokenList = append(uniqueTokenList, token0Path) + } + if _, exists := seen[token1Path]; !exists { + seen[token1Path] = true + uniqueTokenList = append(uniqueTokenList, token1Path) + } + } + + return uniqueTokenList } From 8ce5675a670db88907fce6152cee5eea3c1999e0 Mon Sep 17 00:00:00 2001 From: Blake <104744707+r3v4s@users.noreply.github.com> Date: Tue, 7 Nov 2023 09:59:54 +0900 Subject: [PATCH 32/35] Update router/getter_api.gno Co-authored-by: Lee ByeongJun --- router/getter_api.gno | 50 +++++++++++++++---------------------------- 1 file changed, 17 insertions(+), 33 deletions(-) diff --git a/router/getter_api.gno b/router/getter_api.gno index f03ec600..3e7cdd79 100644 --- a/router/getter_api.gno +++ b/router/getter_api.gno @@ -125,39 +125,23 @@ func getTokenList() []string { func makePoolPath(poolPath string, poolIndex int) string { - poolDatas := strings.Split(poolPath, ",") - - switch poolIndex { - case 0: - token0Path := poolDatas[0] - token1Path := poolDatas[2] - fee := poolDatas[1] - if token0Path < token1Path { - return token0Path + ":" + token1Path + ":" + fee - } else { - return token1Path + ":" + token0Path + ":" + fee - } - case 1: - token0Path := poolDatas[2] - token1Path := poolDatas[4] - fee := poolDatas[3] - if token0Path < token1Path { - return token0Path + ":" + token1Path + ":" + fee - } else { - return token1Path + ":" + token0Path + ":" + fee - } - case 2: - token0Path := poolDatas[4] - token1Path := poolDatas[6] - fee := poolDatas[5] - if token0Path < token1Path { - return token0Path + ":" + token1Path + ":" + fee - } else { - return token1Path + ":" + token0Path + ":" + fee - } - default: - panic(ufmt.Sprintf("[ROUTER] getter_api.gno__makePoolPath() || unknown pool index: %d", poolIndex)) - } + poolDatas := strings.Split(poolPath, ",") + // Calculate the indices for token paths and fee based on poolIndex. + baseIndex := poolIndex * 3 + if baseIndex+2 >= len(poolDatas) { + panic(ufmt.Sprintf("[ROUTER] getter_api.gno__makePoolPath() || index out of range for pool index: %d", poolIndex)) + } + + token0Path := poolDatas[baseIndex] + token1Path := poolDatas[baseIndex+2] + fee := poolDatas[baseIndex+1] + + // Ensure the tokens are in a consistent order. + if token0Path > token1Path { + token0Path, token1Path = token1Path, token0Path + } + + return token0Path + ":" + token1Path + ":" + fee } func calculateSinglePoolPrice(token, swapPath string) bigint { From 4fbe150d16d3deb5703069d679a02ce71612b7eb Mon Sep 17 00:00:00 2001 From: Blake <104744707+r3v4s@users.noreply.github.com> Date: Tue, 7 Nov 2023 10:09:26 +0900 Subject: [PATCH 33/35] Update router/find_path.gno Co-authored-by: Lee ByeongJun --- router/find_path.gno | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/router/find_path.gno b/router/find_path.gno index 5427b489..1d3c0fd0 100644 --- a/router/find_path.gno +++ b/router/find_path.gno @@ -53,7 +53,7 @@ func getSwapPaths( if strings.HasPrefix(output, outputTokenPath) { outputPath, outputFee := singlePoolPathWithFeeDivide(output) directPath := inputTokenPath + "," + outputFee + "," + outputPath - swapPaths[len(swapPaths)] = directPath + swapPaths = append(swapPaths, directPath) tokenPairs[inputTokenPath] = removeItemFromStringArray(tokenPairs[inputTokenPath], output) } From 4775243e1e660a8e50f798a8b65e0e3c49856647 Mon Sep 17 00:00:00 2001 From: n3wbie Date: Tue, 7 Nov 2023 12:54:44 +0900 Subject: [PATCH 34/35] test: more detail getter_api test condition --- router/_TEST_router_getter_api_test.gno | 3 +++ router/_TEST_router_multi_path_swap_test.gnoa | 16 ++++++++-------- router/_TEST_router_single_path_dry_test.gnoa | 16 ++++++++-------- router/_TEST_router_single_path_swap_test.gnoa | 16 ++++++++-------- router/rpc_call.gno | 12 +++--------- 5 files changed, 30 insertions(+), 33 deletions(-) diff --git a/router/_TEST_router_getter_api_test.gno b/router/_TEST_router_getter_api_test.gno index 933bff14..eee4e65c 100644 --- a/router/_TEST_router_getter_api_test.gno +++ b/router/_TEST_router_getter_api_test.gno @@ -55,7 +55,10 @@ func TestApiGetRatiosFromBase(t *testing.T) { jsonOutput := gjson.Parse(jsonStr) shouldEQ(t, len(jsonOutput.Get("response.data").Array()), 4) + shouldEQ(t, jsonOutput.Get("response.data.0").String(), "{\"gno.land/r/foo\":79228162514264337593543950336}") shouldEQ(t, jsonOutput.Get("response.data.1").String(), "{\"gno.land/r/bar\":29147869410676662479573841822}") + shouldEQ(t, jsonOutput.Get("response.data.2").String(), "{\"gno.land/r/baz\":79228162514264337593543950333}") + shouldEQ(t, jsonOutput.Get("response.data.3").String(), "{\"gno.land/r/qux\":215353707227994575755767921538}") /* len(tokenPrice): 4 diff --git a/router/_TEST_router_multi_path_swap_test.gnoa b/router/_TEST_router_multi_path_swap_test.gnoa index 875d69b7..0b486619 100644 --- a/router/_TEST_router_multi_path_swap_test.gnoa +++ b/router/_TEST_router_multi_path_swap_test.gnoa @@ -39,14 +39,14 @@ var ( max_timeout = bigint(9999999999) ) -func init() { - println(gsa, "// gsa") - println(lp01, "// lp01") - println(tr01, "// tr01") - println(poolAddr, "// poolAddr") - println(posAddr, "// posAddr") - println(routerAddr, "// routerAddr") -} +// func init() { +// println(gsa, "// gsa") +// println(lp01, "// lp01") +// println(tr01, "// tr01") +// println(poolAddr, "// poolAddr") +// println(posAddr, "// posAddr") +// println(routerAddr, "// routerAddr") +// } func TestInitManual(t *testing.T) { std.TestSetOrigCaller(gsa) diff --git a/router/_TEST_router_single_path_dry_test.gnoa b/router/_TEST_router_single_path_dry_test.gnoa index 448ac928..e4a25bee 100644 --- a/router/_TEST_router_single_path_dry_test.gnoa +++ b/router/_TEST_router_single_path_dry_test.gnoa @@ -35,14 +35,14 @@ var ( max_timeout = bigint(9999999999) ) -func init() { - println(gsa, "// gsa") - println(lp01, "// lp01") - println(tr01, "// tr01") - println(poolAddr, "// poolAddr") - println(posAddr, "// posAddr") - println(routerAddr, "// routerAddr") -} +// func init() { +// println(gsa, "// gsa") +// println(lp01, "// lp01") +// println(tr01, "// tr01") +// println(poolAddr, "// poolAddr") +// println(posAddr, "// posAddr") +// println(routerAddr, "// routerAddr") +// } func TestInitManual(t *testing.T) { std.TestSetOrigCaller(gsa) diff --git a/router/_TEST_router_single_path_swap_test.gnoa b/router/_TEST_router_single_path_swap_test.gnoa index 743ea8ce..1510809a 100644 --- a/router/_TEST_router_single_path_swap_test.gnoa +++ b/router/_TEST_router_single_path_swap_test.gnoa @@ -38,14 +38,14 @@ var ( max_timeout = bigint(9999999999) ) -func init() { - println(gsa, "// gsa") - println(lp01, "// lp01") - println(tr01, "// tr01") - println(poolAddr, "// poolAddr") - println(posAddr, "// posAddr") - println(routerAddr, "// routerAddr") -} +// func init() { +// println(gsa, "// gsa") +// println(lp01, "// lp01") +// println(tr01, "// tr01") +// println(poolAddr, "// poolAddr") +// println(posAddr, "// posAddr") +// println(routerAddr, "// routerAddr") +// } func TestInitManual(t *testing.T) { std.TestSetOrigCaller(gsa) diff --git a/router/rpc_call.gno b/router/rpc_call.gno index abfa955f..47378d1e 100644 --- a/router/rpc_call.gno +++ b/router/rpc_call.gno @@ -32,19 +32,13 @@ func BestSwapDry( amountSpecified, sqrtPriceLimitX96, ) - if len(quotes) == 0 { - panic("rpc_call.gno__THERE IS NO QUOTE") - } + require(len(quotes) > 0, ufmt.Sprintf("[ROUTER] rpc_call.gno__gno__BestSwapDry() || len(quotes) == 0, inputToken:%s, outputToken:%s", inputToken, outputToken)) bestSwaps := findBestPaths(quotes) - if len(bestSwaps) == 0 { - panic("rpc_call.gno__CAN'T MAKE BestSwapRoute") - } + require(len(bestSwaps) > 0, "[ROUTER] rpc_call.gno__BestSwapDry() || len(bestSwaps) == 0") finalSwaps := removeDuplication(bestSwaps) - if len(bestSwaps) == 0 { - panic("rpc_call.gno__CAN'T MAKE FinalSwapRoute") - } + require(len(finalSwaps) > 0, "[ROUTER] rpc_call.gno__BestSwapDry() || len(finalSwaps) == 0") rpcReturnBestSwap := RpcReturnBestSwap{} rpcReturnBestSwap.SwapType = swapType From cb16f6a9b977b1bead8de7855f4f819141ee85cd Mon Sep 17 00:00:00 2001 From: n3wbie Date: Tue, 7 Nov 2023 12:55:51 +0900 Subject: [PATCH 35/35] refactor: use recursive --- router/find_path.gno | 69 ++++++--------- router/getter_api.gno | 199 ++++++++++++------------------------------ 2 files changed, 80 insertions(+), 188 deletions(-) diff --git a/router/find_path.gno b/router/find_path.gno index 1d3c0fd0..2fd7fa45 100644 --- a/router/find_path.gno +++ b/router/find_path.gno @@ -53,63 +53,46 @@ func getSwapPaths( if strings.HasPrefix(output, outputTokenPath) { outputPath, outputFee := singlePoolPathWithFeeDivide(output) directPath := inputTokenPath + "," + outputFee + "," + outputPath - swapPaths = append(swapPaths, directPath) + swapPaths[len(swapPaths)] = directPath // swapPaths = append(swapPaths, directPath) tokenPairs[inputTokenPath] = removeItemFromStringArray(tokenPairs[inputTokenPath], output) } } - // find nested path - swapPaths = findTwicePath(tokenPairs, inputTokenPath, outputTokenPath, swapPaths) - swapPaths = findThreeTimePath(tokenPairs, inputTokenPath, outputTokenPath, swapPaths) + firstToken := "" + findPath(tokenPairs, inputTokenPath, outputTokenPath, "", 2, &swapPaths, &firstToken) + findPath(tokenPairs, inputTokenPath, outputTokenPath, "", 3, &swapPaths, &firstToken) return swapPaths } -func findTwicePath( +func findPath( tokenPairs TokenPairs, - inputTokenPath string, + currentTokenPath string, outputTokenPath string, - swapPaths SwapPaths, -) SwapPaths { - for _, second := range tokenPairs[inputTokenPath] { - secondPath, secondFee := singlePoolPathWithFeeDivide(second) - - for _, third := range tokenPairs[secondPath] { - thirdPath, thirdFee := singlePoolPathWithFeeDivide(third) - - if strings.HasPrefix(third, outputTokenPath) { - nestedPath := inputTokenPath + "," + secondFee + "," + secondPath + "," + thirdFee + "," + outputTokenPath - swapPaths[len(swapPaths)] = nestedPath - } - } + currentPath string, + remainingHops int, + swapPaths *SwapPaths, + firstToken *string, +) { + if *firstToken == "" { + *firstToken = currentTokenPath } - return swapPaths -} + if remainingHops == 0 { + if strings.HasPrefix(currentTokenPath, outputTokenPath) { + swapPaths[len(*swapPaths)] = (*firstToken + "," + currentPath) + } + return + } -func findThreeTimePath( - tokenPairs TokenPairs, - inputTokenPath string, - outputTokenPath string, - swapPaths SwapPaths, -) SwapPaths { - for _, second := range tokenPairs[inputTokenPath] { - secondPath, secondFee := singlePoolPathWithFeeDivide(second) - - for _, third := range tokenPairs[secondPath] { // bar > bz - thirdPath, thirdFee := singlePoolPathWithFeeDivide(third) - - for _, fourth := range tokenPairs[thirdPath] { - fourthPath, fourthFee := singlePoolPathWithFeeDivide(fourth) - - if strings.HasPrefix(fourth, outputTokenPath) { - // three times - nestedPath := inputTokenPath + "," + secondFee + "," + secondPath + "," + thirdFee + "," + thirdPath + "," + fourthFee + "," + fourthPath - swapPaths[len(swapPaths)] = nestedPath - } - } + for _, next := range tokenPairs[currentTokenPath] { + nextPath, nextFee := singlePoolPathWithFeeDivide(next) + newPath := currentPath + if currentPath != "" { + newPath += "," } + newPath += nextFee + "," + nextPath + findPath(tokenPairs, nextPath, outputTokenPath, newPath, remainingHops-1, swapPaths, firstToken) } - return swapPaths } diff --git a/router/getter_api.gno b/router/getter_api.gno index 3e7cdd79..4c23ef67 100644 --- a/router/getter_api.gno +++ b/router/getter_api.gno @@ -62,7 +62,7 @@ func getRatiosFromBase() []map[string]bigint { numSwapPaths := len(swapPaths) thisTokenPriceX96 := bigint(0) - if numSwapPaths < 1 { + if numSwapPaths < 1 { // NO CONNECTION TO BASE tokenPrice[token] = 0 } else { for _, swapPath := range swapPaths { @@ -72,13 +72,13 @@ func getRatiosFromBase() []map[string]bigint { case 0: thisTokenPriceX96 = 0 case 1: - priceRatio := calculateSinglePoolPrice(token, swapPath) + priceRatio := calculateTokenPrice(token, swapPath, numPools, 0, 1) thisTokenPriceX96 += priceRatio case 2: - priceRatio := calculateTwoPoolPrice(token, swapPath, numPools) + priceRatio := calculateTokenPrice(token, swapPath, numPools, 0, 1) thisTokenPriceX96 += priceRatio case 3: - priceRatio := calculateThreePoolPrice(token, swapPath, numPools) + priceRatio := calculateTokenPrice(token, swapPath, numPools, 0, 1) thisTokenPriceX96 += priceRatio default: thisTokenPriceX96 = 0 @@ -104,165 +104,74 @@ func getRatiosFromBase() []map[string]bigint { } func getTokenList() []string { - seen := make(map[string]bool) - uniqueTokenList := []string{} - poolList := p.GetPoolList() - - for _, poolPath := range poolList { - token0Path, token1Path, _ := poolPathWithFeeDivide(poolPath) - if _, exists := seen[token0Path]; !exists { - seen[token0Path] = true - uniqueTokenList = append(uniqueTokenList, token0Path) - } - if _, exists := seen[token1Path]; !exists { - seen[token1Path] = true - uniqueTokenList = append(uniqueTokenList, token1Path) - } - } + seen := make(map[string]bool) + uniqueTokenList := []string{} + poolList := p.GetPoolList() + + for _, poolPath := range poolList { + token0Path, token1Path, _ := poolPathWithFeeDivide(poolPath) + if _, exists := seen[token0Path]; !exists { + seen[token0Path] = true + uniqueTokenList = append(uniqueTokenList, token0Path) + } + if _, exists := seen[token1Path]; !exists { + seen[token1Path] = true + uniqueTokenList = append(uniqueTokenList, token1Path) + } + } - return uniqueTokenList + return uniqueTokenList } - func makePoolPath(poolPath string, poolIndex int) string { - poolDatas := strings.Split(poolPath, ",") - // Calculate the indices for token paths and fee based on poolIndex. - baseIndex := poolIndex * 3 - if baseIndex+2 >= len(poolDatas) { - panic(ufmt.Sprintf("[ROUTER] getter_api.gno__makePoolPath() || index out of range for pool index: %d", poolIndex)) - } - - token0Path := poolDatas[baseIndex] - token1Path := poolDatas[baseIndex+2] - fee := poolDatas[baseIndex+1] - - // Ensure the tokens are in a consistent order. - if token0Path > token1Path { - token0Path, token1Path = token1Path, token0Path - } - - return token0Path + ":" + token1Path + ":" + fee -} - -func calculateSinglePoolPrice(token, swapPath string) bigint { - poolPathKey := makePoolPath(swapPath, 0) - pool := p.GetPoolFromPoolKey(poolPathKey) - - token0 := pool.GetToken0Path() - token1 := pool.GetToken1Path() - sqrtPriceX96 := pool.GetSlotSqrtPriceX96() - - if token0 == token && BASE_TOKEN == token1 { // token0 is token - ratio := Q96 * Q96 / (sqrtPriceX96 * sqrtPriceX96 / Q96) - return ratio - } else if token1 == token && BASE_TOKEN == token0 { // token1 is token - return sqrtPriceX96 * sqrtPriceX96 / Q96 - } else { - panic("[ROUTER] getter_api.gno__calculateSinglePoolPrice() || wrong condition") + poolDatas := strings.Split(poolPath, ",") + // Calculate the indices for token paths and fee based on poolIndex. + baseIndex := poolIndex * 2 + if baseIndex+2 >= len(poolDatas) { + panic(ufmt.Sprintf("[ROUTER] getter_api.gno__makePoolPath() || index out of range for pool index: %d", poolIndex)) } -} - -func calculateTwoPoolPrice(token, swapPath string, numPools int) bigint { - // first - firstPoolPathKey := makePoolPath(swapPath, 0) - firstPool := p.GetPoolFromPoolKey(firstPoolPathKey) - firstToken0 := firstPool.GetToken0Path() - firstToken1 := firstPool.GetToken1Path() - firstSqrtPriceX96 := firstPool.GetSlotSqrtPriceX96() + token0Path := poolDatas[baseIndex] + token1Path := poolDatas[baseIndex+2] + fee := poolDatas[baseIndex+1] - firstPrice := bigint(0) - useNext := "" - if firstToken0 == token { - firstPrice = Q96 * Q96 / (firstSqrtPriceX96 * firstSqrtPriceX96 / Q96) - useNext = firstToken1 - } else if firstToken1 == token { - firstPrice = firstSqrtPriceX96 * firstSqrtPriceX96 / Q96 - useNext = firstToken0 - } else { - panic("[ROUTER] getter_api.gno__calculateTwoPoolPrice() || wrong condition #1") + // Ensure the tokens are in a consistent order. + if token0Path > token1Path { + token0Path, token1Path = token1Path, token0Path } - // second && last - secondPoolPathKey := makePoolPath(swapPath, 1) - secondPool := p.GetPoolFromPoolKey(secondPoolPathKey) - - secondToken0 := secondPool.GetToken0Path() - secondToken1 := secondPool.GetToken1Path() - secondSqrtPriceX96 := secondPool.GetSlotSqrtPriceX96() - - secondPrice := bigint(0) - if secondToken0 == useNext && BASE_TOKEN == secondToken1 { - secondPrice = Q96 * Q96 / (secondSqrtPriceX96 * secondSqrtPriceX96 / Q96) - } else if secondToken1 == useNext && BASE_TOKEN == secondToken0 { - secondPrice = secondSqrtPriceX96 * secondSqrtPriceX96 / Q96 - } else { - panic("[ROUTER] getter_api.gno__calculateTwoPoolPrice() || wrong condition #2") - } - - twoNestedPrice := firstPrice * secondPrice - return twoNestedPrice / Q96 + return token0Path + ":" + token1Path + ":" + fee } -func calculateThreePoolPrice(token, swapPath string, numPools int) bigint { - // first - firstPoolPathKey := makePoolPath(swapPath, 0) - firstPool := p.GetPoolFromPoolKey(firstPoolPathKey) +func calculateTokenPrice(token, swapPath string, numPools, proceed int, currentPrice bigint) bigint { + currentPoolPathKey := makePoolPath(swapPath, proceed) + currentPool := p.GetPoolFromPoolKey(currentPoolPathKey) - firstToken0 := firstPool.GetToken0Path() - firstToken1 := firstPool.GetToken1Path() - firstSqrtPriceX96 := firstPool.GetSlotSqrtPriceX96() + currentToken0 := currentPool.GetToken0Path() + currentToken1 := currentPool.GetToken1Path() + currentSqrtPriceX96 := currentPool.GetSlotSqrtPriceX96() - firstPrice := bigint(0) - restFirst := "" - if firstToken0 == token { - firstPrice = Q96 * Q96 / (firstSqrtPriceX96 * firstSqrtPriceX96 / Q96) - restFirst = firstToken1 - } else if firstToken1 == token { - firstPrice = firstSqrtPriceX96 * firstSqrtPriceX96 / Q96 - restFirst = firstToken0 + if currentToken0 == token { + currentPrice *= (Q96 * Q96 / (currentSqrtPriceX96 * currentSqrtPriceX96 / Q96)) + token = currentToken1 + } else if currentToken1 == token { + currentPrice *= (currentSqrtPriceX96 * currentSqrtPriceX96 / Q96) + token = currentToken0 } else { - panic("[ROUTER] getter_api.gno__calculateThreePoolPrice() || wrong condition #1") + panic("[ROUTER] getter_api.gno__calculateTokenPrice() || wrong condition") } - // second - secondPoolPathKey := makePoolPath(swapPath, 1) - secondPool := p.GetPoolFromPoolKey(secondPoolPathKey) - - secondToken0 := secondPool.GetToken0Path() - secondToken1 := secondPool.GetToken1Path() - secondSqrtPriceX96 := secondPool.GetSlotSqrtPriceX96() - - secondPrice := bigint(0) - restSecond := "" - if secondToken0 == restFirst { - secondPrice = Q96 * Q96 / (secondSqrtPriceX96 * secondSqrtPriceX96 / Q96) - restSecond = secondToken1 - } else if secondToken1 == restFirst { - secondPrice = secondSqrtPriceX96 * secondSqrtPriceX96 / Q96 - restSecond = secondToken0 - } else { - panic("[ROUTER] getter_api.gno__calculateThreePoolPrice() || wrong condition #2") + if proceed == numPools-1 { + return currentPrice / sqrt(Q96, proceed) } - // third && last - thirdPoolPathKey := makePoolPath(swapPath, 2) - thirdPool := p.GetPoolFromPoolKey(thirdPoolPathKey) - - thirdToken0 := thirdPool.GetToken0Path() - thirdToken1 := thirdPool.GetToken1Path() - thirdSqrtPriceX96 := thirdPool.GetSlotSqrtPriceX96() - - thirdPrice := bigint(0) + return calculateTokenPrice(token, swapPath, numPools, proceed+1, currentPrice) +} - if thirdToken0 == restSecond && BASE_TOKEN == thirdToken1 { - thirdPrice = Q96 * Q96 / (thirdSqrtPriceX96 * thirdSqrtPriceX96 / Q96) - } else if thirdToken1 == restSecond && BASE_TOKEN == thirdToken0 { - thirdPrice = thirdSqrtPriceX96 * thirdSqrtPriceX96 / Q96 - } else { - panic("[ROUTER] getter_api.gno__calculateThreePoolPrice() || wrong condition #3") +func sqrt(x bigint, n int) bigint { + result := bigint(1) + for i := 0; i < n; i++ { + result *= x } - - threeNestedPrice := firstPrice * secondPrice * thirdPrice - return threeNestedPrice / Q96 / Q96 + return result }