diff --git a/x/gov/abci.go b/x/gov/abci.go index e8cce5f5a99f..8f73b9d67e94 100644 --- a/x/gov/abci.go +++ b/x/gov/abci.go @@ -46,8 +46,8 @@ func EndBlocker(ctx sdk.Context, keeper *keeper.Keeper) error { return false, err } - // TODO HV2: https://polygon.atlassian.net/browse/POS-2755 - err = keeper.RefundAndDeleteDeposits(ctx, proposal.Id) // refund deposit if proposal got removed without getting 100% of the proposal + // Distribute the deposits if the proposal got removed without getting 100% of the proposal + err = keeper.DistributeAndDeleteDeposits(ctx, proposal.Id) if err != nil { return false, err } @@ -135,10 +135,17 @@ func EndBlocker(ctx sdk.Context, keeper *keeper.Keeper) error { return false, err } - // HV2: heimdall refunds and deletes deposits in all cases of proposal failures, without caring about burnDeposits - err = keeper.RefundAndDeleteDeposits(ctx, proposal.Id) - if err != nil { - return false, err + // HV2: heimdall distributes and deletes deposits in all cases of proposal failures, without caring about burnDeposits + if passes { + err = keeper.RefundAndDeleteDeposits(ctx, proposal.Id) + if err != nil { + return false, err + } + } else { + err = keeper.DistributeAndDeleteDeposits(ctx, proposal.Id) + if err != nil { + return false, err + } } // If an expedited proposal fails, we do not want to update @@ -315,7 +322,7 @@ func failUnsupportedProposal( return err } - if err := keeper.RefundAndDeleteDeposits(ctx, proposal.Id); err != nil { + if err := keeper.DistributeAndDeleteDeposits(ctx, proposal.Id); err != nil { return err } diff --git a/x/gov/keeper/deposit.go b/x/gov/keeper/deposit.go index c3cd71405167..64a040a80a51 100644 --- a/x/gov/keeper/deposit.go +++ b/x/gov/keeper/deposit.go @@ -9,6 +9,7 @@ import ( "cosmossdk.io/errors" sdkmath "cosmossdk.io/math" + stakeTypes "github.com/0xPolygon/heimdall-v2/x/stake/types" sdk "github.com/cosmos/cosmos-sdk/types" sdkerrors "github.com/cosmos/cosmos-sdk/types/errors" disttypes "github.com/cosmos/cosmos-sdk/x/distribution/types" @@ -285,6 +286,81 @@ func (keeper Keeper) RefundAndDeleteDeposits(ctx context.Context, proposalID uin }) } +// DistributeAndDeleteDeposits distributes the deposits evenly among all the active validators and deletes the deposits on a specific proposal. +func (keeper Keeper) DistributeAndDeleteDeposits(ctx context.Context, proposalID uint64) error { + var validatorAddresses []sdk.AccAddress + + err := keeper.sk.IterateCurrentValidatorsAndApplyFn(ctx, func(validator stakeTypes.Validator) bool { + validatorAddr, err := sdk.AccAddressFromHex(validator.Signer) + if err != nil { + return true + } + validatorAddresses = append(validatorAddresses, validatorAddr) + return false + }) + if err != nil { + return err + } + + numValidators := len(validatorAddresses) + if numValidators == 0 { + return fmt.Errorf("no current validators available") + } + + deposits, err := keeper.GetDeposits(ctx, proposalID) + if err != nil { + return err + } + + var totalDeposits sdk.Coins + var amountPerValidator sdk.Coins + + for _, deposit := range deposits { + depositerAddress, err := keeper.authKeeper.AddressCodec().StringToBytes(deposit.Depositor) + if err != nil { + return err + } + + totalDeposits = totalDeposits.Add(deposit.Amount...) + + for _, coin := range deposit.Amount { + amountPerValidatorPerCoin := sdkmath.LegacyNewDecFromInt(coin.Amount).QuoInt64(int64(numValidators)).TruncateInt() + amountPerValidator = amountPerValidator.Add( + sdk.NewCoin( + coin.Denom, + amountPerValidatorPerCoin, + ), + ) + } + + err = keeper.Deposits.Remove(ctx, collections.Join(deposit.ProposalId, sdk.AccAddress(depositerAddress))) + if err != nil { + return err + } + } + + for _, validator := range validatorAddresses { + err = keeper.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, validator, amountPerValidator) + if err != nil { + return err + } + } + + // Calculate any remaining amount due to truncating issues + usedAmount := amountPerValidator.MulInt(sdkmath.NewInt(int64(numValidators))) + remainingAmount, _ := totalDeposits.SafeSub(usedAmount...) + + if !remainingAmount.IsZero() { + // Send remaining amount to the first validator + err = keeper.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, validatorAddresses[0], remainingAmount) + if err != nil { + return err + } + } + + return err +} + // validateInitialDeposit validates if initial deposit is greater than or equal to the minimum // required at the time of proposal submission. This threshold amount is determined by // the deposit parameters. Returns nil on success, error otherwise.