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

fix(dex): fix calculation of stableswap swaps and add tests #1151

Merged
merged 12 commits into from
Jan 25, 2023
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

* [#1126](https://github.com/NibiruChain/nibiru/pull/1126) - test(oracle): stop the tyrannical behavior of TestFuzz_PickReferencePair
* [#1131](https://github.com/NibiruChain/nibiru/pull/1131) - fix(oracle): use correct distribution module account
* [#1151](https://github.com/NibiruChain/nibiru/pull/1151) - fix(dex): fix swap calculation for stableswap pools

## [v0.16.3](https://github.com/NibiruChain/nibiru/releases/tag/v0.16.3)

Expand Down
2 changes: 1 addition & 1 deletion contrib/scripts/localnet.sh
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ CHAIN_ID="nibiru-localnet-0"
RPC_PORT="26657"
GRPC_PORT="9090"
MNEMONIC="guard cream sadness conduct invite crumble clock pudding hole grit liar hotel maid produce squeeze return argue turtle know drive eight casino maze host"
GENESIS_COINS="10000000000000unibi,10000000000000unusd,10000000000000uusdt"
GENESIS_COINS="10000000000000unibi,10000000000000unusd,10000000000000uusdt,10000000000000uusdc"
CHAIN_DIR="$HOME/.nibid"
echo "CHAIN_DIR: $CHAIN_DIR"
echo "CHAIN_ID: $CHAIN_ID"
Expand Down
34 changes: 19 additions & 15 deletions scripts/e2e/localnet.sh
Original file line number Diff line number Diff line change
Expand Up @@ -9,25 +9,26 @@ console_log_text_color() {
reset=$(tput sgr0)
}

if [ console_log_text_color ]; then echo "succesfully toggled console coloring"
if [ console_log_text_color ]; then
echo "succesfully toggled console coloring"
else
# For Ubuntu and Debian. MacOS has tput by default.
apt-get install libncurses5-dbg -y
fi

echo_info () {
echo_info() {
echo "${blue}"
echo "$1"
echo "${reset}"
}

echo_error () {
echo_error() {
echo "${red}"
echo "$1"
echo "${reset}"
}

echo_success () {
echo_success() {
echo "${green}"
echo "$1"
echo "${reset}"
Expand All @@ -48,9 +49,9 @@ if [[ "$OSTYPE" == "darwin"* ]]; then
fi

# Stop nibid if it is already running
if pgrep -x "$BINARY" > /dev/null; then
echo_error "Terminating $BINARY..."
killall nibid
if pgrep -x "$BINARY" >/dev/null; then
echo_error "Terminating $BINARY..."
killall nibid
fi

# Remove previous data
Expand All @@ -71,7 +72,6 @@ else
echo_error "Failed to initialize $CHAIN_ID"
fi


# Configure keyring-backend to "test"
echo_info "Configuring keyring-backend..."
if $BINARY config keyring-backend test; then
Expand All @@ -80,7 +80,6 @@ else
echo_error "Failed to configure keyring-backend"
fi


# Configure chain-id
echo_info "Configuring chain-id..."
if $BINARY config chain-id $CHAIN_ID; then
Expand Down Expand Up @@ -162,7 +161,7 @@ fi
add_genesis_param() {
echo "jq input $1"
# copy param ($1) to tmp_genesis.json
cat $CHAIN_DIR/config/genesis.json | jq "$1" > $CHAIN_DIR/config/tmp_genesis.json
cat $CHAIN_DIR/config/genesis.json | jq "$1" >$CHAIN_DIR/config/tmp_genesis.json
# rewrite genesis.json with the contents of tmp_genesis.json
mv $CHAIN_DIR/config/tmp_genesis.json $CHAIN_DIR/config/genesis.json
}
Expand All @@ -174,13 +173,13 @@ add_genesis_vpools_with_coingecko_prices() {
curl -X 'GET' \
'https://api.coingecko.com/api/v3/simple/price?ids=bitcoin%2Cethereum&vs_currencies=usd' \
-H 'accept: application/json' \
> $temp_json_fname
>$temp_json_fname

local M=1000000

local num_users=24000
local faucet_nusd_amt=100
local quote_amt=$(($num_users * $faucet_nusd_amt * $M))
local faucet_nusd_amt=100
local quote_amt=$(($num_users * $faucet_nusd_amt * $M))

price_btc=$(cat tmp_vpool_prices.json | jq -r '.bitcoin.usd')
price_btc=${price_btc%.*}
Expand All @@ -205,7 +204,7 @@ add_genesis_vpools_default() {
local quote_amt=10$KILO$MEGA
local base_amt_btc=$(($quote_amt / 16500))
local base_amt_eth=$(($quote_amt / 1200))
$BINARY add-genesis-vpool ubtc:unusd $base_amt_btc $quote_amt 0.1 0.1 0.1 0.0625 12
$BINARY add-genesis-vpool ubtc:unusd $base_amt_btc $quote_amt 0.1 0.1 0.1 0.0625 12
$BINARY add-genesis-vpool ueth:unusd $base_amt_eth $quote_amt 0.1 0.1 0.1 0.0625 10
}

Expand All @@ -231,7 +230,12 @@ add_genesis_param '.app_state.perp.pair_metadata[0].latest_cumulative_premium_fr
add_genesis_param '.app_state.perp.pair_metadata[1].pair = {token0:"ueth",token1:"unusd"}'
add_genesis_param '.app_state.perp.pair_metadata[1].latest_cumulative_premium_fraction = "0"'

cat $HOME/.nibid/config/genesis.json | jq '.app_state.pricefeed.params.twap_lookback_window = "900s"' > $HOME/.nibid/config/tmp_genesis.json && mv $HOME/.nibid/config/tmp_genesis.json $HOME/.nibid/config/genesis.json
add_genesis_param '.app_state.oracle.params.twap_lookback_window = "900s"'
add_genesis_param '.app_state.oracle.params.vote_period = "10"'
add_genesis_param '.app_state.oracle.exchange_rates[0].pair = "ubtc:unusd"'
add_genesis_param '.app_state.oracle.exchange_rates[0].exchange_rate = "20000"'
add_genesis_param '.app_state.oracle.exchange_rates[1].pair = "ueth:unusd"'
add_genesis_param '.app_state.oracle.exchange_rates[1].exchange_rate = "2000"'

# Start the network
echo_info "Starting $CHAIN_ID in $CHAIN_DIR..."
Expand Down
26 changes: 13 additions & 13 deletions x/dex/keeper/msg_server_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -809,12 +809,12 @@ func TestMsgServerExitPool(t *testing.T) {
),
poolSharesIn: sdk.NewInt64Coin(shareDenom, 100),
expectedTokensOut: sdk.NewCoins(
sdk.NewInt64Coin("unibi", 99),
sdk.NewInt64Coin(common.DenomNUSD, 99),
sdk.NewInt64Coin("unibi", 100),
sdk.NewInt64Coin(common.DenomNUSD, 100),
),
expectedJoinerFinalFunds: sdk.NewCoins(
sdk.NewInt64Coin("unibi", 199),
sdk.NewInt64Coin(common.DenomNUSD, 199),
sdk.NewInt64Coin("unibi", 200),
sdk.NewInt64Coin(common.DenomNUSD, 200),
),
expectedFinalPool: mock.DexStablePool(
/*poolId=*/ 1,
Expand Down Expand Up @@ -846,19 +846,19 @@ func TestMsgServerExitPool(t *testing.T) {
),
poolSharesIn: sdk.NewInt64Coin(shareDenom, 50),
expectedTokensOut: sdk.NewCoins(
sdk.NewInt64Coin("unibi", 49),
sdk.NewInt64Coin(common.DenomNUSD, 49),
sdk.NewInt64Coin("unibi", 50),
sdk.NewInt64Coin(common.DenomNUSD, 50),
),
expectedJoinerFinalFunds: sdk.NewCoins(
sdk.NewInt64Coin("unibi", 149),
sdk.NewInt64Coin(common.DenomNUSD, 149),
sdk.NewInt64Coin("unibi", 150),
sdk.NewInt64Coin(common.DenomNUSD, 150),
sdk.NewInt64Coin(shareDenom, 50),
),
expectedFinalPool: mock.DexStablePool(
/*poolId=*/ 1,
/*assets=*/ sdk.NewCoins(
sdk.NewInt64Coin("unibi", 51),
sdk.NewInt64Coin(common.DenomNUSD, 51),
sdk.NewInt64Coin("unibi", 50),
sdk.NewInt64Coin(common.DenomNUSD, 50),
),
/*shares=*/ 50,
),
Expand Down Expand Up @@ -1080,15 +1080,15 @@ func TestMsgServerSwapAssets(t *testing.T) {
),
tokenIn: sdk.NewInt64Coin("unibi", 100),
tokenOutDenom: common.DenomNUSD,
expectedTokenOut: sdk.NewInt64Coin(common.DenomNUSD, 30),
expectedTokenOut: sdk.NewInt64Coin(common.DenomNUSD, 95),
expectedUserFinalFunds: sdk.NewCoins(
sdk.NewInt64Coin(common.DenomNUSD, 30),
sdk.NewInt64Coin(common.DenomNUSD, 95),
),
expectedFinalPool: mock.DexStablePool(
/*poolId=*/ 1,
/*assets=*/ sdk.NewCoins(
sdk.NewInt64Coin("unibi", 200),
sdk.NewInt64Coin(common.DenomNUSD, 70),
sdk.NewInt64Coin(common.DenomNUSD, 5),
),
/*shares=*/ 100,
),
Expand Down
132 changes: 129 additions & 3 deletions x/dex/keeper/swap_test.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package keeper_test

import (
"fmt"
"testing"

"github.com/NibiruChain/nibiru/x/testutil"
Expand Down Expand Up @@ -59,11 +60,11 @@ func TestSwapExactAmountIn(t *testing.T) {
},
tokenIn: sdk.NewInt64Coin("unusd", 1_500_000_000),
tokenOutDenom: "uusdt",
expectedTokenOut: sdk.NewInt64Coin("uusdt", 1_019_823),
expectedTokenOut: sdk.NewInt64Coin("uusdt", 6_670_336),
expectedUserFinalFunds: sdk.NewCoins(
sdk.NewInt64Coin("unibi", 236_534_500),
sdk.NewInt64Coin("unusd", 200_000_000),
sdk.NewInt64Coin("uusdt", 702_804_893),
sdk.NewInt64Coin("uusdt", 708_455_406),
),
expectedFinalPool: types.Pool{
Id: 1,
Expand All @@ -76,14 +77,43 @@ func TestSwapExactAmountIn(t *testing.T) {
PoolAssets: []types.PoolAsset{
{Token: sdk.NewInt64Coin("unusd", 3_010_778_598),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

note to self: use denom constants from x/common

Weight: sdk.NewInt(1)},
{Token: sdk.NewInt64Coin("uusdt", 6_692_233),
{Token: sdk.NewInt64Coin("uusdt", 1_041_720),
Weight: sdk.NewInt(1)},
},
TotalWeight: sdk.NewInt(2),
TotalShares: sdk.NewInt64Coin("nibiru/pool/1", 100),
},
expectedError: nil,
},
{
name: "regular stableswap",
userInitialFunds: sdk.NewCoins(
sdk.NewInt64Coin(common.DenomUSDC, 10),
),
initialPool: mock.DexStablePool(
/*poolId=*/ 1,
/*assets=*/ sdk.NewCoins(
sdk.NewInt64Coin(common.DenomUSDC, 100),
sdk.NewInt64Coin(common.DenomNUSD, 100),
),
/*shares=*/ 100,
),
tokenIn: sdk.NewInt64Coin(common.DenomUSDC, 10),
tokenOutDenom: common.DenomNUSD,
expectedTokenOut: sdk.NewInt64Coin(common.DenomNUSD, 10),
expectedUserFinalFunds: sdk.NewCoins(
sdk.NewInt64Coin(common.DenomNUSD, 10),
),
expectedFinalPool: mock.DexStablePool(
/*poolId=*/ 1,
/*assets=*/ sdk.NewCoins(
sdk.NewInt64Coin(common.DenomUSDC, 110),
sdk.NewInt64Coin(common.DenomNUSD, 90),
),
/*shares=*/ 100,
),
expectedError: nil,
},
{
name: "regular swap",
userInitialFunds: sdk.NewCoins(
Expand Down Expand Up @@ -273,3 +303,99 @@ func TestSwapExactAmountIn(t *testing.T) {
})
}
}

func TestDoubleSwapExactAmountIn(t *testing.T) {
tests := []struct {
name string

// test setup
userInitialFunds sdk.Coins
initialPool types.Pool
tokenIns []sdk.Coin
tokenOutDenoms []string

// expected results
expectedTokenOuts []sdk.Coin
expectedUserFinalFunds sdk.Coins
expectedFinalPool types.Pool
}{
{
name: "double stableswap",
userInitialFunds: sdk.NewCoins(
sdk.NewInt64Coin(common.DenomUSDC, 10_000),
),
initialPool: mock.DexStablePool(
/*poolId=*/ 1,
/*assets=*/ sdk.NewCoins(
sdk.NewInt64Coin(common.DenomUSDC, 100_000_000),
sdk.NewInt64Coin(common.DenomNUSD, 100_000_000),
),
/*shares=*/ 100,
),
tokenIns: []sdk.Coin{sdk.NewInt64Coin(common.DenomUSDC, 10_000), sdk.NewInt64Coin("unusd", 10_000)},
tokenOutDenoms: []string{common.DenomNUSD, common.DenomUSDC},
expectedTokenOuts: []sdk.Coin{sdk.NewInt64Coin(common.DenomNUSD, 10_000), sdk.NewInt64Coin(common.DenomUSDC, 10_001)},
expectedUserFinalFunds: sdk.NewCoins(
sdk.NewInt64Coin(common.DenomUSDC, 10_001), // TODO: fix https://github.com/NibiruChain/nibiru/issues/1152
),
expectedFinalPool: mock.DexStablePool(
/*poolId=*/ 1,
/*assets=*/ sdk.NewCoins(
sdk.NewInt64Coin(common.DenomUSDC, 99_999_999),
sdk.NewInt64Coin(common.DenomNUSD, 100_000_000),
),
/*shares=*/ 100,
),
},
}

for _, tc := range tests {
tc := tc
t.Run(tc.name, func(t *testing.T) {
app, ctx := testapp.NewTestNibiruAppAndContext(true)

// fund pool account
poolAddr := testutil.AccAddress()
tc.initialPool.Address = poolAddr.String()
tc.expectedFinalPool.Address = poolAddr.String()
require.NoError(t,
simapp.FundAccount(
app.BankKeeper,
ctx,
poolAddr,
tc.initialPool.PoolBalances(),
),
)
app.DexKeeper.SetPool(ctx, tc.initialPool)

// fund user account
sender := testutil.AccAddress()
require.NoError(t, simapp.FundAccount(app.BankKeeper, ctx, sender, tc.userInitialFunds))

// swap assets
for i, tokenIn := range tc.tokenIns {
tokenOut, err := app.DexKeeper.SwapExactAmountIn(ctx, sender, tc.initialPool.Id, tokenIn, tc.tokenOutDenoms[i])
require.NoError(t, err)

fmt.Println("-------------", i)
finalPool, err := app.DexKeeper.FetchPool(ctx, tc.initialPool.Id)
require.NoError(t, err)
fmt.Println(finalPool.PoolAssets)

require.NoError(t, err)
require.Equal(t, tc.expectedTokenOuts[i], tokenOut)
}

// check user's final funds
require.Equal(t,
tc.expectedUserFinalFunds,
app.BankKeeper.GetAllBalances(ctx, sender),
)

// check final pool state
finalPool, err := app.DexKeeper.FetchPool(ctx, tc.initialPool.Id)
require.NoError(t, err)
require.Equal(t, tc.expectedFinalPool, finalPool)
})
}
}
5 changes: 5 additions & 0 deletions x/dex/simulation/operations.go
Original file line number Diff line number Diff line change
Expand Up @@ -189,6 +189,11 @@ func SimulateJoinPool(ak types.AccountKeeper, bk types.BankKeeper, k keeper.Keep
TokensIn: tokensIn,
}

_, err = pool.GetD(pool.PoolAssets)
if err != nil {
return simtypes.NoOpMsg(types.ModuleName, msg.Type(), "borked pool"), nil, nil
}

return simulation.GenAndDeliverTxWithRandFees(
simulation.OperationInput{
R: r,
Expand Down
Loading