mirror of
https://github.com/planetmint/planetmint-go.git
synced 2025-07-01 10:22:30 +00:00
214 lines
7.0 KiB
Go
214 lines
7.0 KiB
Go
package keeper
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
"sort"
|
|
"strconv"
|
|
|
|
errorsmod "cosmossdk.io/errors"
|
|
sdk "github.com/cosmos/cosmos-sdk/types"
|
|
"github.com/planetmint/planetmint-go/util"
|
|
"github.com/planetmint/planetmint-go/x/dao/types"
|
|
)
|
|
|
|
type Claims struct {
|
|
challenger map[string]uint64
|
|
challengee map[string]uint64
|
|
initiator map[string]uint64
|
|
}
|
|
|
|
func (k msgServer) DistributionResult(goCtx context.Context, msg *types.MsgDistributionResult) (*types.MsgDistributionResultResponse, error) {
|
|
ctx := sdk.UnwrapSDKContext(goCtx)
|
|
|
|
distribution, found := k.LookupDistributionOrder(ctx, msg.GetLastPop())
|
|
if !found {
|
|
err := errors.New(types.ErrDistributionNotFound.Error() + " for provided block height " + strconv.FormatInt(msg.GetLastPop(), 10))
|
|
util.GetAppLogger().Error(ctx, err, "")
|
|
return nil, errorsmod.Wrap(types.ErrDistributionNotFound, err.Error())
|
|
}
|
|
|
|
distribution.DaoTxID = msg.DaoTxID
|
|
distribution.PopTxID = msg.PopTxID
|
|
distribution.InvestorTxID = msg.InvestorTxID
|
|
distribution.EarlyInvAddr = msg.EarlyInvestorTxID
|
|
distribution.StrategicTxID = msg.StrategicTxID
|
|
|
|
if err := k.clearUnresolvedClaims(ctx, distribution.FirstPop); err != nil {
|
|
util.GetAppLogger().Error(ctx, err, "error while clearing unresolved claims for heights %d-%d", distribution.FirstPop, distribution.LastPop)
|
|
}
|
|
|
|
err := k.resolveStagedClaims(ctx, distribution.FirstPop, distribution.LastPop)
|
|
if err != nil {
|
|
util.GetAppLogger().Error(ctx, err, "%s for provided PoP heights: %d %d", types.ErrResolvingStagedClaims.Error(), distribution.FirstPop, distribution.LastPop)
|
|
return nil, errorsmod.Wrap(types.ErrConvertClaims, err.Error())
|
|
}
|
|
util.GetAppLogger().Info(ctx, "staged claims successfully for provided PoP heights: %d %d", distribution.FirstPop, distribution.LastPop)
|
|
k.StoreDistributionOrder(ctx, distribution)
|
|
|
|
return &types.MsgDistributionResultResponse{}, nil
|
|
}
|
|
|
|
// clearUnresolvedClaims checks for all Challenge participants starting from a given height.
|
|
// An accounts stagedDenom amount should always be 0 except for claims that have not yet been reissued.
|
|
// Calculate the difference for a set of participants and clear out all past unresolved staged claims.
|
|
func (k msgServer) clearUnresolvedClaims(ctx sdk.Context, start int64) (err error) {
|
|
// calculate total amounts for current and future claims
|
|
claims, err := k.getClaims(ctx, start, ctx.BlockHeight())
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
currentAmounts := make(map[string]uint64)
|
|
for address, amount := range claims.challenger {
|
|
currentAmounts[address] += amount
|
|
}
|
|
|
|
for address, amount := range claims.challengee {
|
|
currentAmounts[address] += amount
|
|
}
|
|
|
|
for address, amount := range claims.initiator {
|
|
currentAmounts[address] += amount
|
|
}
|
|
|
|
totalAmounts := make(map[string]uint64)
|
|
for participantAddress := range currentAmounts {
|
|
// the challenger and challengee can be empty
|
|
participantAddressHex, err := sdk.AccAddressFromBech32(participantAddress)
|
|
if err != nil {
|
|
// try to convert as many claims as possible
|
|
continue
|
|
}
|
|
stagedBalance := k.bankKeeper.GetBalance(ctx, participantAddressHex, k.GetParams(ctx).StagedDenom)
|
|
totalAmounts[participantAddress] = stagedBalance.Amount.Uint64()
|
|
}
|
|
|
|
// calculate difference to account balance
|
|
for participantAddress := range totalAmounts {
|
|
totalAmounts[participantAddress] -= currentAmounts[participantAddress]
|
|
}
|
|
|
|
return k.convertOrderedClaim(ctx, totalAmounts)
|
|
}
|
|
|
|
// resolveStagedClaims converts staged claims to claims in an ordered fashion for a given range
|
|
func (k msgServer) resolveStagedClaims(ctx sdk.Context, start int64, end int64) (err error) {
|
|
popParticipantAmounts, err := k.getClaims(ctx, start, end)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = k.convertOrderedClaim(ctx, popParticipantAmounts.initiator); err != nil {
|
|
return err
|
|
}
|
|
|
|
if err = k.convertOrderedClaim(ctx, popParticipantAmounts.challenger); err != nil {
|
|
return err
|
|
}
|
|
|
|
return k.convertOrderedClaim(ctx, popParticipantAmounts.challengee)
|
|
}
|
|
|
|
func (k msgServer) getClaims(ctx sdk.Context, start int64, end int64) (claims Claims, err error) {
|
|
// lookup all challenges for a given range
|
|
challenges, err := k.GetChallengeRange(ctx, start, end)
|
|
if err != nil {
|
|
return
|
|
}
|
|
|
|
claims.initiator = make(map[string]uint64)
|
|
claims.challenger = make(map[string]uint64)
|
|
claims.challengee = make(map[string]uint64)
|
|
|
|
for _, challenge := range challenges {
|
|
initiatorAddr, err := sdk.AccAddressFromBech32(challenge.Initiator)
|
|
if err != nil {
|
|
util.GetAppLogger().Error(ctx, err, "error converting initiator address: %v", challenge.Initiator)
|
|
} else {
|
|
validatorPopReward, found := k.getChallengeInitiatorReward(ctx, challenge.GetHeight())
|
|
if !found {
|
|
err = fmt.Errorf("no PoP initiator reward found for height %v", challenge.GetHeight())
|
|
util.GetAppLogger().Error(ctx, err, "")
|
|
}
|
|
claims.initiator[initiatorAddr.String()] += validatorPopReward
|
|
}
|
|
|
|
// if challenge not finished only initiator has claims
|
|
if !challenge.GetFinished() {
|
|
continue
|
|
}
|
|
_, challengerAmt, challengeeAmt := util.GetPopReward(challenge.Height, k.GetParams(ctx).PopEpochs)
|
|
claims.challenger[challenge.Challenger] += challengerAmt
|
|
if challenge.GetSuccess() {
|
|
claims.challengee[challenge.Challengee] += challengeeAmt
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (k msgServer) convertOrderedClaim(ctx sdk.Context, claims map[string]uint64) (err error) {
|
|
// second data structure because map iteration order is not guaranteed in GO
|
|
keys := make([]string, 0)
|
|
for accountAddress := range claims {
|
|
keys = append(keys, accountAddress)
|
|
}
|
|
|
|
sort.Strings(keys)
|
|
for _, accountAddress := range keys {
|
|
err = k.convertAccountClaim(ctx, accountAddress, claims[accountAddress])
|
|
if err != nil {
|
|
// try to convert as many claims as possible
|
|
continue
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
// convert per account
|
|
func (k msgServer) convertAccountClaim(ctx sdk.Context, participant string, amount uint64) (err error) {
|
|
accAddr, err := sdk.AccAddressFromBech32(participant)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
accStagedClaim := k.bankKeeper.GetBalance(ctx, accAddr, k.GetParams(ctx).StagedDenom)
|
|
|
|
if accStagedClaim.Amount.GTE(sdk.NewIntFromUint64(amount)) {
|
|
burnCoins, mintCoins := k.getConvertCoins(ctx, amount)
|
|
err = k.bankKeeper.SendCoinsFromAccountToModule(ctx, accAddr, types.ModuleName, burnCoins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = k.convertCoins(ctx, burnCoins, mintCoins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
err = k.bankKeeper.SendCoinsFromModuleToAccount(ctx, types.ModuleName, accAddr, mintCoins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
return
|
|
}
|
|
|
|
func (k msgServer) convertCoins(ctx sdk.Context, burnCoins sdk.Coins, mintCoins sdk.Coins) (err error) {
|
|
err = k.bankKeeper.BurnCoins(ctx, types.ModuleName, burnCoins)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
return k.bankKeeper.MintCoins(ctx, types.ModuleName, mintCoins)
|
|
}
|
|
|
|
func (k msgServer) getConvertCoins(ctx sdk.Context, amount uint64) (burnCoins sdk.Coins, mintCoins sdk.Coins) {
|
|
burnCoins = sdk.NewCoins(sdk.NewCoin(k.GetParams(ctx).StagedDenom, sdk.NewIntFromUint64(amount)))
|
|
mintCoins = sdk.NewCoins(sdk.NewCoin(k.GetParams(ctx).ClaimDenom, sdk.NewIntFromUint64(amount)))
|
|
return
|
|
}
|