Skip to content

Commit

Permalink
Test improvisation for Superfluid (#1070)
Browse files Browse the repository at this point in the history
* add utility for SetupGammPoolsAndSuperfluidAssets

* remove hardcoded gamm pool denoms

* fix for main branch ctx => Ctx, app => App in tests

* Test Changes

* Add testing packages

* Delete fmt

* Fix lint

* Remove multiple pacakges for apptesting

* Add parameter for reward allocation

* Improvements on test

Co-authored-by: antstalepresh <stalepresh121@outlook.com>
  • Loading branch information
mattverse and antstalepresh committed Mar 14, 2022
1 parent 052bc20 commit 00b4b2c
Show file tree
Hide file tree
Showing 7 changed files with 209 additions and 111 deletions.
110 changes: 110 additions & 0 deletions app/apptesting/test_suite.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,19 @@ import (
tmproto "github.com/tendermint/tendermint/proto/tendermint/types"

sdk "github.com/cosmos/cosmos-sdk/types"
distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
slashingtypes "github.com/cosmos/cosmos-sdk/x/slashing/types"
"github.com/cosmos/cosmos-sdk/x/staking/teststaking"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

"github.com/osmosis-labs/osmosis/v7/app"
"github.com/osmosis-labs/osmosis/v7/x/gamm/pool-models/balancer"
gammtypes "github.com/osmosis-labs/osmosis/v7/x/gamm/types"
lockupkeeper "github.com/osmosis-labs/osmosis/v7/x/lockup/keeper"
lockuptypes "github.com/osmosis-labs/osmosis/v7/x/lockup/types"

"github.com/stretchr/testify/suite"
"github.com/tendermint/tendermint/crypto/ed25519"
)

type KeeperTestHelper struct {
Expand Down Expand Up @@ -99,3 +106,106 @@ func (keeperTestHelper *KeeperTestHelper) EndBlock() {
reqEndBlock := abci.RequestEndBlock{Height: keeperTestHelper.Ctx.BlockHeight()}
keeperTestHelper.App.EndBlocker(keeperTestHelper.Ctx, reqEndBlock)
}

func (keeperTestHelper *KeeperTestHelper) AllocateRewardsToValidator(valAddr sdk.ValAddress, rewardAmt sdk.Int) {
validator, found := keeperTestHelper.App.StakingKeeper.GetValidator(keeperTestHelper.Ctx, valAddr)
keeperTestHelper.Require().True(found)

// allocate reward tokens to distribution module
coins := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, rewardAmt)}
err := simapp.FundModuleAccount(keeperTestHelper.App.BankKeeper, keeperTestHelper.Ctx, distrtypes.ModuleName, coins)
keeperTestHelper.Require().NoError(err)

// allocate rewards to validator
keeperTestHelper.Ctx = keeperTestHelper.Ctx.WithBlockHeight(keeperTestHelper.Ctx.BlockHeight() + 1)
decTokens := sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: sdk.NewDec(20000)}}
keeperTestHelper.App.DistrKeeper.AllocateTokensToValidator(keeperTestHelper.Ctx, validator, decTokens)
}

// SetupGammPoolsWithBondDenomMultiplier uses given multipliers to set initial pool supply of bond denom.
func (keeperTestHelper *KeeperTestHelper) SetupGammPoolsWithBondDenomMultiplier(multipliers []sdk.Dec) []gammtypes.PoolI {
keeperTestHelper.App.GAMMKeeper.SetParams(keeperTestHelper.Ctx, gammtypes.Params{
PoolCreationFee: sdk.Coins{},
})

bondDenom := keeperTestHelper.App.StakingKeeper.BondDenom(keeperTestHelper.Ctx)
//TODO: use sdk crypto instead of tendermint to generate address
acc1 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address().Bytes())

//fund account with pool creation fee
poolCreationFee := keeperTestHelper.App.GAMMKeeper.GetParams(keeperTestHelper.Ctx)
err := simapp.FundAccount(keeperTestHelper.App.BankKeeper, keeperTestHelper.Ctx, acc1, poolCreationFee.PoolCreationFee)
keeperTestHelper.Require().NoError(err)

pools := []gammtypes.PoolI{}

