Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: improve math for single asset join #1115

Merged
merged 9 commits into from
Dec 13, 2022
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## Unreleased

### Features
* [#1115](https://github.com/NibiruChain/nibiru/pull/1115) - feat: improve single asset join calculation

### Bug Fixes
* [#1113](https://github.com/NibiruChain/nibiru/pull/1113) - fix: fix quick simulation issue
* [#1114](https://github.com/NibiruChain/nibiru/pull/1114) - fix(dex): fix single asset join
Expand Down
2 changes: 2 additions & 0 deletions x/common/common.go
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,8 @@ var (

// Precision for int representation in sdk.Int objects
Precision = int64(1_000_000)

BigIntPrecision = int64(18)
)

//-----------------------------------------------------------------------------
Expand Down
22 changes: 11 additions & 11 deletions x/dex/keeper/keeper_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -669,7 +669,6 @@ func TestJoinPoolAllAssets(t *testing.T) {
{
name: "join with some assets, pool empty in one side, none remaining",
joinerInitialFunds: sdk.NewCoins(
sdk.NewInt64Coin("bar", 100),
sdk.NewInt64Coin("foo", 100),
),
initialPool: mock.DexPool(
Expand All @@ -682,20 +681,20 @@ func TestJoinPoolAllAssets(t *testing.T) {
tokensIn: sdk.NewCoins(
sdk.NewInt64Coin("foo", 100),
),
expectedNumSharesOut: sdk.NewInt64Coin(shareDenom, 900),
expectedRemCoins: sdk.NewCoins(sdk.NewInt64Coin("foo", 1)),
expectedNumSharesOut: sdk.NewInt64Coin(shareDenom, 904),
NibiruHeisenberg marked this conversation as resolved.
Show resolved Hide resolved
expectedRemCoins: sdk.NewCoins(),
expectedJoinerFinalFunds: sdk.NewCoins(
sdk.NewInt64Coin(shareDenom, 50),
sdk.NewInt64Coin(shareDenom, 904),
sdk.NewInt64Coin("bar", 0),
sdk.NewInt64Coin("foo", 0),
),
expectedFinalPool: mock.DexPool(
/*poolId=*/ 1,
/*assets=*/ sdk.NewCoins(
sdk.NewInt64Coin("bar", 100),
sdk.NewInt64Coin("foo", 100),
sdk.NewInt64Coin("foo", 101),
),
/*shares=*/ 1000),
/*shares=*/ 1004),
},
{
name: "join with some assets, but swap done",
Expand All @@ -714,20 +713,20 @@ func TestJoinPoolAllAssets(t *testing.T) {
sdk.NewInt64Coin("bar", 50),
sdk.NewInt64Coin("foo", 75),
),
expectedNumSharesOut: sdk.NewInt64Coin(shareDenom, 61),
expectedNumSharesOut: sdk.NewInt64Coin(shareDenom, 62),
expectedRemCoins: sdk.NewCoins(),
expectedJoinerFinalFunds: sdk.NewCoins(
sdk.NewInt64Coin(shareDenom, 50),
sdk.NewInt64Coin("bar", 35),
sdk.NewInt64Coin("foo", 35),
sdk.NewInt64Coin(shareDenom, 62),
sdk.NewInt64Coin("bar", 50),
sdk.NewInt64Coin("foo", 25),
),
expectedFinalPool: mock.DexPool(
/*poolId=*/ 1,
/*assets=*/ sdk.NewCoins(
sdk.NewInt64Coin("bar", 150),
sdk.NewInt64Coin("foo", 175),
),
/*shares=*/ 161),
/*shares=*/ 162),
},
}

Expand All @@ -749,6 +748,7 @@ func TestJoinPoolAllAssets(t *testing.T) {
require.Equal(t, tc.expectedFinalPool, pool)
require.Equal(t, tc.expectedNumSharesOut, numSharesOut)
require.Equal(t, tc.expectedRemCoins, remCoins)
require.Equal(t, tc.expectedJoinerFinalFunds, app.BankKeeper.GetAllBalances(ctx, joinerAddr))
})
}
}
Expand Down
49 changes: 11 additions & 38 deletions x/dex/types/pool.go
Original file line number Diff line number Diff line change
Expand Up @@ -98,10 +98,6 @@ ret:
func (pool *Pool) AddTokensToPool(tokensIn sdk.Coins) (
numShares sdk.Int, remCoins sdk.Coins, err error,
) {
if tokensIn.Len() != len(pool.PoolAssets) {
return sdk.ZeroInt(), sdk.Coins{}, errors.New("wrong number of assets to deposit into the pool")
}

// Calculate max amount of tokensIn we can deposit into pool (no swap)
if pool.PoolParams.PoolType == PoolType_STABLESWAP {
numShares, err = pool.numSharesOutFromTokensInStableSwap(tokensIn)
Expand All @@ -123,7 +119,7 @@ func (pool *Pool) AddTokensToPool(tokensIn sdk.Coins) (

/*
Adds tokens to a pool optimizing the amount of shares (swap + join) and updates the pool balances (i.e. liquidity).
We compute the swap and then join the pool.
We maximally join with both tokens first, and then perform a single asset join with the remaining assets.

This function is only necessary for balancer pool. Stableswap pool already takes all the deposit from the user.

Expand All @@ -143,48 +139,25 @@ func (pool *Pool) AddAllTokensToPool(tokensIn sdk.Coins) (
return
}

swapToken, err := pool.SwapForSwapAndJoin(tokensIn)
if err != nil {
return
}
if swapToken.Amount.LT(sdk.OneInt()) {
return pool.AddTokensToPool(tokensIn)
}

index, _, err := pool.getPoolAssetAndIndex(swapToken.Denom)

if err != nil {
return
remCoins = tokensIn
if tokensIn.Len() > 1 {
numShares, remCoins, err = pool.AddTokensToPool(tokensIn)
} else {
numShares = sdk.ZeroInt()
}

otherDenom := pool.PoolAssets[1-index].Token.Denom
tokenOut, err := pool.CalcOutAmtGivenIn(
/*tokenIn=*/ swapToken,
/*tokenOutDenom=*/ otherDenom,
/*noFee=*/ true,
)

if err != nil {
if remCoins.Empty() {
return
}

err = pool.ApplySwap(swapToken, tokenOut)

numShares2nd, _, err := pool.AddTokensToPool(remCoins)
if err != nil {
return
}

tokensIn = sdk.Coins{
{
Denom: swapToken.Denom,
Amount: tokensIn.AmountOfNoDenomValidation(swapToken.Denom).Sub(swapToken.Amount),
},
{
Denom: otherDenom,
Amount: tokensIn.AmountOfNoDenomValidation(otherDenom).Add(tokenOut.Amount),
},
}.Sort()
return pool.AddTokensToPool(tokensIn)
numShares = numShares2nd.Add(numShares)
remCoins = sdk.NewCoins()
return
}

/*
Expand Down
80 changes: 41 additions & 39 deletions x/dex/types/pool_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -366,22 +366,20 @@ func TestJoinPoolAllTokens(t *testing.T) {
sdk.NewInt64Coin("aaa", 10),
sdk.NewInt64Coin("bbb", 10),
),
expectedNumShares: sdk.NewInt(6),
expectedRemCoins: sdk.NewCoins(
sdk.NewInt64Coin("aaa", 1),
),
expectedNumShares: sdk.NewInt(7),
expectedRemCoins: sdk.NewCoins(),
expectedPool: Pool{
PoolAssets: []PoolAsset{
{
Token: sdk.NewInt64Coin("aaa", 109),
Token: sdk.NewInt64Coin("aaa", 110),
Weight: sdk.NewInt(1 << 30),
},
{
Token: sdk.NewInt64Coin("bbb", 210),
Weight: sdk.NewInt(1 << 30),
},
},
TotalShares: sdk.NewInt64Coin("nibiru/pool/1", 106),
TotalShares: sdk.NewInt64Coin("nibiru/pool/1", 107),
TotalWeight: sdk.NewInt(2 << 30),
PoolParams: PoolParams{PoolType: PoolType_BALANCER, SwapFee: sdk.ZeroDec()},
},
Expand All @@ -404,69 +402,73 @@ func TestJoinPoolAllTokens(t *testing.T) {
PoolParams: PoolParams{PoolType: PoolType_BALANCER, SwapFee: sdk.ZeroDec()},
},
tokensIn: sdk.NewCoins(
sdk.NewInt64Coin("aaa", 4859), // 0.138885 % of pool
sdk.NewInt64Coin("bbb", 1345), // 0.09580147 % of pool
),
expectedNumShares: sdk.NewInt(1172),
expectedRemCoins: sdk.NewCoins(
sdk.NewInt64Coin("aaa", 3),
sdk.NewInt64Coin("aaa", 4_859), // 0.138885 % of pool
sdk.NewInt64Coin("bbb", 1_345), // 0.09580147 % of pool
),
expectedNumShares: sdk.NewInt(1173),
expectedRemCoins: sdk.NewCoins(),
expectedPool: Pool{
PoolAssets: []PoolAsset{
{
Token: sdk.NewInt64Coin("aaa", 3_503_435),
Token: sdk.NewInt64Coin("aaa", 3_503_438),
Weight: sdk.NewInt(1 << 30),
},
{
Token: sdk.NewInt64Coin("bbb", 1_405_290),
Weight: sdk.NewInt(1 << 30),
},
},
TotalShares: sdk.NewInt64Coin("nibiru/pool/1", 1_001_172),
TotalShares: sdk.NewInt64Coin("nibiru/pool/1", 1_001_173),
TotalWeight: sdk.NewInt(2 << 30),
PoolParams: PoolParams{PoolType: PoolType_BALANCER, SwapFee: sdk.ZeroDec()},
},
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
numShares, remCoins, err := tc.pool.AddAllTokensToPool(tc.tokensIn)
require.NoError(t, err)
require.Equal(t, tc.expectedNumShares, numShares)
require.Equal(t, tc.expectedRemCoins, remCoins)
require.Equal(t, tc.expectedPool, tc.pool)
})
}
}

func TestJoinPoolInvalidInput(t *testing.T) {
for _, tc := range []struct {
name string
pool Pool
tokensIn sdk.Coins
}{
{
name: "not enough tokens",
name: "difficult numbers - single asset join",
pool: Pool{
PoolAssets: []PoolAsset{
{
Token: sdk.NewInt64Coin("aaa", 100),
Token: sdk.NewInt64Coin("aaa", 3_498_579),
Weight: sdk.NewInt(1 << 30),
},
{
Token: sdk.NewInt64Coin("bbb", 200),
Token: sdk.NewInt64Coin("bbb", 1_403_945),
Weight: sdk.NewInt(1 << 30),
},
},
TotalShares: sdk.NewInt64Coin("nibiru/pool/1", 100),
TotalShares: sdk.NewInt64Coin("nibiru/pool/1", 1*common.Precision),
TotalWeight: sdk.NewInt(2 << 30),
PoolParams: PoolParams{PoolType: PoolType_BALANCER, SwapFee: sdk.ZeroDec()},
},
tokensIn: sdk.NewCoins(
sdk.NewInt64Coin("aaa", 10),
sdk.NewInt64Coin("aaa", 4_859), // 0.138885 % of pool
),
expectedNumShares: sdk.NewInt(694),
expectedRemCoins: sdk.NewCoins(),
expectedPool: Pool{
PoolAssets: []PoolAsset{
{
Token: sdk.NewInt64Coin("aaa", 3_503_438),
Weight: sdk.NewInt(1 << 30),
},
{
Token: sdk.NewInt64Coin("bbb", 1_403_945),
Weight: sdk.NewInt(1 << 30),
},
},
TotalShares: sdk.NewInt64Coin("nibiru/pool/1", 1_000_694),
TotalWeight: sdk.NewInt(2 << 30),
PoolParams: PoolParams{PoolType: PoolType_BALANCER, SwapFee: sdk.ZeroDec()},
},
},
} {
tc := tc
t.Run(tc.name, func(t *testing.T) {
_, _, err := tc.pool.AddTokensToPool(tc.tokensIn)
require.Error(t, err)
numShares, remCoins, err := tc.pool.AddAllTokensToPool(tc.tokensIn)
require.NoError(t, err)
require.Equal(t, tc.expectedNumShares, numShares)
require.Equal(t, tc.expectedRemCoins, remCoins)
require.Equal(t, tc.expectedPool, tc.pool)
})
}
}
Expand Down
Loading