The main logic of the Bet module definitions and methods.
Wagering
There are several steps for an incoming bet to be placed:
Check for duplicate UID
Validate the Ticket and unmarshal it to the wager payload type types.WagerTicketPayload
KYC validation according to the creator address in the message and the KYC address of the ticket if required.
Get the corresponding market from the market module and validate the odds existence.
Validate the minimum bet amount according to the module parameters.
Calculate and set the betting fee.
Validate and calculate the payout profit according to the odds type, bet amount, and odds value.
Call ProcessWager method of the Order Book module. this method handles the lock/unlock of the payout, participations, and bet fulfillments and transfers the payout and fees to the corresponding module accounts.
Set the initial Status, Result,CreatedAt, and BetFulfillments attributes.
Calculate the sequential ID, Set bet statistics, and bet to the bet module state.
// Wager stores a new bet in KVStorefunc (k Keeper) Wager(ctx sdk.Context, bet *types.Bet) error { bettorAddress, err := sdk.AccAddressFromBech32(bet.Creator)if err !=nil {return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "%s", err) } market, err := k.getMarket(ctx, bet.MarketUID)if err !=nil {return err }// check if selected odds is validif!market.HasOdds(bet.OddsUID) {return types.ErrOddsUIDNotExist }// check minimum bet amount allowed betConstraints := k.GetConstraints(ctx)if bet.Amount.LT(betConstraints.MinAmount) {return types.ErrBetAmountIsLow }// modify the bet fee and subtracted amount bet.SetFee(betConstraints.Fee)// calculate payoutProfit payoutProfit, err := types.CalculatePayoutProfit(bet.OddsType, bet.OddsValue, bet.Amount)if err !=nil {return err } stats := k.GetBetStats(ctx) stats.Count++ betID := stats.Count betFulfillment, err := k.orderbookKeeper.ProcessWager( ctx, bet.UID, bet.MarketUID, bet.OddsUID, bet.MaxLossMultiplier, bet.Amount, payoutProfit, bettorAddress, bet.Fee, bet.OddsType, bet.OddsValue, betID, )if err !=nil {return sdkerrors.Wrapf(types.ErrInOBWagerProcessing, "%s", err) }// set bet as placed bet.Status = types.Bet_STATUS_PLACED// put bet in the result pending status bet.Result = types.Bet_RESULT_PENDING bet.CreatedAt = ctx.BlockTime().Unix() bet.BetFulfillment = betFulfillment// store bet in the module state k.SetBet(ctx, *bet, betID)// set bet as a pending bet k.SetPendingBet(ctx, types.NewPendingBet(bet.UID, bet.Creator), betID, bet.MarketUID)// set bet stats k.SetBetStats(ctx, stats)returnnil}
Settlement
There are several steps for an incoming bet to be settled:
Check UUID validity
Get the corresponding bet object
Validates the bet creator stored in the state with the incoming bettorAddress of the settlement message.
Check the bet status not to be canceled or settled before.
Get the corresponding market from the market store and check if it exists, and is not aborted or canceled. If it is, calculate the payout and refund the bettor using RefundBettor method of the Order Book module keeper then sets the bet status as settled and finalizes the method.
Sets the proper bet result according to the WinnerOddsUIDs.
According to the bet result determined in the previous step, call BettorWins or BettorLoses of the Order Book module keeper.
Set the settled bet in the bet module state.
// SettleBet settles a single bet and updates it in KVStorefunc (k Keeper) SettleBet(ctx sdk.Context, bettorAddressStr, betUID string) error {if!utils.IsValidUID(betUID) {return types.ErrInvalidBetUID } uid2ID, found := k.GetBetID(ctx, betUID)if!found {return types.ErrNoMatchingBet } bet, found := k.GetBet(ctx, bettorAddressStr, uid2ID.ID)if!found {return types.ErrNoMatchingBet } bettorAddress, err := sdk.AccAddressFromBech32(bet.Creator)if err !=nil {return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "%s", err) }if bet.Creator != bettorAddressStr {return types.ErrBettorAddressNotEqualToCreator }if err := bet.CheckSettlementEligiblity(); err !=nil {// bet cancellation logic will reside here if this feature is requestedreturn err }// get the respective market for the bet market, found := k.marketKeeper.GetMarket(ctx, bet.MarketUID)if!found {return types.ErrNoMatchingMarket }if market.Status == markettypes.MarketStatus_MARKET_STATUS_ABORTED || market.Status == markettypes.MarketStatus_MARKET_STATUS_CANCELED { payoutProfit, err := types.CalculatePayoutProfit(bet.OddsType, bet.OddsValue, bet.Amount)if err !=nil {return err }if err := k.orderbookKeeper.RefundBettor(ctx, bettorAddress, bet.Amount, bet.Fee, payoutProfit.TruncateInt(), bet.UID); err !=nil {return sdkerrors.Wrapf(types.ErrInOBRefund, "%s", err) } bet.Status = types.Bet_STATUS_SETTLED bet.Result = types.Bet_RESULT_REFUNDED k.updateSettlementState(ctx, bet, uid2ID.ID)returnnil }// check if the bet odds is a winner odds or not and set the bet pointer statesif err := bet.SetResult(&market); err !=nil {return err }if err := k.settleResolvedBet(ctx, &bet); err !=nil {return err }if err := k.orderbookKeeper.WithdrawBetFee(ctx, sdk.MustAccAddressFromBech32(market.Creator), bet.Fee); err !=nil {return err } k.updateSettlementState(ctx, bet, uid2ID.ID)returnnil}// updateSettlementState settles bet in the storefunc (k Keeper) updateSettlementState(ctx sdk.Context, bet types.Bet, betID uint64) {// set current height as settlement height bet.SettlementHeight = ctx.BlockHeight()// store bet in the module state k.SetBet(ctx, bet, betID)// remove pending bet k.RemovePendingBet(ctx, bet.MarketUID, betID)// store settled bet in the module state k.SetSettledBet(ctx, types.NewSettledBet(bet.UID, bet.Creator), betID, ctx.BlockHeight())}// settleResolvedBet settles a bet by calling order book functions to unlock fund and payout// based on bet's result, and updates status of bet to settledfunc (k Keeper) settleResolvedBet(ctx sdk.Context, bet *types.Bet) error { bettorAddress, err := sdk.AccAddressFromBech32(bet.Creator)if err !=nil {return sdkerrors.Wrapf(sdkerrors.ErrInvalidAddress, "%s", err) } payout, err := types.CalculatePayoutProfit(bet.OddsType, bet.OddsValue, bet.Amount)if err !=nil {return err }if bet.Result == types.Bet_RESULT_LOST {if err := k.orderbookKeeper.BettorLoses(ctx, bettorAddress, bet.Amount, payout.TruncateInt(), bet.UID, bet.BetFulfillment, bet.MarketUID); err !=nil {return sdkerrors.Wrapf(types.ErrInOBBettorLoses, "%s", err) } bet.Status = types.Bet_STATUS_SETTLED } elseif bet.Result == types.Bet_RESULT_WON {if err := k.orderbookKeeper.BettorWins(ctx, bettorAddress, bet.Amount, payout.TruncateInt(), bet.UID, bet.BetFulfillment, bet.MarketUID); err !=nil {return sdkerrors.Wrapf(types.ErrInOBBettorWins, "%s", err) } bet.Status = types.Bet_STATUS_SETTLED }returnnil}
Batch Settlement
This is being used in the bet module end blocker in order to settle the bets automatically in batch.
// BatchMarketSettlements settles bets of resolved markets// in batch. The markets get into account in FIFO manner.func (k Keeper) BatchMarketSettlements(ctx sdk.Context) error { toFetch := k.GetParams(ctx).BatchSettlementCount// continue looping until reach batch settlement count parameterfor toFetch >0 {// get the first resolved market to process corresponding pending bets. marketUID, found := k.marketKeeper.GetFirstUnsettledResolvedMarket(ctx)// exit loop if there is no resolved bet.if!found {returnnil }// settle market pending bets. settledCount, err := k.batchMarketSettlement(ctx, marketUID, toFetch)if err !=nil {return fmt.Errorf("could not settle market %s%s", marketUID, err) }// check if still there is any pending bet for the market. pendingBetExists, err := k.IsAnyPendingBetForMarket(ctx, marketUID)if err !=nil {return fmt.Errorf("could not check the pending bets %s%s", marketUID, err) }// if there is not any pending bet for the market// we need to remove its uid from the list of unsettled resolved bets.if!pendingBetExists { k.marketKeeper.RemoveUnsettledResolvedMarket(ctx, marketUID) err = k.orderbookKeeper.SetOrderBookAsUnsettledResolved(ctx, marketUID)if err !=nil {return fmt.Errorf("could not resolve orderbook %s%s", marketUID, err) } }// update counter of bets to be processed in the next iteration. toFetch -= settledCount }returnnil}// batchMarketSettlement settles pending bets of a marketsfunc (k Keeper) batchMarketSettlement( ctx sdk.Context, marketUID string, countToBeSettled uint32,) (settledCount uint32, err error) {// initialize iterator for the certain number of pending bets// equal to countToBeSettled iterator := sdk.KVStorePrefixIteratorPaginated( ctx.KVStore(k.storeKey), types.PendingBetListOfMarketPrefix(marketUID), singlePageNum,uint(countToBeSettled))deferfunc() { iterErr := iterator.Close()if iterErr !=nil { err = iterErr } }()// settle bets for the filtered pending betsfor ; iterator.Valid(); iterator.Next() {var val types.PendingBet k.cdc.MustUnmarshal(iterator.Value(), &val) err = k.SettleBet(ctx, val.Creator, val.UID)if err !=nil {return }// update total settled count settledCount++ }return settledCount, nil}