for index, multiplier := range multipliers {
token := fmt.Sprintf("token%d", index)

uosmoAmount := gammtypes.InitPoolSharesSupply.ToDec().Mul(multiplier).RoundInt()

err := simapp.FundAccount(keeperTestHelper.App.BankKeeper, keeperTestHelper.Ctx, acc1, sdk.NewCoins(
sdk.NewCoin(bondDenom, uosmoAmount.Mul(sdk.NewInt(10))),
sdk.NewInt64Coin(token, 100000),
))
keeperTestHelper.NoError(err)

var (
defaultFutureGovernor = ""

// pool assets
defaultFooAsset gammtypes.PoolAsset = gammtypes.PoolAsset{
Weight: sdk.NewInt(100),
Token: sdk.NewCoin(bondDenom, uosmoAmount),
}
defaultBarAsset gammtypes.PoolAsset = gammtypes.PoolAsset{
Weight: sdk.NewInt(100),
Token: sdk.NewCoin(token, sdk.NewInt(10000)),
}
poolAssets []gammtypes.PoolAsset = []gammtypes.PoolAsset{defaultFooAsset, defaultBarAsset}
)

poolId, err := keeperTestHelper.App.GAMMKeeper.CreateBalancerPool(keeperTestHelper.Ctx, acc1, balancer.PoolParams{
SwapFee: sdk.NewDecWithPrec(1, 2),
ExitFee: sdk.NewDecWithPrec(1, 2),
}, poolAssets, defaultFutureGovernor)
keeperTestHelper.Require().NoError(err)

pool, err := keeperTestHelper.App.GAMMKeeper.GetPool(keeperTestHelper.Ctx, poolId)
keeperTestHelper.Require().NoError(err)

pools = append(pools, pool)
}
return pools
}

// SwapAndSetSpotPrice runs a swap to set Spot price of a pool using arbitrary values
// returns spot price after the arbitrary swap
func (keeperTestHelper *KeeperTestHelper) SwapAndSetSpotPrice(poolId uint64, fromAsset gammtypes.PoolAsset, toAsset gammtypes.PoolAsset) sdk.Dec {
// create a dummy account
acc1 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address().Bytes())

// fund dummy account with tokens to swap
coins := sdk.Coins{sdk.NewInt64Coin(fromAsset.Token.Denom, 100000000000000)}
err := simapp.FundAccount(keeperTestHelper.App.BankKeeper, keeperTestHelper.Ctx, acc1, coins)
keeperTestHelper.Require().NoError(err)

_, _, err = keeperTestHelper.App.GAMMKeeper.SwapExactAmountOut(
keeperTestHelper.Ctx, acc1,
poolId, fromAsset.Token.Denom, fromAsset.Token.Amount,
sdk.NewCoin(toAsset.Token.Denom, toAsset.Token.Amount.Quo(sdk.NewInt(4))))
keeperTestHelper.Require().NoError(err)

spotPrice, err := keeperTestHelper.App.GAMMKeeper.CalculateSpotPrice(keeperTestHelper.Ctx, poolId, toAsset.Token.Denom, fromAsset.Token.Denom)
keeperTestHelper.Require().NoError(err)
return spotPrice
}

