diff --git a/app/state.go b/app/state.go index 04aa1b4be..5c87d7734 100644 --- a/app/state.go +++ b/app/state.go @@ -158,13 +158,18 @@ func AppStateRandomizedFn( simappparams.InitiallyBondedValidators, &numInitiallyBonded, r, - func(r *rand.Rand) { numInitiallyBonded = int64(r.Intn(300)) }, + func(r *rand.Rand) { + numInitiallyBonded = int64(r.Intn(300)) + // at least 1 bonded validator + if numInitiallyBonded == 0 { + numInitiallyBonded = 1 + } + }, ) if numInitiallyBonded > numAccs { numInitiallyBonded = numAccs } - fmt.Printf( `Selected randomly generated parameters for simulated genesis: { diff --git a/go.mod b/go.mod index 1f908a92f..8b2a195e3 100644 --- a/go.mod +++ b/go.mod @@ -173,7 +173,7 @@ require ( replace ( github.com/99designs/keyring => github.com/cosmos/keyring v1.1.7-0.20210622111912-ef00f8ac3d76 - github.com/cosmos/cosmos-sdk => github.com/b-harvest/cosmos-sdk v0.45.9-3-canto-lsm-sim + github.com/cosmos/cosmos-sdk => github.com/b-harvest/cosmos-sdk v0.45.9-4-canto-lsm-sim github.com/cosmos/ibc-go/v3 v3.2.0 => github.com/b-harvest/ibc-go/v3 v3.2.0-1-canto-lsm-sim github.com/gogo/protobuf => github.com/regen-network/protobuf v1.3.3-alpha.regen.1 github.com/tendermint/tendermint => github.com/informalsystems/tendermint v0.34.25 diff --git a/go.sum b/go.sum index a1a5ecaca..36d22fee7 100644 --- a/go.sum +++ b/go.sum @@ -108,10 +108,8 @@ github.com/aws/aws-lambda-go v1.13.3/go.mod h1:4UKl9IzQMoD+QF79YdCuzCwp8VbmG4VAQ github.com/aws/aws-sdk-go v1.25.48/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go v1.27.0/go.mod h1:KmX6BPdI08NWTb3/sm4ZGu5ShLoqVDhKgpiN924inxo= github.com/aws/aws-sdk-go-v2 v0.18.0/go.mod h1:JWVYvqSMppoMJC0x5wdwiImzgXTI9FuZwxzkQq9wy+g= -github.com/b-harvest/cosmos-sdk v0.45.9-2-canto-lsm-sim h1:dPbp2D/U5uNX3+VSs06zGMYVjqgVLoFC6BxQ1AgCRsM= -github.com/b-harvest/cosmos-sdk v0.45.9-2-canto-lsm-sim/go.mod h1:Z5M4TX7PsHNHlF/1XanI2DIpORQ+Q/st7oaeufEjnvU= -github.com/b-harvest/cosmos-sdk v0.45.9-3-canto-lsm-sim h1:XJNrdOLu6yYXdBJDXU+diaBwE5Rn8MGhfnC0V1LGMag= -github.com/b-harvest/cosmos-sdk v0.45.9-3-canto-lsm-sim/go.mod h1:Z5M4TX7PsHNHlF/1XanI2DIpORQ+Q/st7oaeufEjnvU= +github.com/b-harvest/cosmos-sdk v0.45.9-4-canto-lsm-sim h1:qFttQEGIicTsXzsiPXpZoHwzlr99jCL9F1yVDxS88lY= +github.com/b-harvest/cosmos-sdk v0.45.9-4-canto-lsm-sim/go.mod h1:Z5M4TX7PsHNHlF/1XanI2DIpORQ+Q/st7oaeufEjnvU= github.com/b-harvest/ibc-go/v3 v3.2.0-1-canto-lsm-sim h1:ODXVMtFDD5GX39xMSZEZEFxJYsgkxmv1pDEp/6EVi9M= github.com/b-harvest/ibc-go/v3 v3.2.0-1-canto-lsm-sim/go.mod h1:ZTUeC/y/r1WW7KXE2AUpax/ieECnDX+6hQ3Qwdd65sM= github.com/beorn7/perks v0.0.0-20180321164747-3a771d992973/go.mod h1:Dwedo/Wpr24TaqPxmxbtue+5NUziq4I4S80YR8gNf3Q= diff --git a/x/liquidstaking/keeper/invariants.go b/x/liquidstaking/keeper/invariants.go index 289569b10..92bf0202f 100644 --- a/x/liquidstaking/keeper/invariants.go +++ b/x/liquidstaking/keeper/invariants.go @@ -43,7 +43,7 @@ func AllInvariants(k Keeper) sdk.Invariant { func NetAmountEssentialsInvariant(k Keeper) sdk.Invariant { return func(ctx sdk.Context) (string, bool) { - nas := k.GetNetAmountStateEssentials(ctx) + nas, _, _, _ := k.GetNetAmountStateEssentials(ctx) // if net amount is positive, it means that there are paired chunks. if nas.LsTokensTotalSupply.IsPositive() && !nas.NetAmount.IsPositive() { return "found positive lsToken supply with non-positive net amount", true diff --git a/x/liquidstaking/keeper/invariants_test.go b/x/liquidstaking/keeper/invariants_test.go index b563789e9..6d3f8366b 100644 --- a/x/liquidstaking/keeper/invariants_test.go +++ b/x/liquidstaking/keeper/invariants_test.go @@ -33,7 +33,7 @@ func (suite *KeeperTestSuite) TestNetAmountInvariant() { suite.ctx = suite.advanceEpoch(suite.ctx) suite.ctx = suite.advanceHeight(suite.ctx, 1, "module epoch reached") - nase := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) + nase, _, _, _ := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) oneChunk, _ := suite.app.LiquidStakingKeeper.GetMinimumRequirements(suite.ctx) suite.True(nase.Equal(types.NetAmountStateEssentials{ MintRate: sdk.MustNewDecFromStr("0.990373683313988266"), diff --git a/x/liquidstaking/keeper/keeper.go b/x/liquidstaking/keeper/keeper.go index 793f26f2d..7ac494dd4 100644 --- a/x/liquidstaking/keeper/keeper.go +++ b/x/liquidstaking/keeper/keeper.go @@ -4,7 +4,6 @@ import ( "github.com/cosmos/cosmos-sdk/codec" sdk "github.com/cosmos/cosmos-sdk/types" paramtypes "github.com/cosmos/cosmos-sdk/x/params/types" - "github.com/tendermint/tendermint/libs/log" "github.com/Canto-Network/Canto/v7/x/liquidstaking/types" @@ -40,7 +39,6 @@ func NewKeeper( if !subspace.HasKeyTable() { subspace = subspace.WithKeyTable(types.ParamKeyTable()) } - return Keeper{ storeKey: storeKey, cdc: cdc, diff --git a/x/liquidstaking/keeper/keeper_test.go b/x/liquidstaking/keeper/keeper_test.go index 4df9c8659..9da16c00e 100644 --- a/x/liquidstaking/keeper/keeper_test.go +++ b/x/liquidstaking/keeper/keeper_test.go @@ -375,7 +375,7 @@ func (suite *KeeperTestSuite) setupLiquidStakeTestingEnv(env testingEnvOptions) // create numPairedChunks delegators delegators, delegatorBalances := suite.AddTestAddrsWithFunding(fundingAccount, env.numPairedChunks, oneChunk.Amount) - nase := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) + nase, _, _, _ := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) suite.True(nase.IsZeroState(), "nothing happened yet so it must be zero state") pairedChunks := suite.liquidStakes(suite.ctx, delegators, delegatorBalances) diff --git a/x/liquidstaking/keeper/liquidstaking.go b/x/liquidstaking/keeper/liquidstaking.go index 0f27075fd..02a496ad6 100644 --- a/x/liquidstaking/keeper/liquidstaking.go +++ b/x/liquidstaking/keeper/liquidstaking.go @@ -122,7 +122,7 @@ func (k Keeper) CollectRewardAndFee( // DistributeReward withdraws delegation rewards from all paired chunks // Keeper.CollectRewardAndFee will be called during withdrawing process. func (k Keeper) DistributeReward(ctx sdk.Context) { - nas := k.GetNetAmountStateEssentials(ctx) + nase, _, _, _ := k.GetNetAmountStateEssentials(ctx) k.IterateAllChunks(ctx, func(chunk types.Chunk) bool { if chunk.Status != types.CHUNK_STATUS_PAIRED { return false @@ -133,7 +133,7 @@ func (k Keeper) DistributeReward(ctx sdk.Context) { panic(err) } - k.CollectRewardAndFee(ctx, nas.FeeRate, chunk, pairedIns) + k.CollectRewardAndFee(ctx, nase.FeeRate, chunk, pairedIns) return false }) } @@ -341,7 +341,7 @@ func (k Keeper) RankInsurances(ctx sdk.Context) ( ins.Status != types.INSURANCE_STATUS_PAIRING { return false } - if _, ok := candidatesValidatorMap[ins.GetValidator().String()]; !ok { + if _, ok := candidatesValidatorMap[ins.ValidatorAddress]; !ok { // Only insurance which directs valid validator can be ranked in validator, found := k.stakingKeeper.GetValidator(ctx, ins.GetValidator()) if !found { @@ -411,7 +411,7 @@ func (k Keeper) RePairRankedInsurances( continue } // Happy case. Same validator so we can skip re-delegation - if newRankInIns.GetValidator().Equals(outIns.GetValidator()) { + if newRankInIns.ValidatorAddress == outIns.ValidatorAddress { // get chunk by outIns.ChunkId chunk := k.mustGetChunk(ctx, outIns.ChunkId) k.rePairChunkAndInsurance(ctx, chunk, newRankInIns, outIns) @@ -522,8 +522,8 @@ func (k Keeper) RePairRankedInsurances( types.EventTypeBeginRedelegate, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), sdk.NewAttribute(types.AttributeKeyChunkId, fmt.Sprintf("%d", chunk.Id)), - sdk.NewAttribute(types.AttributeKeySrcValidator, outIns.GetValidator().String()), - sdk.NewAttribute(types.AttributeKeyDstValidator, newIns.GetValidator().String()), + sdk.NewAttribute(types.AttributeKeySrcValidator, outIns.ValidatorAddress), + sdk.NewAttribute(types.AttributeKeyDstValidator, newIns.ValidatorAddress), sdk.NewAttribute(types.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)), ), ) @@ -566,7 +566,7 @@ func (k Keeper) RePairRankedInsurances( types.EventTypeBeginUndelegate, sdk.NewAttribute(sdk.AttributeKeyModule, types.ModuleName), sdk.NewAttribute(types.AttributeKeyChunkId, fmt.Sprintf("%d", chunk.Id)), - sdk.NewAttribute(types.AttributeKeyValidator, outIns.GetValidator().String()), + sdk.NewAttribute(types.AttributeKeyValidator, outIns.ValidatorAddress), sdk.NewAttribute(types.AttributeKeyCompletionTime, completionTime.Format(time.RFC3339)), sdk.NewAttribute(types.AttributeKeyReason, types.AttributeValueReasonNoCandidateIns), ), @@ -590,18 +590,21 @@ func (k Keeper) DoLiquidStake(ctx sdk.Context, msg *types.MsgLiquidStake) ( return } chunksToCreate := amount.Amount.Quo(types.ChunkSize) - nas := k.GetNetAmountStateEssentials(ctx) - if nas.RemainingChunkSlots.LT(chunksToCreate) { + + nase, _, _, _ := k.GetNetAmountStateEssentials(ctx) + + if nase.RemainingChunkSlots.LT(chunksToCreate) { err = sdkerrors.Wrapf( types.ErrExceedAvailableChunks, "requested chunks to create: %d, available chunks: %s", chunksToCreate, - nas.RemainingChunkSlots.String(), + nase.RemainingChunkSlots.String(), ) return } pairingInsurances, validatorMap := k.GetPairingInsurances(ctx) + numPairingInsurances := sdk.NewIntFromUint64(uint64(len(pairingInsurances))) if chunksToCreate.GT(numPairingInsurances) { err = types.ErrNoPairingInsurance @@ -644,8 +647,8 @@ func (k Keeper) DoLiquidStake(ctx sdk.Context, msg *types.MsgLiquidStake) ( // Mint the liquid staking token lsTokenMintAmount = types.ChunkSize - if nas.LsTokensTotalSupply.IsPositive() { - lsTokenMintAmount = nas.MintRate.MulTruncate(types.ChunkSize.ToDec()).TruncateInt() + if nase.LsTokensTotalSupply.IsPositive() { + lsTokenMintAmount = nase.MintRate.MulTruncate(types.ChunkSize.ToDec()).TruncateInt() } if !lsTokenMintAmount.IsPositive() { err = sdkerrors.Wrapf(types.ErrInvalidAmount, "amount must be greater than or equal to %s", amount.String()) @@ -684,29 +687,22 @@ func (k Keeper) QueueLiquidUnstake(ctx sdk.Context, msg *types.MsgLiquidUnstake) chunksToLiquidUnstake := amount.Amount.Quo(types.ChunkSize).Int64() - chunksWithInsId := make(map[uint64]types.Chunk) - var insurances []types.Insurance - validatorMap := make(map[string]stakingtypes.Validator) - k.IterateAllChunks(ctx, func(chunk types.Chunk) (stop bool) { - if chunk.Status != types.CHUNK_STATUS_PAIRED { - return false - } + nase, pairedChunksWithInsuranceId, pairedInsurances, validatorMap := k.GetNetAmountStateEssentials(ctx) + + // purelyPairedInsurances contains paired insurances which serve chunk which is not in queue for unstaking. + var purelyPairedInsurances []types.Insurance + for _, pairedIns := range pairedInsurances { + chunk := pairedChunksWithInsuranceId[pairedIns.Id] // check whether the chunk is already have unstaking requests in queue. _, found := k.GetUnpairingForUnstakingChunkInfo(ctx, chunk.Id) if found { - return false - } - - pairedIns, validator, _ := k.mustValidatePairedChunk(ctx, chunk) - if _, ok := validatorMap[pairedIns.GetValidator().String()]; !ok { - validatorMap[pairedIns.ValidatorAddress] = validator + delete(pairedChunksWithInsuranceId, pairedIns.Id) + continue } - insurances = append(insurances, pairedIns) - chunksWithInsId[pairedIns.Id] = chunk - return false - }) + purelyPairedInsurances = append(purelyPairedInsurances, pairedIns) + } - pairedChunks := int64(len(chunksWithInsId)) + pairedChunks := int64(len(pairedChunksWithInsuranceId)) if pairedChunks == 0 { err = types.ErrNoPairedChunk return @@ -721,16 +717,16 @@ func (k Keeper) QueueLiquidUnstake(ctx sdk.Context, msg *types.MsgLiquidUnstake) return } // Sort insurances by descend order - types.SortInsurances(validatorMap, insurances, true) + types.SortInsurances(validatorMap, purelyPairedInsurances, true) // How much ls tokens must be burned - nas := k.GetNetAmountStateEssentials(ctx) + liquidBondDenom := k.GetLiquidBondDenom(ctx) for i := int64(0); i < chunksToLiquidUnstake; i++ { // Escrow ls tokens from the delegator lsTokenBurnAmount := types.ChunkSize - if nas.LsTokensTotalSupply.IsPositive() { - lsTokenBurnAmount = lsTokenBurnAmount.ToDec().Mul(nas.MintRate).TruncateInt() + if nase.LsTokensTotalSupply.IsPositive() { + lsTokenBurnAmount = lsTokenBurnAmount.ToDec().Mul(nase.MintRate).TruncateInt() } lsTokensToBurn := sdk.NewCoin(liquidBondDenom, lsTokenBurnAmount) if err = k.bankKeeper.SendCoins( @@ -739,8 +735,8 @@ func (k Keeper) QueueLiquidUnstake(ctx sdk.Context, msg *types.MsgLiquidUnstake) return } - mostExpensiveInsurance := insurances[i] - chunkToBeUndelegated := chunksWithInsId[mostExpensiveInsurance.Id] + mostExpensiveInsurance := purelyPairedInsurances[i] + chunkToBeUndelegated := pairedChunksWithInsuranceId[mostExpensiveInsurance.Id] _, found := k.GetUnpairingForUnstakingChunkInfo(ctx, chunkToBeUndelegated.Id) if found { err = sdkerrors.Wrapf( @@ -757,7 +753,7 @@ func (k Keeper) QueueLiquidUnstake(ctx sdk.Context, msg *types.MsgLiquidUnstake) msg.DelegatorAddress, lsTokensToBurn, ) - toBeUnstakedChunks = append(toBeUnstakedChunks, chunksWithInsId[insurances[i].Id]) + toBeUnstakedChunks = append(toBeUnstakedChunks, pairedChunksWithInsuranceId[mostExpensiveInsurance.Id]) infos = append(infos, info) k.SetUnpairingForUnstakingChunkInfo(ctx, info) } @@ -906,6 +902,7 @@ func (k Keeper) DoDepositInsurance(ctx sdk.Context, msg *types.MsgDepositInsuran if err = k.bankKeeper.SendCoins(ctx, providerAddr, ins.DerivedAddress(), sdk.NewCoins(amount)); err != nil { return } + return } @@ -919,14 +916,14 @@ func (k Keeper) DoClaimDiscountedReward(ctx sdk.Context, msg *types.MsgClaimDisc return } - nas := k.GetNetAmountStateEssentials(ctx) + nase, _, _, _ := k.GetNetAmountStateEssentials(ctx) // discount rate >= minimum discount rate // if discount rate(e.g. 10%) is lower than minimum discount rate(e.g. 20%), then it is not profitable to claim reward. - if nas.DiscountRate.LT(msg.MinimumDiscountRate) { - err = sdkerrors.Wrapf(types.ErrDiscountRateTooLow, "current discount rate: %s", nas.DiscountRate) + if nase.DiscountRate.LT(msg.MinimumDiscountRate) { + err = sdkerrors.Wrapf(types.ErrDiscountRateTooLow, "current discount rate: %s", nase.DiscountRate) return } - discountedMintRate = nas.MintRate.Mul(sdk.OneDec().Sub(nas.DiscountRate)) + discountedMintRate = nase.MintRate.Mul(sdk.OneDec().Sub(nase.DiscountRate)) var claimableCoin sdk.Coin var burnAmt sdk.Coin @@ -952,6 +949,7 @@ func (k Keeper) DoClaimDiscountedReward(ctx sdk.Context, msg *types.MsgClaimDisc if err = k.bankKeeper.SendCoins(ctx, types.RewardPool, msg.GetRequestser(), claimCoins); err != nil { return } + return } @@ -1608,7 +1606,7 @@ func (k Keeper) isRepairingChunk(ctx sdk.Context, chunk types.Chunk) bool { if chunk.HasPairedInsurance() && chunk.HasUnpairingInsurance() { pairedIns := k.mustGetInsurance(ctx, chunk.PairedInsuranceId) unpairingIns := k.mustGetInsurance(ctx, chunk.UnpairingInsuranceId) - if pairedIns.GetValidator().Equals(unpairingIns.GetValidator()) { + if pairedIns.ValidatorAddress == unpairingIns.ValidatorAddress { return true } } diff --git a/x/liquidstaking/keeper/liquidstaking_test.go b/x/liquidstaking/keeper/liquidstaking_test.go index 3324853dc..1afa3ca7d 100644 --- a/x/liquidstaking/keeper/liquidstaking_test.go +++ b/x/liquidstaking/keeper/liquidstaking_test.go @@ -243,7 +243,7 @@ func (suite *KeeperTestSuite) TestLiquidStakeSuccess() { suite.provideInsurances(suite.ctx, providers, valAddrs, balances, sdk.ZeroDec(), nil) delegators, balances := suite.AddTestAddrsWithFunding(fundingAccount, 10, oneChunk.Amount) - nase := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) + nase, _, _, _ := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) liquidBondDenom := suite.app.LiquidStakingKeeper.GetLiquidBondDenom(suite.ctx) // First try @@ -272,7 +272,7 @@ func (suite *KeeperTestSuite) TestLiquidStakeSuccess() { } // NetAmountStateEssentials should be updated correctly - afterNas := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) + afterNas, _, _, _ := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) { suite.True(afterNas.LsTokensTotalSupply.Equal(lsTokenMintAmount), "total ls token supply should be equal to minted amount") suite.True(nase.TotalLiquidTokens.Add(amt1.Amount).Equal(afterNas.TotalLiquidTokens)) @@ -291,7 +291,7 @@ func (suite *KeeperTestSuite) TestLiquidStakeFail() { ) oneChunk, oneInsurance := suite.app.LiquidStakingKeeper.GetMinimumRequirements(suite.ctx) suite.fundAccount(suite.ctx, fundingAccount, oneChunk.Amount.MulRaw(100).Add(oneInsurance.Amount.MulRaw(10))) - nase := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) + nase, _, _, _ := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) remainingChunkSlots := nase.RemainingChunkSlots suite.Equal( remainingChunkSlots, sdk.NewInt(10), @@ -380,7 +380,7 @@ func (suite *KeeperTestSuite) TestLiquidStakeWithAdvanceBlocks() { ) _, oneInsurance := suite.app.LiquidStakingKeeper.GetMinimumRequirements(suite.ctx) - nase := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) + nase, _, _, _ := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) is := suite.getInsuranceState(suite.ctx) pairedChunksInt := sdk.NewInt(int64(len(env.pairedChunks))) // 1 chunk size * number of paired chunks (=3) tokens are liquidated @@ -414,7 +414,7 @@ func (suite *KeeperTestSuite) TestLiquidStakeWithAdvanceBlocks() { suite.ctx = suite.advanceHeight(suite.ctx, 1, "") beforeNas := nase - nase = suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) + nase, _, _, _ = suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) { suite.Equal( "80999676001295325000.000000000000000000", @@ -427,7 +427,7 @@ func (suite *KeeperTestSuite) TestLiquidStakeWithAdvanceBlocks() { beforeIs := is suite.ctx = suite.advanceEpoch(suite.ctx) suite.ctx = suite.advanceHeight(suite.ctx, 1, "delegation reward are distributed to insurance and reward module") - nase = suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) + nase, _, _, _ = suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) is = suite.getInsuranceState(suite.ctx) { suite.Equal( @@ -469,7 +469,7 @@ func (suite *KeeperTestSuite) TestLiquidUnstakeWithAdvanceBlocks() { oneChunk, oneInsurance := suite.app.LiquidStakingKeeper.GetMinimumRequirements(suite.ctx) pairedChunksInt := sdk.NewInt(int64(len(env.pairedChunks))) mostExpensivePairedChunk := suite.getMostExpensivePairedChunk(env.pairedChunks) - nase := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) + nase, _, _, _ := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) is := suite.getInsuranceState(suite.ctx) // 1 chunk size * number of paired chunks (=3) tokens are liquidated currentLiquidatedTokens := types.ChunkSize.Mul(pairedChunksInt) @@ -502,7 +502,7 @@ func (suite *KeeperTestSuite) TestLiquidUnstakeWithAdvanceBlocks() { suite.ctx = suite.advanceHeight(suite.ctx, 1, "") beforeNas := nase - nase = suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) + nase, _, _, _ = suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) { suite.Equal( "80999676001295325000.000000000000000000", @@ -552,7 +552,7 @@ func (suite *KeeperTestSuite) TestLiquidUnstakeWithAdvanceBlocks() { suite.ctx = suite.advanceHeight(suite.ctx, 1, "The actual unstaking started\nThe insurance commission and reward are claimed") beforeNas = nase beforeIs := is - nase = suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) + nase, _, _, _ = suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) is = suite.getInsuranceState(suite.ctx) // Check NetAmounState changed right @@ -613,7 +613,7 @@ func (suite *KeeperTestSuite) TestLiquidUnstakeWithAdvanceBlocks() { suite.ctx = suite.advanceHeight(suite.ctx, 1, "The insurance commission and reward are claimed\nThe unstaking is completed") beforeNas = nase - nase = suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) + nase, _, _, _ = suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) afterBondDenomBalance := suite.app.BankKeeper.GetBalance(suite.ctx, undelegator, env.bondDenom).Amount // Get bondDeno balance of undelegator { @@ -1228,7 +1228,7 @@ func (suite *KeeperTestSuite) TestEndBlocker() { toBeWithdrawnInsurance.GetValidator(), ) suite.True(found) - suite.Equal(toBeWithdrawnInsurance.GetValidator().String(), unbondingDelegation.ValidatorAddress) + suite.Equal(toBeWithdrawnInsurance.ValidatorAddress, unbondingDelegation.ValidatorAddress) } suite.ctx = suite.advanceHeight(suite.ctx, 1, "") @@ -2795,7 +2795,7 @@ func (suite *KeeperTestSuite) TestDynamicFee() { }, ) { - nase := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) + nase, _, _, _ := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) fmt.Println(nase) // Check current state before reaching epoch suite.Equal( @@ -2807,10 +2807,10 @@ func (suite *KeeperTestSuite) TestDynamicFee() { nase.FeeRate.String(), ) } - beforeNas := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) + beforeNas, _, _, _ := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) suite.ctx = suite.advanceEpoch(suite.ctx) suite.ctx = suite.advanceHeight(suite.ctx, 1, "got rewards and dynamic fee is charged") - nase := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) + nase, _, _, _ := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) suite.True( nase.RewardModuleAccBalance.GT(beforeNas.RewardModuleAccBalance), "reward module account's balance increased", @@ -2859,7 +2859,7 @@ func (suite *KeeperTestSuite) TestCalcDiscountRate() { cachedCtx = suite.advanceHeight(cachedCtx, tc.numRewardEpochs-1, fmt.Sprintf("let's pass %d reward epoch", tc.numRewardEpochs)) cachedCtx = suite.advanceEpoch(cachedCtx) // reward is accumulated to reward pool cachedCtx = suite.advanceHeight(cachedCtx, 1, "liquid staking endblocker is triggered") - nase := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(cachedCtx) + nase, _, _, _ := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(cachedCtx) suite.Equal(tc.expectedDiscountRate.String(), nase.DiscountRate.String()) }) } @@ -3006,11 +3006,9 @@ func (suite *KeeperTestSuite) TestDoClaimDiscountedReward() { cachedCtx = suite.advanceEpoch(cachedCtx) // reward is accumulated to reward pool cachedCtx = suite.advanceHeight(cachedCtx, 1, "liquid staking endblocker is triggered") requester := tc.msg.GetRequestser() - nase := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(cachedCtx) + nase, _, _, _ := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(cachedCtx) suite.Equal(tc.expected.discountRate, nase.DiscountRate.String()) - discountedMintRate := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(cachedCtx).MintRate.Mul( - sdk.OneDec().Sub(nase.DiscountRate), - ) + discountedMintRate := nase.MintRate.Mul(sdk.OneDec().Sub(nase.DiscountRate)) claimableAmt := suite.app.BankKeeper.GetBalance(cachedCtx, types.RewardPool, suite.denom) lsTokenToGetAll := claimableAmt.Amount.ToDec().Mul(discountedMintRate).Ceil().TruncateInt() claimAmt := tc.msg.Amount.Amount.ToDec().Quo(discountedMintRate).TruncateInt() @@ -3020,14 +3018,16 @@ func (suite *KeeperTestSuite) TestDoClaimDiscountedReward() { suite.Equal(tc.expected.beforeTokenBal, suite.app.BankKeeper.GetBalance(cachedCtx, requester, suite.denom).Amount.String()) beforeLsTokenBal := suite.app.BankKeeper.GetBalance(cachedCtx, requester, liquidBondDenom).Amount suite.Equal(tc.expected.beforeLsTokenBal, beforeLsTokenBal.String()) - beforeMintRate := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(cachedCtx).MintRate + nase, _, _, _ = suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(cachedCtx) + beforeMintRate := nase.MintRate suite.Equal(tc.expected.beforeMintRate, beforeMintRate.String()) _, _, err := suite.app.LiquidStakingKeeper.DoClaimDiscountedReward(cachedCtx, tc.msg) suite.NoError(err) suite.Equal(tc.afterTokenBal, suite.app.BankKeeper.GetBalance(cachedCtx, requester, suite.denom).Amount.String()) afterLsTokenBal := suite.app.BankKeeper.GetBalance(cachedCtx, requester, liquidBondDenom).Amount suite.Equal(tc.expected.afterLsTokenBal, afterLsTokenBal.String()) - afterMintRate := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(cachedCtx).MintRate + nase, _, _, _ = suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(cachedCtx) + afterMintRate := nase.MintRate suite.Equal(tc.expected.afterMintRate, afterMintRate.String()) suite.Equal(tc.expected.increasedMintRate, afterMintRate.Sub(beforeMintRate).String()) suite.Equal(tc.expected.decreasedLsTokenBal, beforeLsTokenBal.Sub(afterLsTokenBal).String()) @@ -3131,7 +3131,7 @@ func (suite *KeeperTestSuite) TestChunkPositiveBalanceBeforeEpoch() { suite.ctx = suite.advanceHeight(suite.ctx, 1, "liquid staking endblocker is triggered") originReardModuleAccBalance, _ := sdk.NewIntFromString("161999352002591325000") - nase := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) + nase, _, _, _ := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) { additionalCommissions := coin.Amount.ToDec().Mul(TenPercentFeeRate).TruncateInt() suite.Equal( diff --git a/x/liquidstaking/keeper/net_amount.go b/x/liquidstaking/keeper/net_amount.go index f9103875f..df7980ad6 100644 --- a/x/liquidstaking/keeper/net_amount.go +++ b/x/liquidstaking/keeper/net_amount.go @@ -7,7 +7,7 @@ import ( ) func (k Keeper) GetNetAmountState(ctx sdk.Context) types.NetAmountState { - nase := k.GetNetAmountStateEssentials(ctx) + nase, _, _, _ := k.GetNetAmountStateEssentials(ctx) nas := &types.NetAmountState{} deepcopier.Copy(&nase).To(nas) diff --git a/x/liquidstaking/keeper/net_amount_essentials.go b/x/liquidstaking/keeper/net_amount_essentials.go index 2e4ea97f4..ee73e4c80 100644 --- a/x/liquidstaking/keeper/net_amount_essentials.go +++ b/x/liquidstaking/keeper/net_amount_essentials.go @@ -7,7 +7,10 @@ import ( stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" ) -func (k Keeper) GetNetAmountStateEssentials(ctx sdk.Context) (nase types.NetAmountStateEssentials) { +func (k Keeper) GetNetAmountStateEssentials(ctx sdk.Context) ( + nase types.NetAmountStateEssentials, pairedChunkWithInsuranceId map[uint64]types.Chunk, + pairedInsurances []types.Insurance, validatorMap map[string]stakingtypes.Validator, +) { liquidBondDenom := k.GetLiquidBondDenom(ctx) bondDenom := k.stakingKeeper.BondDenom(ctx) totalDelShares := sdk.ZeroDec() @@ -18,8 +21,9 @@ func (k Keeper) GetNetAmountStateEssentials(ctx sdk.Context) (nase types.NetAmou totalUnbondingChunksBalance := sdk.ZeroInt() numPairedChunks := sdk.ZeroInt() + pairedChunkWithInsuranceId = make(map[uint64]types.Chunk) // To reduce gas consumption, store validator info in map - insValMap := make(map[string]stakingtypes.Validator) + validatorMap = make(map[string]stakingtypes.Validator) k.IterateAllChunks(ctx, func(chunk types.Chunk) (stop bool) { balance := k.bankKeeper.GetBalance(ctx, chunk.DerivedAddress(), k.stakingKeeper.BondDenom(ctx)) totalChunksBalance = totalChunksBalance.Add(balance.Amount) @@ -28,16 +32,17 @@ func (k Keeper) GetNetAmountStateEssentials(ctx sdk.Context) (nase types.NetAmou case types.CHUNK_STATUS_PAIRED: numPairedChunks = numPairedChunks.Add(sdk.OneInt()) pairedIns := k.mustGetInsurance(ctx, chunk.PairedInsuranceId) - valAddr := pairedIns.GetValidator() + pairedChunkWithInsuranceId[chunk.PairedInsuranceId] = chunk + pairedInsurances = append(pairedInsurances, pairedIns) // Use map to reduce gas consumption - if _, ok := insValMap[valAddr.String()]; !ok { + if _, ok := validatorMap[pairedIns.ValidatorAddress]; !ok { validator, found := k.stakingKeeper.GetValidator(ctx, pairedIns.GetValidator()) if !found { panic(fmt.Sprintf("validator of paired ins %s not found(insuranceId: %d)", pairedIns.GetValidator(), pairedIns.Id)) } - insValMap[valAddr.String()] = validator + validatorMap[pairedIns.ValidatorAddress] = validator } - validator := insValMap[valAddr.String()] + validator := validatorMap[pairedIns.ValidatorAddress] // Get delegation of chunk del, found := k.stakingKeeper.GetDelegation(ctx, chunk.DerivedAddress(), validator.GetOperator()) @@ -51,9 +56,15 @@ func (k Keeper) GetNetAmountStateEssentials(ctx sdk.Context) (nase types.NetAmou tokenValue = k.calcTokenValueWithInsuranceCoverage(ctx, tokenValue, pairedIns) totalLiquidTokens = totalLiquidTokens.Add(tokenValue) + beforeCachedCtxConsumed := ctx.GasMeter().GasConsumed() cachedCtx, _ := ctx.CacheContext() endingPeriod := k.distributionKeeper.IncrementValidatorPeriod(cachedCtx, validator) delRewards := k.distributionKeeper.CalculateDelegationRewards(cachedCtx, validator, del, endingPeriod) + afterCachedCtxConsumed := cachedCtx.GasMeter().GasConsumed() + cachedCtx.GasMeter().RefundGas( + afterCachedCtxConsumed-beforeCachedCtxConsumed, + "cachedCtx does not write state", + ) // chunk's remaining reward is calculated by // 1. rest = del_reward - insurance_commission // 2. remaining = rest x (1 - module_fee_rate) @@ -61,6 +72,7 @@ func (k Keeper) GetNetAmountStateEssentials(ctx sdk.Context) (nase types.NetAmou insuranceCommission := delReward.Mul(pairedIns.FeeRate) remainingReward := delReward.Sub(insuranceCommission) totalRemainingRewardsBeforeModuleFee = totalRemainingRewardsBeforeModuleFee.Add(remainingReward) + default: k.stakingKeeper.IterateDelegatorUnbondingDelegations(ctx, chunk.DerivedAddress(), func(ubd stakingtypes.UnbondingDelegation) (stop bool) { for _, entry := range ubd.Entries { @@ -71,6 +83,7 @@ func (k Keeper) GetNetAmountStateEssentials(ctx sdk.Context) (nase types.NetAmou return false }) } + return false }) @@ -101,6 +114,7 @@ func (k Keeper) GetNetAmountStateEssentials(ctx sdk.Context) (nase types.NetAmou nase.NetAmount = nase.CalcNetAmount() nase.MintRate = nase.CalcMintRate() nase.DiscountRate = nase.CalcDiscountRate(params.MaximumDiscountRate) + return } diff --git a/x/liquidstaking/keeper/net_amount_essentials_test.go b/x/liquidstaking/keeper/net_amount_essentials_test.go index cd85d48de..02c59c67d 100644 --- a/x/liquidstaking/keeper/net_amount_essentials_test.go +++ b/x/liquidstaking/keeper/net_amount_essentials_test.go @@ -21,7 +21,7 @@ func (suite *KeeperTestSuite) TestGetNetAmountState_TotalRemainingRewards() { }) suite.ctx = suite.advanceHeight(suite.ctx, 100, "delegation rewards are accumulated") - nase := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) + nase, _, _, _ := suite.app.LiquidStakingKeeper.GetNetAmountStateEssentials(suite.ctx) cachedCtx, _ := suite.ctx.CacheContext() suite.app.DistrKeeper.WithdrawDelegationRewards(cachedCtx, env.pairedChunks[0].DerivedAddress(), env.insurances[0].GetValidator()) diff --git a/x/liquidstaking/simulation/operations.go b/x/liquidstaking/simulation/operations.go index dc7b502de..8bb37905a 100644 --- a/x/liquidstaking/simulation/operations.go +++ b/x/liquidstaking/simulation/operations.go @@ -1,7 +1,9 @@ package simulation import ( + authtypes "github.com/cosmos/cosmos-sdk/x/auth/types" stakingtypes "github.com/cosmos/cosmos-sdk/x/staking/types" + ethermint "github.com/evmos/ethermint/types" "math/rand" "github.com/Canto-Network/Canto/v7/app/params" @@ -37,6 +39,9 @@ var ( Amount: sdk.NewInt(0), }, } + // Canto mainnet supply is currently 1.05B + MainnetTotalSupply = sdk.TokensFromConsensusPower(1_050_000_000, ethermint.PowerReduction) + RichAccount = authtypes.NewModuleAddress("fundAccount") ) // WeightedOperations returns all the operations from the module with their respective weights @@ -162,10 +167,11 @@ func SimulateMsgLiquidStake(ak types.AccountKeeper, bk types.BankKeeper, sk type ), ) if !spendable.AmountOf(bondDenom).GTE(stakingCoins[0].Amount) { - if err := bk.MintCoins(ctx, types.ModuleName, stakingCoins); err != nil { - panic(err) + richAccBalance := bk.GetBalance(ctx, RichAccount, bondDenom).Amount + if richAccBalance.LT(stakingCoins[0].Amount) { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgLiquidStake, "total supply is exceeded"), nil, nil } - if err := bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, delegator, stakingCoins); err != nil { + if err := bk.SendCoins(ctx, RichAccount, delegator, stakingCoins); err != nil { panic(err) } spendable = bk.SpendableCoins(ctx, delegator) @@ -322,10 +328,11 @@ func SimulateMsgProvideInsurance(ak types.AccountKeeper, bk types.BankKeeper, sk } if !spendable.AmountOf(bondDenom).GTE(collaterals[0].Amount) { - if err := bk.MintCoins(ctx, types.ModuleName, collaterals); err != nil { - panic(err) + richAccBalance := bk.GetBalance(ctx, RichAccount, bondDenom).Amount + if richAccBalance.LT(collaterals[0].Amount) { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgProvideInsurance, "total supply is exceeded"), nil, nil } - if err := bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, provider, collaterals); err != nil { + if err := bk.SendCoins(ctx, RichAccount, provider, collaterals); err != nil { panic(err) } spendable = bk.SpendableCoins(ctx, provider) @@ -457,10 +464,11 @@ func SimulateMsgDepositInsurance(ak types.AccountKeeper, bk types.BankKeeper, sk ) if !spendable.AmountOf(bondDenom).GTE(deposits[0].Amount) { - if err := bk.MintCoins(ctx, types.ModuleName, deposits); err != nil { - panic(err) + richAccBalance := bk.GetBalance(ctx, RichAccount, bondDenom).Amount + if richAccBalance.LT(deposits[0].Amount) { + return simtypes.NoOpMsg(types.ModuleName, types.TypeMsgDepositInsurance, "total supply is exceeded"), nil, nil } - if err := bk.SendCoinsFromModuleToAccount(ctx, types.ModuleName, provider, deposits); err != nil { + if err := bk.SendCoins(ctx, RichAccount, provider, deposits); err != nil { panic(err) } spendable = bk.SpendableCoins(ctx, provider)