From b50f5a74b864396c0dddfd2e0954d6116972ac92 Mon Sep 17 00:00:00 2001 From: Simon Noetzlin Date: Fri, 8 Dec 2023 10:45:23 +0100 Subject: [PATCH] chore: refactor staking integration, sims and e2e tests (#8) * set min_self_delegation to 0 in TestAminoCodecFullDecodeAndEncode * add WithdrawTokenizeShareRecordReward and WithdrawAllTokenizeShareRecordReward * add methods to distribution/keeper * register distribution msgs * add SimulateMsgWithdrawTokenizeShareRecordReward * LSM distribution queries * LSM distr cli * add BeforeTokenizeShareRecordRemoved hook * add signers to proto distribution * set signers correctly * minimum refactor to build * tag LSM test to be refactored * Merge with feat/lsm/v0.47.x tag LSM tests to be refactored Fix nits * nit * comments more failing tests * make protos * Update x/staking/keeper/msg_server.go Co-authored-by: Marius Poke * Update x/staking/keeper/msg_server.go Co-authored-by: Marius Poke * add go.work and fix silent errors * address comments * refactor staking msg_server_tests.go - distrib hooks cause them to fail * make integration tests pass * clean up * clean up * refactor last integration * nits * revert deterministic tests change * tests: update simulation randfees calc (#9) * address min self delegation depreciation in tests * refactor e2e tests and other nits * tests: appease linter in randfees --------- Co-authored-by: mpoke Co-authored-by: MSalopek --- Makefile | 2 +- snapshots/store.go | 2 +- tests/e2e/staking/suite.go | 9 +- .../integration/staking/keeper/common_test.go | 5 +- .../staking/keeper/delegation_test.go | 170 + .../staking/keeper/determinstic_test.go | 66 +- .../staking/keeper/genesis_test.go | 95 +- .../staking/keeper/liquid_stake_test.go | 274 ++ .../staking/keeper/msg_server_test.go | 2722 +++++++++-------- types/simulation/account.go | 16 +- x/staking/keeper/delegation.go | 10 - x/staking/keeper/delegation_test.go | 191 +- x/staking/keeper/liquid_stake.go | 29 +- x/staking/keeper/liquid_stake_test.go | 1954 +++++------- x/staking/keeper/msg_server.go | 5 +- x/staking/keeper/params.go | 1 - .../keeper/tokenize_share_record_test.go | 101 +- x/staking/migrations/v3/json_test.go | 17 +- x/staking/simulation/genesis.go | 7 +- x/staking/simulation/operations.go | 5 +- x/staking/types/validator.go | 1 + 21 files changed, 2799 insertions(+), 2883 deletions(-) create mode 100644 tests/integration/staking/keeper/liquid_stake_test.go diff --git a/Makefile b/Makefile index d9313246778c..be69109df5a6 100644 --- a/Makefile +++ b/Makefile @@ -347,7 +347,7 @@ benchmark: ############################################################################### golangci_lint_cmd=golangci-lint -golangci_version=v1.50.1 +golangci_version=v1.55.2 lint: @echo "--> Running linter" diff --git a/snapshots/store.go b/snapshots/store.go index 1087c826fab2..3ceafa2bbe03 100644 --- a/snapshots/store.go +++ b/snapshots/store.go @@ -260,7 +260,7 @@ func (s *Store) Save( snapshotHasher := sha256.New() chunkHasher := sha256.New() for chunkBody := range chunks { - defer chunkBody.Close() //nolint:staticcheck + defer chunkBody.Close() dir := s.pathSnapshot(height, format) err = os.MkdirAll(dir, 0o755) if err != nil { diff --git a/tests/e2e/staking/suite.go b/tests/e2e/staking/suite.go index dff54520f9cf..cee0eac0b7b6 100644 --- a/tests/e2e/staking/suite.go +++ b/tests/e2e/staking/suite.go @@ -914,16 +914,19 @@ func (s *E2ETestSuite) TestGetCmdQueryParams() { "with text output", []string{fmt.Sprintf("--%s=text", flags.FlagOutput)}, `bond_denom: stake +global_liquid_staking_cap: "1.000000000000000000" historical_entries: 10000 max_entries: 7 max_validators: 100 min_commission_rate: "0.000000000000000000" -unbonding_time: 1814400s`, +unbonding_time: 1814400s +validator_bond_factor: "-1.000000000000000000" +validator_liquid_staking_cap: "1.000000000000000000"`, }, { "with json output", []string{fmt.Sprintf("--%s=json", flags.FlagOutput)}, - `{"unbonding_time":"1814400s","max_validators":100,"max_entries":7,"historical_entries":10000,"bond_denom":"stake","min_commission_rate":"0.000000000000000000"}`, + `{"unbonding_time":"1814400s","max_validators":100,"max_entries":7,"historical_entries":10000,"bond_denom":"stake","min_commission_rate":"0.000000000000000000","validator_bond_factor":"-1.000000000000000000","global_liquid_staking_cap":"1.000000000000000000","validator_liquid_staking_cap":"1.000000000000000000"}`, }, } for _, tc := range testCases { @@ -1224,7 +1227,7 @@ func (s *E2ETestSuite) TestNewRedelegateCmd() { fmt.Sprintf("--%s=%s", flags.FlagBroadcastMode, flags.BroadcastSync), fmt.Sprintf("--%s=%s", flags.FlagFees, sdk.NewCoins(sdk.NewCoin(s.cfg.BondDenom, sdk.NewInt(10))).String()), }, - false, 31, &sdk.TxResponse{}, + false, 3, &sdk.TxResponse{}, }, { "valid transaction of delegate", diff --git a/tests/integration/staking/keeper/common_test.go b/tests/integration/staking/keeper/common_test.go index f6169b9d44c1..d882a5a666c6 100644 --- a/tests/integration/staking/keeper/common_test.go +++ b/tests/integration/staking/keeper/common_test.go @@ -7,6 +7,7 @@ import ( tmproto "github.com/cometbft/cometbft/proto/tendermint/types" "github.com/stretchr/testify/require" + "cosmossdk.io/math" "cosmossdk.io/simapp" "github.com/cosmos/cosmos-sdk/codec" simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" @@ -89,8 +90,8 @@ func createValidators(t *testing.T, ctx sdk.Context, app *simapp.SimApp, powers return addrs, valAddrs, vals } -func delegateCoinsFromAccount(ctx sdk.Context, app *simapp.SimApp, addr sdk.AccAddress, amount sdk.Int, val types.Validator) error { - _, err := app.StakingKeeper.Delegate(ctx, addr, amount, types.Unbonded, val, true) +func delegateCoinsFromAccount(ctx sdk.Context, sk keeper.Keeper, addr sdk.AccAddress, amount math.Int, val types.ValidatorI) error { + _, err := sk.Delegate(ctx, addr, amount, types.Unbonded, val.(types.Validator), true) return err } diff --git a/tests/integration/staking/keeper/delegation_test.go b/tests/integration/staking/keeper/delegation_test.go index 384e949149f1..0b0ed641dcb1 100644 --- a/tests/integration/staking/keeper/delegation_test.go +++ b/tests/integration/staking/keeper/delegation_test.go @@ -96,3 +96,173 @@ func TestUnbondingDelegationsMaxEntries(t *testing.T) { require.True(math.IntEq(t, newBonded, oldBonded.SubRaw(1))) require.True(math.IntEq(t, newNotBonded, oldNotBonded.AddRaw(1))) } + +func TestValidatorBondUndelegate(t *testing.T) { + _, app, ctx := createTestInput(t) + + addrDels := simtestutil.AddTestAddrs(app.BankKeeper, app.StakingKeeper, ctx, 2, app.StakingKeeper.TokensFromConsensusPower(ctx, 10000)) + addrVals := simtestutil.ConvertAddrsToValAddrs(addrDels) + + startTokens := app.StakingKeeper.TokensFromConsensusPower(ctx, 10) + + bondDenom := app.StakingKeeper.BondDenom(ctx) + notBondedPool := app.StakingKeeper.GetNotBondedPool(ctx) + + require.NoError(t, banktestutil.FundModuleAccount(app.BankKeeper, ctx, notBondedPool.GetName(), sdk.NewCoins(sdk.NewCoin(bondDenom, startTokens)))) + app.AccountKeeper.SetModuleAccount(ctx, notBondedPool) + + // create a validator and a delegator to that validator + validator := testutil.NewValidator(t, addrVals[0], PKs[0]) + validator.Status = types.Bonded + app.StakingKeeper.SetValidator(ctx, validator) + + // set validator bond factor + params := app.StakingKeeper.GetParams(ctx) + params.ValidatorBondFactor = sdk.NewDec(1) + app.StakingKeeper.SetParams(ctx, params) + + // convert to validator self-bond + msgServer := keeper.NewMsgServerImpl(app.StakingKeeper) + + validator, _ = app.StakingKeeper.GetValidator(ctx, addrVals[0]) + err := delegateCoinsFromAccount(ctx, *app.StakingKeeper, addrDels[0], startTokens, validator) + require.NoError(t, err) + _, err = msgServer.ValidatorBond(sdk.WrapSDKContext(ctx), &types.MsgValidatorBond{ + DelegatorAddress: addrDels[0].String(), + ValidatorAddress: addrVals[0].String(), + }) + require.NoError(t, err) + + // tokenize share for 2nd account delegation + validator, _ = app.StakingKeeper.GetValidator(ctx, addrVals[0]) + err = delegateCoinsFromAccount(ctx, *app.StakingKeeper, addrDels[1], startTokens, validator) + require.NoError(t, err) + tokenizeShareResp, err := msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &types.MsgTokenizeShares{ + DelegatorAddress: addrDels[1].String(), + ValidatorAddress: addrVals[0].String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, startTokens), + TokenizedShareOwner: addrDels[0].String(), + }) + require.NoError(t, err) + + // try undelegating + _, err = msgServer.Undelegate(sdk.WrapSDKContext(ctx), &types.MsgUndelegate{ + DelegatorAddress: addrDels[0].String(), + ValidatorAddress: addrVals[0].String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, startTokens), + }) + require.Error(t, err) + + // redeem full amount on 2nd account and try undelegation + validator, _ = app.StakingKeeper.GetValidator(ctx, addrVals[0]) + err = delegateCoinsFromAccount(ctx, *app.StakingKeeper, addrDels[1], startTokens, validator) + require.NoError(t, err) + _, err = msgServer.RedeemTokensForShares(sdk.WrapSDKContext(ctx), &types.MsgRedeemTokensForShares{ + DelegatorAddress: addrDels[1].String(), + Amount: tokenizeShareResp.Amount, + }) + require.NoError(t, err) + + // try undelegating + _, err = msgServer.Undelegate(sdk.WrapSDKContext(ctx), &types.MsgUndelegate{ + DelegatorAddress: addrDels[0].String(), + ValidatorAddress: addrVals[0].String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, startTokens), + }) + require.NoError(t, err) + + validator, _ = app.StakingKeeper.GetValidator(ctx, addrVals[0]) + require.Equal(t, validator.ValidatorBondShares, sdk.ZeroDec()) +} + +func TestValidatorBondRedelegate(t *testing.T) { + _, app, ctx := createTestInput(t) + + addrDels := simtestutil.AddTestAddrs(app.BankKeeper, app.StakingKeeper, ctx, 2, app.StakingKeeper.TokensFromConsensusPower(ctx, 10000)) + addrVals := simtestutil.ConvertAddrsToValAddrs(addrDels) + + startTokens := app.StakingKeeper.TokensFromConsensusPower(ctx, 10) + + bondDenom := app.StakingKeeper.BondDenom(ctx) + notBondedPool := app.StakingKeeper.GetNotBondedPool(ctx) + + startPoolToken := sdk.NewCoins(sdk.NewCoin(bondDenom, startTokens.Mul(sdk.NewInt(2)))) + require.NoError(t, banktestutil.FundModuleAccount(app.BankKeeper, ctx, notBondedPool.GetName(), startPoolToken)) + app.AccountKeeper.SetModuleAccount(ctx, notBondedPool) + + // create a validator and a delegator to that validator + validator := testutil.NewValidator(t, addrVals[0], PKs[0]) + validator.Status = types.Bonded + app.StakingKeeper.SetValidator(ctx, validator) + validator2 := testutil.NewValidator(t, addrVals[1], PKs[1]) + validator.Status = types.Bonded + app.StakingKeeper.SetValidator(ctx, validator2) + + // set validator bond factor + params := app.StakingKeeper.GetParams(ctx) + params.ValidatorBondFactor = sdk.NewDec(1) + app.StakingKeeper.SetParams(ctx, params) + + // set total liquid stake + app.StakingKeeper.SetTotalLiquidStakedTokens(ctx, sdk.NewInt(100)) + + // delegate to each validator + validator, _ = app.StakingKeeper.GetValidator(ctx, addrVals[0]) + err := delegateCoinsFromAccount(ctx, *app.StakingKeeper, addrDels[0], startTokens, validator) + require.NoError(t, err) + + validator2, _ = app.StakingKeeper.GetValidator(ctx, addrVals[1]) + err = delegateCoinsFromAccount(ctx, *app.StakingKeeper, addrDels[1], startTokens, validator2) + require.NoError(t, err) + + // convert to validator self-bond + msgServer := keeper.NewMsgServerImpl(app.StakingKeeper) + _, err = msgServer.ValidatorBond(sdk.WrapSDKContext(ctx), &types.MsgValidatorBond{ + DelegatorAddress: addrDels[0].String(), + ValidatorAddress: addrVals[0].String(), + }) + require.NoError(t, err) + + // tokenize share for 2nd account delegation + validator, _ = app.StakingKeeper.GetValidator(ctx, addrVals[0]) + err = delegateCoinsFromAccount(ctx, *app.StakingKeeper, addrDels[1], startTokens, validator) + require.NoError(t, err) + tokenizeShareResp, err := msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &types.MsgTokenizeShares{ + DelegatorAddress: addrDels[1].String(), + ValidatorAddress: addrVals[0].String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, startTokens), + TokenizedShareOwner: addrDels[0].String(), + }) + require.NoError(t, err) + + // try undelegating + _, err = msgServer.BeginRedelegate(sdk.WrapSDKContext(ctx), &types.MsgBeginRedelegate{ + DelegatorAddress: addrDels[0].String(), + ValidatorSrcAddress: addrVals[0].String(), + ValidatorDstAddress: addrVals[1].String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, startTokens), + }) + require.Error(t, err) + + // redeem full amount on 2nd account and try undelegation + validator, _ = app.StakingKeeper.GetValidator(ctx, addrVals[0]) + err = delegateCoinsFromAccount(ctx, *app.StakingKeeper, addrDels[1], startTokens, validator) + require.NoError(t, err) + _, err = msgServer.RedeemTokensForShares(sdk.WrapSDKContext(ctx), &types.MsgRedeemTokensForShares{ + DelegatorAddress: addrDels[1].String(), + Amount: tokenizeShareResp.Amount, + }) + require.NoError(t, err) + + // try undelegating + _, err = msgServer.BeginRedelegate(sdk.WrapSDKContext(ctx), &types.MsgBeginRedelegate{ + DelegatorAddress: addrDels[0].String(), + ValidatorSrcAddress: addrVals[0].String(), + ValidatorDstAddress: addrVals[1].String(), + Amount: sdk.NewCoin(sdk.DefaultBondDenom, startTokens), + }) + require.NoError(t, err) + + validator, _ = app.StakingKeeper.GetValidator(ctx, addrVals[0]) + require.Equal(t, validator.ValidatorBondShares, sdk.ZeroDec()) +} diff --git a/tests/integration/staking/keeper/determinstic_test.go b/tests/integration/staking/keeper/determinstic_test.go index eefb8c7ec819..ffa5a675f43f 100644 --- a/tests/integration/staking/keeper/determinstic_test.go +++ b/tests/integration/staking/keeper/determinstic_test.go @@ -60,7 +60,6 @@ func (s *DeterministicTestSuite) SetupTest() { ) s.Require().NoError(err) - // s.ctx = app.BaseApp.NewContext(false, tmproto.Header{Height: 1, Time: time.Now()}).WithGasMeter(sdk.NewInfiniteGasMeter()) s.ctx = app.BaseApp.NewContext(false, tmproto.Header{}) queryHelper := baseapp.NewQueryServerTestHelper(s.ctx, interfaceRegistry) @@ -258,7 +257,8 @@ func (suite *DeterministicTestSuite) TestGRPCValidator() { ValidatorAddr: val.OperatorAddress, } - testdata.DeterministicIterations(suite.ctx, suite.Require(), req, suite.queryClient.Validator, 1915, false) + // NOTE: gas consumption delta changed from 1915 to 1933 + testdata.DeterministicIterations(suite.ctx, suite.Require(), req, suite.queryClient.Validator, 1933, false) } func (suite *DeterministicTestSuite) TestGRPCValidators() { @@ -281,7 +281,8 @@ func (suite *DeterministicTestSuite) TestGRPCValidators() { suite.getStaticValidator() suite.getStaticValidator2() - testdata.DeterministicIterations(suite.ctx, suite.Require(), &stakingtypes.QueryValidatorsRequest{}, suite.queryClient.Validators, 3525, false) + // NOTE: gas consumption delta changed from 3525 to 3597 + testdata.DeterministicIterations(suite.ctx, suite.Require(), &stakingtypes.QueryValidatorsRequest{}, suite.queryClient.Validators, 3597, false) } func (suite *DeterministicTestSuite) TestGRPCValidatorDelegations() { @@ -317,7 +318,8 @@ func (suite *DeterministicTestSuite) TestGRPCValidatorDelegations() { ValidatorAddr: validator.OperatorAddress, } - testdata.DeterministicIterations(suite.ctx, suite.Require(), req, suite.queryClient.ValidatorDelegations, 11985, false) + // NOTE: gas consumption delta changed from 11985 to 12615 + testdata.DeterministicIterations(suite.ctx, suite.Require(), req, suite.queryClient.ValidatorDelegations, 12615, false) } func (suite *DeterministicTestSuite) TestGRPCValidatorUnbondingDelegations() { @@ -390,7 +392,8 @@ func (suite *DeterministicTestSuite) TestGRPCDelegation() { DelegatorAddr: delegator1, } - testdata.DeterministicIterations(suite.ctx, suite.Require(), req, suite.queryClient.Delegation, 4635, false) + // NOTE: gas consumption delta changed from 4635 to 4845 + testdata.DeterministicIterations(suite.ctx, suite.Require(), req, suite.queryClient.Delegation, 4845, false) } func (suite *DeterministicTestSuite) TestGRPCUnbondingDelegation() { @@ -457,7 +460,8 @@ func (suite *DeterministicTestSuite) TestGRPCDelegatorDelegations() { DelegatorAddr: delegator1, } - testdata.DeterministicIterations(suite.ctx, suite.Require(), req, suite.queryClient.DelegatorDelegations, 4238, false) + // NOTE: gas consumption delta changed from 4238 to 4448 + testdata.DeterministicIterations(suite.ctx, suite.Require(), req, suite.queryClient.DelegatorDelegations, 4448, false) } func (suite *DeterministicTestSuite) TestGRPCDelegatorValidator() { @@ -488,7 +492,8 @@ func (suite *DeterministicTestSuite) TestGRPCDelegatorValidator() { ValidatorAddr: validator.OperatorAddress, } - testdata.DeterministicIterations(suite.ctx, suite.Require(), req, suite.queryClient.DelegatorValidator, 3563, false) + // NOTE: gas consumption delta changed from 3563 to 3581 + testdata.DeterministicIterations(suite.ctx, suite.Require(), req, suite.queryClient.DelegatorValidator, 3581, false) } func (suite *DeterministicTestSuite) TestGRPCDelegatorUnbondingDelegations() { @@ -579,7 +584,8 @@ func (suite *DeterministicTestSuite) TestGRPCHistoricalInfo() { Height: height, } - testdata.DeterministicIterations(suite.ctx, suite.Require(), req, suite.queryClient.HistoricalInfo, 1930, false) + // NOTE: gas consumption delta changed from 1930 to 1948 + testdata.DeterministicIterations(suite.ctx, suite.Require(), req, suite.queryClient.HistoricalInfo, 1948, false) } func (suite *DeterministicTestSuite) TestGRPCDelegatorValidators() { @@ -609,7 +615,9 @@ func (suite *DeterministicTestSuite) TestGRPCDelegatorValidators() { suite.Require().NoError(err) req := &stakingtypes.QueryDelegatorValidatorsRequest{DelegatorAddr: delegator1} - testdata.DeterministicIterations(suite.ctx, suite.Require(), req, suite.queryClient.DelegatorValidators, 3166, false) + + // NOTE: gas consumption delta changed from 3166 to 3184 + testdata.DeterministicIterations(suite.ctx, suite.Require(), req, suite.queryClient.DelegatorValidators, 3184, false) } func (suite *DeterministicTestSuite) TestGRPCPool() { @@ -621,7 +629,9 @@ func (suite *DeterministicTestSuite) TestGRPCPool() { suite.SetupTest() // reset suite.getStaticValidator() - testdata.DeterministicIterations(suite.ctx, suite.Require(), &stakingtypes.QueryPoolRequest{}, suite.queryClient.Pool, 6185, false) + + // NOTE: gas consumption delta changed from 6185 to 6377 + testdata.DeterministicIterations(suite.ctx, suite.Require(), &stakingtypes.QueryPoolRequest{}, suite.queryClient.Pool, 6377, false) } func (suite *DeterministicTestSuite) TestGRPCRedelegations() { @@ -683,18 +693,22 @@ func (suite *DeterministicTestSuite) TestGRPCRedelegations() { DstValidatorAddr: validator2, } - testdata.DeterministicIterations(suite.ctx, suite.Require(), req, suite.queryClient.Redelegations, 3920, false) + // NOTE: gas consumption delta changed from 3920 to 3938 + testdata.DeterministicIterations(suite.ctx, suite.Require(), req, suite.queryClient.Redelegations, 3938, false) } func (suite *DeterministicTestSuite) TestGRPCParams() { rapid.Check(suite.T(), func(t *rapid.T) { params := stakingtypes.Params{ - BondDenom: rapid.StringMatching(sdk.DefaultCoinDenomRegex()).Draw(t, "bond-denom"), - UnbondingTime: durationGenerator().Draw(t, "duration"), - MaxValidators: rapid.Uint32Min(1).Draw(t, "max-validators"), - MaxEntries: rapid.Uint32Min(1).Draw(t, "max-entries"), - HistoricalEntries: rapid.Uint32Min(1).Draw(t, "historical-entries"), - MinCommissionRate: sdk.NewDecWithPrec(rapid.Int64Range(0, 100).Draw(t, "commission"), 2), + BondDenom: rapid.StringMatching(sdk.DefaultCoinDenomRegex()).Draw(t, "bond-denom"), + UnbondingTime: durationGenerator().Draw(t, "duration"), + MaxValidators: rapid.Uint32Min(1).Draw(t, "max-validators"), + MaxEntries: rapid.Uint32Min(1).Draw(t, "max-entries"), + HistoricalEntries: rapid.Uint32Min(1).Draw(t, "historical-entries"), + MinCommissionRate: sdk.NewDecWithPrec(rapid.Int64Range(0, 100).Draw(t, "commission"), 2), + ValidatorBondFactor: sdk.NewDecWithPrec(rapid.Int64Range(0, 100).Draw(t, "bond-factor"), 2), + GlobalLiquidStakingCap: sdk.NewDecWithPrec(rapid.Int64Range(0, 100).Draw(t, "global-liquid-staking-cap"), 2), + ValidatorLiquidStakingCap: sdk.NewDecWithPrec(rapid.Int64Range(0, 100).Draw(t, "validator-liquid-staking-cap"), 2), } err := suite.stakingKeeper.SetParams(suite.ctx, params) @@ -704,16 +718,20 @@ func (suite *DeterministicTestSuite) TestGRPCParams() { }) params := stakingtypes.Params{ - BondDenom: "denom", - UnbondingTime: time.Hour, - MaxValidators: 85, - MaxEntries: 5, - HistoricalEntries: 5, - MinCommissionRate: sdk.NewDecWithPrec(5, 2), + BondDenom: "denom", + UnbondingTime: time.Hour, + MaxValidators: 85, + MaxEntries: 5, + HistoricalEntries: 5, + MinCommissionRate: sdk.NewDecWithPrec(5, 2), + ValidatorBondFactor: sdk.NewDecWithPrec(18, 2), + GlobalLiquidStakingCap: sdk.NewDecWithPrec(11, 2), + ValidatorLiquidStakingCap: sdk.NewDecWithPrec(2, 2), } err := suite.stakingKeeper.SetParams(suite.ctx, params) suite.Require().NoError(err) - testdata.DeterministicIterations(suite.ctx, suite.Require(), &stakingtypes.QueryParamsRequest{}, suite.queryClient.Params, 1114, false) + // NOTE: gas consumption delta changed from 1114 to 1291 + testdata.DeterministicIterations(suite.ctx, suite.Require(), &stakingtypes.QueryParamsRequest{}, suite.queryClient.Params, 1291, false) } diff --git a/tests/integration/staking/keeper/genesis_test.go b/tests/integration/staking/keeper/genesis_test.go index 7e453a59fbec..0e8a9b222f3d 100644 --- a/tests/integration/staking/keeper/genesis_test.go +++ b/tests/integration/staking/keeper/genesis_test.go @@ -3,6 +3,7 @@ package keeper_test import ( "fmt" "testing" + "time" "cosmossdk.io/math" abci "github.com/cometbft/cometbft/abci/types" @@ -11,8 +12,9 @@ import ( "cosmossdk.io/simapp" codectypes "github.com/cosmos/cosmos-sdk/codec/types" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" - "github.com/cosmos/cosmos-sdk/x/bank/testutil" + banktestutil "github.com/cosmos/cosmos-sdk/x/bank/testutil" "github.com/cosmos/cosmos-sdk/x/staking" "github.com/cosmos/cosmos-sdk/x/staking/types" ) @@ -65,7 +67,7 @@ func TestInitGenesis(t *testing.T) { // mint coins in the bonded pool representing the validators coins i2 := len(validators) - 1 // -1 to exclude genesis validator require.NoError(t, - testutil.FundModuleAccount( + banktestutil.FundModuleAccount( app.BankKeeper, ctx, types.BondedPoolName, @@ -199,7 +201,7 @@ func TestInitGenesisLargeValidatorSet(t *testing.T) { // mint coins in the bonded pool representing the validators coins require.NoError(t, - testutil.FundModuleAccount( + banktestutil.FundModuleAccount( app.BankKeeper, ctx, types.BondedPoolName, @@ -219,46 +221,51 @@ func TestInitGenesisLargeValidatorSet(t *testing.T) { require.Equal(t, abcivals, vals) } -// TODO: refactor LSM TEST func TestInitExportLiquidStakingGenesis(t *testing.T) { - // app, ctx, addrs := bootstrapGenesisTest(t, 2) - // address1, address2 := addrs[0], addrs[1] - - // // Mock out a genesis state - // inGenesisState := types.GenesisState{ - // Params: types.DefaultParams(), - // TokenizeShareRecords: []types.TokenizeShareRecord{ - // {Id: 1, Owner: address1.String(), ModuleAccount: "module1", Validator: "val1"}, - // {Id: 2, Owner: address2.String(), ModuleAccount: "module2", Validator: "val2"}, - // }, - // LastTokenizeShareRecordId: 2, - // TotalLiquidStakedTokens: sdk.NewInt(1_000_000), - // TokenizeShareLocks: []types.TokenizeShareLock{ - // { - // Address: address1.String(), - // Status: types.TOKENIZE_SHARE_LOCK_STATUS_LOCKED.String(), - // }, - // { - // Address: address2.String(), - // Status: types.TOKENIZE_SHARE_LOCK_STATUS_LOCK_EXPIRING.String(), - // CompletionTime: time.Date(2023, 1, 1, 1, 0, 0, 0, time.UTC), - // }, - // }, - // } - - // // Call init and then export genesis - confirming the same state is returned - // staking.InitGenesis(ctx, app.StakingKeeper, app.AccountKeeper, app.BankKeeper, &inGenesisState) - // outGenesisState := *staking.ExportGenesis(ctx, app.StakingKeeper) - - // require.ElementsMatch(t, inGenesisState.TokenizeShareRecords, outGenesisState.TokenizeShareRecords, - // "tokenize share records") - - // require.Equal(t, inGenesisState.LastTokenizeShareRecordId, outGenesisState.LastTokenizeShareRecordId, - // "last tokenize share record ID") - - // require.Equal(t, inGenesisState.TotalLiquidStakedTokens.Int64(), outGenesisState.TotalLiquidStakedTokens.Int64(), - // "total liquid staked") - - // require.ElementsMatch(t, inGenesisState.TokenizeShareLocks, outGenesisState.TokenizeShareLocks, - // "tokenize share locks") + app := simapp.Setup(t, false) + ctx := app.NewContext(false, tmproto.Header{}) + + addresses := simtestutil.AddTestAddrs(app.BankKeeper, app.StakingKeeper, ctx, 2, sdk.OneInt()) + addrAcc1, addrAcc2 := addresses[0], addresses[1] + + // Mock out a genesis state + inGenesisState := types.GenesisState{ + Params: types.DefaultParams(), + TokenizeShareRecords: []types.TokenizeShareRecord{ + {Id: 1, Owner: addrAcc1.String(), ModuleAccount: "module1", Validator: "val1"}, + {Id: 2, Owner: addrAcc2.String(), ModuleAccount: "module2", Validator: "val2"}, + }, + LastTokenizeShareRecordId: 2, + TotalLiquidStakedTokens: sdk.NewInt(1_000_000), + TokenizeShareLocks: []types.TokenizeShareLock{ + { + Address: addrAcc1.String(), + Status: types.TOKENIZE_SHARE_LOCK_STATUS_LOCKED.String(), + }, + { + Address: addrAcc2.String(), + Status: types.TOKENIZE_SHARE_LOCK_STATUS_LOCK_EXPIRING.String(), + CompletionTime: time.Date(2023, 1, 1, 1, 0, 0, 0, time.UTC), + }, + }, + // Ensure that the bonded pool balance matches the bonded coins by passing existing validators + // Note: the above wasn't required in v0.45.16-ics-lsm + Validators: app.StakingKeeper.GetAllValidators(ctx), + } + + // Call init and then export genesis - confirming the same state is returned + app.StakingKeeper.InitGenesis(ctx, &inGenesisState) + outGenesisState := app.StakingKeeper.ExportGenesis(ctx) + + require.ElementsMatch(t, inGenesisState.TokenizeShareRecords, outGenesisState.TokenizeShareRecords, + "tokenize share records") + + require.Equal(t, inGenesisState.LastTokenizeShareRecordId, outGenesisState.LastTokenizeShareRecordId, + "last tokenize share record ID") + + require.Equal(t, inGenesisState.TotalLiquidStakedTokens.Int64(), outGenesisState.TotalLiquidStakedTokens.Int64(), + "total liquid staked") + + require.ElementsMatch(t, inGenesisState.TokenizeShareLocks, outGenesisState.TokenizeShareLocks, + "tokenize share locks") } diff --git a/tests/integration/staking/keeper/liquid_stake_test.go b/tests/integration/staking/keeper/liquid_stake_test.go new file mode 100644 index 000000000000..1f0a96382f16 --- /dev/null +++ b/tests/integration/staking/keeper/liquid_stake_test.go @@ -0,0 +1,274 @@ +package keeper_test + +import ( + "testing" + + "cosmossdk.io/math" + tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + accountkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" + "github.com/cosmos/cosmos-sdk/x/staking/keeper" + "github.com/cosmos/cosmos-sdk/x/staking/testutil" + "github.com/cosmos/cosmos-sdk/x/staking/types" + "github.com/stretchr/testify/require" +) + +// Helper function to clear the Bonded pool balances before a unit test +func clearPoolBalance(t *testing.T, sk keeper.Keeper, ak accountkeeper.AccountKeeper, bk bankkeeper.Keeper, ctx sdk.Context) { + bondDenom := sk.BondDenom(ctx) + initialBondedBalance := bk.GetBalance(ctx, ak.GetModuleAddress(types.BondedPoolName), bondDenom) + + err := bk.SendCoinsFromModuleToModule(ctx, types.BondedPoolName, minttypes.ModuleName, sdk.NewCoins(initialBondedBalance)) + require.NoError(t, err, "no error expected when clearing bonded pool balance") +} + +// Helper function to fund the Bonded pool balances before a unit test +func fundPoolBalance(t *testing.T, sk keeper.Keeper, bk bankkeeper.Keeper, ctx sdk.Context, amount math.Int) { + bondDenom := sk.BondDenom(ctx) + bondedPoolCoin := sdk.NewCoin(bondDenom, amount) + + err := bk.MintCoins(ctx, minttypes.ModuleName, sdk.NewCoins(bondedPoolCoin)) + require.NoError(t, err, "no error expected when minting") + + err = bk.SendCoinsFromModuleToModule(ctx, minttypes.ModuleName, types.BondedPoolName, sdk.NewCoins(bondedPoolCoin)) + require.NoError(t, err, "no error expected when sending tokens to bonded pool") +} + +// Tests CheckExceedsGlobalLiquidStakingCap +func TestCheckExceedsGlobalLiquidStakingCap(t *testing.T) { + var ( + accountKeeper accountkeeper.AccountKeeper + bankKeeper bankkeeper.Keeper + stakingKeeper *keeper.Keeper + ) + + app, err := simtestutil.Setup(testutil.AppConfig, + &accountKeeper, + &bankKeeper, + &stakingKeeper, + ) + require.NoError(t, err) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + testCases := []struct { + name string + globalLiquidCap sdk.Dec + totalLiquidStake math.Int + totalStake math.Int + newLiquidStake math.Int + tokenizingShares bool + expectedExceeds bool + }{ + { + // Cap: 10% - Native Delegation - Delegation Below Threshold + // Total Liquid Stake: 5, Total Stake: 95, New Liquid Stake: 1 + // => Total Liquid Stake: 5+1=6, Total Stake: 95+1=96 => 6/96 = 6% < 10% cap + name: "10 percent cap _ native delegation _ delegation below cap", + globalLiquidCap: sdk.MustNewDecFromStr("0.1"), + totalLiquidStake: sdk.NewInt(5), + totalStake: sdk.NewInt(95), + newLiquidStake: sdk.NewInt(1), + tokenizingShares: false, + expectedExceeds: false, + }, + { + // Cap: 10% - Native Delegation - Delegation At Threshold + // Total Liquid Stake: 5, Total Stake: 95, New Liquid Stake: 5 + // => Total Liquid Stake: 5+5=10, Total Stake: 95+5=100 => 10/100 = 10% == 10% cap + name: "10 percent cap _ native delegation _ delegation equals cap", + globalLiquidCap: sdk.MustNewDecFromStr("0.1"), + totalLiquidStake: sdk.NewInt(5), + totalStake: sdk.NewInt(95), + newLiquidStake: sdk.NewInt(5), + tokenizingShares: false, + expectedExceeds: false, + }, + { + // Cap: 10% - Native Delegation - Delegation Exceeds Threshold + // Total Liquid Stake: 5, Total Stake: 95, New Liquid Stake: 6 + // => Total Liquid Stake: 5+6=11, Total Stake: 95+6=101 => 11/101 = 11% > 10% cap + name: "10 percent cap _ native delegation _ delegation exceeds cap", + globalLiquidCap: sdk.MustNewDecFromStr("0.1"), + totalLiquidStake: sdk.NewInt(5), + totalStake: sdk.NewInt(95), + newLiquidStake: sdk.NewInt(6), + tokenizingShares: false, + expectedExceeds: true, + }, + { + // Cap: 20% - Native Delegation - Delegation Below Threshold + // Total Liquid Stake: 20, Total Stake: 220, New Liquid Stake: 29 + // => Total Liquid Stake: 20+29=49, Total Stake: 220+29=249 => 49/249 = 19% < 20% cap + name: "20 percent cap _ native delegation _ delegation below cap", + globalLiquidCap: sdk.MustNewDecFromStr("0.20"), + totalLiquidStake: sdk.NewInt(20), + totalStake: sdk.NewInt(220), + newLiquidStake: sdk.NewInt(29), + tokenizingShares: false, + expectedExceeds: false, + }, + { + // Cap: 20% - Native Delegation - Delegation At Threshold + // Total Liquid Stake: 20, Total Stake: 220, New Liquid Stake: 30 + // => Total Liquid Stake: 20+30=50, Total Stake: 220+30=250 => 50/250 = 20% == 20% cap + name: "20 percent cap _ native delegation _ delegation equals cap", + globalLiquidCap: sdk.MustNewDecFromStr("0.20"), + totalLiquidStake: sdk.NewInt(20), + totalStake: sdk.NewInt(220), + newLiquidStake: sdk.NewInt(30), + tokenizingShares: false, + expectedExceeds: false, + }, + { + // Cap: 20% - Native Delegation - Delegation Exceeds Threshold + // Total Liquid Stake: 20, Total Stake: 220, New Liquid Stake: 31 + // => Total Liquid Stake: 20+31=51, Total Stake: 220+31=251 => 51/251 = 21% > 20% cap + name: "20 percent cap _ native delegation _ delegation exceeds cap", + globalLiquidCap: sdk.MustNewDecFromStr("0.20"), + totalLiquidStake: sdk.NewInt(20), + totalStake: sdk.NewInt(220), + newLiquidStake: sdk.NewInt(31), + tokenizingShares: false, + expectedExceeds: true, + }, + { + // Cap: 50% - Native Delegation - Delegation Below Threshold + // Total Liquid Stake: 0, Total Stake: 100, New Liquid Stake: 50 + // => Total Liquid Stake: 0+50=50, Total Stake: 100+50=150 => 50/150 = 33% < 50% cap + name: "50 percent cap _ native delegation _ delegation below cap", + globalLiquidCap: sdk.MustNewDecFromStr("0.5"), + totalLiquidStake: sdk.NewInt(0), + totalStake: sdk.NewInt(100), + newLiquidStake: sdk.NewInt(50), + tokenizingShares: false, + expectedExceeds: false, + }, + { + // Cap: 50% - Tokenized Delegation - Delegation At Threshold + // Total Liquid Stake: 0, Total Stake: 100, New Liquid Stake: 50 + // => 50 / 100 = 50% == 50% cap + name: "50 percent cap _ tokenized delegation _ delegation equals cap", + globalLiquidCap: sdk.MustNewDecFromStr("0.5"), + totalLiquidStake: sdk.NewInt(0), + totalStake: sdk.NewInt(100), + newLiquidStake: sdk.NewInt(50), + tokenizingShares: true, + expectedExceeds: false, + }, + { + // Cap: 50% - Native Delegation - Delegation Below Threshold + // Total Liquid Stake: 0, Total Stake: 100, New Liquid Stake: 51 + // => Total Liquid Stake: 0+51=51, Total Stake: 100+51=151 => 51/151 = 33% < 50% cap + name: "50 percent cap _ native delegation _ delegation below cap", + globalLiquidCap: sdk.MustNewDecFromStr("0.5"), + totalLiquidStake: sdk.NewInt(0), + totalStake: sdk.NewInt(100), + newLiquidStake: sdk.NewInt(51), + tokenizingShares: false, + expectedExceeds: false, + }, + { + // Cap: 50% - Tokenized Delegation - Delegation Exceeds Threshold + // Total Liquid Stake: 0, Total Stake: 100, New Liquid Stake: 51 + // => 51 / 100 = 51% > 50% cap + name: "50 percent cap _ tokenized delegation _delegation exceeds cap", + globalLiquidCap: sdk.MustNewDecFromStr("0.5"), + totalLiquidStake: sdk.NewInt(0), + totalStake: sdk.NewInt(100), + newLiquidStake: sdk.NewInt(51), + tokenizingShares: true, + expectedExceeds: true, + }, + { + // Cap of 0% - everything should exceed + name: "0 percent cap", + globalLiquidCap: sdk.ZeroDec(), + totalLiquidStake: sdk.NewInt(0), + totalStake: sdk.NewInt(1_000_000), + newLiquidStake: sdk.NewInt(1), + tokenizingShares: false, + expectedExceeds: true, + }, + { + // Cap of 100% - nothing should exceed + name: "100 percent cap", + globalLiquidCap: sdk.OneDec(), + totalLiquidStake: sdk.NewInt(1), + totalStake: sdk.NewInt(1), + newLiquidStake: sdk.NewInt(1_000_000), + tokenizingShares: false, + expectedExceeds: false, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + // Update the global liquid staking cap + params := stakingKeeper.GetParams(ctx) + params.GlobalLiquidStakingCap = tc.globalLiquidCap + stakingKeeper.SetParams(ctx, params) + + // Update the total liquid tokens + stakingKeeper.SetTotalLiquidStakedTokens(ctx, tc.totalLiquidStake) + + // Fund each pool for the given test case + clearPoolBalance(t, *stakingKeeper, accountKeeper, bankKeeper, ctx) + fundPoolBalance(t, *stakingKeeper, bankKeeper, ctx, tc.totalStake) + + // Check if the new tokens would exceed the global cap + actualExceeds := stakingKeeper.CheckExceedsGlobalLiquidStakingCap(ctx, tc.newLiquidStake, tc.tokenizingShares) + require.Equal(t, tc.expectedExceeds, actualExceeds, tc.name) + }) + } +} + +// Tests SafelyIncreaseTotalLiquidStakedTokens +func TestSafelyIncreaseTotalLiquidStakedTokens(t *testing.T) { + var ( + accountKeeper accountkeeper.AccountKeeper + bankKeeper bankkeeper.Keeper + stakingKeeper *keeper.Keeper + ) + + app, err := simtestutil.Setup(testutil.AppConfig, + &accountKeeper, + &bankKeeper, + &stakingKeeper, + ) + require.NoError(t, err) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + + intitialTotalLiquidStaked := sdk.NewInt(100) + increaseAmount := sdk.NewInt(10) + poolBalance := sdk.NewInt(200) + + // Set the total staked and total liquid staked amounts + // which are required components when checking the global cap + // Total stake is calculated from the pool balance + clearPoolBalance(t, *stakingKeeper, accountKeeper, bankKeeper, ctx) + fundPoolBalance(t, *stakingKeeper, bankKeeper, ctx, poolBalance) + stakingKeeper.SetTotalLiquidStakedTokens(ctx, intitialTotalLiquidStaked) + + // Set the global cap such that a small delegation would exceed the cap + params := stakingKeeper.GetParams(ctx) + params.GlobalLiquidStakingCap = sdk.MustNewDecFromStr("0.0001") + stakingKeeper.SetParams(ctx, params) + + // Attempt to increase the total liquid stake again, it should error since + // the cap was exceeded + err = stakingKeeper.SafelyIncreaseTotalLiquidStakedTokens(ctx, increaseAmount, true) + require.ErrorIs(t, err, types.ErrGlobalLiquidStakingCapExceeded) + require.Equal(t, intitialTotalLiquidStaked, stakingKeeper.GetTotalLiquidStakedTokens(ctx)) + + // Now relax the cap so that the increase succeeds + params.GlobalLiquidStakingCap = sdk.MustNewDecFromStr("0.99") + stakingKeeper.SetParams(ctx, params) + + // Confirm the total increased + err = stakingKeeper.SafelyIncreaseTotalLiquidStakedTokens(ctx, increaseAmount, true) + require.NoError(t, err) + require.Equal(t, intitialTotalLiquidStaked.Add(increaseAmount), stakingKeeper.GetTotalLiquidStakedTokens(ctx)) +} diff --git a/tests/integration/staking/keeper/msg_server_test.go b/tests/integration/staking/keeper/msg_server_test.go index 76ac622175f7..535ecfcc9190 100644 --- a/tests/integration/staking/keeper/msg_server_test.go +++ b/tests/integration/staking/keeper/msg_server_test.go @@ -1,16 +1,28 @@ package keeper_test import ( + "fmt" "testing" "time" + "cosmossdk.io/math" + "cosmossdk.io/simapp" tmproto "github.com/cometbft/cometbft/proto/tendermint/types" + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" sdk "github.com/cosmos/cosmos-sdk/types" - - "cosmossdk.io/simapp" - "github.com/cosmos/cosmos-sdk/x/bank/testutil" + "github.com/cosmos/cosmos-sdk/types/address" + accountkeeper "github.com/cosmos/cosmos-sdk/x/auth/keeper" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + vesting "github.com/cosmos/cosmos-sdk/x/auth/vesting/exported" + vestingtypes "github.com/cosmos/cosmos-sdk/x/auth/vesting/types" + bankkeeper "github.com/cosmos/cosmos-sdk/x/bank/keeper" + banktestutil "github.com/cosmos/cosmos-sdk/x/bank/testutil" + minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" "github.com/cosmos/cosmos-sdk/x/staking/keeper" + "github.com/cosmos/cosmos-sdk/x/staking/testutil" "github.com/cosmos/cosmos-sdk/x/staking/types" + stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" "github.com/stretchr/testify/require" ) @@ -25,7 +37,7 @@ func TestCancelUnbondingDelegation(t *testing.T) { notBondedPool := app.StakingKeeper.GetNotBondedPool(ctx) startTokens := app.StakingKeeper.TokensFromConsensusPower(ctx, 5) - require.NoError(t, testutil.FundModuleAccount(app.BankKeeper, ctx, notBondedPool.GetName(), sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), startTokens)))) + require.NoError(t, banktestutil.FundModuleAccount(app.BankKeeper, ctx, notBondedPool.GetName(), sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), startTokens)))) app.AccountKeeper.SetModuleAccount(ctx, notBondedPool) moduleBalance := app.BankKeeper.GetBalance(ctx, notBondedPool.GetAddress(), app.StakingKeeper.BondDenom(ctx)) @@ -147,1390 +159,1442 @@ func TestCancelUnbondingDelegation(t *testing.T) { } } -// TODO refactor LSM test func TestTokenizeSharesAndRedeemTokens(t *testing.T) { - // app := simapp.Setup(t, false) - // ctx := app.BaseApp.NewContext(false, tmproto.Header{}) - - // liquidStakingCapStrict := sdk.ZeroDec() - // liquidStakingCapConservative := sdk.MustNewDecFromStr("0.8") - // liquidStakingCapDisabled := sdk.OneDec() - - // validatorBondStrict := sdk.OneDec() - // validatorBondConservative := sdk.NewDec(10) - // validatorBondDisabled := sdk.NewDec(-1) - - // testCases := []struct { - // name string - // vestingAmount sdk.Int - // delegationAmount sdk.Int - // tokenizeShareAmount sdk.Int - // redeemAmount sdk.Int - // targetVestingDelAfterShare sdk.Int - // targetVestingDelAfterRedeem sdk.Int - // globalLiquidStakingCap sdk.Dec - // slashFactor sdk.Dec - // validatorLiquidStakingCap sdk.Dec - // validatorBondFactor sdk.Dec - // validatorBondDelegation bool - // validatorBondDelegatorIndex int - // delegatorIsLSTP bool - // expTokenizeErr bool - // expRedeemErr bool - // prevAccountDelegationExists bool - // recordAccountDelegationExists bool - // }{ - // { - // name: "full amount tokenize and redeem", - // vestingAmount: sdk.NewInt(0), - // delegationAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // tokenizeShareAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // redeemAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // slashFactor: sdk.ZeroDec(), - // globalLiquidStakingCap: liquidStakingCapDisabled, - // validatorLiquidStakingCap: liquidStakingCapDisabled, - // validatorBondFactor: validatorBondDisabled, - // validatorBondDelegation: false, - // expTokenizeErr: false, - // expRedeemErr: false, - // prevAccountDelegationExists: false, - // recordAccountDelegationExists: false, - // }, - // { - // name: "full amount tokenize and partial redeem", - // vestingAmount: sdk.NewInt(0), - // delegationAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // tokenizeShareAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // redeemAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // slashFactor: sdk.ZeroDec(), - // globalLiquidStakingCap: liquidStakingCapDisabled, - // validatorLiquidStakingCap: liquidStakingCapDisabled, - // validatorBondFactor: validatorBondDisabled, - // validatorBondDelegation: false, - // expTokenizeErr: false, - // expRedeemErr: false, - // prevAccountDelegationExists: false, - // recordAccountDelegationExists: true, - // }, - // { - // name: "partial amount tokenize and full redeem", - // vestingAmount: sdk.NewInt(0), - // delegationAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // tokenizeShareAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // redeemAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // slashFactor: sdk.ZeroDec(), - // globalLiquidStakingCap: liquidStakingCapDisabled, - // validatorLiquidStakingCap: liquidStakingCapDisabled, - // validatorBondFactor: validatorBondDisabled, - // validatorBondDelegation: false, - // expTokenizeErr: false, - // expRedeemErr: false, - // prevAccountDelegationExists: true, - // recordAccountDelegationExists: false, - // }, - // { - // name: "tokenize and redeem with slash", - // vestingAmount: sdk.NewInt(0), - // delegationAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // tokenizeShareAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // redeemAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // slashFactor: sdk.MustNewDecFromStr("0.1"), - // globalLiquidStakingCap: liquidStakingCapDisabled, - // validatorLiquidStakingCap: liquidStakingCapDisabled, - // validatorBondFactor: validatorBondDisabled, - // validatorBondDelegation: false, - // expTokenizeErr: false, - // expRedeemErr: false, - // prevAccountDelegationExists: false, - // recordAccountDelegationExists: true, - // }, - // { - // name: "over tokenize", - // vestingAmount: sdk.NewInt(0), - // delegationAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // tokenizeShareAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 30), - // redeemAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // slashFactor: sdk.ZeroDec(), - // globalLiquidStakingCap: liquidStakingCapDisabled, - // validatorLiquidStakingCap: liquidStakingCapDisabled, - // validatorBondFactor: validatorBondDisabled, - // validatorBondDelegation: false, - // expTokenizeErr: true, - // expRedeemErr: false, - // }, - // { - // name: "over redeem", - // vestingAmount: sdk.NewInt(0), - // delegationAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // tokenizeShareAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // redeemAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 40), - // slashFactor: sdk.ZeroDec(), - // globalLiquidStakingCap: liquidStakingCapDisabled, - // validatorLiquidStakingCap: liquidStakingCapDisabled, - // validatorBondFactor: validatorBondDisabled, - // validatorBondDelegation: false, - // expTokenizeErr: false, - // expRedeemErr: true, - // }, - // { - // name: "vesting account tokenize share failure", - // vestingAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // delegationAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // tokenizeShareAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // redeemAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // slashFactor: sdk.ZeroDec(), - // globalLiquidStakingCap: liquidStakingCapDisabled, - // validatorLiquidStakingCap: liquidStakingCapDisabled, - // validatorBondFactor: validatorBondDisabled, - // validatorBondDelegation: false, - // expTokenizeErr: true, - // expRedeemErr: false, - // prevAccountDelegationExists: true, - // }, - // { - // name: "vesting account tokenize share success", - // vestingAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // delegationAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // tokenizeShareAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // redeemAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterShare: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterRedeem: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // slashFactor: sdk.ZeroDec(), - // globalLiquidStakingCap: liquidStakingCapDisabled, - // validatorLiquidStakingCap: liquidStakingCapDisabled, - // validatorBondFactor: validatorBondDisabled, - // validatorBondDelegation: false, - // expTokenizeErr: false, - // expRedeemErr: false, - // prevAccountDelegationExists: true, - // }, - // { - // name: "try tokenize share for a validator-bond delegation", - // vestingAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // delegationAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // tokenizeShareAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // redeemAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterShare: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterRedeem: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // slashFactor: sdk.ZeroDec(), - // globalLiquidStakingCap: liquidStakingCapDisabled, - // validatorLiquidStakingCap: liquidStakingCapDisabled, - // validatorBondFactor: validatorBondConservative, - // validatorBondDelegation: true, - // validatorBondDelegatorIndex: 1, - // expTokenizeErr: true, - // expRedeemErr: false, - // prevAccountDelegationExists: true, - // }, - // { - // name: "strict validator-bond - tokenization fails", - // vestingAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // delegationAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // tokenizeShareAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // redeemAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterShare: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterRedeem: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // slashFactor: sdk.ZeroDec(), - // globalLiquidStakingCap: liquidStakingCapDisabled, - // validatorLiquidStakingCap: liquidStakingCapDisabled, - // validatorBondFactor: validatorBondStrict, - // validatorBondDelegation: false, - // expTokenizeErr: true, - // expRedeemErr: false, - // prevAccountDelegationExists: true, - // }, - // { - // name: "conservative validator-bond - successful tokenization", - // vestingAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // delegationAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // tokenizeShareAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // redeemAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterShare: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterRedeem: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // slashFactor: sdk.ZeroDec(), - // globalLiquidStakingCap: liquidStakingCapDisabled, - // validatorLiquidStakingCap: liquidStakingCapDisabled, - // validatorBondFactor: validatorBondConservative, - // validatorBondDelegation: true, - // validatorBondDelegatorIndex: 0, - // expTokenizeErr: false, - // expRedeemErr: false, - // prevAccountDelegationExists: true, - // }, - // { - // name: "strict global liquid staking cap - tokenization fails", - // vestingAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // delegationAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // tokenizeShareAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // redeemAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterShare: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterRedeem: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // slashFactor: sdk.ZeroDec(), - // globalLiquidStakingCap: liquidStakingCapStrict, - // validatorLiquidStakingCap: liquidStakingCapDisabled, - // validatorBondFactor: validatorBondDisabled, - // validatorBondDelegation: true, - // validatorBondDelegatorIndex: 0, - // expTokenizeErr: true, - // expRedeemErr: false, - // prevAccountDelegationExists: true, - // }, - // { - // name: "conservative global liquid staking cap - successful tokenization", - // vestingAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // delegationAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // tokenizeShareAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // redeemAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterShare: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterRedeem: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // slashFactor: sdk.ZeroDec(), - // globalLiquidStakingCap: liquidStakingCapConservative, - // validatorLiquidStakingCap: liquidStakingCapDisabled, - // validatorBondFactor: validatorBondDisabled, - // validatorBondDelegation: true, - // validatorBondDelegatorIndex: 0, - // expTokenizeErr: false, - // expRedeemErr: false, - // prevAccountDelegationExists: true, - // }, - // { - // name: "strict validator liquid staking cap - tokenization fails", - // vestingAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // delegationAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // tokenizeShareAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // redeemAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterShare: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterRedeem: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // slashFactor: sdk.ZeroDec(), - // globalLiquidStakingCap: liquidStakingCapDisabled, - // validatorLiquidStakingCap: liquidStakingCapStrict, - // validatorBondFactor: validatorBondDisabled, - // validatorBondDelegation: true, - // validatorBondDelegatorIndex: 0, - // expTokenizeErr: true, - // expRedeemErr: false, - // prevAccountDelegationExists: true, - // }, - // { - // name: "conservative validator liquid staking cap - successful tokenization", - // vestingAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // delegationAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // tokenizeShareAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // redeemAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterShare: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterRedeem: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // slashFactor: sdk.ZeroDec(), - // globalLiquidStakingCap: liquidStakingCapDisabled, - // validatorLiquidStakingCap: liquidStakingCapConservative, - // validatorBondFactor: validatorBondDisabled, - // validatorBondDelegation: true, - // validatorBondDelegatorIndex: 0, - // expTokenizeErr: false, - // expRedeemErr: false, - // prevAccountDelegationExists: true, - // }, - // { - // name: "all caps set conservatively - successful tokenize share", - // vestingAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // delegationAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // tokenizeShareAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // redeemAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterShare: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterRedeem: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // slashFactor: sdk.ZeroDec(), - // globalLiquidStakingCap: liquidStakingCapConservative, - // validatorLiquidStakingCap: liquidStakingCapConservative, - // validatorBondFactor: validatorBondConservative, - // validatorBondDelegation: true, - // validatorBondDelegatorIndex: 0, - // expTokenizeErr: false, - // expRedeemErr: false, - // prevAccountDelegationExists: true, - // }, - // { - // name: "delegator is a liquid staking provider - accounting should not update", - // vestingAmount: sdk.ZeroInt(), - // delegationAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 20), - // tokenizeShareAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // redeemAmount: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterShare: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // targetVestingDelAfterRedeem: app.StakingKeeper.TokensFromConsensusPower(ctx, 10), - // slashFactor: sdk.ZeroDec(), - // globalLiquidStakingCap: liquidStakingCapConservative, - // validatorLiquidStakingCap: liquidStakingCapConservative, - // validatorBondFactor: validatorBondConservative, - // delegatorIsLSTP: true, - // validatorBondDelegation: true, - // validatorBondDelegatorIndex: 0, - // expTokenizeErr: false, - // expRedeemErr: false, - // prevAccountDelegationExists: true, - // }, - // } - - // for _, tc := range testCases { - // t.Run(tc.name, func(t *testing.T) { - // addrs := simtestutil.AddTestAddrs(app.BankKeeper, ctx, 2, app.StakingKeeper.TokensFromConsensusPower(ctx, 10000)) - // addrAcc1, addrAcc2 := addrs[0], addrs[1] - // addrVal1, addrVal2 := sdk.ValAddress(addrAcc1), sdk.ValAddress(addrAcc2) - - // // Create ICA module account - // icaAccountAddress := createICAAccount(app, ctx) - - // // Fund module account - // delegationCoin := sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), tc.delegationAmount) - // err := app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, sdk.NewCoins(delegationCoin)) - // require.NoError(t, err) - // err = app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, icaAccountAddress, sdk.NewCoins(delegationCoin)) - // require.NoError(t, err) - - // // set the delegator address depending on whether the delegator should be a liquid staking provider - // delegatorAccount := addrAcc2 - // if tc.delegatorIsLSTP { - // delegatorAccount = icaAccountAddress - // } - - // // set validator bond factor and global liquid staking cap - // params := app.StakingKeeper.GetParams(ctx) - // params.ValidatorBondFactor = tc.validatorBondFactor - // params.GlobalLiquidStakingCap = tc.globalLiquidStakingCap - // params.ValidatorLiquidStakingCap = tc.validatorLiquidStakingCap - // app.StakingKeeper.SetParams(ctx, params) - - // // set the total liquid staked tokens - // app.StakingKeeper.SetTotalLiquidStakedTokens(ctx, sdk.ZeroInt()) - - // if !tc.vestingAmount.IsZero() { - // // create vesting account - // pubkey := secp256k1.GenPrivKey().PubKey() - // baseAcc := authtypes.NewBaseAccount(addrAcc2, pubkey, 0, 0) - // initialVesting := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, tc.vestingAmount)) - // baseVestingWithCoins := vestingtypes.NewBaseVestingAccount(baseAcc, initialVesting, ctx.BlockTime().Unix()+86400*365) - // delayedVestingAccount := vestingtypes.NewDelayedVestingAccountRaw(baseVestingWithCoins) - // app.AccountKeeper.SetAccount(ctx, delayedVestingAccount) - // } - - // pubKeys := simtestutil.CreateTestPubKeys(2) - // pk1, pk2 := pubKeys[0], pubKeys[1] - - // // Create Validators and Delegation - // val1 := stakingtypes.NewValidator(addrVal1, pk1, stakingtypes.Description{}) - // val1.Status = sdkstaking.Bonded - // app.StakingKeeper.SetValidator(ctx, val1) - // app.StakingKeeper.SetValidatorByPowerIndex(ctx, val1) - // err = app.StakingKeeper.SetValidatorByConsAddr(ctx, val1) - // require.NoError(t, err) - - // val2 := stakingtypes.NewValidator(addrVal2, pk2, stakingtypes.Description{}) - // val2.Status = sdkstaking.Bonded - // app.StakingKeeper.SetValidator(ctx, val2) - // app.StakingKeeper.SetValidatorByPowerIndex(ctx, val2) - // err = app.StakingKeeper.SetValidatorByConsAddr(ctx, val2) - // require.NoError(t, err) - - // // Delegate from both the main delegator as well as a random account so there is a - // // non-zero delegation after redemption - // err = delegateCoinsFromAccount(ctx, app, delegatorAccount, tc.delegationAmount, val1) - // require.NoError(t, err) - - // // apply TM updates - // applyValidatorSetUpdates(t, ctx, app.StakingKeeper, -1) - - // _, found := app.StakingKeeper.GetDelegation(ctx, delegatorAccount, addrVal1) - // require.True(t, found, "delegation not found after delegate") - - // lastRecordID := app.StakingKeeper.GetLastTokenizeShareRecordID(ctx) - // oldValidator, found := app.StakingKeeper.GetValidator(ctx, addrVal1) - // require.True(t, found) - - // msgServer := keeper.NewMsgServerImpl(app.StakingKeeper) - // if tc.validatorBondDelegation { - // err := delegateCoinsFromAccount(ctx, app, addrs[tc.validatorBondDelegatorIndex], tc.delegationAmount, val1) - // require.NoError(t, err) - // _, err = msgServer.ValidatorBond(sdk.WrapSDKContext(ctx), &types.MsgValidatorBond{ - // DelegatorAddress: addrs[tc.validatorBondDelegatorIndex].String(), - // ValidatorAddress: addrVal1.String(), - // }) - // require.NoError(t, err) - // } - - // resp, err := msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &types.MsgTokenizeShares{ - // DelegatorAddress: delegatorAccount.String(), - // ValidatorAddress: addrVal1.String(), - // Amount: sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), tc.tokenizeShareAmount), - // TokenizedShareOwner: delegatorAccount.String(), - // }) - // if tc.expTokenizeErr { - // require.Error(t, err) - // return - // } - // require.NoError(t, err) - - // // check last record id increase - // require.Equal(t, lastRecordID+1, app.StakingKeeper.GetLastTokenizeShareRecordID(ctx)) - - // // ensure validator's total tokens is consistent - // newValidator, found := app.StakingKeeper.GetValidator(ctx, addrVal1) - // require.True(t, found) - // require.Equal(t, oldValidator.Tokens, newValidator.Tokens) - - // // if the delegator was not a provider, check that the total liquid staked and validator liquid shares increased - // totalLiquidTokensAfterTokenization := app.StakingKeeper.GetTotalLiquidStakedTokens(ctx) - // validatorLiquidSharesAfterTokenization := newValidator.LiquidShares - // if !tc.delegatorIsLSTP { - // require.Equal(t, tc.tokenizeShareAmount.String(), totalLiquidTokensAfterTokenization.String(), "total liquid tokens after tokenization") - // require.Equal(t, tc.tokenizeShareAmount.String(), validatorLiquidSharesAfterTokenization.TruncateInt().String(), "validator liquid shares after tokenization") - // } else { - // require.True(t, totalLiquidTokensAfterTokenization.IsZero(), "zero liquid tokens after tokenization") - // require.True(t, validatorLiquidSharesAfterTokenization.IsZero(), "zero liquid validator shares after tokenization") - // } - - // if tc.vestingAmount.IsPositive() { - // acc := app.AccountKeeper.GetAccount(ctx, addrAcc2) - // vestingAcc := acc.(vesting.VestingAccount) - // require.Equal(t, vestingAcc.GetDelegatedVesting().AmountOf(app.StakingKeeper.BondDenom(ctx)).String(), tc.targetVestingDelAfterShare.String()) - // } - - // if tc.prevAccountDelegationExists { - // _, found = app.StakingKeeper.GetDelegation(ctx, delegatorAccount, addrVal1) - // require.True(t, found, "delegation found after partial tokenize share") - // } else { - // _, found = app.StakingKeeper.GetDelegation(ctx, delegatorAccount, addrVal1) - // require.False(t, found, "delegation found after full tokenize share") - // } - - // shareToken := app.BankKeeper.GetBalance(ctx, delegatorAccount, resp.Amount.Denom) - // require.Equal(t, resp.Amount, shareToken) - // _, found = app.StakingKeeper.GetValidator(ctx, addrVal1) - // require.True(t, found, true, "validator not found") - - // records := app.StakingKeeper.GetAllTokenizeShareRecords(ctx) - // require.Len(t, records, 1) - // delegation, found := app.StakingKeeper.GetDelegation(ctx, records[0].GetModuleAddress(), addrVal1) - // require.True(t, found, "delegation not found from tokenize share module account after tokenize share") - - // // slash before redeem - // slashedTokens := sdk.ZeroInt() - // redeemedShares := tc.redeemAmount - // redeemedTokens := tc.redeemAmount - // if tc.slashFactor.IsPositive() { - // consAddr, err := val1.GetConsAddr() - // require.NoError(t, err) - // ctx = ctx.WithBlockHeight(100) - // val1, found = app.StakingKeeper.GetValidator(ctx, addrVal1) - // require.True(t, found) - // power := app.StakingKeeper.TokensToConsensusPower(ctx, val1.Tokens) - // app.StakingKeeper.Slash(ctx, consAddr, 10, power, tc.slashFactor, 0) - // slashedTokens = sdk.NewDecFromInt(val1.Tokens).Mul(tc.slashFactor).TruncateInt() - - // val1, _ := app.StakingKeeper.GetValidator(ctx, addrVal1) - // redeemedTokens = val1.TokensFromShares(sdk.NewDecFromInt(redeemedShares)).TruncateInt() - // } - - // // get deletagor balance and delegation - // bondDenomAmountBefore := app.BankKeeper.GetBalance(ctx, delegatorAccount, app.StakingKeeper.BondDenom(ctx)) - // val1, found = app.StakingKeeper.GetValidator(ctx, addrVal1) - // require.True(t, found) - // delegation, found = app.StakingKeeper.GetDelegation(ctx, delegatorAccount, addrVal1) - // if !found { - // delegation = types.Delegation{Shares: sdk.ZeroDec()} - // } - // delAmountBefore := val1.TokensFromShares(delegation.Shares) - // oldValidator, found = app.StakingKeeper.GetValidator(ctx, addrVal1) - // require.True(t, found) - - // _, err = msgServer.RedeemTokensForShares(sdk.WrapSDKContext(ctx), &types.MsgRedeemTokensForShares{ - // DelegatorAddress: delegatorAccount.String(), - // Amount: sdk.NewCoin(resp.Amount.Denom, tc.redeemAmount), - // }) - // if tc.expRedeemErr { - // require.Error(t, err) - // return - // } - // require.NoError(t, err) - - // // ensure validator's total tokens is consistent - // newValidator, found = app.StakingKeeper.GetValidator(ctx, addrVal1) - // require.True(t, found) - // require.Equal(t, oldValidator.Tokens, newValidator.Tokens) - - // // if the delegator was not a liuqid staking provider, check that the total liquid staked - // // and liquid shares decreased - // totalLiquidTokensAfterRedemption := app.StakingKeeper.GetTotalLiquidStakedTokens(ctx) - // validatorLiquidSharesAfterRedemption := newValidator.LiquidShares - // expectedLiquidTokens := totalLiquidTokensAfterTokenization.Sub(redeemedTokens).Sub(slashedTokens) - // expectedLiquidShares := validatorLiquidSharesAfterTokenization.Sub(sdk.NewDecFromInt(redeemedShares)) - // if !tc.delegatorIsLSTP { - // require.Equal(t, expectedLiquidTokens.String(), totalLiquidTokensAfterRedemption.String(), "total liquid tokens after redemption") - // require.Equal(t, expectedLiquidShares.String(), validatorLiquidSharesAfterRedemption.String(), "validator liquid shares after tokenization") - // } else { - // require.True(t, totalLiquidTokensAfterRedemption.IsZero(), "zero liquid tokens after redemption") - // require.True(t, validatorLiquidSharesAfterRedemption.IsZero(), "zero liquid validator shares after redemption") - // } - - // if tc.vestingAmount.IsPositive() { - // acc := app.AccountKeeper.GetAccount(ctx, addrAcc2) - // vestingAcc := acc.(vesting.VestingAccount) - // require.Equal(t, vestingAcc.GetDelegatedVesting().AmountOf(app.StakingKeeper.BondDenom(ctx)).String(), tc.targetVestingDelAfterRedeem.String()) - // } - - // expectedDelegatedShares := sdk.NewDecFromInt(tc.delegationAmount.Sub(tc.tokenizeShareAmount).Add(tc.redeemAmount)) - // delegation, found = app.StakingKeeper.GetDelegation(ctx, delegatorAccount, addrVal1) - // require.True(t, found, "delegation not found after redeem tokens") - // require.Equal(t, delegatorAccount.String(), delegation.DelegatorAddress) - // require.Equal(t, addrVal1.String(), delegation.ValidatorAddress) - // require.Equal(t, expectedDelegatedShares, delegation.Shares, "delegation shares after redeem") - - // // check delegator balance is not changed - // bondDenomAmountAfter := app.BankKeeper.GetBalance(ctx, delegatorAccount, app.StakingKeeper.BondDenom(ctx)) - // require.Equal(t, bondDenomAmountAfter.Amount.String(), bondDenomAmountBefore.Amount.String()) - - // // get delegation amount is changed correctly - // val1, found = app.StakingKeeper.GetValidator(ctx, addrVal1) - // require.True(t, found) - // delegation, found = app.StakingKeeper.GetDelegation(ctx, delegatorAccount, addrVal1) - // if !found { - // delegation = types.Delegation{Shares: sdk.ZeroDec()} - // } - // delAmountAfter := val1.TokensFromShares(delegation.Shares) - // require.Equal(t, delAmountAfter.String(), delAmountBefore.Add(sdk.NewDecFromInt(tc.redeemAmount).Mul(sdk.OneDec().Sub(tc.slashFactor))).String()) - - // shareToken = app.BankKeeper.GetBalance(ctx, delegatorAccount, resp.Amount.Denom) - // require.Equal(t, shareToken.Amount.String(), tc.tokenizeShareAmount.Sub(tc.redeemAmount).String()) - // _, found = app.StakingKeeper.GetValidator(ctx, addrVal1) - // require.True(t, found, true, "validator not found") - - // if tc.recordAccountDelegationExists { - // _, found = app.StakingKeeper.GetDelegation(ctx, records[0].GetModuleAddress(), addrVal1) - // require.True(t, found, "delegation not found from tokenize share module account after redeem partial amount") - - // records = app.StakingKeeper.GetAllTokenizeShareRecords(ctx) - // require.Len(t, records, 1) - // } else { - // _, found = app.StakingKeeper.GetDelegation(ctx, records[0].GetModuleAddress(), addrVal1) - // require.False(t, found, "delegation found from tokenize share module account after redeem full amount") - - // records = app.StakingKeeper.GetAllTokenizeShareRecords(ctx) - // require.Len(t, records, 0) - // } - // }) - // } + _, app, ctx := createTestInput(t) + var ( + stakingKeeper = app.StakingKeeper + ) + + liquidStakingCapStrict := sdk.ZeroDec() + liquidStakingCapConservative := sdk.MustNewDecFromStr("0.8") + liquidStakingCapDisabled := sdk.OneDec() + + validatorBondStrict := sdk.OneDec() + validatorBondConservative := sdk.NewDec(10) + validatorBondDisabled := sdk.NewDec(-1) + + testCases := []struct { + name string + vestingAmount math.Int + delegationAmount math.Int + tokenizeShareAmount math.Int + redeemAmount math.Int + targetVestingDelAfterShare math.Int + targetVestingDelAfterRedeem math.Int + globalLiquidStakingCap sdk.Dec + slashFactor sdk.Dec + validatorLiquidStakingCap sdk.Dec + validatorBondFactor sdk.Dec + validatorBondDelegation bool + validatorBondDelegatorIndex int + delegatorIsLSTP bool + expTokenizeErr bool + expRedeemErr bool + prevAccountDelegationExists bool + recordAccountDelegationExists bool + }{ + { + name: "full amount tokenize and redeem", + vestingAmount: sdk.NewInt(0), + delegationAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + tokenizeShareAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + redeemAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + slashFactor: sdk.ZeroDec(), + globalLiquidStakingCap: liquidStakingCapDisabled, + validatorLiquidStakingCap: liquidStakingCapDisabled, + validatorBondFactor: validatorBondDisabled, + validatorBondDelegation: false, + expTokenizeErr: false, + expRedeemErr: false, + prevAccountDelegationExists: false, + recordAccountDelegationExists: false, + }, + { + name: "full amount tokenize and partial redeem", + vestingAmount: sdk.NewInt(0), + delegationAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + tokenizeShareAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + redeemAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + slashFactor: sdk.ZeroDec(), + globalLiquidStakingCap: liquidStakingCapDisabled, + validatorLiquidStakingCap: liquidStakingCapDisabled, + validatorBondFactor: validatorBondDisabled, + validatorBondDelegation: false, + expTokenizeErr: false, + expRedeemErr: false, + prevAccountDelegationExists: false, + recordAccountDelegationExists: true, + }, + { + name: "partial amount tokenize and full redeem", + vestingAmount: sdk.NewInt(0), + delegationAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + tokenizeShareAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + redeemAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + slashFactor: sdk.ZeroDec(), + globalLiquidStakingCap: liquidStakingCapDisabled, + validatorLiquidStakingCap: liquidStakingCapDisabled, + validatorBondFactor: validatorBondDisabled, + validatorBondDelegation: false, + expTokenizeErr: false, + expRedeemErr: false, + prevAccountDelegationExists: true, + recordAccountDelegationExists: false, + }, + { + name: "tokenize and redeem with slash", + vestingAmount: sdk.NewInt(0), + delegationAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + tokenizeShareAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + redeemAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + slashFactor: sdk.MustNewDecFromStr("0.1"), + globalLiquidStakingCap: liquidStakingCapDisabled, + validatorLiquidStakingCap: liquidStakingCapDisabled, + validatorBondFactor: validatorBondDisabled, + validatorBondDelegation: false, + expTokenizeErr: false, + expRedeemErr: false, + prevAccountDelegationExists: false, + recordAccountDelegationExists: true, + }, + { + name: "over tokenize", + vestingAmount: sdk.NewInt(0), + delegationAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + tokenizeShareAmount: stakingKeeper.TokensFromConsensusPower(ctx, 30), + redeemAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + slashFactor: sdk.ZeroDec(), + globalLiquidStakingCap: liquidStakingCapDisabled, + validatorLiquidStakingCap: liquidStakingCapDisabled, + validatorBondFactor: validatorBondDisabled, + validatorBondDelegation: false, + expTokenizeErr: true, + expRedeemErr: false, + }, + { + name: "over redeem", + vestingAmount: sdk.NewInt(0), + delegationAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + tokenizeShareAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + redeemAmount: stakingKeeper.TokensFromConsensusPower(ctx, 40), + slashFactor: sdk.ZeroDec(), + globalLiquidStakingCap: liquidStakingCapDisabled, + validatorLiquidStakingCap: liquidStakingCapDisabled, + validatorBondFactor: validatorBondDisabled, + validatorBondDelegation: false, + expTokenizeErr: false, + expRedeemErr: true, + }, + { + name: "vesting account tokenize share failure", + vestingAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + delegationAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + tokenizeShareAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + redeemAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + slashFactor: sdk.ZeroDec(), + globalLiquidStakingCap: liquidStakingCapDisabled, + validatorLiquidStakingCap: liquidStakingCapDisabled, + validatorBondFactor: validatorBondDisabled, + validatorBondDelegation: false, + expTokenizeErr: true, + expRedeemErr: false, + prevAccountDelegationExists: true, + }, + { + name: "vesting account tokenize share success", + vestingAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + delegationAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + tokenizeShareAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + redeemAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterShare: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterRedeem: stakingKeeper.TokensFromConsensusPower(ctx, 10), + slashFactor: sdk.ZeroDec(), + globalLiquidStakingCap: liquidStakingCapDisabled, + validatorLiquidStakingCap: liquidStakingCapDisabled, + validatorBondFactor: validatorBondDisabled, + validatorBondDelegation: false, + expTokenizeErr: false, + expRedeemErr: false, + prevAccountDelegationExists: true, + }, + { + name: "try tokenize share for a validator-bond delegation", + vestingAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + delegationAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + tokenizeShareAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + redeemAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterShare: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterRedeem: stakingKeeper.TokensFromConsensusPower(ctx, 10), + slashFactor: sdk.ZeroDec(), + globalLiquidStakingCap: liquidStakingCapDisabled, + validatorLiquidStakingCap: liquidStakingCapDisabled, + validatorBondFactor: validatorBondConservative, + validatorBondDelegation: true, + validatorBondDelegatorIndex: 1, + expTokenizeErr: true, + expRedeemErr: false, + prevAccountDelegationExists: true, + }, + { + name: "strict validator-bond - tokenization fails", + vestingAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + delegationAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + tokenizeShareAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + redeemAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterShare: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterRedeem: stakingKeeper.TokensFromConsensusPower(ctx, 10), + slashFactor: sdk.ZeroDec(), + globalLiquidStakingCap: liquidStakingCapDisabled, + validatorLiquidStakingCap: liquidStakingCapDisabled, + validatorBondFactor: validatorBondStrict, + validatorBondDelegation: false, + expTokenizeErr: true, + expRedeemErr: false, + prevAccountDelegationExists: true, + }, + { + name: "conservative validator-bond - successful tokenization", + vestingAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + delegationAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + tokenizeShareAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + redeemAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterShare: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterRedeem: stakingKeeper.TokensFromConsensusPower(ctx, 10), + slashFactor: sdk.ZeroDec(), + globalLiquidStakingCap: liquidStakingCapDisabled, + validatorLiquidStakingCap: liquidStakingCapDisabled, + validatorBondFactor: validatorBondConservative, + validatorBondDelegation: true, + validatorBondDelegatorIndex: 0, + expTokenizeErr: false, + expRedeemErr: false, + prevAccountDelegationExists: true, + }, + { + name: "strict global liquid staking cap - tokenization fails", + vestingAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + delegationAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + tokenizeShareAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + redeemAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterShare: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterRedeem: stakingKeeper.TokensFromConsensusPower(ctx, 10), + slashFactor: sdk.ZeroDec(), + globalLiquidStakingCap: liquidStakingCapStrict, + validatorLiquidStakingCap: liquidStakingCapDisabled, + validatorBondFactor: validatorBondDisabled, + validatorBondDelegation: true, + validatorBondDelegatorIndex: 0, + expTokenizeErr: true, + expRedeemErr: false, + prevAccountDelegationExists: true, + }, + { + name: "conservative global liquid staking cap - successful tokenization", + vestingAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + delegationAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + tokenizeShareAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + redeemAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterShare: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterRedeem: stakingKeeper.TokensFromConsensusPower(ctx, 10), + slashFactor: sdk.ZeroDec(), + globalLiquidStakingCap: liquidStakingCapConservative, + validatorLiquidStakingCap: liquidStakingCapDisabled, + validatorBondFactor: validatorBondDisabled, + validatorBondDelegation: true, + validatorBondDelegatorIndex: 0, + expTokenizeErr: false, + expRedeemErr: false, + prevAccountDelegationExists: true, + }, + { + name: "strict validator liquid staking cap - tokenization fails", + vestingAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + delegationAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + tokenizeShareAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + redeemAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterShare: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterRedeem: stakingKeeper.TokensFromConsensusPower(ctx, 10), + slashFactor: sdk.ZeroDec(), + globalLiquidStakingCap: liquidStakingCapDisabled, + validatorLiquidStakingCap: liquidStakingCapStrict, + validatorBondFactor: validatorBondDisabled, + validatorBondDelegation: true, + validatorBondDelegatorIndex: 0, + expTokenizeErr: true, + expRedeemErr: false, + prevAccountDelegationExists: true, + }, + { + name: "conservative validator liquid staking cap - successful tokenization", + vestingAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + delegationAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + tokenizeShareAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + redeemAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterShare: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterRedeem: stakingKeeper.TokensFromConsensusPower(ctx, 10), + slashFactor: sdk.ZeroDec(), + globalLiquidStakingCap: liquidStakingCapDisabled, + validatorLiquidStakingCap: liquidStakingCapConservative, + validatorBondFactor: validatorBondDisabled, + validatorBondDelegation: true, + validatorBondDelegatorIndex: 0, + expTokenizeErr: false, + expRedeemErr: false, + prevAccountDelegationExists: true, + }, + { + name: "all caps set conservatively - successful tokenize share", + vestingAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + delegationAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + tokenizeShareAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + redeemAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterShare: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterRedeem: stakingKeeper.TokensFromConsensusPower(ctx, 10), + slashFactor: sdk.ZeroDec(), + globalLiquidStakingCap: liquidStakingCapConservative, + validatorLiquidStakingCap: liquidStakingCapConservative, + validatorBondFactor: validatorBondConservative, + validatorBondDelegation: true, + validatorBondDelegatorIndex: 0, + expTokenizeErr: false, + expRedeemErr: false, + prevAccountDelegationExists: true, + }, + { + name: "delegator is a liquid staking provider - accounting should not update", + vestingAmount: sdk.ZeroInt(), + delegationAmount: stakingKeeper.TokensFromConsensusPower(ctx, 20), + tokenizeShareAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + redeemAmount: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterShare: stakingKeeper.TokensFromConsensusPower(ctx, 10), + targetVestingDelAfterRedeem: stakingKeeper.TokensFromConsensusPower(ctx, 10), + slashFactor: sdk.ZeroDec(), + globalLiquidStakingCap: liquidStakingCapConservative, + validatorLiquidStakingCap: liquidStakingCapConservative, + validatorBondFactor: validatorBondConservative, + delegatorIsLSTP: true, + validatorBondDelegation: true, + validatorBondDelegatorIndex: 0, + expTokenizeErr: false, + expRedeemErr: false, + prevAccountDelegationExists: true, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, app, ctx := createTestInput(t) + var ( + bankKeeper = app.BankKeeper + accountKeeper = app.AccountKeeper + stakingKeeper = app.StakingKeeper + ) + addrs := simtestutil.AddTestAddrs(bankKeeper, stakingKeeper, ctx, 2, stakingKeeper.TokensFromConsensusPower(ctx, 10000)) + addrAcc1, addrAcc2 := addrs[0], addrs[1] + addrVal1, addrVal2 := sdk.ValAddress(addrAcc1), sdk.ValAddress(addrAcc2) + + // Create ICA module account + icaAccountAddress := createICAAccount(ctx, accountKeeper) + + // Fund module account + delegationCoin := sdk.NewCoin(stakingKeeper.BondDenom(ctx), tc.delegationAmount) + err := bankKeeper.MintCoins(ctx, minttypes.ModuleName, sdk.NewCoins(delegationCoin)) + require.NoError(t, err) + err = bankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, icaAccountAddress, sdk.NewCoins(delegationCoin)) + require.NoError(t, err) + + // set the delegator address depending on whether the delegator should be a liquid staking provider + delegatorAccount := addrAcc2 + if tc.delegatorIsLSTP { + delegatorAccount = icaAccountAddress + } + + // set validator bond factor and global liquid staking cap + params := stakingKeeper.GetParams(ctx) + params.ValidatorBondFactor = tc.validatorBondFactor + params.GlobalLiquidStakingCap = tc.globalLiquidStakingCap + params.ValidatorLiquidStakingCap = tc.validatorLiquidStakingCap + stakingKeeper.SetParams(ctx, params) + + // set the total liquid staked tokens + stakingKeeper.SetTotalLiquidStakedTokens(ctx, sdk.ZeroInt()) + + if !tc.vestingAmount.IsZero() { + // create vesting account + pubkey := secp256k1.GenPrivKey().PubKey() + baseAcc := authtypes.NewBaseAccount(addrAcc2, pubkey, 0, 0) + initialVesting := sdk.NewCoins(sdk.NewCoin(sdk.DefaultBondDenom, tc.vestingAmount)) + baseVestingWithCoins := vestingtypes.NewBaseVestingAccount(baseAcc, initialVesting, ctx.BlockTime().Unix()+86400*365) + delayedVestingAccount := vestingtypes.NewDelayedVestingAccountRaw(baseVestingWithCoins) + accountKeeper.SetAccount(ctx, delayedVestingAccount) + } + + pubKeys := simtestutil.CreateTestPubKeys(2) + pk1, pk2 := pubKeys[0], pubKeys[1] + + // Create Validators and Delegation + val1 := testutil.NewValidator(t, addrVal1, pk1) + val1.Status = stakingtypes.Bonded + app.StakingKeeper.SetValidator(ctx, val1) + app.StakingKeeper.SetValidatorByPowerIndex(ctx, val1) + err = app.StakingKeeper.SetValidatorByConsAddr(ctx, val1) + require.NoError(t, err) + + val2 := testutil.NewValidator(t, addrVal2, pk2) + val2.Status = stakingtypes.Bonded + app.StakingKeeper.SetValidator(ctx, val2) + app.StakingKeeper.SetValidatorByPowerIndex(ctx, val2) + err = app.StakingKeeper.SetValidatorByConsAddr(ctx, val2) + require.NoError(t, err) + + // Delegate from both the main delegator as well as a random account so there is a + // non-zero delegation after redemption + err = delegateCoinsFromAccount(ctx, *stakingKeeper, delegatorAccount, tc.delegationAmount, val1) + require.NoError(t, err) + + // apply TM updates + applyValidatorSetUpdates(t, ctx, stakingKeeper, -1) + + _, found := stakingKeeper.GetDelegation(ctx, delegatorAccount, addrVal1) + require.True(t, found, "delegation not found after delegate") + + lastRecordID := stakingKeeper.GetLastTokenizeShareRecordID(ctx) + oldValidator, found := stakingKeeper.GetValidator(ctx, addrVal1) + require.True(t, found) + + msgServer := keeper.NewMsgServerImpl(stakingKeeper) + if tc.validatorBondDelegation { + err := delegateCoinsFromAccount(ctx, *stakingKeeper, addrs[tc.validatorBondDelegatorIndex], tc.delegationAmount, val1) + require.NoError(t, err) + _, err = msgServer.ValidatorBond(sdk.WrapSDKContext(ctx), &types.MsgValidatorBond{ + DelegatorAddress: addrs[tc.validatorBondDelegatorIndex].String(), + ValidatorAddress: addrVal1.String(), + }) + require.NoError(t, err) + } + + resp, err := msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &types.MsgTokenizeShares{ + DelegatorAddress: delegatorAccount.String(), + ValidatorAddress: addrVal1.String(), + Amount: sdk.NewCoin(stakingKeeper.BondDenom(ctx), tc.tokenizeShareAmount), + TokenizedShareOwner: delegatorAccount.String(), + }) + if tc.expTokenizeErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + // check last record id increase + require.Equal(t, lastRecordID+1, stakingKeeper.GetLastTokenizeShareRecordID(ctx)) + + // ensure validator's total tokens is consistent + newValidator, found := stakingKeeper.GetValidator(ctx, addrVal1) + require.True(t, found) + require.Equal(t, oldValidator.Tokens, newValidator.Tokens) + + // if the delegator was not a provider, check that the total liquid staked and validator liquid shares increased + totalLiquidTokensAfterTokenization := stakingKeeper.GetTotalLiquidStakedTokens(ctx) + validatorLiquidSharesAfterTokenization := newValidator.LiquidShares + if !tc.delegatorIsLSTP { + require.Equal(t, tc.tokenizeShareAmount.String(), totalLiquidTokensAfterTokenization.String(), "total liquid tokens after tokenization") + require.Equal(t, tc.tokenizeShareAmount.String(), validatorLiquidSharesAfterTokenization.TruncateInt().String(), "validator liquid shares after tokenization") + } else { + require.True(t, totalLiquidTokensAfterTokenization.IsZero(), "zero liquid tokens after tokenization") + require.True(t, validatorLiquidSharesAfterTokenization.IsZero(), "zero liquid validator shares after tokenization") + } + + if tc.vestingAmount.IsPositive() { + acc := accountKeeper.GetAccount(ctx, addrAcc2) + vestingAcc := acc.(vesting.VestingAccount) + require.Equal(t, vestingAcc.GetDelegatedVesting().AmountOf(stakingKeeper.BondDenom(ctx)).String(), tc.targetVestingDelAfterShare.String()) + } + + if tc.prevAccountDelegationExists { + _, found = stakingKeeper.GetDelegation(ctx, delegatorAccount, addrVal1) + require.True(t, found, "delegation found after partial tokenize share") + } else { + _, found = stakingKeeper.GetDelegation(ctx, delegatorAccount, addrVal1) + require.False(t, found, "delegation found after full tokenize share") + } + + shareToken := bankKeeper.GetBalance(ctx, delegatorAccount, resp.Amount.Denom) + require.Equal(t, resp.Amount, shareToken) + _, found = stakingKeeper.GetValidator(ctx, addrVal1) + require.True(t, found, true, "validator not found") + + records := stakingKeeper.GetAllTokenizeShareRecords(ctx) + require.Len(t, records, 1) + delegation, found := stakingKeeper.GetDelegation(ctx, records[0].GetModuleAddress(), addrVal1) + require.True(t, found, "delegation not found from tokenize share module account after tokenize share") + + // slash before redeem + slashedTokens := sdk.ZeroInt() + redeemedShares := tc.redeemAmount + redeemedTokens := tc.redeemAmount + if tc.slashFactor.IsPositive() { + consAddr, err := val1.GetConsAddr() + require.NoError(t, err) + ctx = ctx.WithBlockHeight(100) + val1, found = stakingKeeper.GetValidator(ctx, addrVal1) + require.True(t, found) + power := stakingKeeper.TokensToConsensusPower(ctx, val1.Tokens) + stakingKeeper.Slash(ctx, consAddr, 10, power, tc.slashFactor) + slashedTokens = sdk.NewDecFromInt(val1.Tokens).Mul(tc.slashFactor).TruncateInt() + + val1, _ := stakingKeeper.GetValidator(ctx, addrVal1) + redeemedTokens = val1.TokensFromShares(sdk.NewDecFromInt(redeemedShares)).TruncateInt() + } + + // get delegator balance and delegation + bondDenomAmountBefore := bankKeeper.GetBalance(ctx, delegatorAccount, stakingKeeper.BondDenom(ctx)) + val1, found = stakingKeeper.GetValidator(ctx, addrVal1) + require.True(t, found) + delegation, found = stakingKeeper.GetDelegation(ctx, delegatorAccount, addrVal1) + if !found { + delegation = types.Delegation{Shares: sdk.ZeroDec()} + } + delAmountBefore := val1.TokensFromShares(delegation.Shares) + oldValidator, found = stakingKeeper.GetValidator(ctx, addrVal1) + require.True(t, found) + + _, err = msgServer.RedeemTokensForShares(sdk.WrapSDKContext(ctx), &types.MsgRedeemTokensForShares{ + DelegatorAddress: delegatorAccount.String(), + Amount: sdk.NewCoin(resp.Amount.Denom, tc.redeemAmount), + }) + if tc.expRedeemErr { + require.Error(t, err) + return + } + require.NoError(t, err) + + // ensure validator's total tokens is consistent + newValidator, found = stakingKeeper.GetValidator(ctx, addrVal1) + require.True(t, found) + require.Equal(t, oldValidator.Tokens, newValidator.Tokens) + + // if the delegator was not a liquid staking provider, check that the total liquid staked + // and liquid shares decreased + totalLiquidTokensAfterRedemption := stakingKeeper.GetTotalLiquidStakedTokens(ctx) + validatorLiquidSharesAfterRedemption := newValidator.LiquidShares + expectedLiquidTokens := totalLiquidTokensAfterTokenization.Sub(redeemedTokens).Sub(slashedTokens) + expectedLiquidShares := validatorLiquidSharesAfterTokenization.Sub(sdk.NewDecFromInt(redeemedShares)) + if !tc.delegatorIsLSTP { + require.Equal(t, expectedLiquidTokens.String(), totalLiquidTokensAfterRedemption.String(), "total liquid tokens after redemption") + require.Equal(t, expectedLiquidShares.String(), validatorLiquidSharesAfterRedemption.String(), "validator liquid shares after tokenization") + } else { + require.True(t, totalLiquidTokensAfterRedemption.IsZero(), "zero liquid tokens after redemption") + require.True(t, validatorLiquidSharesAfterRedemption.IsZero(), "zero liquid validator shares after redemption") + } + + if tc.vestingAmount.IsPositive() { + acc := accountKeeper.GetAccount(ctx, addrAcc2) + vestingAcc := acc.(vesting.VestingAccount) + require.Equal(t, vestingAcc.GetDelegatedVesting().AmountOf(stakingKeeper.BondDenom(ctx)).String(), tc.targetVestingDelAfterRedeem.String()) + } + + expectedDelegatedShares := sdk.NewDecFromInt(tc.delegationAmount.Sub(tc.tokenizeShareAmount).Add(tc.redeemAmount)) + delegation, found = stakingKeeper.GetDelegation(ctx, delegatorAccount, addrVal1) + require.True(t, found, "delegation not found after redeem tokens") + require.Equal(t, delegatorAccount.String(), delegation.DelegatorAddress) + require.Equal(t, addrVal1.String(), delegation.ValidatorAddress) + require.Equal(t, expectedDelegatedShares, delegation.Shares, "delegation shares after redeem") + + // check delegator balance is not changed + bondDenomAmountAfter := bankKeeper.GetBalance(ctx, delegatorAccount, stakingKeeper.BondDenom(ctx)) + require.Equal(t, bondDenomAmountAfter.Amount.String(), bondDenomAmountBefore.Amount.String()) + + // get delegation amount is changed correctly + val1, found = stakingKeeper.GetValidator(ctx, addrVal1) + require.True(t, found) + delegation, found = stakingKeeper.GetDelegation(ctx, delegatorAccount, addrVal1) + if !found { + delegation = types.Delegation{Shares: sdk.ZeroDec()} + } + delAmountAfter := val1.TokensFromShares(delegation.Shares) + require.Equal(t, delAmountAfter.String(), delAmountBefore.Add(sdk.NewDecFromInt(tc.redeemAmount).Mul(sdk.OneDec().Sub(tc.slashFactor))).String()) + + shareToken = bankKeeper.GetBalance(ctx, delegatorAccount, resp.Amount.Denom) + require.Equal(t, shareToken.Amount.String(), tc.tokenizeShareAmount.Sub(tc.redeemAmount).String()) + _, found = stakingKeeper.GetValidator(ctx, addrVal1) + require.True(t, found, true, "validator not found") + + if tc.recordAccountDelegationExists { + _, found = stakingKeeper.GetDelegation(ctx, records[0].GetModuleAddress(), addrVal1) + require.True(t, found, "delegation not found from tokenize share module account after redeem partial amount") + + records = stakingKeeper.GetAllTokenizeShareRecords(ctx) + require.Len(t, records, 1) + } else { + _, found = stakingKeeper.GetDelegation(ctx, records[0].GetModuleAddress(), addrVal1) + require.False(t, found, "delegation found from tokenize share module account after redeem full amount") + + records = stakingKeeper.GetAllTokenizeShareRecords(ctx) + require.Len(t, records, 0) + } + }) + } } -// TODO refactor LSM test -// // Helper function to setup a delegator and validator for the Tokenize/Redeem conversion tests func setupTestTokenizeAndRedeemConversion( t *testing.T, - app *simapp.SimApp, + sk keeper.Keeper, + bk bankkeeper.Keeper, ctx sdk.Context, ) (delAddress sdk.AccAddress, valAddress sdk.ValAddress) { - // addresses := simapp.AddTestAddrs(app, ctx, 2, sdk.NewInt(1_000_000)) - // pubKeys := simapp.CreateTestPubKeys(1) + addresses := simtestutil.AddTestAddrs(bk, sk, ctx, 2, sdk.NewInt(1_000_000)) - // delegatorAddress := addresses[0] - // validatorAddress := sdk.ValAddress(addresses[1]) + pubKeys := simtestutil.CreateTestPubKeys(1) - // validator := stakingtypes.NewValidator(validatorAddress, pubKeys[0], stakingtypes.Description{}) - // validator.DelegatorShares = sdk.NewDec(1_000_000) - // validator.Tokens = sdk.NewInt(1_000_000) - // validator.LiquidShares = sdk.NewDec(0) - // validator.Status = types.Bonded + delegatorAddress := addresses[0] + validatorAddress := sdk.ValAddress(addresses[1]) + + validator, err := stakingtypes.NewValidator(validatorAddress, pubKeys[0], stakingtypes.Description{}) + require.NoError(t, err) + validator.DelegatorShares = sdk.NewDec(1_000_000) + validator.Tokens = sdk.NewInt(1_000_000) + validator.LiquidShares = sdk.NewDec(0) + validator.Status = types.Bonded - // app.StakingKeeper.SetValidator(ctx, validator) - // app.StakingKeeper.SetValidatorByConsAddr(ctx, validator) + sk.SetValidator(ctx, validator) + sk.SetValidatorByConsAddr(ctx, validator) - // return delegatorAddress, validatorAddress - return + return delegatorAddress, validatorAddress } -// TODO refactor LSM test -// // Simulate a slash by decrementing the validator's tokens // We'll do this in a way such that the exchange rate is not an even integer // and the shares associated with a delegation will have a long decimal -func simulateSlashWithImprecision(t *testing.T, app *simapp.SimApp, ctx sdk.Context, valAddress sdk.ValAddress) { - // validator, found := app.StakingKeeper.GetValidator(ctx, valAddress) - // require.True(t, found) +func simulateSlashWithImprecision(t *testing.T, sk keeper.Keeper, ctx sdk.Context, valAddress sdk.ValAddress) { + validator, found := sk.GetValidator(ctx, valAddress) + require.True(t, found) - // slashMagnitude := sdk.MustNewDecFromStr("0.1111111111") - // slashTokens := validator.Tokens.ToDec().Mul(slashMagnitude).TruncateInt() - // validator.Tokens = validator.Tokens.Sub(slashTokens) + slashMagnitude := sdk.MustNewDecFromStr("0.1111111111") + slashTokens := sdk.NewDecFromInt(validator.Tokens).Mul(slashMagnitude).TruncateInt() + validator.Tokens = validator.Tokens.Sub(slashTokens) - // app.StakingKeeper.SetValidator(ctx, validator) + sk.SetValidator(ctx, validator) } -// TODO refactor LSM test // Tests the conversion from tokenization and redemption from the following scenario: // Slash -> Delegate -> Tokenize -> Redeem // Note, in this example, there 2 tokens are lost during the decimal to int conversion // during the unbonding step within tokenization and redemption func TestTokenizeAndRedeemConversion_SlashBeforeDelegation(t *testing.T) { - // app := simapp.Setup(t, false) - // ctx := app.BaseApp.NewContext(false, tmproto.Header{}) - // msgServer := keeper.NewMsgServerImpl(app.StakingKeeper) - - // delegatorAddress, validatorAddress := setupTestTokenizeAndRedeemConversion(t, app, ctx) - - // // slash the validator - // simulateSlashWithImprecision(t, app, ctx, validatorAddress) - // validator, found := app.StakingKeeper.GetValidator(ctx, validatorAddress) - // require.True(t, found) - - // // Delegate and confirm the delegation record was created - // delegateAmount := sdk.NewInt(1000) - // delegateCoin := sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), delegateAmount) - // _, err := msgServer.Delegate(sdk.WrapSDKContext(ctx), &types.MsgDelegate{ - // DelegatorAddress: delegatorAddress.String(), - // ValidatorAddress: validatorAddress.String(), - // Amount: delegateCoin, - // }) - // require.NoError(t, err, "no error expected when delegating") - - // delegation, found := app.StakingKeeper.GetDelegation(ctx, delegatorAddress, validatorAddress) - // require.True(t, found, "delegation should have been found") - - // // Tokenize the full delegation amount - // _, err = msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &types.MsgTokenizeShares{ - // DelegatorAddress: delegatorAddress.String(), - // ValidatorAddress: validatorAddress.String(), - // Amount: delegateCoin, - // TokenizedShareOwner: delegatorAddress.String(), - // }) - // require.NoError(t, err, "no error expected when tokenizing") - - // // Confirm the number of shareTokens equals the number of shares truncated - // // Note: 1 token is lost during unbonding due to rounding - // shareDenom := validatorAddress.String() + "/1" - // shareToken := app.BankKeeper.GetBalance(ctx, delegatorAddress, shareDenom) - // expectedShareTokens := delegation.Shares.TruncateInt().Int64() - 1 // 1 token was lost during unbonding - // require.Equal(t, expectedShareTokens, shareToken.Amount.Int64(), "share token amount") + _, app, ctx := createTestInput(t) + var ( + stakingKeeper = app.StakingKeeper + bankKeeper = app.BankKeeper + ) + msgServer := keeper.NewMsgServerImpl(stakingKeeper) + delegatorAddress, validatorAddress := setupTestTokenizeAndRedeemConversion(t, *stakingKeeper, bankKeeper, ctx) - // // Redeem the share tokens - // _, err = msgServer.RedeemTokensForShares(sdk.WrapSDKContext(ctx), &types.MsgRedeemTokensForShares{ - // DelegatorAddress: delegatorAddress.String(), - // Amount: shareToken, - // }) - // require.NoError(t, err, "no error expected when redeeming") - - // // Confirm (almost) the full delegation was recovered - minus the 2 tokens from the precision error - // // (1 occurs during tokenization, and 1 occurs during redemption) - // newDelegation, found := app.StakingKeeper.GetDelegation(ctx, delegatorAddress, validatorAddress) - // require.True(t, found) - - // endDelegationTokens := validator.TokensFromShares(newDelegation.Shares).TruncateInt().Int64() - // expectedDelegationTokens := delegateAmount.Int64() - 2 - // require.Equal(t, expectedDelegationTokens, endDelegationTokens, "final delegation tokens") + // slash the validator + simulateSlashWithImprecision(t, *stakingKeeper, ctx, validatorAddress) + validator, found := stakingKeeper.GetValidator(ctx, validatorAddress) + require.True(t, found) + + // Delegate and confirm the delegation record was created + delegateAmount := sdk.NewInt(1000) + delegateCoin := sdk.NewCoin(stakingKeeper.BondDenom(ctx), delegateAmount) + _, err := msgServer.Delegate(sdk.WrapSDKContext(ctx), &types.MsgDelegate{ + DelegatorAddress: delegatorAddress.String(), + ValidatorAddress: validatorAddress.String(), + Amount: delegateCoin, + }) + require.NoError(t, err, "no error expected when delegating") + + delegation, found := stakingKeeper.GetDelegation(ctx, delegatorAddress, validatorAddress) + require.True(t, found, "delegation should have been found") + + // Tokenize the full delegation amount + _, err = msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &types.MsgTokenizeShares{ + DelegatorAddress: delegatorAddress.String(), + ValidatorAddress: validatorAddress.String(), + Amount: delegateCoin, + TokenizedShareOwner: delegatorAddress.String(), + }) + require.NoError(t, err, "no error expected when tokenizing") + + // Confirm the number of shareTokens equals the number of shares truncated + // Note: 1 token is lost during unbonding due to rounding + shareDenom := validatorAddress.String() + "/1" + shareToken := bankKeeper.GetBalance(ctx, delegatorAddress, shareDenom) + expectedShareTokens := delegation.Shares.TruncateInt().Int64() - 1 // 1 token was lost during unbonding + require.Equal(t, expectedShareTokens, shareToken.Amount.Int64(), "share token amount") + + // Redeem the share tokens + _, err = msgServer.RedeemTokensForShares(sdk.WrapSDKContext(ctx), &types.MsgRedeemTokensForShares{ + DelegatorAddress: delegatorAddress.String(), + Amount: shareToken, + }) + require.NoError(t, err, "no error expected when redeeming") + + // Confirm (almost) the full delegation was recovered - minus the 2 tokens from the precision error + // (1 occurs during tokenization, and 1 occurs during redemption) + newDelegation, found := stakingKeeper.GetDelegation(ctx, delegatorAddress, validatorAddress) + require.True(t, found) + + endDelegationTokens := validator.TokensFromShares(newDelegation.Shares).TruncateInt().Int64() + expectedDelegationTokens := delegateAmount.Int64() - 2 + require.Equal(t, expectedDelegationTokens, endDelegationTokens, "final delegation tokens") } -// TODO refactor LSM test -// // Tests the conversion from tokenization and redemption from the following scenario: // Delegate -> Slash -> Tokenize -> Redeem // Note, in this example, there 1 token lost during the decimal to int conversion // during the unbonding step within tokenization func TestTokenizeAndRedeemConversion_SlashBeforeTokenization(t *testing.T) { - // app := simapp.Setup(t, false) - // ctx := app.BaseApp.NewContext(false, tmproto.Header{}) - // msgServer := keeper.NewMsgServerImpl(app.StakingKeeper) - - // delegatorAddress, validatorAddress := setupTestTokenizeAndRedeemConversion(t, app, ctx) - - // // Delegate and confirm the delegation record was created - // delegateAmount := sdk.NewInt(1000) - // delegateCoin := sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), delegateAmount) - // _, err := msgServer.Delegate(sdk.WrapSDKContext(ctx), &types.MsgDelegate{ - // DelegatorAddress: delegatorAddress.String(), - // ValidatorAddress: validatorAddress.String(), - // Amount: delegateCoin, - // }) - // require.NoError(t, err, "no error expected when delegating") - - // _, found := app.StakingKeeper.GetDelegation(ctx, delegatorAddress, validatorAddress) - // require.True(t, found, "delegation should have been found") - - // // slash the validator - // simulateSlashWithImprecision(t, app, ctx, validatorAddress) - // validator, found := app.StakingKeeper.GetValidator(ctx, validatorAddress) - // require.True(t, found) - - // // Tokenize the new amount after the slash - // delegationAmountAfterSlash := validator.TokensFromShares(delegateAmount.ToDec()).TruncateInt() - // tokenizationCoin := sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), delegationAmountAfterSlash) - - // _, err = msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &types.MsgTokenizeShares{ - // DelegatorAddress: delegatorAddress.String(), - // ValidatorAddress: validatorAddress.String(), - // Amount: tokenizationCoin, - // TokenizedShareOwner: delegatorAddress.String(), - // }) - // require.NoError(t, err, "no error expected when tokenizing") - - // // The number of share tokens should line up with the **new** number of shares associated - // // with the original delegated amount - // // Note: 1 token is lost during unbonding due to rounding - // shareDenom := validatorAddress.String() + "/1" - // shareToken := app.BankKeeper.GetBalance(ctx, delegatorAddress, shareDenom) - // expectedShareTokens, err := validator.SharesFromTokens(tokenizationCoin.Amount) - // require.Equal(t, expectedShareTokens.TruncateInt().Int64()-1, shareToken.Amount.Int64(), "share token amount") - - // // // Redeem the share tokens - // _, err = msgServer.RedeemTokensForShares(sdk.WrapSDKContext(ctx), &types.MsgRedeemTokensForShares{ - // DelegatorAddress: delegatorAddress.String(), - // Amount: shareToken, - // }) - // require.NoError(t, err, "no error expected when redeeming") - - // // Confirm the full tokenization amount was recovered - minus the 1 token from the precision error - // newDelegation, found := app.StakingKeeper.GetDelegation(ctx, delegatorAddress, validatorAddress) - // require.True(t, found) - - // endDelegationTokens := validator.TokensFromShares(newDelegation.Shares).TruncateInt().Int64() - // expectedDelegationTokens := delegationAmountAfterSlash.Int64() - 1 - // require.Equal(t, expectedDelegationTokens, endDelegationTokens, "final delegation tokens") + _, app, ctx := createTestInput(t) + var ( + stakingKeeper = app.StakingKeeper + bankKeeper = app.BankKeeper + ) + msgServer := keeper.NewMsgServerImpl(stakingKeeper) + delegatorAddress, validatorAddress := setupTestTokenizeAndRedeemConversion(t, *stakingKeeper, bankKeeper, ctx) + + // Delegate and confirm the delegation record was created + delegateAmount := sdk.NewInt(1000) + delegateCoin := sdk.NewCoin(stakingKeeper.BondDenom(ctx), delegateAmount) + _, err := msgServer.Delegate(sdk.WrapSDKContext(ctx), &types.MsgDelegate{ + DelegatorAddress: delegatorAddress.String(), + ValidatorAddress: validatorAddress.String(), + Amount: delegateCoin, + }) + require.NoError(t, err, "no error expected when delegating") + + _, found := stakingKeeper.GetDelegation(ctx, delegatorAddress, validatorAddress) + require.True(t, found, "delegation should have been found") + + // slash the validator + simulateSlashWithImprecision(t, *stakingKeeper, ctx, validatorAddress) + validator, found := stakingKeeper.GetValidator(ctx, validatorAddress) + require.True(t, found) + + // Tokenize the new amount after the slash + delegationAmountAfterSlash := validator.TokensFromShares(sdk.NewDecFromInt(delegateAmount)).TruncateInt() + tokenizationCoin := sdk.NewCoin(stakingKeeper.BondDenom(ctx), delegationAmountAfterSlash) + + _, err = msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &types.MsgTokenizeShares{ + DelegatorAddress: delegatorAddress.String(), + ValidatorAddress: validatorAddress.String(), + Amount: tokenizationCoin, + TokenizedShareOwner: delegatorAddress.String(), + }) + require.NoError(t, err, "no error expected when tokenizing") + + // The number of share tokens should line up with the **new** number of shares associated + // with the original delegated amount + // Note: 1 token is lost during unbonding due to rounding + shareDenom := validatorAddress.String() + "/1" + shareToken := bankKeeper.GetBalance(ctx, delegatorAddress, shareDenom) + expectedShareTokens, err := validator.SharesFromTokens(tokenizationCoin.Amount) + require.Equal(t, expectedShareTokens.TruncateInt().Int64()-1, shareToken.Amount.Int64(), "share token amount") + + // // Redeem the share tokens + _, err = msgServer.RedeemTokensForShares(sdk.WrapSDKContext(ctx), &types.MsgRedeemTokensForShares{ + DelegatorAddress: delegatorAddress.String(), + Amount: shareToken, + }) + require.NoError(t, err, "no error expected when redeeming") + + // Confirm the full tokenization amount was recovered - minus the 1 token from the precision error + newDelegation, found := stakingKeeper.GetDelegation(ctx, delegatorAddress, validatorAddress) + require.True(t, found) + + endDelegationTokens := validator.TokensFromShares(newDelegation.Shares).TruncateInt().Int64() + expectedDelegationTokens := delegationAmountAfterSlash.Int64() - 1 + require.Equal(t, expectedDelegationTokens, endDelegationTokens, "final delegation tokens") } -// TODO refactor LSM test -// // Tests the conversion from tokenization and redemption from the following scenario: // Delegate -> Tokenize -> Slash -> Redeem // Note, in this example, there 1 token lost during the decimal to int conversion // during the unbonding step within redemption -func TestTokenizeAndRedeemConversion_SlashBeforeRedemptino(t *testing.T) { - // app := simapp.Setup(t, false) - // ctx := app.BaseApp.NewContext(false, tmproto.Header{}) - // msgServer := keeper.NewMsgServerImpl(app.StakingKeeper) - - // delegatorAddress, validatorAddress := setupTestTokenizeAndRedeemConversion(t, app, ctx) - - // // Delegate and confirm the delegation record was created - // delegateAmount := sdk.NewInt(1000) - // delegateCoin := sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), delegateAmount) - // _, err := msgServer.Delegate(sdk.WrapSDKContext(ctx), &types.MsgDelegate{ - // DelegatorAddress: delegatorAddress.String(), - // ValidatorAddress: validatorAddress.String(), - // Amount: delegateCoin, - // }) - // require.NoError(t, err, "no error expected when delegating") - - // _, found := app.StakingKeeper.GetDelegation(ctx, delegatorAddress, validatorAddress) - // require.True(t, found, "delegation should have been found") - - // // Tokenize the full delegation amount - // _, err = msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &types.MsgTokenizeShares{ - // DelegatorAddress: delegatorAddress.String(), - // ValidatorAddress: validatorAddress.String(), - // Amount: delegateCoin, - // TokenizedShareOwner: delegatorAddress.String(), - // }) - // require.NoError(t, err, "no error expected when tokenizing") - - // // The number of share tokens should line up 1:1 with the number of issued shares - // // Since the validator has not been slashed, the shares also line up 1;1 - // // with the original delegation amount - // shareDenom := validatorAddress.String() + "/1" - // shareToken := app.BankKeeper.GetBalance(ctx, delegatorAddress, shareDenom) - // expectedShareTokens := delegateAmount - // require.Equal(t, expectedShareTokens.Int64(), shareToken.Amount.Int64(), "share token amount") - - // // slash the validator - // simulateSlashWithImprecision(t, app, ctx, validatorAddress) - // validator, found := app.StakingKeeper.GetValidator(ctx, validatorAddress) - // require.True(t, found) +func TestTokenizeAndRedeemConversion_SlashBeforeRedemption(t *testing.T) { + _, app, ctx := createTestInput(t) + var ( + stakingKeeper = app.StakingKeeper + bankKeeper = app.BankKeeper + ) + msgServer := keeper.NewMsgServerImpl(stakingKeeper) + delegatorAddress, validatorAddress := setupTestTokenizeAndRedeemConversion(t, *stakingKeeper, bankKeeper, ctx) + + // Delegate and confirm the delegation record was created + delegateAmount := sdk.NewInt(1000) + delegateCoin := sdk.NewCoin(stakingKeeper.BondDenom(ctx), delegateAmount) + _, err := msgServer.Delegate(sdk.WrapSDKContext(ctx), &types.MsgDelegate{ + DelegatorAddress: delegatorAddress.String(), + ValidatorAddress: validatorAddress.String(), + Amount: delegateCoin, + }) + require.NoError(t, err, "no error expected when delegating") + + _, found := stakingKeeper.GetDelegation(ctx, delegatorAddress, validatorAddress) + require.True(t, found, "delegation should have been found") + + // Tokenize the full delegation amount + _, err = msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &types.MsgTokenizeShares{ + DelegatorAddress: delegatorAddress.String(), + ValidatorAddress: validatorAddress.String(), + Amount: delegateCoin, + TokenizedShareOwner: delegatorAddress.String(), + }) + require.NoError(t, err, "no error expected when tokenizing") + + // The number of share tokens should line up 1:1 with the number of issued shares + // Since the validator has not been slashed, the shares also line up 1;1 + // with the original delegation amount + shareDenom := validatorAddress.String() + "/1" + shareToken := bankKeeper.GetBalance(ctx, delegatorAddress, shareDenom) + expectedShareTokens := delegateAmount + require.Equal(t, expectedShareTokens.Int64(), shareToken.Amount.Int64(), "share token amount") + + // slash the validator + simulateSlashWithImprecision(t, *stakingKeeper, ctx, validatorAddress) + validator, found := stakingKeeper.GetValidator(ctx, validatorAddress) + require.True(t, found) - // // Redeem the share tokens - // _, err = msgServer.RedeemTokensForShares(sdk.WrapSDKContext(ctx), &types.MsgRedeemTokensForShares{ - // DelegatorAddress: delegatorAddress.String(), - // Amount: shareToken, - // }) - // require.NoError(t, err, "no error expected when redeeming") - - // // Confirm the original delegation, minus the slash, was recovered - // // There's an additional 1 token lost from precision error during unbonding - // delegationAmountAfterSlash := validator.TokensFromShares(delegateAmount.ToDec()).TruncateInt().Int64() - // newDelegation, found := app.StakingKeeper.GetDelegation(ctx, delegatorAddress, validatorAddress) - // require.True(t, found) - - // endDelegationTokens := validator.TokensFromShares(newDelegation.Shares).TruncateInt().Int64() - // require.Equal(t, delegationAmountAfterSlash-1, endDelegationTokens, "final delegation tokens") + // Redeem the share tokens + _, err = msgServer.RedeemTokensForShares(sdk.WrapSDKContext(ctx), &types.MsgRedeemTokensForShares{ + DelegatorAddress: delegatorAddress.String(), + Amount: shareToken, + }) + require.NoError(t, err, "no error expected when redeeming") + + // Confirm the original delegation, minus the slash, was recovered + // There's an additional 1 token lost from precision error during unbonding + delegationAmountAfterSlash := validator.TokensFromShares(sdk.NewDecFromInt(delegateAmount)).TruncateInt().Int64() + newDelegation, found := stakingKeeper.GetDelegation(ctx, delegatorAddress, validatorAddress) + require.True(t, found) + + endDelegationTokens := validator.TokensFromShares(newDelegation.Shares).TruncateInt().Int64() + require.Equal(t, delegationAmountAfterSlash-1, endDelegationTokens, "final delegation tokens") } -// TODO refactor LSM test func TestTransferTokenizeShareRecord(t *testing.T) { - // app := simapp.Setup(t, false) - // ctx := app.BaseApp.NewContext(false, tmproto.Header{}) - // msgServer := keeper.NewMsgServerImpl(app.StakingKeeper) - - // addrs := simapp.AddTestAddrs(app, ctx, 3, app.StakingKeeper.TokensFromConsensusPower(ctx, 10000)) - // addrAcc1, addrAcc2, valAcc := addrs[0], addrs[1], addrs[2] - // addrVal := sdk.ValAddress(valAcc) - - // pubKeys := simapp.CreateTestPubKeys(1) - // pk := pubKeys[0] - - // val := stakingtypes.NewValidator(addrVal, pk, stakingtypes.Description{}) - // app.StakingKeeper.SetValidator(ctx, val) - // app.StakingKeeper.SetValidatorByPowerIndex(ctx, val) - - // // apply TM updates - // applyValidatorSetUpdates(t, ctx, app.StakingKeeper, -1) - - // msgServer := keeper.NewMsgServerImpl(app.StakingKeeper) - - // err := app.StakingKeeper.AddTokenizeShareRecord(ctx, types.TokenizeShareRecord{ - // Id: 1, - // Owner: addrAcc1.String(), - // ModuleAccount: "module_account", - // Validator: val.String(), - // }) - // require.NoError(t, err) - - // _, err = msgServer.TransferTokenizeShareRecord(sdk.WrapSDKContext(ctx), &types.MsgTransferTokenizeShareRecord{ - // TokenizeShareRecordId: 1, - // Sender: addrAcc1.String(), - // NewOwner: addrAcc2.String(), - // }) - // require.NoError(t, err) - - // record, err := app.StakingKeeper.GetTokenizeShareRecord(ctx, 1) - // require.NoError(t, err) - // require.Equal(t, record.Owner, addrAcc2.String()) - - // records := app.StakingKeeper.GetTokenizeShareRecordsByOwner(ctx, addrAcc1) - // require.Len(t, records, 0) - // records = app.StakingKeeper.GetTokenizeShareRecordsByOwner(ctx, addrAcc2) - // require.Len(t, records, 1) + var ( + bankKeeper bankkeeper.Keeper + stakingKeeper *keeper.Keeper + ) + app, err := simtestutil.Setup(testutil.AppConfig, + &bankKeeper, + &stakingKeeper, + ) + require.NoError(t, err) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + msgServer := keeper.NewMsgServerImpl(stakingKeeper) + + addrs := simtestutil.AddTestAddrs(bankKeeper, stakingKeeper, ctx, 3, stakingKeeper.TokensFromConsensusPower(ctx, 10000)) + addrAcc1, addrAcc2, valAcc := addrs[0], addrs[1], addrs[2] + addrVal := sdk.ValAddress(valAcc) + + pubKeys := simtestutil.CreateTestPubKeys(1) + pk := pubKeys[0] + + val, err := stakingtypes.NewValidator(addrVal, pk, stakingtypes.Description{}) + require.NoError(t, err) + + stakingKeeper.SetValidator(ctx, val) + stakingKeeper.SetValidatorByPowerIndex(ctx, val) + + // apply TM updates + applyValidatorSetUpdates(t, ctx, stakingKeeper, -1) + + err = stakingKeeper.AddTokenizeShareRecord(ctx, types.TokenizeShareRecord{ + Id: 1, + Owner: addrAcc1.String(), + ModuleAccount: "module_account", + Validator: val.String(), + }) + require.NoError(t, err) + + _, err = msgServer.TransferTokenizeShareRecord(sdk.WrapSDKContext(ctx), &types.MsgTransferTokenizeShareRecord{ + TokenizeShareRecordId: 1, + Sender: addrAcc1.String(), + NewOwner: addrAcc2.String(), + }) + require.NoError(t, err) + + record, err := stakingKeeper.GetTokenizeShareRecord(ctx, 1) + require.NoError(t, err) + require.Equal(t, record.Owner, addrAcc2.String()) + + records := stakingKeeper.GetTokenizeShareRecordsByOwner(ctx, addrAcc1) + require.Len(t, records, 0) + records = stakingKeeper.GetTokenizeShareRecordsByOwner(ctx, addrAcc2) + require.Len(t, records, 1) } -// TODO refactor LSM test func TestValidatorBond(t *testing.T) { - // app := simapp.Setup(t, false) - // ctx := app.BaseApp.NewContext(false, tmproto.Header{}) - - // testCases := []struct { - // name string - // createValidator bool - // createDelegation bool - // alreadyValidatorBond bool - // delegatorIsLSTP bool - // expectedErr error - // }{ - // { - // name: "successful validator bond", - // createValidator: true, - // createDelegation: true, - // alreadyValidatorBond: false, - // delegatorIsLSTP: false, - // }, - // { - // name: "successful with existing validator bond", - // createValidator: true, - // createDelegation: true, - // alreadyValidatorBond: true, - // delegatorIsLSTP: false, - // }, - // { - // name: "validator does not not exist", - // createValidator: false, - // createDelegation: false, - // alreadyValidatorBond: false, - // delegatorIsLSTP: false, - // expectedErr: sdkstaking.ErrNoValidatorFound, - // }, - // { - // name: "delegation not exist case", - // createValidator: true, - // createDelegation: false, - // alreadyValidatorBond: false, - // delegatorIsLSTP: false, - // expectedErr: sdkstaking.ErrNoDelegation, - // }, - // { - // name: "delegator is a liquid staking provider", - // createValidator: true, - // createDelegation: true, - // alreadyValidatorBond: false, - // delegatorIsLSTP: true, - // expectedErr: types.ErrValidatorBondNotAllowedFromModuleAccount, - // }, - // } - - // for _, tc := range testCases { - // t.Run(tc.name, func(t *testing.T) { - // _, app, ctx = createTestInput() - - // pubKeys := simapp.CreateTestPubKeys(2) - // validatorPubKey := pubKeys[0] - // delegatorPubKey := pubKeys[1] - - // delegatorAddress := sdk.AccAddress(delegatorPubKey.Address()) - // validatorAddress := sdk.ValAddress(validatorPubKey.Address()) - // icaAccountAddress := createICAAccount(app, ctx) - - // // Set the delegator address to either be a user account or an ICA account depending on the test case - // if tc.delegatorIsLSTP { - // delegatorAddress = icaAccountAddress - // } - - // // Fund the delegator - // delegationAmount := app.StakingKeeper.TokensFromConsensusPower(ctx, 20) - // coins := sdk.NewCoins(sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), delegationAmount)) - - // err := app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, coins) - // require.NoError(t, err, "no error expected when minting") - - // err = app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, delegatorAddress, coins) - // require.NoError(t, err, "no error expected when funding account") - - // // Create Validator and delegation - // if tc.createValidator { - // validator := stakingtypes.NewValidator(validatorAddress, validatorPubKey, stakingtypes.Description{}) - // validator.Status = sdkstaking.Bonded - // app.StakingKeeper.SetValidator(ctx, validator) - // app.StakingKeeper.SetValidatorByPowerIndex(ctx, validator) - // err = app.StakingKeeper.SetValidatorByConsAddr(ctx, validator) - // require.NoError(t, err) - - // // Optionally create the delegation, depending on the test case - // if tc.createDelegation { - // _, err = app.StakingKeeper.Delegate(ctx, delegatorAddress, delegationAmount, sdkstaking.Unbonded, validator, true) - // require.NoError(t, err, "no error expected when delegating") - - // // Optionally, convert the delegation into a validator bond - // if tc.alreadyValidatorBond { - // delegation, found := app.StakingKeeper.GetDelegation(ctx, delegatorAddress, validatorAddress) - // require.True(t, found, "delegation should have been found") - - // delegation.ValidatorBond = true - // app.StakingKeeper.SetDelegation(ctx, delegation) - // } - // } - // } - - // // Call ValidatorBond - // msgServer := keeper.NewMsgServerImpl(app.StakingKeeper) - // _, err = msgServer.ValidatorBond(sdk.WrapSDKContext(ctx), &types.MsgValidatorBond{ - // DelegatorAddress: delegatorAddress.String(), - // ValidatorAddress: validatorAddress.String(), - // }) - - // if tc.expectedErr != nil { - // require.ErrorContains(t, err, tc.expectedErr.Error()) - // } else { - // require.NoError(t, err, "no error expected from validator bond transaction") - - // // check validator bond true - // delegation, found := app.StakingKeeper.GetDelegation(ctx, delegatorAddress, validatorAddress) - // require.True(t, found, "delegation should have been found after validator bond") - // require.True(t, delegation.ValidatorBond, "delegation should be marked as a validator bond") - - // // check validator bond shares - // validator, found := app.StakingKeeper.GetValidator(ctx, validatorAddress) - // require.True(t, found, "validator should have been found after validator bond") - - // if tc.alreadyValidatorBond { - // require.True(t, validator.ValidatorBondShares.IsZero(), "validator bond shares should still be zero") - // } else { - // require.Equal(t, delegation.Shares.String(), validator.ValidatorBondShares.String(), - // "validator total shares should have increased") - // } - // } - // }) - // } + testCases := []struct { + name string + createValidator bool + createDelegation bool + alreadyValidatorBond bool + delegatorIsLSTP bool + expectedErr error + }{ + { + name: "successful validator bond", + createValidator: true, + createDelegation: true, + alreadyValidatorBond: false, + delegatorIsLSTP: false, + }, + { + name: "successful with existing validator bond", + createValidator: true, + createDelegation: true, + alreadyValidatorBond: true, + delegatorIsLSTP: false, + }, + { + name: "validator does not not exist", + createValidator: false, + createDelegation: false, + alreadyValidatorBond: false, + delegatorIsLSTP: false, + expectedErr: stakingtypes.ErrNoValidatorFound, + }, + { + name: "delegation not exist case", + createValidator: true, + createDelegation: false, + alreadyValidatorBond: false, + delegatorIsLSTP: false, + expectedErr: stakingtypes.ErrNoDelegation, + }, + { + name: "delegator is a liquid staking provider", + createValidator: true, + createDelegation: true, + alreadyValidatorBond: false, + delegatorIsLSTP: true, + expectedErr: types.ErrValidatorBondNotAllowedFromModuleAccount, + }, + } + + for _, tc := range testCases { + t.Run(tc.name, func(t *testing.T) { + _, app, ctx := createTestInput(t) + var ( + accountKeeper = app.AccountKeeper + stakingKeeper = app.StakingKeeper + bankKeeper = app.BankKeeper + ) + pubKeys := simtestutil.CreateTestPubKeys(2) + + validatorPubKey := pubKeys[0] + delegatorPubKey := pubKeys[1] + + delegatorAddress := sdk.AccAddress(delegatorPubKey.Address()) + validatorAddress := sdk.ValAddress(validatorPubKey.Address()) + icaAccountAddress := createICAAccount(ctx, accountKeeper) + + // Set the delegator address to either be a user account or an ICA account depending on the test case + if tc.delegatorIsLSTP { + delegatorAddress = icaAccountAddress + } + + // Fund the delegator + delegationAmount := stakingKeeper.TokensFromConsensusPower(ctx, 20) + coins := sdk.NewCoins(sdk.NewCoin(stakingKeeper.BondDenom(ctx), delegationAmount)) + + err := bankKeeper.MintCoins(ctx, minttypes.ModuleName, coins) + require.NoError(t, err, "no error expected when minting") + + err = bankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, delegatorAddress, coins) + require.NoError(t, err, "no error expected when funding account") + + // Create Validator and delegation + if tc.createValidator { + validator, err := stakingtypes.NewValidator(validatorAddress, validatorPubKey, stakingtypes.Description{}) + require.NoError(t, err) + validator.Status = stakingtypes.Bonded + stakingKeeper.SetValidator(ctx, validator) + stakingKeeper.SetValidatorByPowerIndex(ctx, validator) + err = stakingKeeper.SetValidatorByConsAddr(ctx, validator) + require.NoError(t, err) + + // Optionally create the delegation, depending on the test case + if tc.createDelegation { + _, err = stakingKeeper.Delegate(ctx, delegatorAddress, delegationAmount, stakingtypes.Unbonded, validator, true) + require.NoError(t, err, "no error expected when delegating") + + // Optionally, convert the delegation into a validator bond + if tc.alreadyValidatorBond { + delegation, found := stakingKeeper.GetDelegation(ctx, delegatorAddress, validatorAddress) + require.True(t, found, "delegation should have been found") + + delegation.ValidatorBond = true + stakingKeeper.SetDelegation(ctx, delegation) + } + } + } + + // Call ValidatorBond + msgServer := keeper.NewMsgServerImpl(stakingKeeper) + _, err = msgServer.ValidatorBond(sdk.WrapSDKContext(ctx), &types.MsgValidatorBond{ + DelegatorAddress: delegatorAddress.String(), + ValidatorAddress: validatorAddress.String(), + }) + + if tc.expectedErr != nil { + require.ErrorContains(t, err, tc.expectedErr.Error()) + } else { + require.NoError(t, err, "no error expected from validator bond transaction") + + // check validator bond true + delegation, found := stakingKeeper.GetDelegation(ctx, delegatorAddress, validatorAddress) + require.True(t, found, "delegation should have been found after validator bond") + require.True(t, delegation.ValidatorBond, "delegation should be marked as a validator bond") + + // check validator bond shares + validator, found := stakingKeeper.GetValidator(ctx, validatorAddress) + require.True(t, found, "validator should have been found after validator bond") + + if tc.alreadyValidatorBond { + require.True(t, validator.ValidatorBondShares.IsZero(), "validator bond shares should still be zero") + } else { + require.Equal(t, delegation.Shares.String(), validator.ValidatorBondShares.String(), + "validator total shares should have increased") + } + } + }) + } } -// TODO refactor LSM test func TestChangeValidatorBond(t *testing.T) { - // app := simapp.Setup(t, false) - // ctx := app.BaseApp.NewContext(false, tmproto.Header{}) - // msgServer := keeper.NewMsgServerImpl(app.StakingKeeper) - - // checkValidatorBondShares := func(validatorAddress sdk.ValAddress, expectedShares sdk.Int) { - // validator, found := app.StakingKeeper.GetValidator(ctx, validatorAddress) - // require.True(t, found, "validator should have been found") - // require.Equal(t, expectedShares.Int64(), validator.ValidatorBondShares.TruncateInt64(), "validator bond shares") - // } - - // // Create a delegator and 3 validators - // addresses := simapp.AddTestAddrs(app, ctx, 4, sdk.NewInt(1_000_000)) - // pubKeys := simapp.CreateTestPubKeys(4) - - // validatorAPubKey := pubKeys[1] - // validatorBPubKey := pubKeys[2] - // validatorCPubKey := pubKeys[3] - - // delegatorAddress := addresses[0] - // validatorAAddress := sdk.ValAddress(validatorAPubKey.Address()) - // validatorBAddress := sdk.ValAddress(validatorBPubKey.Address()) - // validatorCAddress := sdk.ValAddress(validatorCPubKey.Address()) - - // validatorA := stakingtypes.NewValidator(validatorAAddress, validatorAPubKey, stakingtypes.Description{}) - // validatorB := stakingtypes.NewValidator(validatorBAddress, validatorBPubKey, stakingtypes.Description{}) - // validatorC := stakingtypes.NewValidator(validatorCAddress, validatorCPubKey, stakingtypes.Description{}) - - // validatorA.Tokens = sdk.NewInt(1_000_000) - // validatorB.Tokens = sdk.NewInt(1_000_000) - // validatorC.Tokens = sdk.NewInt(1_000_000) - // validatorA.DelegatorShares = sdk.NewDec(1_000_000) - // validatorB.DelegatorShares = sdk.NewDec(1_000_000) - // validatorC.DelegatorShares = sdk.NewDec(1_000_000) - - // app.StakingKeeper.SetValidator(ctx, validatorA) - // app.StakingKeeper.SetValidator(ctx, validatorB) - // app.StakingKeeper.SetValidator(ctx, validatorC) - - // // The test will go through Delegate/Redelegate/Undelegate messages with the following - // delegation1Amount := sdk.NewInt(1000) - // delegation2Amount := sdk.NewInt(1000) - // redelegateAmount := sdk.NewInt(500) - // undelegateAmount := sdk.NewInt(500) - - // delegate1Coin := sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), delegation1Amount) - // delegate2Coin := sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), delegation2Amount) - // redelegateCoin := sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), redelegateAmount) - // undelegateCoin := sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), undelegateAmount) - - // // Delegate to validator's A and C - validator bond shares should not change - // _, err := msgServer.Delegate(sdk.WrapSDKContext(ctx), &types.MsgDelegate{ - // DelegatorAddress: delegatorAddress.String(), - // ValidatorAddress: validatorAAddress.String(), - // Amount: delegate1Coin, - // }) - // require.NoError(t, err, "no error expected during first delegation") - - // _, err = msgServer.Delegate(sdk.WrapSDKContext(ctx), &types.MsgDelegate{ - // DelegatorAddress: delegatorAddress.String(), - // ValidatorAddress: validatorCAddress.String(), - // Amount: delegate1Coin, - // }) - // require.NoError(t, err, "no error expected during first delegation") - - // checkValidatorBondShares(validatorAAddress, sdk.ZeroInt()) - // checkValidatorBondShares(validatorBAddress, sdk.ZeroInt()) - // checkValidatorBondShares(validatorCAddress, sdk.ZeroInt()) - - // // Flag the the delegations to validator A and C validator bond's - // // Their bond shares should increase - // _, err = msgServer.ValidatorBond(sdk.WrapSDKContext(ctx), &types.MsgValidatorBond{ - // DelegatorAddress: delegatorAddress.String(), - // ValidatorAddress: validatorAAddress.String(), - // }) - // require.NoError(t, err, "no error expected during validator bond") - - // _, err = msgServer.ValidatorBond(sdk.WrapSDKContext(ctx), &types.MsgValidatorBond{ - // DelegatorAddress: delegatorAddress.String(), - // ValidatorAddress: validatorCAddress.String(), - // }) - // require.NoError(t, err, "no error expected during validator bond") - - // checkValidatorBondShares(validatorAAddress, delegation1Amount) - // checkValidatorBondShares(validatorBAddress, sdk.ZeroInt()) - // checkValidatorBondShares(validatorCAddress, delegation1Amount) - - // // Delegate more to validator A - it should increase the validator bond shares - // _, err = msgServer.Delegate(sdk.WrapSDKContext(ctx), &types.MsgDelegate{ - // DelegatorAddress: delegatorAddress.String(), - // ValidatorAddress: validatorAAddress.String(), - // Amount: delegate2Coin, - // }) - // require.NoError(t, err, "no error expected during second delegation") - - // checkValidatorBondShares(validatorAAddress, delegation1Amount.Add(delegation2Amount)) - // checkValidatorBondShares(validatorBAddress, sdk.ZeroInt()) - // checkValidatorBondShares(validatorCAddress, delegation1Amount) - - // // Redelegate partially from A to B (where A is a validator bond and B is not) - // // It should remove the bond shares from A, and B's validator bond shares should not change - // _, err = msgServer.BeginRedelegate(sdk.WrapSDKContext(ctx), &types.MsgBeginRedelegate{ - // DelegatorAddress: delegatorAddress.String(), - // ValidatorSrcAddress: validatorAAddress.String(), - // ValidatorDstAddress: validatorBAddress.String(), - // Amount: redelegateCoin, - // }) - // require.NoError(t, err, "no error expected during redelegation") - - // expectedBondSharesA := delegation1Amount.Add(delegation2Amount).Sub(redelegateAmount) - // checkValidatorBondShares(validatorAAddress, expectedBondSharesA) - // checkValidatorBondShares(validatorBAddress, sdk.ZeroInt()) - // checkValidatorBondShares(validatorCAddress, delegation1Amount) - - // // Now redelegate from B to C (where B is not a validator bond, but C is) - // // Validator B's bond shares should remain at zero, but C's bond shares should increase - // _, err = msgServer.BeginRedelegate(sdk.WrapSDKContext(ctx), &types.MsgBeginRedelegate{ - // DelegatorAddress: delegatorAddress.String(), - // ValidatorSrcAddress: validatorBAddress.String(), - // ValidatorDstAddress: validatorCAddress.String(), - // Amount: redelegateCoin, - // }) - // require.NoError(t, err, "no error expected during redelegation") - - // checkValidatorBondShares(validatorAAddress, expectedBondSharesA) - // checkValidatorBondShares(validatorBAddress, sdk.ZeroInt()) - // checkValidatorBondShares(validatorCAddress, delegation1Amount.Add(redelegateAmount)) - - // // Redelegate partially from A to C (where C is a validator bond delegation) - // // It should remove the bond shares from A, and increase the bond shares on validator C - // _, err = msgServer.BeginRedelegate(sdk.WrapSDKContext(ctx), &types.MsgBeginRedelegate{ - // DelegatorAddress: delegatorAddress.String(), - // ValidatorSrcAddress: validatorAAddress.String(), - // ValidatorDstAddress: validatorCAddress.String(), - // Amount: redelegateCoin, - // }) - // require.NoError(t, err, "no error expected during redelegation") - - // expectedBondSharesA = expectedBondSharesA.Sub(redelegateAmount) - // expectedBondSharesC := delegation1Amount.Add(redelegateAmount).Add(redelegateAmount) - // checkValidatorBondShares(validatorAAddress, expectedBondSharesA) - // checkValidatorBondShares(validatorBAddress, sdk.ZeroInt()) - // checkValidatorBondShares(validatorCAddress, expectedBondSharesC) - - // // Undelegate from validator A - it should remove shares - // _, err = msgServer.Undelegate(sdk.WrapSDKContext(ctx), &types.MsgUndelegate{ - // DelegatorAddress: delegatorAddress.String(), - // ValidatorAddress: validatorAAddress.String(), - // Amount: undelegateCoin, - // }) - // require.NoError(t, err, "no error expected during undelegation") - - // expectedBondSharesA = expectedBondSharesA.Sub(undelegateAmount) - // checkValidatorBondShares(validatorAAddress, expectedBondSharesA) - // checkValidatorBondShares(validatorBAddress, sdk.ZeroInt()) - // checkValidatorBondShares(validatorCAddress, expectedBondSharesC) + _, app, ctx := createTestInput(t) + var ( + stakingKeeper = app.StakingKeeper + bankKeeper = app.BankKeeper + ) + + checkValidatorBondShares := func(validatorAddress sdk.ValAddress, expectedShares math.Int) { + validator, found := stakingKeeper.GetValidator(ctx, validatorAddress) + require.True(t, found, "validator should have been found") + require.Equal(t, expectedShares.Int64(), validator.ValidatorBondShares.TruncateInt64(), "validator bond shares") + } + + // Create a delegator and 3 validators + addresses := simtestutil.AddTestAddrs(bankKeeper, stakingKeeper, ctx, 4, sdk.NewInt(1_000_000)) + pubKeys := simtestutil.CreateTestPubKeys(4) + + validatorAPubKey := pubKeys[1] + validatorBPubKey := pubKeys[2] + validatorCPubKey := pubKeys[3] + + delegatorAddress := addresses[0] + validatorAAddress := sdk.ValAddress(validatorAPubKey.Address()) + validatorBAddress := sdk.ValAddress(validatorBPubKey.Address()) + validatorCAddress := sdk.ValAddress(validatorCPubKey.Address()) + + validatorA, err := stakingtypes.NewValidator(validatorAAddress, validatorAPubKey, stakingtypes.Description{}) + require.NoError(t, err) + validatorB, err := stakingtypes.NewValidator(validatorBAddress, validatorBPubKey, stakingtypes.Description{}) + require.NoError(t, err) + validatorC, err := stakingtypes.NewValidator(validatorCAddress, validatorCPubKey, stakingtypes.Description{}) + require.NoError(t, err) + + validatorA.Tokens = sdk.NewInt(1_000_000) + validatorB.Tokens = sdk.NewInt(1_000_000) + validatorC.Tokens = sdk.NewInt(1_000_000) + validatorA.DelegatorShares = sdk.NewDec(1_000_000) + validatorB.DelegatorShares = sdk.NewDec(1_000_000) + validatorC.DelegatorShares = sdk.NewDec(1_000_000) + + stakingKeeper.SetValidator(ctx, validatorA) + stakingKeeper.SetValidator(ctx, validatorB) + stakingKeeper.SetValidator(ctx, validatorC) + + // The test will go through Delegate/Redelegate/Undelegate messages with the following + delegation1Amount := sdk.NewInt(1000) + delegation2Amount := sdk.NewInt(1000) + redelegateAmount := sdk.NewInt(500) + undelegateAmount := sdk.NewInt(500) + + delegate1Coin := sdk.NewCoin(stakingKeeper.BondDenom(ctx), delegation1Amount) + delegate2Coin := sdk.NewCoin(stakingKeeper.BondDenom(ctx), delegation2Amount) + redelegateCoin := sdk.NewCoin(stakingKeeper.BondDenom(ctx), redelegateAmount) + undelegateCoin := sdk.NewCoin(stakingKeeper.BondDenom(ctx), undelegateAmount) + + // Delegate to validator's A and C - validator bond shares should not change + msgServer := keeper.NewMsgServerImpl(stakingKeeper) + _, err = msgServer.Delegate(sdk.WrapSDKContext(ctx), &types.MsgDelegate{ + DelegatorAddress: delegatorAddress.String(), + ValidatorAddress: validatorAAddress.String(), + Amount: delegate1Coin, + }) + require.NoError(t, err, "no error expected during first delegation") + + _, err = msgServer.Delegate(sdk.WrapSDKContext(ctx), &types.MsgDelegate{ + DelegatorAddress: delegatorAddress.String(), + ValidatorAddress: validatorCAddress.String(), + Amount: delegate1Coin, + }) + require.NoError(t, err, "no error expected during first delegation") + + checkValidatorBondShares(validatorAAddress, sdk.ZeroInt()) + checkValidatorBondShares(validatorBAddress, sdk.ZeroInt()) + checkValidatorBondShares(validatorCAddress, sdk.ZeroInt()) + + // Flag the the delegations to validator A and C validator bond's + // Their bond shares should increase + _, err = msgServer.ValidatorBond(sdk.WrapSDKContext(ctx), &types.MsgValidatorBond{ + DelegatorAddress: delegatorAddress.String(), + ValidatorAddress: validatorAAddress.String(), + }) + require.NoError(t, err, "no error expected during validator bond") + + _, err = msgServer.ValidatorBond(sdk.WrapSDKContext(ctx), &types.MsgValidatorBond{ + DelegatorAddress: delegatorAddress.String(), + ValidatorAddress: validatorCAddress.String(), + }) + require.NoError(t, err, "no error expected during validator bond") + + checkValidatorBondShares(validatorAAddress, delegation1Amount) + checkValidatorBondShares(validatorBAddress, sdk.ZeroInt()) + checkValidatorBondShares(validatorCAddress, delegation1Amount) + + // Delegate more to validator A - it should increase the validator bond shares + _, err = msgServer.Delegate(sdk.WrapSDKContext(ctx), &types.MsgDelegate{ + DelegatorAddress: delegatorAddress.String(), + ValidatorAddress: validatorAAddress.String(), + Amount: delegate2Coin, + }) + require.NoError(t, err, "no error expected during second delegation") + + checkValidatorBondShares(validatorAAddress, delegation1Amount.Add(delegation2Amount)) + checkValidatorBondShares(validatorBAddress, sdk.ZeroInt()) + checkValidatorBondShares(validatorCAddress, delegation1Amount) + + // Redelegate partially from A to B (where A is a validator bond and B is not) + // It should remove the bond shares from A, and B's validator bond shares should not change + _, err = msgServer.BeginRedelegate(sdk.WrapSDKContext(ctx), &types.MsgBeginRedelegate{ + DelegatorAddress: delegatorAddress.String(), + ValidatorSrcAddress: validatorAAddress.String(), + ValidatorDstAddress: validatorBAddress.String(), + Amount: redelegateCoin, + }) + require.NoError(t, err, "no error expected during redelegation") + + expectedBondSharesA := delegation1Amount.Add(delegation2Amount).Sub(redelegateAmount) + checkValidatorBondShares(validatorAAddress, expectedBondSharesA) + checkValidatorBondShares(validatorBAddress, sdk.ZeroInt()) + checkValidatorBondShares(validatorCAddress, delegation1Amount) + + // Now redelegate from B to C (where B is not a validator bond, but C is) + // Validator B's bond shares should remain at zero, but C's bond shares should increase + _, err = msgServer.BeginRedelegate(sdk.WrapSDKContext(ctx), &types.MsgBeginRedelegate{ + DelegatorAddress: delegatorAddress.String(), + ValidatorSrcAddress: validatorBAddress.String(), + ValidatorDstAddress: validatorCAddress.String(), + Amount: redelegateCoin, + }) + require.NoError(t, err, "no error expected during redelegation") + + checkValidatorBondShares(validatorAAddress, expectedBondSharesA) + checkValidatorBondShares(validatorBAddress, sdk.ZeroInt()) + checkValidatorBondShares(validatorCAddress, delegation1Amount.Add(redelegateAmount)) + + // Redelegate partially from A to C (where C is a validator bond delegation) + // It should remove the bond shares from A, and increase the bond shares on validator C + _, err = msgServer.BeginRedelegate(sdk.WrapSDKContext(ctx), &types.MsgBeginRedelegate{ + DelegatorAddress: delegatorAddress.String(), + ValidatorSrcAddress: validatorAAddress.String(), + ValidatorDstAddress: validatorCAddress.String(), + Amount: redelegateCoin, + }) + require.NoError(t, err, "no error expected during redelegation") + + expectedBondSharesA = expectedBondSharesA.Sub(redelegateAmount) + expectedBondSharesC := delegation1Amount.Add(redelegateAmount).Add(redelegateAmount) + checkValidatorBondShares(validatorAAddress, expectedBondSharesA) + checkValidatorBondShares(validatorBAddress, sdk.ZeroInt()) + checkValidatorBondShares(validatorCAddress, expectedBondSharesC) + + // Undelegate from validator A - it should remove shares + _, err = msgServer.Undelegate(sdk.WrapSDKContext(ctx), &types.MsgUndelegate{ + DelegatorAddress: delegatorAddress.String(), + ValidatorAddress: validatorAAddress.String(), + Amount: undelegateCoin, + }) + require.NoError(t, err, "no error expected during undelegation") + + expectedBondSharesA = expectedBondSharesA.Sub(undelegateAmount) + checkValidatorBondShares(validatorAAddress, expectedBondSharesA) + checkValidatorBondShares(validatorBAddress, sdk.ZeroInt()) + checkValidatorBondShares(validatorCAddress, expectedBondSharesC) } -// TODO refactor LSM test func TestEnableDisableTokenizeShares(t *testing.T) { - // app := simapp.Setup(t, false) - // ctx := app.BaseApp.NewContext(false, tmproto.Header{}) - // msgServer := keeper.NewMsgServerImpl(app.StakingKeeper) - // // Create a delegator and validator - // stakeAmount := sdk.NewInt(1000) - // stakeToken := sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), stakeAmount) - - // addresses := simapp.AddTestAddrs(app, ctx, 2, stakeAmount) - // delegatorAddress := addresses[0] - - // pubKeys := simapp.CreateTestPubKeys(1) - // validatorAddress := sdk.ValAddress(addresses[1]) - // validator := stakingtypes.NewValidator(validatorAddress, pubKeys[0], stakingtypes.Description{}) - - // validator.DelegatorShares = sdk.NewDec(1_000_000) - // validator.Tokens = sdk.NewInt(1_000_000) - // validator.Status = types.Bonded - // app.StakingKeeper.SetValidator(ctx, validator) - - // // Fix block time and set unbonding period to 1 day - // blockTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) - // ctx = ctx.WithBlockTime(blockTime) - - // unbondingPeriod := time.Hour * 24 - // params := app.StakingKeeper.GetParams(ctx) - // params.UnbondingTime = unbondingPeriod - // app.StakingKeeper.SetParams(ctx, params) - // unlockTime := blockTime.Add(unbondingPeriod) - - // // Build test messages (some of which will be reused) - // delegateMsg := types.MsgDelegate{ - // DelegatorAddress: delegatorAddress.String(), - // ValidatorAddress: validatorAddress.String(), - // Amount: stakeToken, - // } - // tokenizeMsg := types.MsgTokenizeShares{ - // DelegatorAddress: delegatorAddress.String(), - // ValidatorAddress: validatorAddress.String(), - // Amount: stakeToken, - // TokenizedShareOwner: delegatorAddress.String(), - // } - // redeemMsg := types.MsgRedeemTokensForShares{ - // DelegatorAddress: delegatorAddress.String(), - // } - // disableMsg := types.MsgDisableTokenizeShares{ - // DelegatorAddress: delegatorAddress.String(), - // } - // enableMsg := types.MsgEnableTokenizeShares{ - // DelegatorAddress: delegatorAddress.String(), - // } - - // // Delegate normally - // _, err := msgServer.Delegate(sdk.WrapSDKContext(ctx), &delegateMsg) - // require.NoError(t, err, "no error expected when delegating") - - // // Tokenize shares - it should succeed - // _, err = msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &tokenizeMsg) - // require.NoError(t, err, "no error expected when tokenizing shares for the first time") - - // liquidToken := app.BankKeeper.GetBalance(ctx, delegatorAddress, validatorAddress.String()+"/1") - // require.Equal(t, stakeAmount.Int64(), liquidToken.Amount.Int64(), "user received token after tokenizing share") - - // // Redeem to remove all tokenized shares - // redeemMsg.Amount = liquidToken - // _, err = msgServer.RedeemTokensForShares(sdk.WrapSDKContext(ctx), &redeemMsg) - // require.NoError(t, err, "no error expected when redeeming") - - // // Attempt to enable tokenizing shares when there is no lock in place, it should error - // _, err = msgServer.EnableTokenizeShares(sdk.WrapSDKContext(ctx), &enableMsg) - // require.ErrorIs(t, err, types.ErrTokenizeSharesAlreadyEnabledForAccount) - - // // Attempt to disable when no lock is in place, it should succeed - // _, err = msgServer.DisableTokenizeShares(sdk.WrapSDKContext(ctx), &disableMsg) - // require.NoError(t, err, "no error expected when disabling tokenization") - - // // Disabling again while the lock is already in place, should error - // _, err = msgServer.DisableTokenizeShares(sdk.WrapSDKContext(ctx), &disableMsg) - // require.ErrorIs(t, err, types.ErrTokenizeSharesAlreadyDisabledForAccount) - - // // Attempt to tokenize, it should fail since tokenization is disabled - // _, err = msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &tokenizeMsg) - // require.ErrorIs(t, err, types.ErrTokenizeSharesDisabledForAccount) - - // // Now enable tokenization - // _, err = msgServer.EnableTokenizeShares(sdk.WrapSDKContext(ctx), &enableMsg) - // require.NoError(t, err, "no error expected when enabling tokenization") - - // // Attempt to tokenize again, it should still fail since the unbonding period has - // // not passed and the lock is still active - // _, err = msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &tokenizeMsg) - // require.ErrorIs(t, err, types.ErrTokenizeSharesDisabledForAccount) - // require.ErrorContains(t, err, fmt.Sprintf("tokenization will be allowed at %s", - // blockTime.Add(unbondingPeriod))) - - // // Confirm the unlock is queued - // authorizations := app.StakingKeeper.GetPendingTokenizeShareAuthorizations(ctx, unlockTime) - // require.Equal(t, []string{delegatorAddress.String()}, authorizations.Addresses, - // "pending tokenize share authorizations") - - // // Disable tokenization again - it should remove the pending record from the queue - // _, err = msgServer.DisableTokenizeShares(sdk.WrapSDKContext(ctx), &disableMsg) - // require.NoError(t, err, "no error expected when re-enabling tokenization") - - // authorizations = app.StakingKeeper.GetPendingTokenizeShareAuthorizations(ctx, unlockTime) - // require.Empty(t, authorizations.Addresses, "there should be no pending authorizations in the queue") - - // // Enable one more time - // _, err = msgServer.EnableTokenizeShares(sdk.WrapSDKContext(ctx), &enableMsg) - // require.NoError(t, err, "no error expected when enabling tokenization again") - - // // Increment the block time by the unbonding period and remove the expired locks - // ctx = ctx.WithBlockTime(unlockTime) - // app.StakingKeeper.RemoveExpiredTokenizeShareLocks(ctx, ctx.BlockTime()) - - // // Attempt to tokenize again, it should succeed this time since the lock has expired - // _, err = msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &tokenizeMsg) - // require.NoError(t, err, "no error expected when tokenizing after lock has expired") + _, app, ctx := createTestInput(t) + var ( + stakingKeeper = app.StakingKeeper + bankKeeper = app.BankKeeper + ) + // Create a delegator and validator + stakeAmount := sdk.NewInt(1000) + stakeToken := sdk.NewCoin(stakingKeeper.BondDenom(ctx), stakeAmount) + + addresses := simtestutil.AddTestAddrs(bankKeeper, stakingKeeper, ctx, 2, stakeAmount) + delegatorAddress := addresses[0] + + pubKeys := simtestutil.CreateTestPubKeys(1) + validatorAddress := sdk.ValAddress(addresses[1]) + validator, err := stakingtypes.NewValidator(validatorAddress, pubKeys[0], stakingtypes.Description{}) + require.NoError(t, err) + + validator.DelegatorShares = sdk.NewDec(1_000_000) + validator.Tokens = sdk.NewInt(1_000_000) + validator.Status = types.Bonded + stakingKeeper.SetValidator(ctx, validator) + + // Fix block time and set unbonding period to 1 day + blockTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) + ctx = ctx.WithBlockTime(blockTime) + + unbondingPeriod := time.Hour * 24 + params := stakingKeeper.GetParams(ctx) + params.UnbondingTime = unbondingPeriod + stakingKeeper.SetParams(ctx, params) + unlockTime := blockTime.Add(unbondingPeriod) + + // Build test messages (some of which will be reused) + delegateMsg := types.MsgDelegate{ + DelegatorAddress: delegatorAddress.String(), + ValidatorAddress: validatorAddress.String(), + Amount: stakeToken, + } + tokenizeMsg := types.MsgTokenizeShares{ + DelegatorAddress: delegatorAddress.String(), + ValidatorAddress: validatorAddress.String(), + Amount: stakeToken, + TokenizedShareOwner: delegatorAddress.String(), + } + redeemMsg := types.MsgRedeemTokensForShares{ + DelegatorAddress: delegatorAddress.String(), + } + disableMsg := types.MsgDisableTokenizeShares{ + DelegatorAddress: delegatorAddress.String(), + } + enableMsg := types.MsgEnableTokenizeShares{ + DelegatorAddress: delegatorAddress.String(), + } + + msgServer := keeper.NewMsgServerImpl(stakingKeeper) + // Delegate normally + _, err = msgServer.Delegate(sdk.WrapSDKContext(ctx), &delegateMsg) + require.NoError(t, err, "no error expected when delegating") + + // Tokenize shares - it should succeed + _, err = msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &tokenizeMsg) + require.NoError(t, err, "no error expected when tokenizing shares for the first time") + + liquidToken := bankKeeper.GetBalance(ctx, delegatorAddress, validatorAddress.String()+"/1") + require.Equal(t, stakeAmount.Int64(), liquidToken.Amount.Int64(), "user received token after tokenizing share") + + // Redeem to remove all tokenized shares + redeemMsg.Amount = liquidToken + _, err = msgServer.RedeemTokensForShares(sdk.WrapSDKContext(ctx), &redeemMsg) + require.NoError(t, err, "no error expected when redeeming") + + // Attempt to enable tokenizing shares when there is no lock in place, it should error + _, err = msgServer.EnableTokenizeShares(sdk.WrapSDKContext(ctx), &enableMsg) + require.ErrorIs(t, err, types.ErrTokenizeSharesAlreadyEnabledForAccount) + + // Attempt to disable when no lock is in place, it should succeed + _, err = msgServer.DisableTokenizeShares(sdk.WrapSDKContext(ctx), &disableMsg) + require.NoError(t, err, "no error expected when disabling tokenization") + + // Disabling again while the lock is already in place, should error + _, err = msgServer.DisableTokenizeShares(sdk.WrapSDKContext(ctx), &disableMsg) + require.ErrorIs(t, err, types.ErrTokenizeSharesAlreadyDisabledForAccount) + + // Attempt to tokenize, it should fail since tokenization is disabled + _, err = msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &tokenizeMsg) + require.ErrorIs(t, err, types.ErrTokenizeSharesDisabledForAccount) + + // Now enable tokenization + _, err = msgServer.EnableTokenizeShares(sdk.WrapSDKContext(ctx), &enableMsg) + require.NoError(t, err, "no error expected when enabling tokenization") + + // Attempt to tokenize again, it should still fail since the unbonding period has + // not passed and the lock is still active + _, err = msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &tokenizeMsg) + require.ErrorIs(t, err, types.ErrTokenizeSharesDisabledForAccount) + require.ErrorContains(t, err, fmt.Sprintf("tokenization will be allowed at %s", + blockTime.Add(unbondingPeriod))) + + // Confirm the unlock is queued + authorizations := stakingKeeper.GetPendingTokenizeShareAuthorizations(ctx, unlockTime) + require.Equal(t, []string{delegatorAddress.String()}, authorizations.Addresses, + "pending tokenize share authorizations") + + // Disable tokenization again - it should remove the pending record from the queue + _, err = msgServer.DisableTokenizeShares(sdk.WrapSDKContext(ctx), &disableMsg) + require.NoError(t, err, "no error expected when re-enabling tokenization") + + authorizations = stakingKeeper.GetPendingTokenizeShareAuthorizations(ctx, unlockTime) + require.Empty(t, authorizations.Addresses, "there should be no pending authorizations in the queue") + + // Enable one more time + _, err = msgServer.EnableTokenizeShares(sdk.WrapSDKContext(ctx), &enableMsg) + require.NoError(t, err, "no error expected when enabling tokenization again") + + // Increment the block time by the unbonding period and remove the expired locks + ctx = ctx.WithBlockTime(unlockTime) + stakingKeeper.RemoveExpiredTokenizeShareLocks(ctx, ctx.BlockTime()) + + // Attempt to tokenize again, it should succeed this time since the lock has expired + _, err = msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &tokenizeMsg) + require.NoError(t, err, "no error expected when tokenizing after lock has expired") } -// TODO refactor LSM test func TestUnbondValidator(t *testing.T) { - // app := simapp.Setup(t, false) - // ctx := app.BaseApp.NewContext(false, tmproto.Header{}) - // msgServer := keeper.NewMsgServerImpl(app.StakingKeeper) - - // addrs := simapp.AddTestAddrs(app, ctx, 2, app.StakingKeeper.TokensFromConsensusPower(ctx, 10000)) - // addrAcc1 := addrs[0] - // addrVal1 := sdk.ValAddress(addrAcc1) - - // pubKeys := simapp.CreateTestPubKeys(1) - // pk1 := pubKeys[0] - - // // Create Validators and Delegation - // val1 := stakingtypes.NewValidator(addrVal1, pk1, stakingtypes.Description{}) - // val1.Status = sdkstaking.Bonded - // app.StakingKeeper.SetValidator(ctx, val1) - // app.StakingKeeper.SetValidatorByPowerIndex(ctx, val1) - // err := app.StakingKeeper.SetValidatorByConsAddr(ctx, val1) - // require.NoError(t, err) - - // // try unbonding not available validator - // msgServer := keeper.NewMsgServerImpl(app.StakingKeeper) - // _, err = msgServer.UnbondValidator(sdk.WrapSDKContext(ctx), &types.MsgUnbondValidator{ - // ValidatorAddress: sdk.ValAddress(addrs[1]).String(), - // }) - // require.Error(t, err) - - // // unbond validator - // _, err = msgServer.UnbondValidator(sdk.WrapSDKContext(ctx), &types.MsgUnbondValidator{ - // ValidatorAddress: addrVal1.String(), - // }) - // require.NoError(t, err) - - // // check if validator is jailed - // validator, found := app.StakingKeeper.GetValidator(ctx, addrVal1) - // require.True(t, found) - // require.True(t, validator.Jailed) + var ( + bankKeeper bankkeeper.Keeper + stakingKeeper *keeper.Keeper + ) + + app, err := simtestutil.Setup(testutil.AppConfig, + &bankKeeper, + &stakingKeeper, + ) + require.NoError(t, err) + ctx := app.BaseApp.NewContext(false, tmproto.Header{}) + msgServer := keeper.NewMsgServerImpl(stakingKeeper) + + addrs := simtestutil.AddTestAddrs(bankKeeper, stakingKeeper, ctx, 2, stakingKeeper.TokensFromConsensusPower(ctx, 10000)) + addrAcc1 := addrs[0] + addrVal1 := sdk.ValAddress(addrAcc1) + + pubKeys := simtestutil.CreateTestPubKeys(1) + pk1 := pubKeys[0] + + // Create Validators and Delegation + val1, err := stakingtypes.NewValidator(addrVal1, pk1, stakingtypes.Description{}) + require.NoError(t, err) + val1.Status = stakingtypes.Bonded + stakingKeeper.SetValidator(ctx, val1) + stakingKeeper.SetValidatorByPowerIndex(ctx, val1) + err = stakingKeeper.SetValidatorByConsAddr(ctx, val1) + require.NoError(t, err) + + // try unbonding not available validator + msgServer = keeper.NewMsgServerImpl(stakingKeeper) + _, err = msgServer.UnbondValidator(sdk.WrapSDKContext(ctx), &types.MsgUnbondValidator{ + ValidatorAddress: sdk.ValAddress(addrs[1]).String(), + }) + require.Error(t, err) + + // unbond validator + _, err = msgServer.UnbondValidator(sdk.WrapSDKContext(ctx), &types.MsgUnbondValidator{ + ValidatorAddress: addrVal1.String(), + }) + require.NoError(t, err) + + // check if validator is jailed + validator, found := stakingKeeper.GetValidator(ctx, addrVal1) + require.True(t, found) + require.True(t, validator.Jailed) } -// TODO refactor LSM test -// // TestICADelegateUndelegate tests that an ICA account can undelegate // sequentially right after delegating. func TestICADelegateUndelegate(t *testing.T) { - // app := simapp.Setup(t, false) - // ctx := app.BaseApp.NewContext(false, tmproto.Header{}) - // msgServer := keeper.NewMsgServerImpl(app.StakingKeeper) - - // // Create a delegator and validator (the delegator will be an ICA account) - // delegateAmount := sdk.NewInt(1000) - // delegateCoin := sdk.NewCoin(app.StakingKeeper.BondDenom(ctx), delegateAmount) - // icaAccountAddress := createICAAccount(app, ctx) - - // // Fund ICA account - // err := app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, sdk.NewCoins(delegateCoin)) - // require.NoError(t, err) - // err = app.BankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, icaAccountAddress, sdk.NewCoins(delegateCoin)) - // require.NoError(t, err) - - // addresses := simapp.AddTestAddrs(app, ctx, 1, sdk.NewInt(0)) - // pubKeys := simapp.CreateTestPubKeys(1) - // validatorAddress := sdk.ValAddress(addresses[0]) - // validator := stakingtypes.NewValidator(validatorAddress, pubKeys[0], stakingtypes.Description{}) - - // validator.DelegatorShares = sdk.NewDec(1_000_000) - // validator.Tokens = sdk.NewInt(1_000_000) - // validator.LiquidShares = sdk.NewDec(0) - // app.StakingKeeper.SetValidator(ctx, validator) - - // delegateMsg := types.MsgDelegate{ - // DelegatorAddress: icaAccountAddress.String(), - // ValidatorAddress: validatorAddress.String(), - // Amount: delegateCoin, - // } - - // undelegateMsg := types.MsgUndelegate{ - // DelegatorAddress: icaAccountAddress.String(), - // ValidatorAddress: validatorAddress.String(), - // Amount: delegateCoin, - // } - - // // Delegate normally - // _, err = msgServer.Delegate(sdk.WrapSDKContext(ctx), &delegateMsg) - // require.NoError(t, err, "no error expected when delegating") - - // // Confirm delegation record - // _, found := app.StakingKeeper.GetDelegation(ctx, icaAccountAddress, validatorAddress) - // require.True(t, found, "delegation should have been found") - - // // Confirm liquid staking totals were incremented - // expectedTotalLiquidStaked := delegateAmount.Int64() - // actualTotalLiquidStaked := app.StakingKeeper.GetTotalLiquidStakedTokens(ctx).Int64() - // require.Equal(t, expectedTotalLiquidStaked, actualTotalLiquidStaked, "total liquid staked tokens after delegation") - - // validator, found = app.StakingKeeper.GetValidator(ctx, validatorAddress) - // require.True(t, found, "validator should have been found") - // require.Equal(t, delegateAmount.ToDec(), validator.LiquidShares, "validator liquid shares after delegation") - - // // Try to undelegate - // _, err = msgServer.Undelegate(sdk.WrapSDKContext(ctx), &undelegateMsg) - // require.NoError(t, err, "no error expected when sequentially undelegating") - - // // Confirm delegation record was removed - // _, found = app.StakingKeeper.GetDelegation(ctx, icaAccountAddress, validatorAddress) - // require.False(t, found, "delegation not have been found") - - // // Confirm liquid staking totals were decremented - // actualTotalLiquidStaked = app.StakingKeeper.GetTotalLiquidStakedTokens(ctx).Int64() - // require.Zero(t, actualTotalLiquidStaked, "total liquid staked tokens after undelegation") - - // validator, found = app.StakingKeeper.GetValidator(ctx, validatorAddress) - // require.True(t, found, "validator should have been found") - // require.Equal(t, sdk.ZeroDec(), validator.LiquidShares, "validator liquid shares after undelegation") + _, app, ctx := createTestInput(t) + var ( + accountKeeper = app.AccountKeeper + stakingKeeper = app.StakingKeeper + bankKeeper = app.BankKeeper + ) + + // Create a delegator and validator (the delegator will be an ICA account) + delegateAmount := sdk.NewInt(1000) + delegateCoin := sdk.NewCoin(stakingKeeper.BondDenom(ctx), delegateAmount) + icaAccountAddress := createICAAccount(ctx, accountKeeper) + + // Fund ICA account + err := bankKeeper.MintCoins(ctx, minttypes.ModuleName, sdk.NewCoins(delegateCoin)) + require.NoError(t, err) + err = bankKeeper.SendCoinsFromModuleToAccount(ctx, minttypes.ModuleName, icaAccountAddress, sdk.NewCoins(delegateCoin)) + require.NoError(t, err) + + addresses := simtestutil.AddTestAddrs(bankKeeper, stakingKeeper, ctx, 1, sdk.NewInt(0)) + pubKeys := simtestutil.CreateTestPubKeys(1) + validatorAddress := sdk.ValAddress(addresses[0]) + validator, err := stakingtypes.NewValidator(validatorAddress, pubKeys[0], stakingtypes.Description{}) + require.NoError(t, err) + + validator.DelegatorShares = sdk.NewDec(1_000_000) + validator.Tokens = sdk.NewInt(1_000_000) + validator.LiquidShares = sdk.NewDec(0) + stakingKeeper.SetValidator(ctx, validator) + + delegateMsg := types.MsgDelegate{ + DelegatorAddress: icaAccountAddress.String(), + ValidatorAddress: validatorAddress.String(), + Amount: delegateCoin, + } + + undelegateMsg := types.MsgUndelegate{ + DelegatorAddress: icaAccountAddress.String(), + ValidatorAddress: validatorAddress.String(), + Amount: delegateCoin, + } + + msgServer := keeper.NewMsgServerImpl(stakingKeeper) + + // Delegate normally + _, err = msgServer.Delegate(sdk.WrapSDKContext(ctx), &delegateMsg) + require.NoError(t, err, "no error expected when delegating") + + // Confirm delegation record + _, found := stakingKeeper.GetDelegation(ctx, icaAccountAddress, validatorAddress) + require.True(t, found, "delegation should have been found") + + // Confirm liquid staking totals were incremented + expectedTotalLiquidStaked := delegateAmount.Int64() + actualTotalLiquidStaked := stakingKeeper.GetTotalLiquidStakedTokens(ctx).Int64() + require.Equal(t, expectedTotalLiquidStaked, actualTotalLiquidStaked, "total liquid staked tokens after delegation") + + validator, found = stakingKeeper.GetValidator(ctx, validatorAddress) + require.True(t, found, "validator should have been found") + require.Equal(t, sdk.NewDecFromInt(delegateAmount), validator.LiquidShares, "validator liquid shares after delegation") + + // Try to undelegate + _, err = msgServer.Undelegate(sdk.WrapSDKContext(ctx), &undelegateMsg) + require.NoError(t, err, "no error expected when sequentially undelegating") + + // Confirm delegation record was removed + _, found = stakingKeeper.GetDelegation(ctx, icaAccountAddress, validatorAddress) + require.False(t, found, "delegation not have been found") + + // Confirm liquid staking totals were decremented + actualTotalLiquidStaked = stakingKeeper.GetTotalLiquidStakedTokens(ctx).Int64() + require.Zero(t, actualTotalLiquidStaked, "total liquid staked tokens after undelegation") + + validator, found = stakingKeeper.GetValidator(ctx, validatorAddress) + require.True(t, found, "validator should have been found") + require.Equal(t, sdk.ZeroDec(), validator.LiquidShares, "validator liquid shares after undelegation") +} + +// Helper function to create 32-length account +// Used to mock an liquid staking provider's ICA account +func createICAAccount(ctx sdk.Context, ak accountkeeper.AccountKeeper) sdk.AccAddress { + icahost := "icahost" + connectionID := "connection-0" + portID := icahost + + moduleAddress := authtypes.NewModuleAddress(icahost) + icaAddress := sdk.AccAddress(address.Derive(moduleAddress, []byte(connectionID+portID))) + + account := authtypes.NewBaseAccountWithAddress(icaAddress) + ak.SetAccount(ctx, account) + + return icaAddress } diff --git a/types/simulation/account.go b/types/simulation/account.go index b9f69ffa6116..1f04d558def7 100644 --- a/types/simulation/account.go +++ b/types/simulation/account.go @@ -3,6 +3,7 @@ package simulation import ( "fmt" "math/rand" + "strings" "github.com/cosmos/cosmos-sdk/crypto/keys/ed25519" "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" @@ -66,14 +67,23 @@ func FindAccount(accs []Account, address sdk.Address) (Account, bool) { // amount from the account's available balance. If the user doesn't have enough // funds for paying fees, it returns empty coins. func RandomFees(r *rand.Rand, ctx sdk.Context, spendableCoins sdk.Coins) (sdk.Coins, error) { - if spendableCoins.Empty() { + spendable := sdk.NewCoins() + // remove liquid staking denoms from spendable coins since fees cannot be paid in those denoms + for _, coin := range spendableCoins { + if strings.Contains(coin.Denom, sdk.GetConfig().GetBech32ValidatorAddrPrefix()) { + continue + } + spendable = append(spendable, coin) + } + + if spendable.Empty() { return nil, nil } - perm := r.Perm(len(spendableCoins)) + perm := r.Perm(len(spendable)) var randCoin sdk.Coin for _, index := range perm { - randCoin = spendableCoins[index] + randCoin = spendable[index] if !randCoin.Amount.IsZero() { break } diff --git a/x/staking/keeper/delegation.go b/x/staking/keeper/delegation.go index 23cb929099e8..cc97f9ec6bee 100644 --- a/x/staking/keeper/delegation.go +++ b/x/staking/keeper/delegation.go @@ -746,16 +746,6 @@ func (k Keeper) Unbond( return amount, err } - isValidatorOperator := delegatorAddress.Equals(validator.GetOperator()) - - // If the delegation is the operator of the validator and undelegating will decrease the validator's - // self-delegation below their minimum, we jail the validator. - if isValidatorOperator && !validator.Jailed && - validator.TokensFromShares(delegation.Shares).TruncateInt().LT(validator.MinSelfDelegation) { - k.jailValidator(ctx, validator) - validator = k.mustGetValidator(ctx, validator.GetOperator()) - } - if delegation.Shares.IsZero() { err = k.RemoveDelegation(ctx, delegation) } else { diff --git a/x/staking/keeper/delegation_test.go b/x/staking/keeper/delegation_test.go index 98ed2aa9378d..ee205bcf2b0f 100644 --- a/x/staking/keeper/delegation_test.go +++ b/x/staking/keeper/delegation_test.go @@ -602,6 +602,7 @@ func (s *KeeperTestSuite) TestRedelegationMaxEntries() { require.NoError(err) } +// NOTE: redelegating self-delegations does not put validators in unbonding state with LSM func (s *KeeperTestSuite) TestRedelegateSelfDelegation() { ctx, keeper := s.ctx, s.stakingKeeper require := s.Require() @@ -644,13 +645,12 @@ func (s *KeeperTestSuite) TestRedelegateSelfDelegation() { require.NoError(err) // end block - s.bankKeeper.EXPECT().SendCoinsFromModuleToModule(gomock.Any(), stakingtypes.BondedPoolName, stakingtypes.NotBondedPoolName, gomock.Any()) s.applyValidatorSetUpdates(ctx, keeper, 2) validator, found := keeper.GetValidator(ctx, addrVals[0]) require.True(found) require.Equal(valTokens, validator.Tokens) - require.Equal(stakingtypes.Unbonding, validator.Status) + require.Equal(stakingtypes.Bonded, validator.Status) } func (s *KeeperTestSuite) TestRedelegateFromUnbondingValidator() { @@ -686,7 +686,7 @@ func (s *KeeperTestSuite) TestRedelegateFromUnbondingValidator() { validator2, issuedShares = validator2.AddTokensFromDel(valTokens) require.Equal(valTokens, issuedShares.RoundInt()) s.bankKeeper.EXPECT().SendCoinsFromModuleToModule(gomock.Any(), stakingtypes.NotBondedPoolName, stakingtypes.BondedPoolName, gomock.Any()) - _ = stakingkeeper.TestingUpdateValidator(keeper, ctx, validator2, true) + validator2 = stakingkeeper.TestingUpdateValidator(keeper, ctx, validator2, true) header := ctx.BlockHeader() blockHeight := int64(10) @@ -733,6 +733,7 @@ func (s *KeeperTestSuite) TestRedelegateFromUnbondingValidator() { require.True(blockTime.Add(params.UnbondingTime).Equal(ubd.Entries[0].CompletionTime)) } +// NOTE: undelegating all self-delegation does not put a validator in unbonding state with LSM func (s *KeeperTestSuite) TestRedelegateFromUnbondedValidator() { ctx, keeper := s.ctx, s.stakingKeeper require := s.Require() @@ -772,10 +773,12 @@ func (s *KeeperTestSuite) TestRedelegateFromUnbondedValidator() { ctx = ctx.WithBlockHeight(10) ctx = ctx.WithBlockTime(time.Unix(333, 0)) - // unbond the all self-delegation to put validator in unbonding state + // unbond the all self-delegation s.bankKeeper.EXPECT().SendCoinsFromModuleToModule(gomock.Any(), stakingtypes.BondedPoolName, stakingtypes.NotBondedPoolName, gomock.Any()) _, err := keeper.Undelegate(ctx, val0AccAddr, addrVals[0], sdk.NewDecFromInt(delTokens)) require.NoError(err) + // jail to put validator in unbonding state + keeper.Jail(ctx, sdk.ConsAddress(PKs[0].Address())) // end block s.bankKeeper.EXPECT().SendCoinsFromModuleToModule(gomock.Any(), stakingtypes.BondedPoolName, stakingtypes.NotBondedPoolName, gomock.Any()) @@ -800,183 +803,3 @@ func (s *KeeperTestSuite) TestRedelegateFromUnbondedValidator() { red, found := keeper.GetRedelegation(ctx, addrDels[0], addrVals[0], addrVals[1]) require.False(found, "%v", red) } - -/*TODO refactor LSM tests: - -- Note that in v0.45.16-lsm the redelegation tests are renamed such that: -TestRedelegateFromUnbondingValidator -> TestValidatorBondUndelegate and -TestRedelegateFromUnbondedValidator -> TestValidatorBondUndelegate - -- Note that in v0.45.16-lsm the keeper tests are still using testing.T -and simapp, which should updated to unit test with gomock, see tests above. - -*/ -// func TestValidatorBondUndelegate(t *testing.T) { -// _, app, ctx := createTestInput() - -// addrDels := simapp.AddTestAddrs(app, ctx, 2, app.StakingKeeper.TokensFromConsensusPower(ctx, 10000)) -// addrVals := simapp.ConvertAddrsToValAddrs(addrDels) - -// startTokens := app.StakingKeeper.TokensFromConsensusPower(ctx, 10) - -// bondDenom := app.StakingKeeper.BondDenom(ctx) -// notBondedPool := app.StakingKeeper.GetNotBondedPool(ctx) - -// require.NoError(t, simapp.FundModuleAccount(app.BankKeeper, ctx, notBondedPool.GetName(), sdk.NewCoins(sdk.NewCoin(bondDenom, startTokens)))) -// app.AccountKeeper.SetModuleAccount(ctx, notBondedPool) - -// // create a validator and a delegator to that validator -// validator := teststaking.NewValidator(t, addrVals[0], PKs[0]) -// validator.Status = types.Bonded -// app.StakingKeeper.SetValidator(ctx, validator) - -// // set validator bond factor -// params := app.StakingKeeper.GetParams(ctx) -// params.ValidatorBondFactor = sdk.NewDec(1) -// app.StakingKeeper.SetParams(ctx, params) - -// // convert to validator self-bond -// msgServer := keeper.NewMsgServerImpl(app.StakingKeeper) - -// validator, _ = app.StakingKeeper.GetValidator(ctx, addrVals[0]) -// err := delegateCoinsFromAccount(ctx, app, addrDels[0], startTokens, validator) -// require.NoError(t, err) -// _, err = msgServer.ValidatorBond(sdk.WrapSDKContext(ctx), &types.MsgValidatorBond{ -// DelegatorAddress: addrDels[0].String(), -// ValidatorAddress: addrVals[0].String(), -// }) -// require.NoError(t, err) - -// // tokenize share for 2nd account delegation -// validator, _ = app.StakingKeeper.GetValidator(ctx, addrVals[0]) -// err = delegateCoinsFromAccount(ctx, app, addrDels[1], startTokens, validator) -// require.NoError(t, err) -// tokenizeShareResp, err := msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &types.MsgTokenizeShares{ -// DelegatorAddress: addrDels[1].String(), -// ValidatorAddress: addrVals[0].String(), -// Amount: sdk.NewCoin(sdk.DefaultBondDenom, startTokens), -// TokenizedShareOwner: addrDels[0].String(), -// }) -// require.NoError(t, err) - -// // try undelegating -// _, err = msgServer.Undelegate(sdk.WrapSDKContext(ctx), &types.MsgUndelegate{ -// DelegatorAddress: addrDels[0].String(), -// ValidatorAddress: addrVals[0].String(), -// Amount: sdk.NewCoin(sdk.DefaultBondDenom, startTokens), -// }) -// require.Error(t, err) - -// // redeem full amount on 2nd account and try undelegation -// validator, _ = app.StakingKeeper.GetValidator(ctx, addrVals[0]) -// err = delegateCoinsFromAccount(ctx, app, addrDels[1], startTokens, validator) -// require.NoError(t, err) -// _, err = msgServer.RedeemTokensForShares(sdk.WrapSDKContext(ctx), &types.MsgRedeemTokensForShares{ -// DelegatorAddress: addrDels[1].String(), -// Amount: tokenizeShareResp.Amount, -// }) -// require.NoError(t, err) - -// // try undelegating -// _, err = msgServer.Undelegate(sdk.WrapSDKContext(ctx), &types.MsgUndelegate{ -// DelegatorAddress: addrDels[0].String(), -// ValidatorAddress: addrVals[0].String(), -// Amount: sdk.NewCoin(sdk.DefaultBondDenom, startTokens), -// }) -// require.NoError(t, err) - -// validator, _ = app.StakingKeeper.GetValidator(ctx, addrVals[0]) -// require.Equal(t, validator.ValidatorBondShares, sdk.ZeroDec()) -// } - -// func TestValidatorBondRedelegate(t *testing.T) { -// _, app, ctx := createTestInput() - -// addrDels := simapp.AddTestAddrs(app, ctx, 2, app.StakingKeeper.TokensFromConsensusPower(ctx, 10000)) -// addrVals := simapp.ConvertAddrsToValAddrs(addrDels) - -// startTokens := app.StakingKeeper.TokensFromConsensusPower(ctx, 10) - -// bondDenom := app.StakingKeeper.BondDenom(ctx) -// notBondedPool := app.StakingKeeper.GetNotBondedPool(ctx) - -// startPoolToken := sdk.NewCoins(sdk.NewCoin(bondDenom, startTokens.Mul(sdk.NewInt(2)))) -// require.NoError(t, simapp.FundModuleAccount(app.BankKeeper, ctx, notBondedPool.GetName(), startPoolToken)) -// app.AccountKeeper.SetModuleAccount(ctx, notBondedPool) - -// // create a validator and a delegator to that validator -// validator := teststaking.NewValidator(t, addrVals[0], PKs[0]) -// validator.Status = types.Bonded -// app.StakingKeeper.SetValidator(ctx, validator) -// validator2 := teststaking.NewValidator(t, addrVals[1], PKs[1]) -// validator.Status = types.Bonded -// app.StakingKeeper.SetValidator(ctx, validator2) - -// // set validator bond factor -// params := app.StakingKeeper.GetParams(ctx) -// params.ValidatorBondFactor = sdk.NewDec(1) -// app.StakingKeeper.SetParams(ctx, params) - -// // set total liquid stake -// app.StakingKeeper.SetTotalLiquidStakedTokens(ctx, sdk.NewInt(100)) - -// // delegate to each validator -// validator, _ = app.StakingKeeper.GetValidator(ctx, addrVals[0]) -// err := delegateCoinsFromAccount(ctx, app, addrDels[0], startTokens, validator) -// require.NoError(t, err) - -// validator2, _ = app.StakingKeeper.GetValidator(ctx, addrVals[1]) -// err = delegateCoinsFromAccount(ctx, app, addrDels[1], startTokens, validator2) -// require.NoError(t, err) - -// // convert to validator self-bond -// msgServer := keeper.NewMsgServerImpl(app.StakingKeeper) -// _, err = msgServer.ValidatorBond(sdk.WrapSDKContext(ctx), &types.MsgValidatorBond{ -// DelegatorAddress: addrDels[0].String(), -// ValidatorAddress: addrVals[0].String(), -// }) -// require.NoError(t, err) - -// // tokenize share for 2nd account delegation -// validator, _ = app.StakingKeeper.GetValidator(ctx, addrVals[0]) -// err = delegateCoinsFromAccount(ctx, app, addrDels[1], startTokens, validator) -// require.NoError(t, err) -// tokenizeShareResp, err := msgServer.TokenizeShares(sdk.WrapSDKContext(ctx), &types.MsgTokenizeShares{ -// DelegatorAddress: addrDels[1].String(), -// ValidatorAddress: addrVals[0].String(), -// Amount: sdk.NewCoin(sdk.DefaultBondDenom, startTokens), -// TokenizedShareOwner: addrDels[0].String(), -// }) -// require.NoError(t, err) - -// // try undelegating -// _, err = msgServer.BeginRedelegate(sdk.WrapSDKContext(ctx), &types.MsgBeginRedelegate{ -// DelegatorAddress: addrDels[0].String(), -// ValidatorSrcAddress: addrVals[0].String(), -// ValidatorDstAddress: addrVals[1].String(), -// Amount: sdk.NewCoin(sdk.DefaultBondDenom, startTokens), -// }) -// require.Error(t, err) - -// // redeem full amount on 2nd account and try undelegation -// validator, _ = app.StakingKeeper.GetValidator(ctx, addrVals[0]) -// err = delegateCoinsFromAccount(ctx, app, addrDels[1], startTokens, validator) -// require.NoError(t, err) -// _, err = msgServer.RedeemTokensForShares(sdk.WrapSDKContext(ctx), &types.MsgRedeemTokensForShares{ -// DelegatorAddress: addrDels[1].String(), -// Amount: tokenizeShareResp.Amount, -// }) -// require.NoError(t, err) - -// // try undelegating -// _, err = msgServer.BeginRedelegate(sdk.WrapSDKContext(ctx), &types.MsgBeginRedelegate{ -// DelegatorAddress: addrDels[0].String(), -// ValidatorSrcAddress: addrVals[0].String(), -// ValidatorDstAddress: addrVals[1].String(), -// Amount: sdk.NewCoin(sdk.DefaultBondDenom, startTokens), -// }) -// require.NoError(t, err) - -// validator, _ = app.StakingKeeper.GetValidator(ctx, addrVals[0]) -// require.Equal(t, validator.ValidatorBondShares, sdk.ZeroDec()) -// } diff --git a/x/staking/keeper/liquid_stake.go b/x/staking/keeper/liquid_stake.go index 64c44e4cd35d..62b2473f50fc 100644 --- a/x/staking/keeper/liquid_stake.go +++ b/x/staking/keeper/liquid_stake.go @@ -4,13 +4,12 @@ import ( "time" "cosmossdk.io/math" - sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/x/staking/types" ) // SetTotalLiquidStakedTokens stores the total outstanding tokens owned by a liquid staking provider -func (k Keeper) SetTotalLiquidStakedTokens(ctx sdk.Context, tokens sdk.Int) { +func (k Keeper) SetTotalLiquidStakedTokens(ctx sdk.Context, tokens math.Int) { store := ctx.KVStore(k.storeKey) tokensBz, err := tokens.Marshal() @@ -23,7 +22,7 @@ func (k Keeper) SetTotalLiquidStakedTokens(ctx sdk.Context, tokens sdk.Int) { // GetTotalLiquidStakedTokens returns the total outstanding tokens owned by a liquid staking provider // Returns zero if the total liquid stake amount has not been initialized -func (k Keeper) GetTotalLiquidStakedTokens(ctx sdk.Context) sdk.Int { +func (k Keeper) GetTotalLiquidStakedTokens(ctx sdk.Context) math.Int { store := ctx.KVStore(k.storeKey) tokensBz := store.Get(types.TotalLiquidStakedTokensKey) @@ -31,7 +30,7 @@ func (k Keeper) GetTotalLiquidStakedTokens(ctx sdk.Context) sdk.Int { return sdk.ZeroInt() } - var tokens sdk.Int + var tokens math.Int if err := tokens.Unmarshal(tokensBz); err != nil { panic(err) } @@ -62,7 +61,7 @@ func (k Keeper) DelegatorIsLiquidStaker(delegatorAddress sdk.AccAddress) bool { // the tokens are already included in the bonded pool // If the delegation's shares are not bonded (e.g. normal delegation), // we need to add the tokens to the current bonded pool balance to get the total staked -func (k Keeper) CheckExceedsGlobalLiquidStakingCap(ctx sdk.Context, tokens sdk.Int, sharesAlreadyBonded bool) bool { +func (k Keeper) CheckExceedsGlobalLiquidStakingCap(ctx sdk.Context, tokens math.Int, sharesAlreadyBonded bool) bool { liquidStakingCap := k.GlobalLiquidStakingCap(ctx) liquidStakedAmount := k.GetTotalLiquidStakedTokens(ctx) @@ -76,8 +75,9 @@ func (k Keeper) CheckExceedsGlobalLiquidStakingCap(ctx sdk.Context, tokens sdk.I } // Calculate the percentage of stake that is liquid - updatedLiquidStaked := math.LegacyNewDec(liquidStakedAmount.Add(tokens).Int64()) - liquidStakePercent := updatedLiquidStaked.Quo(math.LegacyNewDec(totalStakedAmount.Int64())) + + updatedLiquidStaked := sdk.NewDecFromInt(liquidStakedAmount.Add(tokens)) + liquidStakePercent := updatedLiquidStaked.Quo(sdk.NewDecFromInt(totalStakedAmount)) return liquidStakePercent.GT(liquidStakingCap) } @@ -114,7 +114,7 @@ func (k Keeper) CheckExceedsValidatorLiquidStakingCap(ctx sdk.Context, validator // // The percentage of liquid staked tokens must be less than the GlobalLiquidStakingCap: // (TotalLiquidStakedTokens / TotalStakedTokens) <= GlobalLiquidStakingCap -func (k Keeper) SafelyIncreaseTotalLiquidStakedTokens(ctx sdk.Context, amount sdk.Int, sharesAlreadyBonded bool) error { +func (k Keeper) SafelyIncreaseTotalLiquidStakedTokens(ctx sdk.Context, amount math.Int, sharesAlreadyBonded bool) error { if k.CheckExceedsGlobalLiquidStakingCap(ctx, amount, sharesAlreadyBonded) { return types.ErrGlobalLiquidStakingCapExceeded } @@ -124,7 +124,7 @@ func (k Keeper) SafelyIncreaseTotalLiquidStakedTokens(ctx sdk.Context, amount sd } // DecreaseTotalLiquidStakedTokens decrements the total liquid staked tokens -func (k Keeper) DecreaseTotalLiquidStakedTokens(ctx sdk.Context, amount sdk.Int) error { +func (k Keeper) DecreaseTotalLiquidStakedTokens(ctx sdk.Context, amount math.Int) error { totalLiquidStake := k.GetTotalLiquidStakedTokens(ctx) if amount.GT(totalLiquidStake) { return types.ErrTotalLiquidStakedUnderflow @@ -363,14 +363,17 @@ func (k Keeper) RemoveExpiredTokenizeShareLocks(ctx sdk.Context, blockTime time. // collect all unlocked addresses unlockedAddresses = []string{} + keys := [][]byte{} for ; iterator.Valid(); iterator.Next() { authorizations := types.PendingTokenizeShareAuthorizations{} k.cdc.MustUnmarshal(iterator.Value(), &authorizations) + unlockedAddresses = append(unlockedAddresses, authorizations.Addresses...) + keys = append(keys, iterator.Key()) + } - for _, addressString := range authorizations.Addresses { - unlockedAddresses = append(unlockedAddresses, addressString) - } - store.Delete(iterator.Key()) + // delete unlocked addresses keys + for _, k := range keys { + store.Delete(k) } // remove the lock from each unlocked address diff --git a/x/staking/keeper/liquid_stake_test.go b/x/staking/keeper/liquid_stake_test.go index abeba9b9343b..9e622c815d08 100644 --- a/x/staking/keeper/liquid_stake_test.go +++ b/x/staking/keeper/liquid_stake_test.go @@ -1,1209 +1,749 @@ package keeper_test -// TODO refactor LSM tests - -// import ( -// "fmt" -// "testing" -// "time" - -// testutil "github.com/cosmos/cosmos-sdk/testutil/sims" - -// "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" -// "github.com/cosmos/cosmos-sdk/simapp" -// sdk "github.com/cosmos/cosmos-sdk/types" -// "github.com/cosmos/cosmos-sdk/types/address" -// authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" -// minttypes "github.com/cosmos/cosmos-sdk/x/mint/types" -// "github.com/cosmos/cosmos-sdk/x/staking/types" -// "github.com/stretchr/testify/require" -// ) - -// // Helper function to create a base account from an account name -// // Used to differentiate against liquid staking provider module account -// func createBaseAccount(app *simapp.SimApp, ctx sdk.Context, accountName string) sdk.AccAddress { -// baseAccountAddress := sdk.AccAddress(accountName) -// app.AccountKeeper.SetAccount(ctx, authtypes.NewBaseAccountWithAddress(baseAccountAddress)) -// return baseAccountAddress -// } - -// // Helper function to create 32-length account -// // Used to mock an liquid staking provider's ICA account -// func createICAAccount(app *simapp.SimApp, ctx sdk.Context) sdk.AccAddress { -// icahost := "icahost" -// connectionID := "connection-0" -// portID := icahost - -// moduleAddress := authtypes.NewModuleAddress(icahost) -// icaAddress := sdk.AccAddress(address.Derive(moduleAddress, []byte(connectionID+portID))) - -// account := authtypes.NewBaseAccountWithAddress(icaAddress) -// app.AccountKeeper.SetAccount(ctx, account) - -// return icaAddress -// } - -// // Helper function to create a module account address from a tokenized share -// // Used to mock the delegation owner of a tokenized share -// func createTokenizeShareModuleAccount(recordID uint64) sdk.AccAddress { -// record := types.TokenizeShareRecord{ -// Id: recordID, -// ModuleAccount: fmt.Sprintf("%s%d", types.TokenizeShareModuleAccountPrefix, recordID), -// } -// return record.GetModuleAddress() -// } - -// // Tests Set/Get TotalLiquidStakedTokens -// func (s *KeeperTestSuite) TestTotalLiquidStakedTokens(t *testing.T) { -// ctx, keeper := s.ctx, s.stakingKeeper -// require := s.Require() - -// // Update the total liquid staked -// total := sdk.NewInt(100) -// keeper.SetTotalLiquidStakedTokens(ctx, total) - -// // Confirm it was updated -// require.Equal(t, total, keeper.GetTotalLiquidStakedTokens(ctx), "initial") -// } - -// // Tests Increase/Decrease TotalValidatorLiquidShares -// func (s *KeeperTestSuite) TestValidatorLiquidShares(t *testing.T) { -// ctx, keeper := s.ctx, s.stakingKeeper - -// // Create a validator address -// privKey := secp256k1.GenPrivKey() -// pubKey := privKey.PubKey() -// valAddress := sdk.ValAddress(pubKey.Address()) - -// // Set an initial total -// initial := sdk.NewDec(100) -// validator := types.Validator{ -// OperatorAddress: valAddress.String(), -// LiquidShares: initial, -// } -// keeper.SetValidator(ctx, validator) -// } - -// // Tests DelegatorIsLiquidStaker -// func (s *KeeperTestSuite) TestDelegatorIsLiquidStaker(t *testing.T) { -// ctx, keeper := s.ctx, s.stakingKeeper -// require := s.Require() - -// // Create base and ICA accounts -// baseAccountAddress := createBaseAccount(app, ctx, "base-account") -// icaAccountAddress := createICAAccount(app, ctx) - -// // Only the ICA module account should be considered a liquid staking provider -// require.False(keeper.DelegatorIsLiquidStaker(baseAccountAddress), "base account") -// require.True(keeper.DelegatorIsLiquidStaker(icaAccountAddress), "ICA module account") -// } - -// // Helper function to clear the Bonded pool balances before a unit test -// func clearPoolBalance(t *testing.T, app *simapp.SimApp, ctx sdk.Context) { -// bondDenom := keeper.BondDenom(ctx) -// initialBondedBalance := app.BankKeeper.GetBalance(ctx, app.AccountKeeper.GetModuleAddress(types.BondedPoolName), bondDenom) - -// err := app.BankKeeper.SendCoinsFromModuleToModule(ctx, types.BondedPoolName, minttypes.ModuleName, sdk.NewCoins(initialBondedBalance)) -// require.NoError(t, err, "no error expected when clearing bonded pool balance") -// } - -// // Helper function to fund the Bonded pool balances before a unit test -// func fundPoolBalance(t *testing.T, app *simapp.SimApp, ctx sdk.Context, amount sdk.Int) { -// bondDenom := keeper.BondDenom(ctx) -// bondedPoolCoin := sdk.NewCoin(bondDenom, amount) - -// err := app.BankKeeper.MintCoins(ctx, minttypes.ModuleName, sdk.NewCoins(bondedPoolCoin)) -// require.NoError(t, err, "no error expected when minting") - -// err = app.BankKeeper.SendCoinsFromModuleToModule(ctx, minttypes.ModuleName, types.BondedPoolName, sdk.NewCoins(bondedPoolCoin)) -// require.NoError(t, err, "no error expected when sending tokens to bonded pool") -// } - -// // Tests CheckExceedsGlobalLiquidStakingCap -// func (s *KeeperTestSuite) TestCheckExceedsGlobalLiquidStakingCap(t *testing.T) { -// ctx, keeper := s.ctx, s.stakingKeeper -// require := s.Require() - -// testCases := []struct { -// name string -// globalLiquidCap sdk.Dec -// totalLiquidStake sdk.Int -// totalStake sdk.Int -// newLiquidStake sdk.Int -// tokenizingShares bool -// expectedExceeds bool -// }{ -// { -// // Cap: 10% - Native Delegation - Delegation Below Threshold -// // Total Liquid Stake: 5, Total Stake: 95, New Liquid Stake: 1 -// // => Total Liquid Stake: 5+1=6, Total Stake: 95+1=96 => 6/96 = 6% < 10% cap -// name: "10 percent cap _ native delegation _ delegation below cap", -// globalLiquidCap: sdk.MustNewDecFromStr("0.1"), -// totalLiquidStake: sdk.NewInt(5), -// totalStake: sdk.NewInt(95), -// newLiquidStake: sdk.NewInt(1), -// tokenizingShares: false, -// expectedExceeds: false, -// }, -// { -// // Cap: 10% - Native Delegation - Delegation At Threshold -// // Total Liquid Stake: 5, Total Stake: 95, New Liquid Stake: 5 -// // => Total Liquid Stake: 5+5=10, Total Stake: 95+5=100 => 10/100 = 10% == 10% cap -// name: "10 percent cap _ native delegation _ delegation equals cap", -// globalLiquidCap: sdk.MustNewDecFromStr("0.1"), -// totalLiquidStake: sdk.NewInt(5), -// totalStake: sdk.NewInt(95), -// newLiquidStake: sdk.NewInt(5), -// tokenizingShares: false, -// expectedExceeds: false, -// }, -// { -// // Cap: 10% - Native Delegation - Delegation Exceeds Threshold -// // Total Liquid Stake: 5, Total Stake: 95, New Liquid Stake: 6 -// // => Total Liquid Stake: 5+6=11, Total Stake: 95+6=101 => 11/101 = 11% > 10% cap -// name: "10 percent cap _ native delegation _ delegation exceeds cap", -// globalLiquidCap: sdk.MustNewDecFromStr("0.1"), -// totalLiquidStake: sdk.NewInt(5), -// totalStake: sdk.NewInt(95), -// newLiquidStake: sdk.NewInt(6), -// tokenizingShares: false, -// expectedExceeds: true, -// }, -// { -// // Cap: 20% - Native Delegation - Delegation Below Threshold -// // Total Liquid Stake: 20, Total Stake: 220, New Liquid Stake: 29 -// // => Total Liquid Stake: 20+29=49, Total Stake: 220+29=249 => 49/249 = 19% < 20% cap -// name: "20 percent cap _ native delegation _ delegation below cap", -// globalLiquidCap: sdk.MustNewDecFromStr("0.20"), -// totalLiquidStake: sdk.NewInt(20), -// totalStake: sdk.NewInt(220), -// newLiquidStake: sdk.NewInt(29), -// tokenizingShares: false, -// expectedExceeds: false, -// }, -// { -// // Cap: 20% - Native Delegation - Delegation At Threshold -// // Total Liquid Stake: 20, Total Stake: 220, New Liquid Stake: 30 -// // => Total Liquid Stake: 20+30=50, Total Stake: 220+30=250 => 50/250 = 20% == 20% cap -// name: "20 percent cap _ native delegation _ delegation equals cap", -// globalLiquidCap: sdk.MustNewDecFromStr("0.20"), -// totalLiquidStake: sdk.NewInt(20), -// totalStake: sdk.NewInt(220), -// newLiquidStake: sdk.NewInt(30), -// tokenizingShares: false, -// expectedExceeds: false, -// }, -// { -// // Cap: 20% - Native Delegation - Delegation Exceeds Threshold -// // Total Liquid Stake: 20, Total Stake: 220, New Liquid Stake: 31 -// // => Total Liquid Stake: 20+31=51, Total Stake: 220+31=251 => 51/251 = 21% > 20% cap -// name: "20 percent cap _ native delegation _ delegation exceeds cap", -// globalLiquidCap: sdk.MustNewDecFromStr("0.20"), -// totalLiquidStake: sdk.NewInt(20), -// totalStake: sdk.NewInt(220), -// newLiquidStake: sdk.NewInt(31), -// tokenizingShares: false, -// expectedExceeds: true, -// }, -// { -// // Cap: 50% - Native Delegation - Delegation Below Threshold -// // Total Liquid Stake: 0, Total Stake: 100, New Liquid Stake: 50 -// // => Total Liquid Stake: 0+50=50, Total Stake: 100+50=150 => 50/150 = 33% < 50% cap -// name: "50 percent cap _ native delegation _ delegation below cap", -// globalLiquidCap: sdk.MustNewDecFromStr("0.5"), -// totalLiquidStake: sdk.NewInt(0), -// totalStake: sdk.NewInt(100), -// newLiquidStake: sdk.NewInt(50), -// tokenizingShares: false, -// expectedExceeds: false, -// }, -// { -// // Cap: 50% - Tokenized Delegation - Delegation At Threshold -// // Total Liquid Stake: 0, Total Stake: 100, New Liquid Stake: 50 -// // => 50 / 100 = 50% == 50% cap -// name: "50 percent cap _ tokenized delegation _ delegation equals cap", -// globalLiquidCap: sdk.MustNewDecFromStr("0.5"), -// totalLiquidStake: sdk.NewInt(0), -// totalStake: sdk.NewInt(100), -// newLiquidStake: sdk.NewInt(50), -// tokenizingShares: true, -// expectedExceeds: false, -// }, -// { -// // Cap: 50% - Native Delegation - Delegation Below Threshold -// // Total Liquid Stake: 0, Total Stake: 100, New Liquid Stake: 51 -// // => Total Liquid Stake: 0+51=51, Total Stake: 100+51=151 => 51/151 = 33% < 50% cap -// name: "50 percent cap _ native delegation _ delegation below cap", -// globalLiquidCap: sdk.MustNewDecFromStr("0.5"), -// totalLiquidStake: sdk.NewInt(0), -// totalStake: sdk.NewInt(100), -// newLiquidStake: sdk.NewInt(51), -// tokenizingShares: false, -// expectedExceeds: false, -// }, -// { -// // Cap: 50% - Tokenized Delegation - Delegation Exceeds Threshold -// // Total Liquid Stake: 0, Total Stake: 100, New Liquid Stake: 51 -// // => 51 / 100 = 51% > 50% cap -// name: "50 percent cap _ tokenized delegation _delegation exceeds cap", -// globalLiquidCap: sdk.MustNewDecFromStr("0.5"), -// totalLiquidStake: sdk.NewInt(0), -// totalStake: sdk.NewInt(100), -// newLiquidStake: sdk.NewInt(51), -// tokenizingShares: true, -// expectedExceeds: true, -// }, -// { -// // Cap of 0% - everything should exceed -// name: "0 percent cap", -// globalLiquidCap: sdk.ZeroDec(), -// totalLiquidStake: sdk.NewInt(0), -// totalStake: sdk.NewInt(1_000_000), -// newLiquidStake: sdk.NewInt(1), -// tokenizingShares: false, -// expectedExceeds: true, -// }, -// { -// // Cap of 100% - nothing should exceed -// name: "100 percent cap", -// globalLiquidCap: sdk.OneDec(), -// totalLiquidStake: sdk.NewInt(1), -// totalStake: sdk.NewInt(1), -// newLiquidStake: sdk.NewInt(1_000_000), -// tokenizingShares: false, -// expectedExceeds: false, -// }, -// } - -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// // Update the global liquid staking cap -// params := keeper.GetParams(ctx) -// params.GlobalLiquidStakingCap = tc.globalLiquidCap -// keeper.SetParams(ctx, params) - -// // Update the total liquid tokens -// keeper.SetTotalLiquidStakedTokens(ctx, tc.totalLiquidStake) - -// // Fund each pool for the given test case -// clearPoolBalance(t, app, ctx) -// fundPoolBalance(t, app, ctx, tc.totalStake) - -// // Check if the new tokens would exceed the global cap -// actualExceeds := keeper.CheckExceedsGlobalLiquidStakingCap(ctx, tc.newLiquidStake, tc.tokenizingShares) -// require.Equal(t, tc.expectedExceeds, actualExceeds, tc.name) -// }) -// } -// } - -// // Tests SafelyIncreaseTotalLiquidStakedTokens -// func (s *KeeperTestSuite) TestSafelyIncreaseTotalLiquidStakedTokens(t *testing.T) { -// ctx, keeper := s.ctx, s.stakingKeeper -// require := s.Require() - -// intitialTotalLiquidStaked := sdk.NewInt(100) -// increaseAmount := sdk.NewInt(10) -// poolBalance := sdk.NewInt(200) - -// // Set the total staked and total liquid staked amounts -// // which are required components when checking the global cap -// // Total stake is calculated from the pool balance -// clearPoolBalance(t, app, ctx) -// fundPoolBalance(t, app, ctx, poolBalance) -// keeper.SetTotalLiquidStakedTokens(ctx, intitialTotalLiquidStaked) - -// // Set the global cap such that a small delegation would exceed the cap -// params := keeper.GetParams(ctx) -// params.GlobalLiquidStakingCap = sdk.MustNewDecFromStr("0.0001") -// keeper.SetParams(ctx, params) - -// // Attempt to increase the total liquid stake again, it should error since -// // the cap was exceeded -// err := keeper.SafelyIncreaseTotalLiquidStakedTokens(ctx, increaseAmount, true) -// require.ErrorIs(t, err, types.ErrGlobalLiquidStakingCapExceeded) -// require.Equal(t, intitialTotalLiquidStaked, keeper.GetTotalLiquidStakedTokens(ctx)) - -// // Now relax the cap so that the increase succeeds -// params.GlobalLiquidStakingCap = sdk.MustNewDecFromStr("0.99") -// keeper.SetParams(ctx, params) - -// // Confirm the total increased -// err = keeper.SafelyIncreaseTotalLiquidStakedTokens(ctx, increaseAmount, true) -// require.NoError(t, err) -// require.Equal(t, intitialTotalLiquidStaked.Add(increaseAmount), keeper.GetTotalLiquidStakedTokens(ctx)) -// } - -// // Tests DecreaseTotalLiquidStakedTokens -// func (s *KeeperTestSuite) TestDecreaseTotalLiquidStakedTokens(t *testing.T) { -// ctx, keeper := s.ctx, s.stakingKeeper -// require := s.Require() - -// intitialTotalLiquidStaked := sdk.NewInt(100) -// decreaseAmount := sdk.NewInt(10) - -// // Set the total liquid staked to an arbitrary value -// keeper.SetTotalLiquidStakedTokens(ctx, intitialTotalLiquidStaked) - -// // Decrease the total liquid stake and confirm the total was updated -// err := keeper.DecreaseTotalLiquidStakedTokens(ctx, decreaseAmount) -// require.NoError(t, err, "no error expected when decreasing total liquid staked tokens") -// require.Equal(t, intitialTotalLiquidStaked.Sub(decreaseAmount), keeper.GetTotalLiquidStakedTokens(ctx)) - -// // Attempt to decrease by an excessive amount, it should error -// err = keeper.DecreaseTotalLiquidStakedTokens(ctx, intitialTotalLiquidStaked) -// require.ErrorIs(err, types.ErrTotalLiquidStakedUnderflow) -// } - -// // Tests CheckExceedsValidatorBondCap -// func (s *KeeperTestSuite) TestCheckExceedsValidatorBondCap(t *testing.T) { -// ctx, keeper := s.ctx, s.stakingKeeper -// require := s.Require() - -// testCases := []struct { -// name string -// validatorShares sdk.Dec -// validatorBondFactor sdk.Dec -// currentLiquidShares sdk.Dec -// newShares sdk.Dec -// expectedExceeds bool -// }{ -// { -// // Validator Shares: 100, Factor: 1, Current Shares: 90 => 100 Max Shares, Capacity: 10 -// // New Shares: 5 - below cap -// name: "factor 1 - below cap", -// validatorShares: sdk.NewDec(100), -// validatorBondFactor: sdk.NewDec(1), -// currentLiquidShares: sdk.NewDec(90), -// newShares: sdk.NewDec(5), -// expectedExceeds: false, -// }, -// { -// // Validator Shares: 100, Factor: 1, Current Shares: 90 => 100 Max Shares, Capacity: 10 -// // New Shares: 10 - at cap -// name: "factor 1 - at cap", -// validatorShares: sdk.NewDec(100), -// validatorBondFactor: sdk.NewDec(1), -// currentLiquidShares: sdk.NewDec(90), -// newShares: sdk.NewDec(10), -// expectedExceeds: false, -// }, -// { -// // Validator Shares: 100, Factor: 1, Current Shares: 90 => 100 Max Shares, Capacity: 10 -// // New Shares: 15 - above cap -// name: "factor 1 - above cap", -// validatorShares: sdk.NewDec(100), -// validatorBondFactor: sdk.NewDec(1), -// currentLiquidShares: sdk.NewDec(90), -// newShares: sdk.NewDec(15), -// expectedExceeds: true, -// }, -// { -// // Validator Shares: 100, Factor: 2, Current Shares: 90 => 200 Max Shares, Capacity: 110 -// // New Shares: 5 - below cap -// name: "factor 2 - well below cap", -// validatorShares: sdk.NewDec(100), -// validatorBondFactor: sdk.NewDec(2), -// currentLiquidShares: sdk.NewDec(90), -// newShares: sdk.NewDec(5), -// expectedExceeds: false, -// }, -// { -// // Validator Shares: 100, Factor: 2, Current Shares: 90 => 200 Max Shares, Capacity: 110 -// // New Shares: 100 - below cap -// name: "factor 2 - below cap", -// validatorShares: sdk.NewDec(100), -// validatorBondFactor: sdk.NewDec(2), -// currentLiquidShares: sdk.NewDec(90), -// newShares: sdk.NewDec(100), -// expectedExceeds: false, -// }, -// { -// // Validator Shares: 100, Factor: 2, Current Shares: 90 => 200 Max Shares, Capacity: 110 -// // New Shares: 110 - below cap -// name: "factor 2 - at cap", -// validatorShares: sdk.NewDec(100), -// validatorBondFactor: sdk.NewDec(2), -// currentLiquidShares: sdk.NewDec(90), -// newShares: sdk.NewDec(110), -// expectedExceeds: false, -// }, -// { -// // Validator Shares: 100, Factor: 2, Current Shares: 90 => 200 Max Shares, Capacity: 110 -// // New Shares: 111 - above cap -// name: "factor 2 - above cap", -// validatorShares: sdk.NewDec(100), -// validatorBondFactor: sdk.NewDec(2), -// currentLiquidShares: sdk.NewDec(90), -// newShares: sdk.NewDec(111), -// expectedExceeds: true, -// }, -// { -// // Validator Shares: 100, Factor: 100, Current Shares: 90 => 10000 Max Shares, Capacity: 9910 -// // New Shares: 100 - below cap -// name: "factor 100 - below cap", -// validatorShares: sdk.NewDec(100), -// validatorBondFactor: sdk.NewDec(100), -// currentLiquidShares: sdk.NewDec(90), -// newShares: sdk.NewDec(100), -// expectedExceeds: false, -// }, -// { -// // Validator Shares: 100, Factor: 100, Current Shares: 90 => 10000 Max Shares, Capacity: 9910 -// // New Shares: 9910 - at cap -// name: "factor 100 - at cap", -// validatorShares: sdk.NewDec(100), -// validatorBondFactor: sdk.NewDec(100), -// currentLiquidShares: sdk.NewDec(90), -// newShares: sdk.NewDec(9910), -// expectedExceeds: false, -// }, -// { -// // Validator Shares: 100, Factor: 100, Current Shares: 90 => 10000 Max Shares, Capacity: 9910 -// // New Shares: 9911 - above cap -// name: "factor 100 - above cap", -// validatorShares: sdk.NewDec(100), -// validatorBondFactor: sdk.NewDec(100), -// currentLiquidShares: sdk.NewDec(90), -// newShares: sdk.NewDec(9911), -// expectedExceeds: true, -// }, -// { -// // Factor of -1 (disabled): Should always return false -// name: "factor disabled", -// validatorShares: sdk.NewDec(1), -// validatorBondFactor: sdk.NewDec(-1), -// currentLiquidShares: sdk.NewDec(1), -// newShares: sdk.NewDec(1_000_000), -// expectedExceeds: false, -// }, -// } - -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// // Update the validator bond factor -// params := keeper.GetParams(ctx) -// params.ValidatorBondFactor = tc.validatorBondFactor -// keeper.SetParams(ctx, params) - -// // Create a validator with designated self-bond shares -// validator := types.Validator{ -// LiquidShares: tc.currentLiquidShares, -// ValidatorBondShares: tc.validatorShares, -// } - -// // Check whether the cap is exceeded -// actualExceeds := keeper.CheckExceedsValidatorBondCap(ctx, validator, tc.newShares) -// require.Equal(t, tc.expectedExceeds, actualExceeds, tc.name) -// }) -// } -// } - -// // Tests TestCheckExceedsValidatorLiquidStakingCap -// func (s *KeeperTestSuite) TestCheckExceedsValidatorLiquidStakingCap(t *testing.T) { -// ctx, keeper := s.ctx, s.stakingKeeper -// require := s.Require() - -// testCases := []struct { -// name string -// validatorLiquidCap sdk.Dec -// validatorLiquidShares sdk.Dec -// validatorTotalShares sdk.Dec -// newLiquidShares sdk.Dec -// expectedExceeds bool -// }{ -// { -// // Cap: 10% - Delegation Below Threshold -// // Liquid Shares: 5, Total Shares: 95, New Liquid Shares: 1 -// // => Liquid Shares: 5+1=6, Total Shares: 95+1=96 => 6/96 = 6% < 10% cap -// name: "10 percent cap _ delegation below cap", -// validatorLiquidCap: sdk.MustNewDecFromStr("0.1"), -// validatorLiquidShares: sdk.NewDec(5), -// validatorTotalShares: sdk.NewDec(95), -// newLiquidShares: sdk.NewDec(1), -// expectedExceeds: false, -// }, -// { -// // Cap: 10% - Delegation At Threshold -// // Liquid Shares: 5, Total Shares: 95, New Liquid Shares: 5 -// // => Liquid Shares: 5+5=10, Total Shares: 95+5=100 => 10/100 = 10% == 10% cap -// name: "10 percent cap _ delegation equals cap", -// validatorLiquidCap: sdk.MustNewDecFromStr("0.1"), -// validatorLiquidShares: sdk.NewDec(5), -// validatorTotalShares: sdk.NewDec(95), -// newLiquidShares: sdk.NewDec(4), -// expectedExceeds: false, -// }, -// { -// // Cap: 10% - Delegation Exceeds Threshold -// // Liquid Shares: 5, Total Shares: 95, New Liquid Shares: 6 -// // => Liquid Shares: 5+6=11, Total Shares: 95+6=101 => 11/101 = 11% > 10% cap -// name: "10 percent cap _ delegation exceeds cap", -// validatorLiquidCap: sdk.MustNewDecFromStr("0.1"), -// validatorLiquidShares: sdk.NewDec(5), -// validatorTotalShares: sdk.NewDec(95), -// newLiquidShares: sdk.NewDec(6), -// expectedExceeds: true, -// }, -// { -// // Cap: 20% - Delegation Below Threshold -// // Liquid Shares: 20, Total Shares: 220, New Liquid Shares: 29 -// // => Liquid Shares: 20+29=49, Total Shares: 220+29=249 => 49/249 = 19% < 20% cap -// name: "20 percent cap _ delegation below cap", -// validatorLiquidCap: sdk.MustNewDecFromStr("0.2"), -// validatorLiquidShares: sdk.NewDec(20), -// validatorTotalShares: sdk.NewDec(220), -// newLiquidShares: sdk.NewDec(29), -// expectedExceeds: false, -// }, -// { -// // Cap: 20% - Delegation At Threshold -// // Liquid Shares: 20, Total Shares: 220, New Liquid Shares: 30 -// // => Liquid Shares: 20+30=50, Total Shares: 220+30=250 => 50/250 = 20% == 20% cap -// name: "20 percent cap _ delegation equals cap", -// validatorLiquidCap: sdk.MustNewDecFromStr("0.2"), -// validatorLiquidShares: sdk.NewDec(20), -// validatorTotalShares: sdk.NewDec(220), -// newLiquidShares: sdk.NewDec(30), -// expectedExceeds: false, -// }, -// { -// // Cap: 20% - Delegation Exceeds Threshold -// // Liquid Shares: 20, Total Shares: 220, New Liquid Shares: 31 -// // => Liquid Shares: 20+31=51, Total Shares: 220+31=251 => 51/251 = 21% > 20% cap -// name: "20 percent cap _ delegation exceeds cap", -// validatorLiquidCap: sdk.MustNewDecFromStr("0.2"), -// validatorLiquidShares: sdk.NewDec(20), -// validatorTotalShares: sdk.NewDec(220), -// newLiquidShares: sdk.NewDec(31), -// expectedExceeds: true, -// }, -// { -// // Cap of 0% - everything should exceed -// name: "0 percent cap", -// validatorLiquidCap: sdk.ZeroDec(), -// validatorLiquidShares: sdk.NewDec(0), -// validatorTotalShares: sdk.NewDec(1_000_000), -// newLiquidShares: sdk.NewDec(1), -// expectedExceeds: true, -// }, -// { -// // Cap of 100% - nothing should exceed -// name: "100 percent cap", -// validatorLiquidCap: sdk.OneDec(), -// validatorLiquidShares: sdk.NewDec(1), -// validatorTotalShares: sdk.NewDec(1_000_000), -// newLiquidShares: sdk.NewDec(1), -// expectedExceeds: false, -// }, -// } - -// for _, tc := range testCases { -// t.Run(tc.name, func(t *testing.T) { -// // Update the validator liquid staking cap -// params := keeper.GetParams(ctx) -// params.ValidatorLiquidStakingCap = tc.validatorLiquidCap -// keeper.SetParams(ctx, params) - -// // Create a validator with designated self-bond shares -// validator := types.Validator{ -// LiquidShares: tc.validatorLiquidShares, -// DelegatorShares: tc.validatorTotalShares, -// } - -// // Check whether the cap is exceeded -// actualExceeds := keeper.CheckExceedsValidatorLiquidStakingCap(ctx, validator, tc.newLiquidShares) -// require.Equal(t, tc.expectedExceeds, actualExceeds, tc.name) -// }) -// } -// } - -// // Tests SafelyIncreaseValidatorLiquidShares -// func (s *KeeperTestSuite) TestSafelyIncreaseValidatorLiquidShares(t *testing.T) { -// ctx, keeper := s.ctx, s.stakingKeeper -// require := s.Require() - -// // Generate a test validator address -// privKey := secp256k1.GenPrivKey() -// pubKey := privKey.PubKey() -// valAddress := sdk.ValAddress(pubKey.Address()) - -// // Helper function to check the validator's liquid shares -// checkValidatorLiquidShares := func(expected sdk.Dec, description string) { -// actualValidator, found := keeper.GetValidator(ctx, valAddress) -// require.True(found) -// require.Equal(expected.TruncateInt64(), actualValidator.LiquidShares.TruncateInt64(), description) -// } - -// // Start with the following: -// // Initial Liquid Shares: 0 -// // Validator Bond Shares: 10 -// // Validator TotalShares: 75 -// // -// // Initial Caps: -// // ValidatorBondFactor: 1 (Cap applied at 10 shares) -// // ValidatorLiquidStakingCap: 25% (Cap applied at 25 shares) -// // -// // Cap Increases: -// // ValidatorBondFactor: 10 (Cap applied at 100 shares) -// // ValidatorLiquidStakingCap: 40% (Cap applied at 50 shares) -// initialLiquidShares := sdk.NewDec(0) -// validatorBondShares := sdk.NewDec(10) -// validatorTotalShares := sdk.NewDec(75) - -// firstIncreaseAmount := sdk.NewDec(20) -// secondIncreaseAmount := sdk.NewDec(10) // total increase of 30 - -// initialBondFactor := sdk.NewDec(1) -// finalBondFactor := sdk.NewDec(10) -// initialLiquidStakingCap := sdk.MustNewDecFromStr("0.25") -// finalLiquidStakingCap := sdk.MustNewDecFromStr("0.4") - -// // Create a validator with designated self-bond shares -// initialValidator := types.Validator{ -// OperatorAddress: valAddress.String(), -// LiquidShares: initialLiquidShares, -// ValidatorBondShares: validatorBondShares, -// DelegatorShares: validatorTotalShares, -// } -// keeper.SetValidator(ctx, initialValidator) - -// // Set validator bond factor to a small number such that any delegation would fail, -// // and set the liquid staking cap such that the first stake would succeed, but the second -// // would fail -// params := keeper.GetParams(ctx) -// params.ValidatorBondFactor = initialBondFactor -// params.ValidatorLiquidStakingCap = initialLiquidStakingCap -// keeper.SetParams(ctx, params) - -// // Attempt to increase the validator liquid shares, it should throw an -// // error that the validator bond cap was exceeded -// _, err := keeper.SafelyIncreaseValidatorLiquidShares(ctx, valAddress, firstIncreaseAmount) -// require.ErrorIs(t, err, types.ErrInsufficientValidatorBondShares) -// checkValidatorLiquidShares(initialLiquidShares, "shares after low bond factor") - -// // Change validator bond factor to a more conservative number, so that the increase succeeds -// params.ValidatorBondFactor = finalBondFactor -// keeper.SetParams(ctx, params) - -// // Try the increase again and check that it succeeded -// expectedLiquidSharesAfterFirstStake := initialLiquidShares.Add(firstIncreaseAmount) -// _, err = keeper.SafelyIncreaseValidatorLiquidShares(ctx, valAddress, firstIncreaseAmount) -// require.NoError(t, err) -// checkValidatorLiquidShares(expectedLiquidSharesAfterFirstStake, "shares with cap loose bond cap") - -// // Attempt another increase, it should fail from the liquid staking cap -// _, err = keeper.SafelyIncreaseValidatorLiquidShares(ctx, valAddress, secondIncreaseAmount) -// require.ErrorIs(t, err, types.ErrValidatorLiquidStakingCapExceeded) -// checkValidatorLiquidShares(expectedLiquidSharesAfterFirstStake, "shares after liquid staking cap hit") - -// // Raise the liquid staking cap so the new increment succeeds -// params.ValidatorLiquidStakingCap = finalLiquidStakingCap -// keeper.SetParams(ctx, params) - -// // Finally confirm that the increase succeeded this time -// expectedLiquidSharesAfterSecondStake := expectedLiquidSharesAfterFirstStake.Add(secondIncreaseAmount) -// _, err = keeper.SafelyIncreaseValidatorLiquidShares(ctx, valAddress, secondIncreaseAmount) -// require.NoError(t, err, "no error expected after increasing liquid staking cap") -// checkValidatorLiquidShares(expectedLiquidSharesAfterSecondStake, "shares after loose liquid stake cap") -// } - -// // Tests DecreaseValidatorLiquidShares -// func (s *KeeperTestSuite) TestDecreaseValidatorLiquidShares(t *testing.T) { -// ctx, keeper := s.ctx, s.stakingKeeper -// require := s.Require() - -// initialLiquidShares := sdk.NewDec(100) -// decreaseAmount := sdk.NewDec(10) - -// // Create a validator with designated self-bond shares -// privKey := secp256k1.GenPrivKey() -// pubKey := privKey.PubKey() -// valAddress := sdk.ValAddress(pubKey.Address()) - -// initialValidator := types.Validator{ -// OperatorAddress: valAddress.String(), -// LiquidShares: initialLiquidShares, -// } -// keeper.SetValidator(ctx, initialValidator) - -// // Decrease the validator liquid shares, and confirm the new share amount has been updated -// _, err := keeper.DecreaseValidatorLiquidShares(ctx, valAddress, decreaseAmount) -// require.NoError(t, err, "no error expected when decreasing validator liquid shares") - -// actualValidator, found := keeper.GetValidator(ctx, valAddress) -// require.True(t, found) -// require.Equal(t, initialLiquidShares.Sub(decreaseAmount), actualValidator.LiquidShares, "liquid shares") - -// // Attempt to decrease by a larger amount than it has, it should fail -// _, err = keeper.DecreaseValidatorLiquidShares(ctx, valAddress, initialLiquidShares) -// require.ErrorIs(t, err, types.ErrValidatorLiquidSharesUnderflow) -// } - -// // Tests SafelyDecreaseValidatorBond -// func (s *KeeperTestSuite) TestSafelyDecreaseValidatorBond(t *testing.T) { -// ctx, keeper := s.ctx, s.stakingKeeper -// require := s.Require() - -// // Initial Bond Factor: 100, Initial Validator Bond: 10 -// // => Max Liquid Shares 1000 (Initial Liquid Shares: 200) -// initialBondFactor := sdk.NewDec(100) -// initialValidatorBondShares := sdk.NewDec(10) -// initialLiquidShares := sdk.NewDec(200) - -// // Create a validator with designated self-bond shares -// privKey := secp256k1.GenPrivKey() -// pubKey := privKey.PubKey() -// valAddress := sdk.ValAddress(pubKey.Address()) - -// initialValidator := types.Validator{ -// OperatorAddress: valAddress.String(), -// ValidatorBondShares: initialValidatorBondShares, -// LiquidShares: initialLiquidShares, -// } -// keeper.SetValidator(ctx, initialValidator) - -// // Set the bond factor -// params := keeper.GetParams(ctx) -// params.ValidatorBondFactor = initialBondFactor -// keeper.SetParams(ctx, params) - -// // Decrease the validator bond from 10 to 5 (minus 5) -// // This will adjust the cap (factor * shares) -// // from (100 * 10 = 1000) to (100 * 5 = 500) -// // Since this is still above the initial liquid shares of 200, this will succeed -// decreaseAmount, expectedBondShares := sdk.NewDec(5), sdk.NewDec(5) -// err := keeper.SafelyDecreaseValidatorBond(ctx, valAddress, decreaseAmount) -// require.NoError(t, err) - -// actualValidator, found := keeper.GetValidator(ctx, valAddress) -// require.True(t, found) -// require.Equal(t, expectedBondShares, actualValidator.ValidatorBondShares, "validator bond shares shares") - -// // Now attempt to decrease the validator bond again from 5 to 1 (minus 4) -// // This time, the cap will be reduced to (factor * shares) = (100 * 1) = 100 -// // However, the liquid shares are currently 200, so this should fail -// decreaseAmount, expectedBondShares = sdk.NewDec(4), sdk.NewDec(1) -// err = keeper.SafelyDecreaseValidatorBond(ctx, valAddress, decreaseAmount) -// require.ErrorIs(t, err, types.ErrInsufficientValidatorBondShares) - -// // Finally, disable the cap and attempt to decrease again -// // This time it should succeed -// params.ValidatorBondFactor = types.ValidatorBondCapDisabled -// keeper.SetParams(ctx, params) - -// err = keeper.SafelyDecreaseValidatorBond(ctx, valAddress, decreaseAmount) -// require.NoError(t, err) - -// actualValidator, found = keeper.GetValidator(ctx, valAddress) -// require.True(t, found) -// require.Equal(t, expectedBondShares, actualValidator.ValidatorBondShares, "validator bond shares shares") -// } - -// // Tests Add/Remove/Get/SetTokenizeSharesLock -// func (s *KeeperTestSuite) TestTokenizeSharesLock(t *testing.T) { -// ctx, keeper := s.ctx, s.stakingKeeper -// require := s.Require() - -// addresses := simtestutil.AddTestAddrs(s.bankKeeper, ctx, 2, sdk.NewInt(1)) -// addressA, addressB := addresses[0], addresses[1] - -// unlocked := types.TOKENIZE_SHARE_LOCK_STATUS_UNLOCKED.String() -// locked := types.TOKENIZE_SHARE_LOCK_STATUS_LOCKED.String() -// lockExpiring := types.TOKENIZE_SHARE_LOCK_STATUS_LOCK_EXPIRING.String() - -// // Confirm both accounts start unlocked -// status, _ := keeper.GetTokenizeSharesLock(ctx, addressA) -// require.Equal(t, unlocked, status.String(), "addressA unlocked at start") - -// status, _ = keeper.GetTokenizeSharesLock(ctx, addressB) -// require.Equal(t, unlocked, status.String(), "addressB unlocked at start") - -// // Lock the first account -// keeper.AddTokenizeSharesLock(ctx, addressA) - -// // The first account should now have tokenize shares disabled -// // and the unlock time should be the zero time -// status, _ = keeper.GetTokenizeSharesLock(ctx, addressA) -// require.Equal(t, locked, status.String(), "addressA locked") - -// status, _ = keeper.GetTokenizeSharesLock(ctx, addressB) -// require.Equal(t, unlocked, status.String(), "addressB still unlocked") - -// // Update the lock time and confirm it was set -// expectedUnlockTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) -// keeper.SetTokenizeSharesUnlockTime(ctx, addressA, expectedUnlockTime) - -// status, actualUnlockTime := keeper.GetTokenizeSharesLock(ctx, addressA) -// require.Equal(t, lockExpiring, status.String(), "addressA lock expiring") -// require.Equal(t, expectedUnlockTime, actualUnlockTime, "addressA unlock time") - -// // Confirm B is still unlocked -// status, _ = keeper.GetTokenizeSharesLock(ctx, addressB) -// require.Equal(t, unlocked, status.String(), "addressB still unlocked") - -// // Remove the lock -// keeper.RemoveTokenizeSharesLock(ctx, addressA) -// status, _ = keeper.GetTokenizeSharesLock(ctx, addressA) -// require.Equal(t, unlocked, status.String(), "addressA unlocked at end") - -// status, _ = keeper.GetTokenizeSharesLock(ctx, addressB) -// require.Equal(t, unlocked, status.String(), "addressB unlocked at end") -// } - -// // Tests GetAllTokenizeSharesLocks -// func (s *KeeperTestSuite) TestGetAllTokenizeSharesLocks(t *testing.T) { -// ctx, keeper := s.ctx, s.stakingKeeper -// require := s.Require() - -// addresses := simapp.AddTestAddrs(app, ctx, 4, sdk.NewInt(1)) - -// // Set 2 locked accounts, and two accounts with a lock expiring -// keeper.AddTokenizeSharesLock(ctx, addresses[0]) -// keeper.AddTokenizeSharesLock(ctx, addresses[1]) - -// unlockTime1 := time.Date(2023, 1, 1, 1, 0, 0, 0, time.UTC) -// unlockTime2 := time.Date(2023, 1, 2, 1, 0, 0, 0, time.UTC) -// keeper.SetTokenizeSharesUnlockTime(ctx, addresses[2], unlockTime1) -// keeper.SetTokenizeSharesUnlockTime(ctx, addresses[3], unlockTime2) - -// // Defined expected locks after GetAll -// expectedLocks := map[string]types.TokenizeShareLock{ -// addresses[0].String(): { -// Status: types.TOKENIZE_SHARE_LOCK_STATUS_LOCKED.String(), -// }, -// addresses[1].String(): { -// Status: types.TOKENIZE_SHARE_LOCK_STATUS_LOCKED.String(), -// }, -// addresses[2].String(): { -// Status: types.TOKENIZE_SHARE_LOCK_STATUS_LOCK_EXPIRING.String(), -// CompletionTime: unlockTime1, -// }, -// addresses[3].String(): { -// Status: types.TOKENIZE_SHARE_LOCK_STATUS_LOCK_EXPIRING.String(), -// CompletionTime: unlockTime2, -// }, -// } - -// // Check output from GetAll -// actualLocks := keeper.GetAllTokenizeSharesLocks(ctx) -// require.Len(actualLocks, len(expectedLocks), "number of locks") - -// for i, actual := range actualLocks { -// expected, ok := expectedLocks[actual.Address] -// require.True(ok, "address %s not expected", actual.Address) -// require.Equal(expected.Status, actual.Status, "tokenize share lock #%d status", i) -// require.Equal(expected.CompletionTime, actual.CompletionTime, "tokenize share lock #%d completion time", i) -// } -// } - -// // Test Get/SetPendingTokenizeShareAuthorizations -// func (s *KeeperTestSuite) TestPendingTokenizeShareAuthorizations(t *testing.T) { -// ctx, keeper := s.ctx, s.stakingKeeper -// require := s.Require() - -// // Create dummy accounts and completion times -// addresses := simapp.AddTestAddrs(app, ctx, 3, sdk.NewInt(1)) -// addressStrings := []string{} -// for _, address := range addresses { -// addressStrings = append(addressStrings, address.String()) -// } - -// timeA := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) -// timeB := timeA.Add(time.Hour) - -// // There should be no addresses returned originally -// authorizationsA := keeper.GetPendingTokenizeShareAuthorizations(ctx, timeA) -// require.Empty(t, authorizationsA.Addresses, "no addresses at timeA expected") - -// authorizationsB := keeper.GetPendingTokenizeShareAuthorizations(ctx, timeB) -// require.Empty(t, authorizationsB.Addresses, "no addresses at timeB expected") - -// // Store addresses for timeB -// keeper.SetPendingTokenizeShareAuthorizations(ctx, timeB, types.PendingTokenizeShareAuthorizations{ -// Addresses: addressStrings, -// }) - -// // Check addresses -// authorizationsA = keeper.GetPendingTokenizeShareAuthorizations(ctx, timeA) -// require.Empty(t, authorizationsA.Addresses, "no addresses at timeA expected at end") - -// authorizationsB = keeper.GetPendingTokenizeShareAuthorizations(ctx, timeB) -// require.Equal(t, addressStrings, authorizationsB.Addresses, "address length") -// } - -// // Test QueueTokenizeSharesAuthorization and RemoveExpiredTokenizeShareLocks -// func (s *KeeperTestSuite) TestTokenizeShareAuthorizationQueue(t *testing.T) { -// ctx, keeper := s.ctx, s.stakingKeeper -// require := s.Require() - -// // We'll start by adding the following addresses to the queue -// // Time 0: [address0] -// // Time 1: [] -// // Time 2: [address1, address2, address3] -// // Time 3: [address4, address5] -// // Time 4: [address6] -// addresses := simapp.AddTestAddrs(app, ctx, 7, sdk.NewInt(1)) -// addressesByTime := map[int][]sdk.AccAddress{ -// 0: {addresses[0]}, -// 1: {}, -// 2: {addresses[1], addresses[2], addresses[3]}, -// 3: {addresses[4], addresses[5]}, -// 4: {addresses[6]}, -// } - -// // Set the unbonding time to 1 day -// unbondingPeriod := time.Hour * 24 -// params := keeper.GetParams(ctx) -// params.UnbondingTime = unbondingPeriod -// keeper.SetParams(ctx, params) - -// // Add each address to the queue and then increment the block time -// // such that the times line up as follows -// // Time 0: 2023-01-01 00:00:00 -// // Time 1: 2023-01-01 00:01:00 -// // Time 2: 2023-01-01 00:02:00 -// // Time 3: 2023-01-01 00:03:00 -// startTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) -// ctx = ctx.WithBlockTime(startTime) -// blockTimeIncrement := time.Hour - -// for timeIndex := 0; timeIndex <= 4; timeIndex++ { -// for _, address := range addressesByTime[timeIndex] { -// keeper.QueueTokenizeSharesAuthorization(ctx, address) -// } -// ctx = ctx.WithBlockTime(ctx.BlockTime().Add(blockTimeIncrement)) -// } - -// // We'll unlock the tokens using the following progression -// // The "alias'"/keys for these times assume a starting point of the Time 0 -// // from above, plus the Unbonding Time -// // Time -1 (2023-01-01 23:59:99): [] -// // Time 0 (2023-01-02 00:00:00): [address0] -// // Time 1 (2023-01-02 00:01:00): [] -// // Time 2.5 (2023-01-02 00:02:30): [address1, address2, address3] -// // Time 10 (2023-01-02 00:10:00): [address4, address5, address6] -// unlockBlockTimes := map[string]time.Time{ -// "-1": startTime.Add(unbondingPeriod).Add(-time.Second), -// "0": startTime.Add(unbondingPeriod), -// "1": startTime.Add(unbondingPeriod).Add(blockTimeIncrement), -// "2.5": startTime.Add(unbondingPeriod).Add(2 * blockTimeIncrement).Add(blockTimeIncrement / 2), -// "10": startTime.Add(unbondingPeriod).Add(10 * blockTimeIncrement), -// } -// expectedUnlockedAddresses := map[string][]string{ -// "-1": {}, -// "0": {addresses[0].String()}, -// "1": {}, -// "2.5": {addresses[1].String(), addresses[2].String(), addresses[3].String()}, -// "10": {addresses[4].String(), addresses[5].String(), addresses[6].String()}, -// } - -// // Now we'll remove items from the queue sequentially -// // First check with a block time before the first expiration - it should remove no addresses -// actualAddresses := keeper.RemoveExpiredTokenizeShareLocks(ctx, unlockBlockTimes["-1"]) -// require.Equal(t, expectedUnlockedAddresses["-1"], actualAddresses, "no addresses unlocked from time -1") - -// // Then pass in (time 0 + unbonding time) - it should remove the first address -// actualAddresses = keeper.RemoveExpiredTokenizeShareLocks(ctx, unlockBlockTimes["0"]) -// require.Equal(t, expectedUnlockedAddresses["0"], actualAddresses, "one address unlocked from time 0") - -// // Now pass in (time 1 + unbonding time) - it should remove no addresses since -// // the address at time 0 was already removed -// actualAddresses = keeper.RemoveExpiredTokenizeShareLocks(ctx, unlockBlockTimes["1"]) -// require.Equal(t, expectedUnlockedAddresses["1"], actualAddresses, "no addresses unlocked from time 1") - -// // Now pass in (time 2.5 + unbonding time) - it should remove the three addresses from time 2 -// actualAddresses = keeper.RemoveExpiredTokenizeShareLocks(ctx, unlockBlockTimes["2.5"]) -// require.Equal(t, expectedUnlockedAddresses["2.5"], actualAddresses, "addresses unlocked from time 2.5") - -// // Finally pass in a block time far in the future, which should remove all the remaining locks -// actualAddresses = keeper.RemoveExpiredTokenizeShareLocks(ctx, unlockBlockTimes["10"]) -// require.Equal(t, expectedUnlockedAddresses["10"], actualAddresses, "addresses unlocked from time 10") -// } - -// // Test RefreshTotalLiquidStaked -// func (s *KeeperTestSuite) TestRefreshTotalLiquidStaked(t *testing.T) { -// ctx, keeper := s.ctx, s.stakingKeeper -// require := s.Require() - -// // Set an arbitrary total liquid staked tokens amount that will get overwritten by the refresh -// keeper.SetTotalLiquidStakedTokens(ctx, sdk.NewInt(999)) - -// // Add validator's with various exchange rates -// validators := []types.Validator{ -// { -// // Exchange rate of 1 -// OperatorAddress: "valA", -// Tokens: sdk.NewInt(100), -// DelegatorShares: sdk.NewDec(100), -// LiquidShares: sdk.NewDec(100), // should be overwritten -// }, -// { -// // Exchange rate of 0.9 -// OperatorAddress: "valB", -// Tokens: sdk.NewInt(90), -// DelegatorShares: sdk.NewDec(100), -// LiquidShares: sdk.NewDec(200), // should be overwritten -// }, -// { -// // Exchange rate of 0.75 -// OperatorAddress: "valC", -// Tokens: sdk.NewInt(75), -// DelegatorShares: sdk.NewDec(100), -// LiquidShares: sdk.NewDec(300), // should be overwritten -// }, -// } - -// // Add various delegations across the above validator's -// // Total Liquid Staked: 1,849 + 922 = 2,771 -// // Liquid Shares: -// // ValA: 400 + 325 = 725 -// // ValB: 860 + 580 = 1,440 -// // ValC: 900 + 100 = 1,000 -// expectedTotalLiquidStaked := int64(2771) -// expectedValidatorLiquidShares := map[string]sdk.Dec{ -// "valA": sdk.NewDec(725), -// "valB": sdk.NewDec(1440), -// "valC": sdk.NewDec(1000), -// } - -// delegations := []struct { -// delegation types.Delegation -// isLSTP bool -// isTokenized bool -// }{ -// // Delegator A - Not a liquid staking provider -// // Number of tokens/shares is irrelevant for this test -// { -// isLSTP: false, -// delegation: types.Delegation{ -// DelegatorAddress: "delA", -// ValidatorAddress: "valA", -// Shares: sdk.NewDec(100), -// }, -// }, -// { -// isLSTP: false, -// delegation: types.Delegation{ -// DelegatorAddress: "delA", -// ValidatorAddress: "valB", -// Shares: sdk.NewDec(860), -// }, -// }, -// { -// isLSTP: false, -// delegation: types.Delegation{ -// DelegatorAddress: "delA", -// ValidatorAddress: "valC", -// Shares: sdk.NewDec(750), -// }, -// }, -// // Delegator B - Liquid staking provider, tokens included in total -// // Total liquid staked: 400 + 774 + 675 = 1,849 -// { -// // Shares: 400 shares, Exchange Rate: 1.0, Tokens: 400 -// isLSTP: true, -// delegation: types.Delegation{ -// DelegatorAddress: "delB-LSTP", -// ValidatorAddress: "valA", -// Shares: sdk.NewDec(400), -// }, -// }, -// { -// // Shares: 860 shares, Exchange Rate: 0.9, Tokens: 774 -// isLSTP: true, -// delegation: types.Delegation{ -// DelegatorAddress: "delB-LSTP", -// ValidatorAddress: "valB", -// Shares: sdk.NewDec(860), -// }, -// }, -// { -// // Shares: 900 shares, Exchange Rate: 0.75, Tokens: 675 -// isLSTP: true, -// delegation: types.Delegation{ -// DelegatorAddress: "delB-LSTP", -// ValidatorAddress: "valC", -// Shares: sdk.NewDec(900), -// }, -// }, -// // Delegator C - Tokenized shares, tokens included in total -// // Total liquid staked: 325 + 522 + 75 = 922 -// { -// // Shares: 325 shares, Exchange Rate: 1.0, Tokens: 325 -// isTokenized: true, -// delegation: types.Delegation{ -// DelegatorAddress: "delC-LSTP", -// ValidatorAddress: "valA", -// Shares: sdk.NewDec(325), -// }, -// }, -// { -// // Shares: 580 shares, Exchange Rate: 0.9, Tokens: 522 -// isTokenized: true, -// delegation: types.Delegation{ -// DelegatorAddress: "delC-LSTP", -// ValidatorAddress: "valB", -// Shares: sdk.NewDec(580), -// }, -// }, -// { -// // Shares: 100 shares, Exchange Rate: 0.75, Tokens: 75 -// isTokenized: true, -// delegation: types.Delegation{ -// DelegatorAddress: "delC-LSTP", -// ValidatorAddress: "valC", -// Shares: sdk.NewDec(100), -// }, -// }, -// } - -// // Create validators based on the above (must use an actual validator address) -// addresses := testutil.AddTestAddrsIncremental(s.bankKeeper, ctx, 5, keeper.TokensFromConsensusPower(ctx, 300)) -// validatorAddresses := map[string]sdk.ValAddress{ -// "valA": sdk.ValAddress(addresses[0]), -// "valB": sdk.ValAddress(addresses[1]), -// "valC": sdk.ValAddress(addresses[2]), -// } -// for _, validator := range validators { -// validator.OperatorAddress = validatorAddresses[validator.OperatorAddress].String() -// keeper.SetValidator(ctx, validator) -// } - -// // Create the delegations based on the above (must use actual delegator addresses) -// for _, delegationCase := range delegations { -// var delegatorAddress sdk.AccAddress -// switch { -// case delegationCase.isLSTP: -// delegatorAddress = createICAAccount(app, ctx) -// case delegationCase.isTokenized: -// delegatorAddress = createTokenizeShareModuleAccount(1) -// default: -// delegatorAddress = createBaseAccount(app, ctx, delegationCase.delegation.DelegatorAddress) -// } - -// delegation := delegationCase.delegation -// delegation.DelegatorAddress = delegatorAddress.String() -// delegation.ValidatorAddress = validatorAddresses[delegation.ValidatorAddress].String() -// keeper.SetDelegation(ctx, delegation) -// } - -// // Refresh the total liquid staked and validator liquid shares -// err := keeper.RefreshTotalLiquidStaked(ctx) -// require.NoError(t, err, "no error expected when refreshing total liquid staked") - -// // Check the total liquid staked and liquid shares by validator -// actualTotalLiquidStaked := keeper.GetTotalLiquidStakedTokens(ctx) -// require.Equal(t, expectedTotalLiquidStaked, actualTotalLiquidStaked.Int64(), "total liquid staked tokens") - -// for _, moniker := range []string{"valA", "valB", "valC"} { -// address := validatorAddresses[moniker] -// expectedLiquidShares := expectedValidatorLiquidShares[moniker] - -// actualValidator, found := keeper.GetValidator(ctx, address) -// require.True(t, found, "validator %s should have been found after refresh", moniker) - -// actualLiquidShares := actualValidator.LiquidShares -// require.Equal(t, expectedLiquidShares.TruncateInt64(), actualLiquidShares.TruncateInt64(), -// "liquid staked shares for validator %s", moniker) -// } -// } +import ( + "time" + + "github.com/cosmos/cosmos-sdk/crypto/keys/secp256k1" + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + sdk "github.com/cosmos/cosmos-sdk/types" + "github.com/cosmos/cosmos-sdk/types/address" + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" + "github.com/cosmos/cosmos-sdk/x/staking/types" +) + +// Tests Set/Get TotalLiquidStakedTokens +func (s *KeeperTestSuite) TestTotalLiquidStakedTokens() { + ctx, keeper := s.ctx, s.stakingKeeper + require := s.Require() + + // Update the total liquid staked + total := sdk.NewInt(100) + keeper.SetTotalLiquidStakedTokens(ctx, total) + + // Confirm it was updated + require.Equal(total, keeper.GetTotalLiquidStakedTokens(ctx), "initial") +} + +// Tests Increase/Decrease TotalValidatorLiquidShares +func (s *KeeperTestSuite) TestValidatorLiquidShares() { + ctx, keeper := s.ctx, s.stakingKeeper + + // Create a validator address + privKey := secp256k1.GenPrivKey() + pubKey := privKey.PubKey() + valAddress := sdk.ValAddress(pubKey.Address()) + + // Set an initial total + initial := sdk.NewDec(100) + validator := types.Validator{ + OperatorAddress: valAddress.String(), + LiquidShares: initial, + } + keeper.SetValidator(ctx, validator) +} + +// Tests DecreaseTotalLiquidStakedTokens +func (s *KeeperTestSuite) TestDecreaseTotalLiquidStakedTokens() { + ctx, keeper := s.ctx, s.stakingKeeper + require := s.Require() + + intitialTotalLiquidStaked := sdk.NewInt(100) + decreaseAmount := sdk.NewInt(10) + + // Set the total liquid staked to an arbitrary value + keeper.SetTotalLiquidStakedTokens(ctx, intitialTotalLiquidStaked) + + // Decrease the total liquid stake and confirm the total was updated + err := keeper.DecreaseTotalLiquidStakedTokens(ctx, decreaseAmount) + require.NoError(err, "no error expected when decreasing total liquid staked tokens") + require.Equal(intitialTotalLiquidStaked.Sub(decreaseAmount), keeper.GetTotalLiquidStakedTokens(ctx)) + + // Attempt to decrease by an excessive amount, it should error + err = keeper.DecreaseTotalLiquidStakedTokens(ctx, intitialTotalLiquidStaked) + require.ErrorIs(err, types.ErrTotalLiquidStakedUnderflow) +} + +// Tests CheckExceedsValidatorBondCap +func (s *KeeperTestSuite) TestCheckExceedsValidatorBondCap() { + ctx, keeper := s.ctx, s.stakingKeeper + require := s.Require() + + testCases := []struct { + name string + validatorShares sdk.Dec + validatorBondFactor sdk.Dec + currentLiquidShares sdk.Dec + newShares sdk.Dec + expectedExceeds bool + }{ + { + // Validator Shares: 100, Factor: 1, Current Shares: 90 => 100 Max Shares, Capacity: 10 + // New Shares: 5 - below cap + name: "factor 1 - below cap", + validatorShares: sdk.NewDec(100), + validatorBondFactor: sdk.NewDec(1), + currentLiquidShares: sdk.NewDec(90), + newShares: sdk.NewDec(5), + expectedExceeds: false, + }, + { + // Validator Shares: 100, Factor: 1, Current Shares: 90 => 100 Max Shares, Capacity: 10 + // New Shares: 10 - at cap + name: "factor 1 - at cap", + validatorShares: sdk.NewDec(100), + validatorBondFactor: sdk.NewDec(1), + currentLiquidShares: sdk.NewDec(90), + newShares: sdk.NewDec(10), + expectedExceeds: false, + }, + { + // Validator Shares: 100, Factor: 1, Current Shares: 90 => 100 Max Shares, Capacity: 10 + // New Shares: 15 - above cap + name: "factor 1 - above cap", + validatorShares: sdk.NewDec(100), + validatorBondFactor: sdk.NewDec(1), + currentLiquidShares: sdk.NewDec(90), + newShares: sdk.NewDec(15), + expectedExceeds: true, + }, + { + // Validator Shares: 100, Factor: 2, Current Shares: 90 => 200 Max Shares, Capacity: 110 + // New Shares: 5 - below cap + name: "factor 2 - well below cap", + validatorShares: sdk.NewDec(100), + validatorBondFactor: sdk.NewDec(2), + currentLiquidShares: sdk.NewDec(90), + newShares: sdk.NewDec(5), + expectedExceeds: false, + }, + { + // Validator Shares: 100, Factor: 2, Current Shares: 90 => 200 Max Shares, Capacity: 110 + // New Shares: 100 - below cap + name: "factor 2 - below cap", + validatorShares: sdk.NewDec(100), + validatorBondFactor: sdk.NewDec(2), + currentLiquidShares: sdk.NewDec(90), + newShares: sdk.NewDec(100), + expectedExceeds: false, + }, + { + // Validator Shares: 100, Factor: 2, Current Shares: 90 => 200 Max Shares, Capacity: 110 + // New Shares: 110 - below cap + name: "factor 2 - at cap", + validatorShares: sdk.NewDec(100), + validatorBondFactor: sdk.NewDec(2), + currentLiquidShares: sdk.NewDec(90), + newShares: sdk.NewDec(110), + expectedExceeds: false, + }, + { + // Validator Shares: 100, Factor: 2, Current Shares: 90 => 200 Max Shares, Capacity: 110 + // New Shares: 111 - above cap + name: "factor 2 - above cap", + validatorShares: sdk.NewDec(100), + validatorBondFactor: sdk.NewDec(2), + currentLiquidShares: sdk.NewDec(90), + newShares: sdk.NewDec(111), + expectedExceeds: true, + }, + { + // Validator Shares: 100, Factor: 100, Current Shares: 90 => 10000 Max Shares, Capacity: 9910 + // New Shares: 100 - below cap + name: "factor 100 - below cap", + validatorShares: sdk.NewDec(100), + validatorBondFactor: sdk.NewDec(100), + currentLiquidShares: sdk.NewDec(90), + newShares: sdk.NewDec(100), + expectedExceeds: false, + }, + { + // Validator Shares: 100, Factor: 100, Current Shares: 90 => 10000 Max Shares, Capacity: 9910 + // New Shares: 9910 - at cap + name: "factor 100 - at cap", + validatorShares: sdk.NewDec(100), + validatorBondFactor: sdk.NewDec(100), + currentLiquidShares: sdk.NewDec(90), + newShares: sdk.NewDec(9910), + expectedExceeds: false, + }, + { + // Validator Shares: 100, Factor: 100, Current Shares: 90 => 10000 Max Shares, Capacity: 9910 + // New Shares: 9911 - above cap + name: "factor 100 - above cap", + validatorShares: sdk.NewDec(100), + validatorBondFactor: sdk.NewDec(100), + currentLiquidShares: sdk.NewDec(90), + newShares: sdk.NewDec(9911), + expectedExceeds: true, + }, + { + // Factor of -1 (disabled): Should always return false + name: "factor disabled", + validatorShares: sdk.NewDec(1), + validatorBondFactor: sdk.NewDec(-1), + currentLiquidShares: sdk.NewDec(1), + newShares: sdk.NewDec(1_000_000), + expectedExceeds: false, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + // Update the validator bond factor + params := keeper.GetParams(ctx) + params.ValidatorBondFactor = tc.validatorBondFactor + keeper.SetParams(ctx, params) + + // Create a validator with designated self-bond shares + validator := types.Validator{ + LiquidShares: tc.currentLiquidShares, + ValidatorBondShares: tc.validatorShares, + } + + // Check whether the cap is exceeded + actualExceeds := keeper.CheckExceedsValidatorBondCap(ctx, validator, tc.newShares) + require.Equal(tc.expectedExceeds, actualExceeds, tc.name) + }) + } +} + +// Tests TestCheckExceedsValidatorLiquidStakingCap +func (s *KeeperTestSuite) TestCheckExceedsValidatorLiquidStakingCap() { + ctx, keeper := s.ctx, s.stakingKeeper + require := s.Require() + + testCases := []struct { + name string + validatorLiquidCap sdk.Dec + validatorLiquidShares sdk.Dec + validatorTotalShares sdk.Dec + newLiquidShares sdk.Dec + expectedExceeds bool + }{ + { + // Cap: 10% - Delegation Below Threshold + // Liquid Shares: 5, Total Shares: 95, New Liquid Shares: 1 + // => Liquid Shares: 5+1=6, Total Shares: 95+1=96 => 6/96 = 6% < 10% cap + name: "10 percent cap _ delegation below cap", + validatorLiquidCap: sdk.MustNewDecFromStr("0.1"), + validatorLiquidShares: sdk.NewDec(5), + validatorTotalShares: sdk.NewDec(95), + newLiquidShares: sdk.NewDec(1), + expectedExceeds: false, + }, + { + // Cap: 10% - Delegation At Threshold + // Liquid Shares: 5, Total Shares: 95, New Liquid Shares: 5 + // => Liquid Shares: 5+5=10, Total Shares: 95+5=100 => 10/100 = 10% == 10% cap + name: "10 percent cap _ delegation equals cap", + validatorLiquidCap: sdk.MustNewDecFromStr("0.1"), + validatorLiquidShares: sdk.NewDec(5), + validatorTotalShares: sdk.NewDec(95), + newLiquidShares: sdk.NewDec(4), + expectedExceeds: false, + }, + { + // Cap: 10% - Delegation Exceeds Threshold + // Liquid Shares: 5, Total Shares: 95, New Liquid Shares: 6 + // => Liquid Shares: 5+6=11, Total Shares: 95+6=101 => 11/101 = 11% > 10% cap + name: "10 percent cap _ delegation exceeds cap", + validatorLiquidCap: sdk.MustNewDecFromStr("0.1"), + validatorLiquidShares: sdk.NewDec(5), + validatorTotalShares: sdk.NewDec(95), + newLiquidShares: sdk.NewDec(6), + expectedExceeds: true, + }, + { + // Cap: 20% - Delegation Below Threshold + // Liquid Shares: 20, Total Shares: 220, New Liquid Shares: 29 + // => Liquid Shares: 20+29=49, Total Shares: 220+29=249 => 49/249 = 19% < 20% cap + name: "20 percent cap _ delegation below cap", + validatorLiquidCap: sdk.MustNewDecFromStr("0.2"), + validatorLiquidShares: sdk.NewDec(20), + validatorTotalShares: sdk.NewDec(220), + newLiquidShares: sdk.NewDec(29), + expectedExceeds: false, + }, + { + // Cap: 20% - Delegation At Threshold + // Liquid Shares: 20, Total Shares: 220, New Liquid Shares: 30 + // => Liquid Shares: 20+30=50, Total Shares: 220+30=250 => 50/250 = 20% == 20% cap + name: "20 percent cap _ delegation equals cap", + validatorLiquidCap: sdk.MustNewDecFromStr("0.2"), + validatorLiquidShares: sdk.NewDec(20), + validatorTotalShares: sdk.NewDec(220), + newLiquidShares: sdk.NewDec(30), + expectedExceeds: false, + }, + { + // Cap: 20% - Delegation Exceeds Threshold + // Liquid Shares: 20, Total Shares: 220, New Liquid Shares: 31 + // => Liquid Shares: 20+31=51, Total Shares: 220+31=251 => 51/251 = 21% > 20% cap + name: "20 percent cap _ delegation exceeds cap", + validatorLiquidCap: sdk.MustNewDecFromStr("0.2"), + validatorLiquidShares: sdk.NewDec(20), + validatorTotalShares: sdk.NewDec(220), + newLiquidShares: sdk.NewDec(31), + expectedExceeds: true, + }, + { + // Cap of 0% - everything should exceed + name: "0 percent cap", + validatorLiquidCap: sdk.ZeroDec(), + validatorLiquidShares: sdk.NewDec(0), + validatorTotalShares: sdk.NewDec(1_000_000), + newLiquidShares: sdk.NewDec(1), + expectedExceeds: true, + }, + { + // Cap of 100% - nothing should exceed + name: "100 percent cap", + validatorLiquidCap: sdk.OneDec(), + validatorLiquidShares: sdk.NewDec(1), + validatorTotalShares: sdk.NewDec(1_000_000), + newLiquidShares: sdk.NewDec(1), + expectedExceeds: false, + }, + } + + for _, tc := range testCases { + s.Run(tc.name, func() { + // Update the validator liquid staking cap + params := keeper.GetParams(ctx) + params.ValidatorLiquidStakingCap = tc.validatorLiquidCap + keeper.SetParams(ctx, params) + + // Create a validator with designated self-bond shares + validator := types.Validator{ + LiquidShares: tc.validatorLiquidShares, + DelegatorShares: tc.validatorTotalShares, + } + + // Check whether the cap is exceeded + actualExceeds := keeper.CheckExceedsValidatorLiquidStakingCap(ctx, validator, tc.newLiquidShares) + require.Equal(tc.expectedExceeds, actualExceeds, tc.name) + }) + } +} + +// Tests SafelyIncreaseValidatorLiquidShares +func (s *KeeperTestSuite) TestSafelyIncreaseValidatorLiquidShares() { + ctx, keeper := s.ctx, s.stakingKeeper + require := s.Require() + + // Generate a test validator address + privKey := secp256k1.GenPrivKey() + pubKey := privKey.PubKey() + valAddress := sdk.ValAddress(pubKey.Address()) + + // Helper function to check the validator's liquid shares + checkValidatorLiquidShares := func(expected sdk.Dec, description string) { + actualValidator, found := keeper.GetValidator(ctx, valAddress) + require.True(found) + require.Equal(expected.TruncateInt64(), actualValidator.LiquidShares.TruncateInt64(), description) + } + + // Start with the following: + // Initial Liquid Shares: 0 + // Validator Bond Shares: 10 + // Validator TotalShares: 75 + // + // Initial Caps: + // ValidatorBondFactor: 1 (Cap applied at 10 shares) + // ValidatorLiquidStakingCap: 25% (Cap applied at 25 shares) + // + // Cap Increases: + // ValidatorBondFactor: 10 (Cap applied at 100 shares) + // ValidatorLiquidStakingCap: 40% (Cap applied at 50 shares) + initialLiquidShares := sdk.NewDec(0) + validatorBondShares := sdk.NewDec(10) + validatorTotalShares := sdk.NewDec(75) + + firstIncreaseAmount := sdk.NewDec(20) + secondIncreaseAmount := sdk.NewDec(10) // total increase of 30 + + initialBondFactor := sdk.NewDec(1) + finalBondFactor := sdk.NewDec(10) + initialLiquidStakingCap := sdk.MustNewDecFromStr("0.25") + finalLiquidStakingCap := sdk.MustNewDecFromStr("0.4") + + // Create a validator with designated self-bond shares + initialValidator := types.Validator{ + OperatorAddress: valAddress.String(), + LiquidShares: initialLiquidShares, + ValidatorBondShares: validatorBondShares, + DelegatorShares: validatorTotalShares, + } + keeper.SetValidator(ctx, initialValidator) + + // Set validator bond factor to a small number such that any delegation would fail, + // and set the liquid staking cap such that the first stake would succeed, but the second + // would fail + params := keeper.GetParams(ctx) + params.ValidatorBondFactor = initialBondFactor + params.ValidatorLiquidStakingCap = initialLiquidStakingCap + keeper.SetParams(ctx, params) + + // Attempt to increase the validator liquid shares, it should throw an + // error that the validator bond cap was exceeded + _, err := keeper.SafelyIncreaseValidatorLiquidShares(ctx, valAddress, firstIncreaseAmount) + require.ErrorIs(err, types.ErrInsufficientValidatorBondShares) + checkValidatorLiquidShares(initialLiquidShares, "shares after low bond factor") + + // Change validator bond factor to a more conservative number, so that the increase succeeds + params.ValidatorBondFactor = finalBondFactor + keeper.SetParams(ctx, params) + + // Try the increase again and check that it succeeded + expectedLiquidSharesAfterFirstStake := initialLiquidShares.Add(firstIncreaseAmount) + _, err = keeper.SafelyIncreaseValidatorLiquidShares(ctx, valAddress, firstIncreaseAmount) + require.NoError(err) + checkValidatorLiquidShares(expectedLiquidSharesAfterFirstStake, "shares with cap loose bond cap") + + // Attempt another increase, it should fail from the liquid staking cap + _, err = keeper.SafelyIncreaseValidatorLiquidShares(ctx, valAddress, secondIncreaseAmount) + require.ErrorIs(err, types.ErrValidatorLiquidStakingCapExceeded) + checkValidatorLiquidShares(expectedLiquidSharesAfterFirstStake, "shares after liquid staking cap hit") + + // Raise the liquid staking cap so the new increment succeeds + params.ValidatorLiquidStakingCap = finalLiquidStakingCap + keeper.SetParams(ctx, params) + + // Finally confirm that the increase succeeded this time + expectedLiquidSharesAfterSecondStake := expectedLiquidSharesAfterFirstStake.Add(secondIncreaseAmount) + _, err = keeper.SafelyIncreaseValidatorLiquidShares(ctx, valAddress, secondIncreaseAmount) + require.NoError(err, "no error expected after increasing liquid staking cap") + checkValidatorLiquidShares(expectedLiquidSharesAfterSecondStake, "shares after loose liquid stake cap") +} + +// Tests DecreaseValidatorLiquidShares +func (s *KeeperTestSuite) TestDecreaseValidatorLiquidShares() { + ctx, keeper := s.ctx, s.stakingKeeper + require := s.Require() + + initialLiquidShares := sdk.NewDec(100) + decreaseAmount := sdk.NewDec(10) + + // Create a validator with designated self-bond shares + privKey := secp256k1.GenPrivKey() + pubKey := privKey.PubKey() + valAddress := sdk.ValAddress(pubKey.Address()) + + initialValidator := types.Validator{ + OperatorAddress: valAddress.String(), + LiquidShares: initialLiquidShares, + } + keeper.SetValidator(ctx, initialValidator) + + // Decrease the validator liquid shares, and confirm the new share amount has been updated + _, err := keeper.DecreaseValidatorLiquidShares(ctx, valAddress, decreaseAmount) + require.NoError(err, "no error expected when decreasing validator liquid shares") + + actualValidator, found := keeper.GetValidator(ctx, valAddress) + require.True(found) + require.Equal(initialLiquidShares.Sub(decreaseAmount), actualValidator.LiquidShares, "liquid shares") + + // Attempt to decrease by a larger amount than it has, it should fail + _, err = keeper.DecreaseValidatorLiquidShares(ctx, valAddress, initialLiquidShares) + require.ErrorIs(err, types.ErrValidatorLiquidSharesUnderflow) +} + +// Tests SafelyDecreaseValidatorBond +func (s *KeeperTestSuite) TestSafelyDecreaseValidatorBond() { + ctx, keeper := s.ctx, s.stakingKeeper + require := s.Require() + + // Initial Bond Factor: 100, Initial Validator Bond: 10 + // => Max Liquid Shares 1000 (Initial Liquid Shares: 200) + initialBondFactor := sdk.NewDec(100) + initialValidatorBondShares := sdk.NewDec(10) + initialLiquidShares := sdk.NewDec(200) + + // Create a validator with designated self-bond shares + privKey := secp256k1.GenPrivKey() + pubKey := privKey.PubKey() + valAddress := sdk.ValAddress(pubKey.Address()) + + initialValidator := types.Validator{ + OperatorAddress: valAddress.String(), + ValidatorBondShares: initialValidatorBondShares, + LiquidShares: initialLiquidShares, + } + keeper.SetValidator(ctx, initialValidator) + + // Set the bond factor + params := keeper.GetParams(ctx) + params.ValidatorBondFactor = initialBondFactor + keeper.SetParams(ctx, params) + + // Decrease the validator bond from 10 to 5 (minus 5) + // This will adjust the cap (factor * shares) + // from (100 * 10 = 1000) to (100 * 5 = 500) + // Since this is still above the initial liquid shares of 200, this will succeed + decreaseAmount, expectedBondShares := sdk.NewDec(5), sdk.NewDec(5) + err := keeper.SafelyDecreaseValidatorBond(ctx, valAddress, decreaseAmount) + require.NoError(err) + + actualValidator, found := keeper.GetValidator(ctx, valAddress) + require.True(found) + require.Equal(expectedBondShares, actualValidator.ValidatorBondShares, "validator bond shares shares") + + // Now attempt to decrease the validator bond again from 5 to 1 (minus 4) + // This time, the cap will be reduced to (factor * shares) = (100 * 1) = 100 + // However, the liquid shares are currently 200, so this should fail + decreaseAmount, expectedBondShares = sdk.NewDec(4), sdk.NewDec(1) + err = keeper.SafelyDecreaseValidatorBond(ctx, valAddress, decreaseAmount) + require.ErrorIs(err, types.ErrInsufficientValidatorBondShares) + + // Finally, disable the cap and attempt to decrease again + // This time it should succeed + params.ValidatorBondFactor = types.ValidatorBondCapDisabled + keeper.SetParams(ctx, params) + + err = keeper.SafelyDecreaseValidatorBond(ctx, valAddress, decreaseAmount) + require.NoError(err) + + actualValidator, found = keeper.GetValidator(ctx, valAddress) + require.True(found) + require.Equal(expectedBondShares, actualValidator.ValidatorBondShares, "validator bond shares shares") +} + +// Tests Add/Remove/Get/SetTokenizeSharesLock +func (s *KeeperTestSuite) TestTokenizeSharesLock() { + ctx, keeper := s.ctx, s.stakingKeeper + require := s.Require() + + addresses := simtestutil.CreateIncrementalAccounts(2) + addressA, addressB := addresses[0], addresses[1] + + unlocked := types.TOKENIZE_SHARE_LOCK_STATUS_UNLOCKED.String() + locked := types.TOKENIZE_SHARE_LOCK_STATUS_LOCKED.String() + lockExpiring := types.TOKENIZE_SHARE_LOCK_STATUS_LOCK_EXPIRING.String() + + // Confirm both accounts start unlocked + status, _ := keeper.GetTokenizeSharesLock(ctx, addressA) + require.Equal(unlocked, status.String(), "addressA unlocked at start") + + status, _ = keeper.GetTokenizeSharesLock(ctx, addressB) + require.Equal(unlocked, status.String(), "addressB unlocked at start") + + // Lock the first account + keeper.AddTokenizeSharesLock(ctx, addressA) + + // The first account should now have tokenize shares disabled + // and the unlock time should be the zero time + status, _ = keeper.GetTokenizeSharesLock(ctx, addressA) + require.Equal(locked, status.String(), "addressA locked") + + status, _ = keeper.GetTokenizeSharesLock(ctx, addressB) + require.Equal(unlocked, status.String(), "addressB still unlocked") + + // Update the lock time and confirm it was set + expectedUnlockTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) + keeper.SetTokenizeSharesUnlockTime(ctx, addressA, expectedUnlockTime) + + status, actualUnlockTime := keeper.GetTokenizeSharesLock(ctx, addressA) + require.Equal(lockExpiring, status.String(), "addressA lock expiring") + require.Equal(expectedUnlockTime, actualUnlockTime, "addressA unlock time") + + // Confirm B is still unlocked + status, _ = keeper.GetTokenizeSharesLock(ctx, addressB) + require.Equal(unlocked, status.String(), "addressB still unlocked") + + // Remove the lock + keeper.RemoveTokenizeSharesLock(ctx, addressA) + status, _ = keeper.GetTokenizeSharesLock(ctx, addressA) + require.Equal(unlocked, status.String(), "addressA unlocked at end") + + status, _ = keeper.GetTokenizeSharesLock(ctx, addressB) + require.Equal(unlocked, status.String(), "addressB unlocked at end") +} + +// Tests GetAllTokenizeSharesLocks +func (s *KeeperTestSuite) TestGetAllTokenizeSharesLocks() { + ctx, keeper := s.ctx, s.stakingKeeper + require := s.Require() + + addresses := simtestutil.CreateIncrementalAccounts(4) + + // Set 2 locked accounts, and two accounts with a lock expiring + keeper.AddTokenizeSharesLock(ctx, addresses[0]) + keeper.AddTokenizeSharesLock(ctx, addresses[1]) + + unlockTime1 := time.Date(2023, 1, 1, 1, 0, 0, 0, time.UTC) + unlockTime2 := time.Date(2023, 1, 2, 1, 0, 0, 0, time.UTC) + keeper.SetTokenizeSharesUnlockTime(ctx, addresses[2], unlockTime1) + keeper.SetTokenizeSharesUnlockTime(ctx, addresses[3], unlockTime2) + + // Defined expected locks after GetAll + expectedLocks := map[string]types.TokenizeShareLock{ + addresses[0].String(): { + Status: types.TOKENIZE_SHARE_LOCK_STATUS_LOCKED.String(), + }, + addresses[1].String(): { + Status: types.TOKENIZE_SHARE_LOCK_STATUS_LOCKED.String(), + }, + addresses[2].String(): { + Status: types.TOKENIZE_SHARE_LOCK_STATUS_LOCK_EXPIRING.String(), + CompletionTime: unlockTime1, + }, + addresses[3].String(): { + Status: types.TOKENIZE_SHARE_LOCK_STATUS_LOCK_EXPIRING.String(), + CompletionTime: unlockTime2, + }, + } + + // Check output from GetAll + actualLocks := keeper.GetAllTokenizeSharesLocks(ctx) + require.Len(actualLocks, len(expectedLocks), "number of locks") + + for i, actual := range actualLocks { + expected, ok := expectedLocks[actual.Address] + require.True(ok, "address %s not expected", actual.Address) + require.Equal(expected.Status, actual.Status, "tokenize share lock #%d status", i) + require.Equal(expected.CompletionTime, actual.CompletionTime, "tokenize share lock #%d completion time", i) + } +} + +// Test Get/SetPendingTokenizeShareAuthorizations +func (s *KeeperTestSuite) TestPendingTokenizeShareAuthorizations() { + ctx, keeper := s.ctx, s.stakingKeeper + require := s.Require() + + // Create dummy accounts and completion times + + addresses := simtestutil.CreateIncrementalAccounts(4) + addressStrings := []string{} + for _, address := range addresses { + addressStrings = append(addressStrings, address.String()) + } + + timeA := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) + timeB := timeA.Add(time.Hour) + + // There should be no addresses returned originally + authorizationsA := keeper.GetPendingTokenizeShareAuthorizations(ctx, timeA) + require.Empty(authorizationsA.Addresses, "no addresses at timeA expected") + + authorizationsB := keeper.GetPendingTokenizeShareAuthorizations(ctx, timeB) + require.Empty(authorizationsB.Addresses, "no addresses at timeB expected") + + // Store addresses for timeB + keeper.SetPendingTokenizeShareAuthorizations(ctx, timeB, types.PendingTokenizeShareAuthorizations{ + Addresses: addressStrings, + }) + + // Check addresses + authorizationsA = keeper.GetPendingTokenizeShareAuthorizations(ctx, timeA) + require.Empty(authorizationsA.Addresses, "no addresses at timeA expected at end") + + authorizationsB = keeper.GetPendingTokenizeShareAuthorizations(ctx, timeB) + require.Equal(addressStrings, authorizationsB.Addresses, "address length") +} + +// Test QueueTokenizeSharesAuthorization and RemoveExpiredTokenizeShareLocks +func (s *KeeperTestSuite) TestTokenizeShareAuthorizationQueue() { + ctx, keeper := s.ctx, s.stakingKeeper + require := s.Require() + + // Create dummy accounts and completion times + + // We'll start by adding the following addresses to the queue + // Time 0: [address0] + // Time 1: [] + // Time 2: [address1, address2, address3] + // Time 3: [address4, address5] + // Time 4: [address6] + addresses := simtestutil.CreateIncrementalAccounts(7) + addressesByTime := map[int][]sdk.AccAddress{ + 0: {addresses[0]}, + 1: {}, + 2: {addresses[1], addresses[2], addresses[3]}, + 3: {addresses[4], addresses[5]}, + 4: {addresses[6]}, + } + + // Set the unbonding time to 1 day + unbondingPeriod := time.Hour * 24 + params := keeper.GetParams(ctx) + params.UnbondingTime = unbondingPeriod + keeper.SetParams(ctx, params) + + // Add each address to the queue and then increment the block time + // such that the times line up as follows + // Time 0: 2023-01-01 00:00:00 + // Time 1: 2023-01-01 00:01:00 + // Time 2: 2023-01-01 00:02:00 + // Time 3: 2023-01-01 00:03:00 + startTime := time.Date(2023, 1, 1, 0, 0, 0, 0, time.UTC) + ctx = ctx.WithBlockTime(startTime) + blockTimeIncrement := time.Hour + + for timeIndex := 0; timeIndex <= 4; timeIndex++ { + for _, address := range addressesByTime[timeIndex] { + keeper.QueueTokenizeSharesAuthorization(ctx, address) + } + ctx = ctx.WithBlockTime(ctx.BlockTime().Add(blockTimeIncrement)) + } + + // We'll unlock the tokens using the following progression + // The "alias'"/keys for these times assume a starting point of the Time 0 + // from above, plus the Unbonding Time + // Time -1 (2023-01-01 23:59:99): [] + // Time 0 (2023-01-02 00:00:00): [address0] + // Time 1 (2023-01-02 00:01:00): [] + // Time 2.5 (2023-01-02 00:02:30): [address1, address2, address3] + // Time 10 (2023-01-02 00:10:00): [address4, address5, address6] + unlockBlockTimes := map[string]time.Time{ + "-1": startTime.Add(unbondingPeriod).Add(-time.Second), + "0": startTime.Add(unbondingPeriod), + "1": startTime.Add(unbondingPeriod).Add(blockTimeIncrement), + "2.5": startTime.Add(unbondingPeriod).Add(2 * blockTimeIncrement).Add(blockTimeIncrement / 2), + "10": startTime.Add(unbondingPeriod).Add(10 * blockTimeIncrement), + } + expectedUnlockedAddresses := map[string][]string{ + "-1": {}, + "0": {addresses[0].String()}, + "1": {}, + "2.5": {addresses[1].String(), addresses[2].String(), addresses[3].String()}, + "10": {addresses[4].String(), addresses[5].String(), addresses[6].String()}, + } + + // Now we'll remove items from the queue sequentially + // First check with a block time before the first expiration - it should remove no addresses + actualAddresses := keeper.RemoveExpiredTokenizeShareLocks(ctx, unlockBlockTimes["-1"]) + require.Equal(expectedUnlockedAddresses["-1"], actualAddresses, "no addresses unlocked from time -1") + + // Then pass in (time 0 + unbonding time) - it should remove the first address + actualAddresses = keeper.RemoveExpiredTokenizeShareLocks(ctx, unlockBlockTimes["0"]) + require.Equal(expectedUnlockedAddresses["0"], actualAddresses, "one address unlocked from time 0") + + // Now pass in (time 1 + unbonding time) - it should remove no addresses since + // the address at time 0 was already removed + actualAddresses = keeper.RemoveExpiredTokenizeShareLocks(ctx, unlockBlockTimes["1"]) + require.Equal(expectedUnlockedAddresses["1"], actualAddresses, "no addresses unlocked from time 1") + + // Now pass in (time 2.5 + unbonding time) - it should remove the three addresses from time 2 + actualAddresses = keeper.RemoveExpiredTokenizeShareLocks(ctx, unlockBlockTimes["2.5"]) + require.Equal(expectedUnlockedAddresses["2.5"], actualAddresses, "addresses unlocked from time 2.5") + + // Finally pass in a block time far in the future, which should remove all the remaining locks + actualAddresses = keeper.RemoveExpiredTokenizeShareLocks(ctx, unlockBlockTimes["10"]) + require.Equal(expectedUnlockedAddresses["10"], actualAddresses, "addresses unlocked from time 10") +} + +// Tests DelegatorIsLiquidStaker +func (s *KeeperTestSuite) TestDelegatorIsLiquidStaker() { + _, keeper := s.ctx, s.stakingKeeper + require := s.Require() + + // Create base and ICA accounts + baseAccountAddress := sdk.AccAddress("base-account") + icaAccountAddress := sdk.AccAddress( + address.Derive(authtypes.NewModuleAddress("icahost"), []byte("connection-0"+"icahost")), + ) + + // Only the ICA module account should be considered a liquid staking provider + require.False(keeper.DelegatorIsLiquidStaker(baseAccountAddress), "base account") + require.True(keeper.DelegatorIsLiquidStaker(icaAccountAddress), "ICA module account") +} diff --git a/x/staking/keeper/msg_server.go b/x/staking/keeper/msg_server.go index 843c120a41a3..b74ad429281e 100644 --- a/x/staking/keeper/msg_server.go +++ b/x/staking/keeper/msg_server.go @@ -6,7 +6,6 @@ import ( "strconv" "time" - "cosmossdk.io/math" "github.com/armon/go-metrics" cryptotypes "github.com/cosmos/cosmos-sdk/crypto/types" "github.com/cosmos/cosmos-sdk/telemetry" @@ -643,13 +642,13 @@ func (k msgServer) UnbondValidator(goCtx context.Context, msg *types.MsgUnbondVa if err != nil { return nil, err } + // validator must already be registered validator, found := k.GetValidator(ctx, valAddr) if !found { return nil, types.ErrNoValidatorFound } - // jail the validator. k.jailValidator(ctx, validator) return &types.MsgUnbondValidatorResponse{}, nil } @@ -855,7 +854,7 @@ func (k msgServer) RedeemTokensForShares(goCtx context.Context, msg *types.MsgRe // Similar to undelegations, if the account is attempting to tokenize the full delegation, // but there's a precision error due to the decimal to int conversion, round up to the // full decimal amount before modifying the delegation - shares := math.LegacyNewDec(shareToken.Amount.Int64()) + shares := sdk.NewDecFromInt(shareToken.Amount) if shareToken.Amount.Equal(delegation.Shares.TruncateInt()) { shares = delegation.Shares } diff --git a/x/staking/keeper/params.go b/x/staking/keeper/params.go index 06dfad471852..640525e53a26 100644 --- a/x/staking/keeper/params.go +++ b/x/staking/keeper/params.go @@ -47,7 +47,6 @@ func (k Keeper) PowerReduction(ctx sdk.Context) math.Int { // Validator bond factor for all validators func (k Keeper) ValidatorBondFactor(ctx sdk.Context) (res sdk.Dec) { return k.GetParams(ctx).ValidatorBondFactor - } // Global liquid staking cap across all liquid staking providers diff --git a/x/staking/keeper/tokenize_share_record_test.go b/x/staking/keeper/tokenize_share_record_test.go index 0fc9043f96cf..fd9dd76a6765 100644 --- a/x/staking/keeper/tokenize_share_record_test.go +++ b/x/staking/keeper/tokenize_share_record_test.go @@ -1,5 +1,10 @@ package keeper_test +import ( + simtestutil "github.com/cosmos/cosmos-sdk/testutil/sims" + "github.com/cosmos/cosmos-sdk/x/staking/types" +) + func (suite *KeeperTestSuite) TestGetLastTokenizeShareRecordId() { ctx, keeper := suite.ctx, suite.stakingKeeper lastTokenizeShareRecordID := keeper.GetLastTokenizeShareRecordID(ctx) @@ -9,52 +14,50 @@ func (suite *KeeperTestSuite) TestGetLastTokenizeShareRecordId() { suite.Equal(lastTokenizeShareRecordID, uint64(100)) } -// TODO: refactor LSM test -// Note that this test might be moved to the integration tests -// -// func (suite *KeeperTestSuite) TestGetTokenizeShareRecord() { -// app, ctx := suite.app, suite.ctx -// owner1, owner2 := suite.addrs[0], suite.addrs[1] - -// tokenizeShareRecord1 := types.TokenizeShareRecord{ -// Id: 0, -// Owner: owner1.String(), -// ModuleAccount: "test-module-account-1", -// Validator: "test-validator", -// } -// tokenizeShareRecord2 := types.TokenizeShareRecord{ -// Id: 1, -// Owner: owner2.String(), -// ModuleAccount: "test-module-account-2", -// Validator: "test-validator", -// } -// tokenizeShareRecord3 := types.TokenizeShareRecord{ -// Id: 2, -// Owner: owner1.String(), -// ModuleAccount: "test-module-account-3", -// Validator: "test-validator", -// } -// err := app.StakingKeeper.AddTokenizeShareRecord(ctx, tokenizeShareRecord1) -// suite.NoError(err) -// err = app.StakingKeeper.AddTokenizeShareRecord(ctx, tokenizeShareRecord2) -// suite.NoError(err) -// err = app.StakingKeeper.AddTokenizeShareRecord(ctx, tokenizeShareRecord3) -// suite.NoError(err) - -// tokenizeShareRecord, err := app.StakingKeeper.GetTokenizeShareRecord(ctx, 2) -// suite.NoError(err) -// suite.Equal(tokenizeShareRecord, tokenizeShareRecord3) - -// tokenizeShareRecord, err = app.StakingKeeper.GetTokenizeShareRecordByDenom(ctx, tokenizeShareRecord2.GetShareTokenDenom()) -// suite.NoError(err) -// suite.Equal(tokenizeShareRecord, tokenizeShareRecord2) - -// tokenizeShareRecords := app.StakingKeeper.GetAllTokenizeShareRecords(ctx) -// suite.Equal(len(tokenizeShareRecords), 3) - -// tokenizeShareRecords = app.StakingKeeper.GetTokenizeShareRecordsByOwner(ctx, owner1) -// suite.Equal(len(tokenizeShareRecords), 2) - -// tokenizeShareRecords = app.StakingKeeper.GetTokenizeShareRecordsByOwner(ctx, owner2) -// suite.Equal(len(tokenizeShareRecords), 1) -// } +func (suite *KeeperTestSuite) TestGetTokenizeShareRecord() { + ctx, keeper := suite.ctx, suite.stakingKeeper + addrs := simtestutil.CreateIncrementalAccounts(2) + + owner1, owner2 := addrs[0], addrs[1] + tokenizeShareRecord1 := types.TokenizeShareRecord{ + Id: 0, + Owner: owner1.String(), + ModuleAccount: "test-module-account-1", + Validator: "test-validator", + } + tokenizeShareRecord2 := types.TokenizeShareRecord{ + Id: 1, + Owner: owner2.String(), + ModuleAccount: "test-module-account-2", + Validator: "test-validator", + } + tokenizeShareRecord3 := types.TokenizeShareRecord{ + Id: 2, + Owner: owner1.String(), + ModuleAccount: "test-module-account-3", + Validator: "test-validator", + } + err := keeper.AddTokenizeShareRecord(ctx, tokenizeShareRecord1) + suite.NoError(err) + err = keeper.AddTokenizeShareRecord(ctx, tokenizeShareRecord2) + suite.NoError(err) + err = keeper.AddTokenizeShareRecord(ctx, tokenizeShareRecord3) + suite.NoError(err) + + tokenizeShareRecord, err := keeper.GetTokenizeShareRecord(ctx, 2) + suite.NoError(err) + suite.Equal(tokenizeShareRecord, tokenizeShareRecord3) + + tokenizeShareRecord, err = keeper.GetTokenizeShareRecordByDenom(ctx, tokenizeShareRecord2.GetShareTokenDenom()) + suite.NoError(err) + suite.Equal(tokenizeShareRecord, tokenizeShareRecord2) + + tokenizeShareRecords := keeper.GetAllTokenizeShareRecords(ctx) + suite.Equal(len(tokenizeShareRecords), 3) + + tokenizeShareRecords = keeper.GetTokenizeShareRecordsByOwner(ctx, owner1) + suite.Equal(len(tokenizeShareRecords), 2) + + tokenizeShareRecords = keeper.GetTokenizeShareRecordsByOwner(ctx, owner2) + suite.Equal(len(tokenizeShareRecords), 1) +} diff --git a/x/staking/migrations/v3/json_test.go b/x/staking/migrations/v3/json_test.go index c111f366515e..52f3c2ea27ed 100644 --- a/x/staking/migrations/v3/json_test.go +++ b/x/staking/migrations/v3/json_test.go @@ -34,21 +34,34 @@ func TestMigrateJSON(t *testing.T) { indentedBz, err := json.MarshalIndent(jsonObj, "", "\t") require.NoError(t, err) - // Make sure about new param MinCommissionRate. + // Make sure about new params MinCommissionRate. + + // NOTE: the LSM module introduces: + // params: + // - GlobalLiquidStakingCap, ValidatorBondFactor, ValidatorLiquidStakingCap + // states: + // - LastTokenizeShareRecordId, TokenizeShareLocks, TokenizeShareRecords, TotalLiquidStakeTokens expected := `{ "delegations": [], "exported": false, + "last_tokenize_share_record_id": "0", "last_total_power": "0", "last_validator_powers": [], "params": { "bond_denom": "stake", + "global_liquid_staking_cap": "1.000000000000000000", "historical_entries": 10000, "max_entries": 7, "max_validators": 100, "min_commission_rate": "0.000000000000000000", - "unbonding_time": "1814400s" + "unbonding_time": "1814400s", + "validator_bond_factor": "-1.000000000000000000", + "validator_liquid_staking_cap": "1.000000000000000000" }, "redelegations": [], + "tokenize_share_locks": [], + "tokenize_share_records": [], + "total_liquid_staked_tokens": "0", "unbonding_delegations": [], "validators": [] }` diff --git a/x/staking/simulation/genesis.go b/x/staking/simulation/genesis.go index 6a6de6a408bb..2787e532f96a 100644 --- a/x/staking/simulation/genesis.go +++ b/x/staking/simulation/genesis.go @@ -11,7 +11,6 @@ import ( sdk "github.com/cosmos/cosmos-sdk/types" "github.com/cosmos/cosmos-sdk/types/module" "github.com/cosmos/cosmos-sdk/types/simulation" - simtypes "github.com/cosmos/cosmos-sdk/types/simulation" "github.com/cosmos/cosmos-sdk/x/staking/types" ) @@ -42,17 +41,17 @@ func getHistEntries(r *rand.Rand) uint32 { // getGlobalLiquidStakingCap returns randomized GlobalLiquidStakingCap between 0-1. func getGlobalLiquidStakingCap(r *rand.Rand) sdk.Dec { - return simtypes.RandomDecAmount(r, sdk.OneDec()) + return simulation.RandomDecAmount(r, sdk.OneDec()) } // getValidatorLiquidStakingCap returns randomized ValidatorLiquidStakingCap between 0-1. func getValidatorLiquidStakingCap(r *rand.Rand) sdk.Dec { - return simtypes.RandomDecAmount(r, sdk.OneDec()) + return simulation.RandomDecAmount(r, sdk.OneDec()) } // getValidatorBondFactor returns randomized ValidatorBondCap between -1 and 300. func getValidatorBondFactor(r *rand.Rand) sdk.Dec { - return sdk.NewDec(int64(simtypes.RandIntBetween(r, -1, 300))) + return sdk.NewDec(int64(simulation.RandIntBetween(r, -1, 300))) } // RandomizedGenState generates a random GenesisState for staking diff --git a/x/staking/simulation/operations.go b/x/staking/simulation/operations.go index 68680096243b..1f920462638b 100644 --- a/x/staking/simulation/operations.go +++ b/x/staking/simulation/operations.go @@ -4,7 +4,6 @@ import ( "fmt" "math/rand" - "cosmossdk.io/math" "github.com/cosmos/cosmos-sdk/baseapp" "github.com/cosmos/cosmos-sdk/codec" "github.com/cosmos/cosmos-sdk/testutil" @@ -808,11 +807,11 @@ func SimulateMsgTokenizeShares(ak types.AccountKeeper, bk types.BankKeeper, k *k // check that tokenization would not exceed global cap params := k.GetParams(ctx) - totalStaked := math.LegacyNewDec(k.TotalBondedTokens(ctx).Int64()) + totalStaked := sdk.NewDecFromInt(k.TotalBondedTokens(ctx)) if totalStaked.IsZero() { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgTokenizeShares, "cannot happened - no validators bonded if stake is 0.0"), nil, nil // skip } - totalLiquidStaked := math.LegacyNewDec(k.GetTotalLiquidStakedTokens(ctx).Add(tokenizeShareAmt).Int64()) + totalLiquidStaked := sdk.NewDecFromInt(k.GetTotalLiquidStakedTokens(ctx).Add(tokenizeShareAmt)) liquidStakedPercent := totalLiquidStaked.Quo(totalStaked) if liquidStakedPercent.GT(params.GlobalLiquidStakingCap) { return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgTokenizeShares, "global liquid staking cap exceeded"), nil, nil diff --git a/x/staking/types/validator.go b/x/staking/types/validator.go index f8467933380b..c3f093ad1703 100644 --- a/x/staking/types/validator.go +++ b/x/staking/types/validator.go @@ -58,6 +58,7 @@ func NewValidator(operator sdk.ValAddress, pubKey cryptotypes.PubKey, descriptio UnbondingHeight: int64(0), UnbondingTime: time.Unix(0, 0).UTC(), Commission: NewCommission(math.LegacyZeroDec(), math.LegacyZeroDec(), math.LegacyZeroDec()), + MinSelfDelegation: sdk.ZeroInt(), UnbondingOnHoldRefCount: 0, ValidatorBondShares: sdk.ZeroDec(), LiquidShares: sdk.ZeroDec(),