func (keeperTestHelper *KeeperTestHelper) LockTokens(addr sdk.AccAddress, coins sdk.Coins, duration time.Duration) (lockID uint64) {
msgServer := lockupkeeper.NewMsgServerImpl(keeperTestHelper.App.LockupKeeper)
err := simapp.FundAccount(keeperTestHelper.App.BankKeeper, keeperTestHelper.Ctx, addr, coins)
keeperTestHelper.Require().NoError(err)
msgResponse, err := msgServer.LockTokens(sdk.WrapSDKContext(keeperTestHelper.Ctx), lockuptypes.NewMsgLockTokens(addr, duration, coins))
keeperTestHelper.Require().NoError(err)
return msgResponse.ID
}
1 change: 1 addition & 0 deletions proto/osmosis/incentives/gauge.proto
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ message Gauge {
// these queries
osmosis.lockup.QueryCondition distribute_to = 3
[ (gogoproto.nullable) = false ];
// total amount of Coins that has been in the gauge.
repeated cosmos.base.v1beta1.Coin coins = 4 [
(gogoproto.nullable) = false,
(gogoproto.castrepeated) = "github.com/cosmos/cosmos-sdk/types.Coins"
Expand Down
5 changes: 3 additions & 2 deletions x/incentives/types/gauge.pb.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 1 addition & 19 deletions x/superfluid/keeper/distribution_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,29 +2,11 @@ package keeper_test

import (
sdk "github.com/cosmos/cosmos-sdk/types"
distrtypes "github.com/cosmos/cosmos-sdk/x/distribution/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
lockuptypes "github.com/osmosis-labs/osmosis/v7/x/lockup/types"
minttypes "github.com/osmosis-labs/osmosis/v7/x/mint/types"
"github.com/osmosis-labs/osmosis/v7/x/superfluid/keeper"
)

func (suite *KeeperTestSuite) allocateRewardsToValidator(valAddr sdk.ValAddress) {
validator, found := suite.App.StakingKeeper.GetValidator(suite.Ctx, valAddr)
suite.Require().True(found)

// allocate reward tokens to distribution module
coins := sdk.Coins{sdk.NewCoin(sdk.DefaultBondDenom, sdk.NewInt(20000))}
suite.App.BankKeeper.MintCoins(suite.Ctx, minttypes.ModuleName, coins)
suite.App.BankKeeper.SendCoinsFromModuleToModule(suite.Ctx, minttypes.ModuleName, distrtypes.ModuleName, coins)

// allocate rewards to validator
suite.Ctx = suite.Ctx.WithBlockHeight(suite.Ctx.BlockHeight() + 1)
decTokens := sdk.DecCoins{{Denom: sdk.DefaultBondDenom, Amount: sdk.NewDec(20000)}}
suite.App.DistrKeeper.AllocateTokensToValidator(suite.Ctx, validator, decTokens)
suite.App.DistrKeeper.IncrementValidatorPeriod(suite.Ctx, validator)
}

func (suite *KeeperTestSuite) TestMoveSuperfluidDelegationRewardToGauges() {
type gaugeChecker struct {
intermediaryAccIndex uint64
Expand Down Expand Up @@ -94,7 +76,7 @@ func (suite *KeeperTestSuite) TestMoveSuperfluidDelegationRewardToGauges() {

// allocate rewards to first validator
for _, valIndex := range tc.rewardedVals {
suite.allocateRewardsToValidator(valAddrs[valIndex])
suite.AllocateRewardsToValidator(valAddrs[valIndex], sdk.NewInt(20000))
}

// move intermediary account delegation rewards to gauges
Expand Down
41 changes: 22 additions & 19 deletions x/superfluid/keeper/hooks_test.go
Original file line number Diff line number Diff line change
@@ -1,10 +1,8 @@
package keeper_test

import (
"github.com/cosmos/cosmos-sdk/simapp"
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"
"github.com/tendermint/tendermint/crypto/ed25519"
)

func (suite *KeeperTestSuite) TestSuperfluidAfterEpochEnd() {
Expand All @@ -18,7 +16,7 @@ func (suite *KeeperTestSuite) TestSuperfluidAfterEpochEnd() {
"happy path with single validator and delegator",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
[]superfluidDelegation{{0, 0, 0, 1000000}},
sdk.Coins{},
sdk.Coins{{Amount: sdk.NewInt(999990), Denom: "stake"}},
},
}

Expand All @@ -27,34 +25,39 @@ func (suite *KeeperTestSuite) TestSuperfluidAfterEpochEnd() {
suite.SetupTest()
valAddrs := suite.SetupValidators(tc.validatorStats)

// we create two additional pools: total three pools, 10 gauges
denoms, poolIds := suite.SetupGammPoolsAndSuperfluidAssets([]sdk.Dec{sdk.NewDec(20), sdk.NewDec(20)})

// Generate delegator addresses
delAddrs := CreateRandomAccounts(1)
intermediaryAccs, _ := suite.SetupSuperfluidDelegations(delAddrs, valAddrs, tc.superDelegations, denoms)
intermediaryAccs, locks := suite.SetupSuperfluidDelegations(delAddrs, valAddrs, tc.superDelegations, denoms)
suite.checkIntermediaryAccountDelegations(intermediaryAccs)

// gamm swap operation before refresh
acc1 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address().Bytes())
// run swap and set spot price
pool, err := suite.App.GAMMKeeper.GetPool(suite.Ctx, poolIds[0])
suite.Require().NoError(err)
poolAssets := pool.GetAllPoolAssets()

coins := sdk.Coins{sdk.NewInt64Coin(poolAssets[1].Token.Denom, 100000000000000)}
err = simapp.FundAccount(suite.App.BankKeeper, suite.Ctx, acc1, coins)
suite.Require().NoError(err)
_, _, err = suite.App.GAMMKeeper.SwapExactAmountOut(
suite.Ctx, acc1,
poolIds[0], poolAssets[1].Token.Denom, poolAssets[1].Token.Amount,
sdk.NewCoin(poolAssets[0].Token.Denom, poolAssets[0].Token.Amount.Quo(sdk.NewInt(4))))
suite.Require().NoError(err)
suite.SwapAndSetSpotPrice(poolIds[0], poolAssets[1], poolAssets[0])

// run epoch actions
suite.BeginNewBlock(true)

// check lptoken twap value set
newEpochTwap := suite.App.SuperfluidKeeper.GetOsmoEquivalentMultiplier(suite.Ctx, denoms[0])
suite.Require().Equal(newEpochTwap.String(), "15.000000000000000000")
newEpochMultiplier := suite.App.SuperfluidKeeper.GetOsmoEquivalentMultiplier(suite.Ctx, denoms[0])
suite.Require().Equal(newEpochMultiplier, sdk.NewDec(15))

// check gauge creation in new block
intermediaryAccAddr := suite.App.SuperfluidKeeper.GetLockIdIntermediaryAccountConnection(suite.Ctx, locks[0].ID)
intermediaryAcc := suite.App.SuperfluidKeeper.GetIntermediaryAccount(suite.Ctx, intermediaryAccAddr)
gauge, err := suite.App.IncentivesKeeper.GetGaugeByID(suite.Ctx, intermediaryAcc.GaugeId)

suite.Require().NoError(err)
suite.Require().Equal(gauge.Id, intermediaryAcc.GaugeId)
suite.Require().Equal(gauge.IsPerpetual, true)
suite.Require().Equal(gauge.Coins, tc.expRewards)
suite.Require().Equal(gauge.NumEpochsPaidOver, uint64(1))
suite.Require().Equal(gauge.FilledEpochs, uint64(1))
suite.Require().Equal(gauge.DistributedCoins, tc.expRewards)

// check delegation changes
for _, acc := range intermediaryAccs {
Expand All @@ -63,9 +66,9 @@ func (suite *KeeperTestSuite) TestSuperfluidAfterEpochEnd() {
delegation, found := suite.App.StakingKeeper.GetDelegation(suite.Ctx, acc.GetAccAddress(), valAddr)
suite.Require().True(found)
suite.Require().Equal(sdk.NewDec(7500000), delegation.Shares)
// TODO: Check reward distribution
// suite.Require().NotEqual(sdk.Coins{}, )
}
balance := suite.App.BankKeeper.GetAllBalances(suite.Ctx, delAddrs[0])
suite.Require().Equal(tc.expRewards, balance)
})
}
}
Expand Down
67 changes: 67 additions & 0 deletions x/superfluid/keeper/intermediary_account_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,76 @@ package keeper_test

import (
sdk "github.com/cosmos/cosmos-sdk/types"
stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types"

"github.com/osmosis-labs/osmosis/v7/x/superfluid/types"
)

func (suite *KeeperTestSuite) TestIntermediaryAccountCreation() {
testCases := []struct {
name string
validatorStats []stakingtypes.BondStatus
delegatorNumber int64
superDelegations []superfluidDelegation
}{
{
"test intermediary account with single superfluid delegation",
[]stakingtypes.BondStatus{stakingtypes.Bonded},
1,
[]superfluidDelegation{{0, 0, 0, 1000000}},
},
{
"test multiple intermediary accounts with multiple superfluid delegation",
[]stakingtypes.BondStatus{stakingtypes.Bonded, stakingtypes.Bonded},
2,
[]superfluidDelegation{{0, 0, 0, 1000000}, {1, 1, 0, 1000000}},
},
}

for _, tc := range testCases {
suite.Run(tc.name, func() {
suite.SetupTest()
valAddrs := suite.SetupValidators(tc.validatorStats)
delAddrs := CreateRandomAccounts(int(tc.delegatorNumber))

// we create two additional pools: total three pools, 10 gauges
denoms, _ := suite.SetupGammPoolsAndSuperfluidAssets([]sdk.Dec{sdk.NewDec(20), sdk.NewDec(20)})

var interAccs []types.SuperfluidIntermediaryAccount

for _, superDelegation := range tc.superDelegations {
delAddr := delAddrs[superDelegation.delIndex]
valAddr := valAddrs[superDelegation.valIndex]
denom := denoms[superDelegation.lpIndex]

// check intermediary Account prior to superfluid delegation, should have nil Intermediary Account
expAcc := types.NewSuperfluidIntermediaryAccount(denom, valAddr.String(), 0)
interAcc := suite.App.SuperfluidKeeper.GetIntermediaryAccount(suite.Ctx, expAcc.GetAccAddress())
suite.Require().NotEqual(expAcc.GetAccAddress(), interAcc.GetAccAddress())
suite.Require().Equal("", interAcc.Denom)
suite.Require().Equal(uint64(0), interAcc.GaugeId)
suite.Require().Equal("", interAcc.ValAddr)

lock := suite.SetupSuperfluidDelegate(delAddr, valAddr, denom, superDelegation.lpAmount)

// check that intermediary Account connection is established
interAccConnection := suite.App.SuperfluidKeeper.GetLockIdIntermediaryAccountConnection(suite.Ctx, lock.ID)
suite.Require().Equal(expAcc.GetAccAddress(), interAccConnection)

interAcc = suite.App.SuperfluidKeeper.GetIntermediaryAccount(suite.Ctx, interAccConnection)
suite.Require().Equal(expAcc.GetAccAddress(), interAcc.GetAccAddress())

// check on interAcc that has been created
suite.Require().Equal(denom, interAcc.Denom)
suite.Require().Equal(valAddr.String(), interAcc.ValAddr)

interAccs = append(interAccs, interAcc)
}
suite.checkIntermediaryAccountDelegations(interAccs)
})
}
}

func (suite *KeeperTestSuite) TestIntermediaryAccountsSetGetDeleteFlow() {
suite.SetupTest()

Expand Down
Loading

0 comments on commit 00b4b2c

Please sign in to comment.