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

Test improvisation for Superfluid #1070

Merged
merged 11 commits into from
Mar 14, 2022
104 changes: 104 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,100 @@ func (keeperTestHelper *KeeperTestHelper) EndBlock() {
reqEndBlock := abci.RequestEndBlock{Height: keeperTestHelper.Ctx.BlockHeight()}
keeperTestHelper.App.EndBlocker(keeperTestHelper.Ctx, reqEndBlock)
}

func (keeperTestHelper *KeeperTestHelper) AllocateRewardsToValidator(valAddr sdk.ValAddress) {
mattverse marked this conversation as resolved.
Show resolved Hide resolved
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, sdk.NewInt(20000))}
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)
keeperTestHelper.App.DistrKeeper.IncrementValidatorPeriod(keeperTestHelper.Ctx, validator)
mattverse marked this conversation as resolved.
Show resolved Hide resolved
}

func (keeperTestHelper *KeeperTestHelper) SetupGammPoolsWithBondDenomMultiplier(multipliers []sdk.Dec) []gammtypes.PoolI {
mattverse marked this conversation as resolved.
Show resolved Hide resolved
keeperTestHelper.App.GAMMKeeper.SetParams(keeperTestHelper.Ctx, gammtypes.Params{
PoolCreationFee: sdk.Coins{},
})
mattverse marked this conversation as resolved.
Show resolved Hide resolved

bondDenom := keeperTestHelper.App.StakingKeeper.BondDenom(keeperTestHelper.Ctx)
acc1 := sdk.AccAddress(ed25519.GenPrivKey().PubKey().Address().Bytes())
mattverse marked this conversation as resolved.
Show resolved Hide resolved

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])
}

// 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"}},
mattverse marked this conversation as resolved.
Show resolved Hide resolved
},
}

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