Skip to content

Commit

Permalink
chore(amm): amm swaps rewards UT (#1120)
Browse files Browse the repository at this point in the history
  • Loading branch information
mtsitrin authored Aug 21, 2024
1 parent 7883280 commit c6edccf
Show file tree
Hide file tree
Showing 4 changed files with 267 additions and 3 deletions.
141 changes: 141 additions & 0 deletions app/apptesting/gamm.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
package apptesting

import (
"math/big"

sdk "github.com/cosmos/cosmos-sdk/types"

gammkeeper "github.com/osmosis-labs/osmosis/v15/x/gamm/keeper"
"github.com/osmosis-labs/osmosis/v15/x/gamm/pool-models/balancer"
gammtypes "github.com/osmosis-labs/osmosis/v15/x/gamm/types"
poolmanagertypes "github.com/osmosis-labs/osmosis/v15/x/poolmanager/types"
)

// 10^18 multiplier
var EXP = sdk.NewIntFromBigInt(new(big.Int).Exp(big.NewInt(10), big.NewInt(18), nil))

// Default sender for amm messages
var sender = sdk.MustAccAddressFromBech32(alice)

var DefaultAcctFunds sdk.Coins = sdk.NewCoins(
sdk.NewCoin("adym", EXP.Mul(sdk.NewInt(1_000_000))),
sdk.NewCoin("foo", EXP.Mul(sdk.NewInt(1_000_000))),
sdk.NewCoin("bar", EXP.Mul(sdk.NewInt(1_000_000))),
sdk.NewCoin("baz", EXP.Mul(sdk.NewInt(1_000_000))),
)

var DefaultPoolParams = balancer.PoolParams{
SwapFee: sdk.NewDec(0),
ExitFee: sdk.NewDec(0),
}

var DefaultPoolAssets = []balancer.PoolAsset{
{
Weight: sdk.NewInt(100),
Token: sdk.NewCoin("foo", EXP.Mul(sdk.NewInt(500))),
},
{
Weight: sdk.NewInt(100),
Token: sdk.NewCoin("adym", EXP.Mul(sdk.NewInt(500))),
},
}

// PrepareCustomPool sets up a Balancer pool with an array of assets and given parameters
// This is the generic method called by other PreparePool wrappers
// It funds the sender account with DefaultAcctFunds
func (s *KeeperTestHelper) PrepareCustomPool(assets []balancer.PoolAsset, params balancer.PoolParams) uint64 {
s.FundAcc(sender, DefaultAcctFunds)

msg := balancer.NewMsgCreateBalancerPool(sender, params, assets, "")
poolId, err := s.App.PoolManagerKeeper.CreatePool(s.Ctx, msg)
s.NoError(err)
return poolId
}

// PrepareDefaultPool sets up a pool with default pool assets and parameters.
func (s *KeeperTestHelper) PrepareDefaultPool() uint64 {
poolId := s.PrepareCustomPool(DefaultPoolAssets, DefaultPoolParams)

spotPrice, err := s.App.GAMMKeeper.CalculateSpotPrice(s.Ctx, poolId, "foo", "adym")
s.NoError(err)
s.Equal(sdk.NewDec(1).String(), spotPrice.String())

return poolId
}

// PreparePoolWithCoins returns a pool consisted of given coins with equal weight and default pool parameters.
func (s *KeeperTestHelper) PreparePoolWithCoins(coins sdk.Coins) uint64 {
poolAssets := coinsToAssets(coins)
return s.PrepareCustomPool(poolAssets, DefaultPoolParams)
}

// PreparePoolWithPoolParams sets up a pool with given poolParams and default pool assets.
func (s *KeeperTestHelper) PreparePoolWithPoolParams(poolParams balancer.PoolParams) uint64 {
return s.PrepareCustomPool(DefaultPoolAssets, poolParams)
}

// PrepareCustomPoolFromCoins sets up a Balancer pool with an array of coins and given parameters
// The coins are converted to pool assets where each asset has a weight of 1.
func (s *KeeperTestHelper) PrepareCustomPoolFromCoins(coins sdk.Coins, params balancer.PoolParams) uint64 {
poolAssets := coinsToAssets(coins)
return s.PrepareCustomPool(poolAssets, params)
}

func coinsToAssets(coins sdk.Coins) []balancer.PoolAsset {
var poolAssets []balancer.PoolAsset
for _, coin := range coins {
poolAsset := balancer.PoolAsset{
Weight: sdk.NewInt(1),
Token: coin,
}
poolAssets = append(poolAssets, poolAsset)
}
return poolAssets
}

func (s *KeeperTestHelper) RunBasicSwap(poolId uint64, from string, swapIn sdk.Coin, outDenom string) {
msg := gammtypes.MsgSwapExactAmountIn{
Sender: from,
Routes: []poolmanagertypes.SwapAmountInRoute{{PoolId: poolId, TokenOutDenom: outDenom}},
TokenIn: swapIn,
TokenOutMinAmount: sdk.ZeroInt(),
}

gammMsgServer := gammkeeper.NewMsgServerImpl(s.App.GAMMKeeper)
_, err := gammMsgServer.SwapExactAmountIn(sdk.WrapSDKContext(s.Ctx), &msg)
s.Require().NoError(err)
}

func (s *KeeperTestHelper) RunBasicExit(poolId uint64, shares sdk.Int, from string) (out sdk.Coins) {
msg := gammtypes.MsgExitPool{
Sender: from,
PoolId: poolId,
ShareInAmount: shares,
TokenOutMins: sdk.NewCoins(),
}

gammMsgServer := gammkeeper.NewMsgServerImpl(s.App.GAMMKeeper)
res, err := gammMsgServer.ExitPool(sdk.WrapSDKContext(s.Ctx), &msg)
s.Require().NoError(err)
return res.TokenOut
}

// RunBasicJoin joins the pool with 10% of the total pool shares
func (s *KeeperTestHelper) RunBasicJoin(poolId uint64, from string) (shares sdk.Int, cost sdk.Coins) {
pool, err := s.App.GAMMKeeper.GetPoolAndPoke(s.Ctx, poolId)
s.Require().NoError(err)

totalPoolShare := pool.GetTotalShares()
msg := gammtypes.MsgJoinPool{
Sender: from,
PoolId: poolId,
ShareOutAmount: totalPoolShare.Quo(sdk.NewInt(10)),
TokenInMaxs: sdk.NewCoins(),
}

gammMsgServer := gammkeeper.NewMsgServerImpl(s.App.GAMMKeeper)
res, err := gammMsgServer.JoinPool(sdk.WrapSDKContext(s.Ctx), &msg)
s.Require().NoError(err)

return res.ShareOutAmount, res.TokenIn
}
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ replace (
github.com/evmos/ethermint => github.com/dymensionxyz/ethermint v0.22.0-dymension-v0.4.1.0.20240625101522-b1506ae83050
github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1
github.com/osmosis-labs/osmosis/osmomath => github.com/dymensionxyz/osmosis/osmomath v0.0.6-dymension-v0.1.0.20240820121212-c0e21fa21e43
github.com/osmosis-labs/osmosis/v15 => github.com/dymensionxyz/osmosis/v15 v15.2.1-0.20240627111157-f2243f47cdb3
github.com/osmosis-labs/osmosis/v15 => github.com/dymensionxyz/osmosis/v15 v15.2.1-0.20240820121212-c0e21fa21e43

// broken goleveldb
github.com/syndtr/goleveldb => github.com/syndtr/goleveldb v1.0.1-0.20210819022825-2ae1ddf74ef7
Expand Down
4 changes: 2 additions & 2 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -506,8 +506,8 @@ github.com/dymensionxyz/gerr-cosmos v1.0.0 h1:oi91rgOkpJWr41oX9JOyjvvBnhGY54tj51
github.com/dymensionxyz/gerr-cosmos v1.0.0/go.mod h1:n+0olxPogzWqFKba45mCpvrHLGmeS8W9UZjggHnWk6c=
github.com/dymensionxyz/osmosis/osmomath v0.0.6-dymension-v0.1.0.20240820121212-c0e21fa21e43 h1:EskhZ6ILN3vwJ6l8gPWPZ49RFSB52WghT5v+pmzrNCI=
github.com/dymensionxyz/osmosis/osmomath v0.0.6-dymension-v0.1.0.20240820121212-c0e21fa21e43/go.mod h1:SdGCL9CZb14twRAJUSzb7bRE0OoopRpF2Hnd1UhJpFU=
github.com/dymensionxyz/osmosis/v15 v15.2.1-0.20240627111157-f2243f47cdb3 h1:4VD23Jv5d8hqXEhLNNcLXlpSDJCWAGYJLF0kisJtkIk=
github.com/dymensionxyz/osmosis/v15 v15.2.1-0.20240627111157-f2243f47cdb3/go.mod h1:2rsnXAdjYfXtyEw0mNwAdOiAccALYjAPvINGUf9Qg7Y=
github.com/dymensionxyz/osmosis/v15 v15.2.1-0.20240820121212-c0e21fa21e43 h1:ugbpHwwlckB4W/aNXUTEsxaakPFgXi+LAsCtvfJ200Q=
github.com/dymensionxyz/osmosis/v15 v15.2.1-0.20240820121212-c0e21fa21e43/go.mod h1:2rsnXAdjYfXtyEw0mNwAdOiAccALYjAPvINGUf9Qg7Y=
github.com/dymensionxyz/sdk-utils v0.2.7 h1:9Hy56ivv81ISIG7x9AMaNo6GABRuEgZQ+MmS2PdfuW0=
github.com/dymensionxyz/sdk-utils v0.2.7/go.mod h1:it9owYOpnIe17+ftTATQNDN4z+mBQx20/2Jm8SK15Rk=
github.com/eapache/go-resiliency v1.1.0/go.mod h1:kFI+JgMyC7bLPUVY133qvEBtVayf5mFgVsvEsIPBvNs=
Expand Down
123 changes: 123 additions & 0 deletions x/gamm/amm_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package keeper_test

import (
"fmt"
"testing"

sdk "github.com/cosmos/cosmos-sdk/types"
"github.com/osmosis-labs/osmosis/v15/x/gamm/pool-models/balancer"
"github.com/stretchr/testify/suite"

cometbftproto "github.com/cometbft/cometbft/proto/tendermint/types"
"github.com/dymensionxyz/dymension/v3/app/apptesting"
"github.com/dymensionxyz/dymension/v3/testutil/sample"
)

type KeeperTestSuite struct {
apptesting.KeeperTestHelper
}

func TestKeeperTestSuite(t *testing.T) {
suite.Run(t, new(KeeperTestSuite))
}

func (s *KeeperTestSuite) SetupTest() {
app := apptesting.Setup(s.T(), false)
ctx := app.GetBaseApp().NewContext(false, cometbftproto.Header{})

// set txfees basedenom
err := app.TxFeesKeeper.SetBaseDenom(ctx, "adym")
s.Require().NoError(err)

s.App = app
s.Ctx = ctx
}

func (s *KeeperTestSuite) TestSwapsRevenue() {
// Create a pool with 100_000 DYM and 100_000 FOO
poolCoins := sdk.NewCoins(
sdk.NewCoin("adym", apptesting.EXP.Mul(sdk.NewInt(100_000))),
sdk.NewCoin("foo", apptesting.EXP.Mul(sdk.NewInt(100_000))),
)

testCases := []struct {
name string
swapFee sdk.Dec
takerFee sdk.Dec
expRevenue bool
}{
{
name: "1% swap fee, 1% taker fee",
swapFee: sdk.NewDecWithPrec(1, 2), // 1%
takerFee: sdk.NewDecWithPrec(1, 2), // 1%
expRevenue: true,
},
{
name: "1% swap fee, no taker fee",
swapFee: sdk.NewDecWithPrec(1, 2), // 1%
takerFee: sdk.ZeroDec(), // 0%
expRevenue: true,
},
{
name: "0% swap fee, 1% taker fee",
swapFee: sdk.ZeroDec(), // 0%
takerFee: sdk.NewDecWithPrec(1, 2), // 1%
expRevenue: false,
},
{
name: "0% swap fee, no taker fee",
swapFee: sdk.ZeroDec(), // 0%
takerFee: sdk.ZeroDec(), // 0%
expRevenue: false,
},
}

for _, tc := range testCases {
s.Run(tc.name, func() {
params := s.App.GAMMKeeper.GetParams(s.Ctx)
params.TakerFee = tc.takerFee
s.App.GAMMKeeper.SetParams(s.Ctx, params)

poolId := s.PrepareCustomPoolFromCoins(poolCoins, balancer.PoolParams{
SwapFee: tc.swapFee,
ExitFee: sdk.ZeroDec(),
})

// join pool
addr := sample.Acc()
s.FundAcc(addr, apptesting.DefaultAcctFunds)
shares, _ := s.RunBasicJoin(poolId, addr.String())

// check position
p, _ := s.App.GAMMKeeper.GetPool(s.Ctx, poolId)
pool := p.(*balancer.Pool) // nolint: errcheck
position, err := pool.CalcExitPoolCoinsFromShares(s.Ctx, shares, sdk.ZeroDec())
s.Require().NoError(err)
liquidity := pool.GetTotalPoolLiquidity(s.Ctx)
spot, err := s.App.GAMMKeeper.CalculateSpotPrice(s.Ctx, poolId, "foo", "adym")
s.Require().NoError(err)
s.T().Logf("positionBefore: %s, liquidity: %s, spot: %s", position, liquidity, spot)

// swap tokens (swap 5 DYM for FOO) and vice versa
s.RunBasicSwap(poolId, addr.String(), sdk.NewCoin("adym", apptesting.EXP.Mul(sdk.NewInt(5))), "foo")
s.RunBasicSwap(poolId, addr.String(), sdk.NewCoin("foo", apptesting.EXP.Mul(sdk.NewInt(5))), "adym")

// check position
p, _ = s.App.GAMMKeeper.GetPool(s.Ctx, poolId)
pool = p.(*balancer.Pool) // nolint: errcheck
liquidity = pool.GetTotalPoolLiquidity(s.Ctx)
positionAfter, err := pool.CalcExitPoolCoinsFromShares(s.Ctx, shares, sdk.ZeroDec())
s.Require().NoError(err)
spot, err = s.App.GAMMKeeper.CalculateSpotPrice(s.Ctx, poolId, "foo", "adym")
s.Require().NoError(err)
s.T().Logf("positionAfterSwap: %s, liquidity: %s, spot: %s", positionAfter, liquidity, spot)

// assert
if tc.expRevenue {
s.True(positionAfter.IsAllGT(position), fmt.Sprintf("positionBefore: %s, positionAfter: %s", position, positionAfter))
} else {
s.True(positionAfter.IsAnyGT(position), fmt.Sprintf("positionBefore: %s, positionAfter: %s", position, positionAfter))
}
})
}
}

0 comments on commit c6edccf

Please sign in to comment.