-
Notifications
You must be signed in to change notification settings - Fork 0
/
settle_bet.go
241 lines (197 loc) · 6.99 KB
/
settle_bet.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
package keeper
import (
"fmt"
sdk "github.com/cosmos/cosmos-sdk/types"
sdkerrors "github.com/cosmos/cosmos-sdk/types/errors"
"github.com/furynet/furynetwork/x/bet/types"
markettypes "github.com/furynet/furynetwork/x/market/types"
)
// singlePageNum used to return single page result in pagination.
const singlePageNum = 1
// SettleBet settles a single bet and updates it in KVStore
func (k Keeper) SettleBet(ctx sdk.Context, bettorAddressStr, betUID string) error {
if !types.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 := checkBetStatus(bet.Status); err != nil {
// bet cancellation logic will reside here if this feature is requested
return 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.srKeeper.RefundBettor(ctx, bettorAddress, bet.Amount, payoutProfit.TruncateInt(), bet.UID); err != nil {
return sdkerrors.Wrapf(types.ErrInSRRefund, "%s", err)
}
bet.Status = types.Bet_STATUS_SETTLED
bet.Result = types.Bet_RESULT_REFUNDED
k.updateSettlementState(ctx, bet, uid2ID.ID)
return nil
}
// check if the bet odds is a winner odds or not and set the bet pointer states
if err := processBetResultAndStatus(&bet, market); err != nil {
return err
}
if err := k.settleResolvedBet(ctx, &bet); err != nil {
return err
}
k.updateSettlementState(ctx, bet, uid2ID.ID)
return nil
}
// updateSettlementState settles bet in the store
func (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 strategicReserve functions to unlock fund and payout
// based on bet's result, and updates status of bet to settled
func (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.srKeeper.BettorLoses(ctx, bettorAddress, bet.Amount, payout.TruncateInt(), bet.UID, bet.BetFulfillment, bet.MarketUID); err != nil {
return sdkerrors.Wrapf(types.ErrInSRBettorLoses, "%s", err)
}
bet.Status = types.Bet_STATUS_SETTLED
} else if bet.Result == types.Bet_RESULT_WON {
if err := k.srKeeper.BettorWins(ctx, bettorAddress, bet.Amount, payout.TruncateInt(), bet.UID, bet.BetFulfillment, bet.MarketUID); err != nil {
return sdkerrors.Wrapf(types.ErrInSRBettorWins, "%s", err)
}
bet.Status = types.Bet_STATUS_SETTLED
}
return nil
}
// 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 parameter
for 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 {
return nil
}
// settle market pending bets.
settledCount, err := k.batchSettlementOfMarket(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.srKeeper.SetOrderBookAsSettled(ctx, marketUID)
if err != nil {
return fmt.Errorf("could not resolve strategicreserve %s %s", marketUID, err)
}
}
// update counter of bets to be processed in the next iteration.
toFetch -= settledCount
}
return nil
}
// batchSettlementOfMarket settles pending bets of a markets
func (k Keeper) batchSettlementOfMarket(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))
defer func() {
iterErr := iterator.Close()
if iterErr != nil {
err = iterErr
}
}()
// settle bets for the filtered pending bets
for ; 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
}
// checkBetStatus checks status of bet. It returns an error if
// bet is canceled or settled already
func checkBetStatus(betstatus types.Bet_Status) error {
switch betstatus {
case types.Bet_STATUS_SETTLED:
return types.ErrBetIsSettled
case types.Bet_STATUS_CANCELED:
return types.ErrBetIsCanceled
}
return nil
}
// processBetResultAndStatus determines the result and status of the given bet, it can be lost or won.
func processBetResultAndStatus(bet *types.Bet, market markettypes.Market) error {
// check if market result is declared or not
if market.Status != markettypes.MarketStatus_MARKET_STATUS_RESULT_DECLARED {
return types.ErrResultNotDeclared
}
var exist bool
for _, wid := range market.WinnerOddsUIDs {
if wid == bet.OddsUID {
exist = true
break
}
}
if exist {
// bettor is winner
bet.Result = types.Bet_RESULT_WON
bet.Status = types.Bet_STATUS_RESULT_DECLARED
return nil
}
// bettor is loser
bet.Result = types.Bet_RESULT_LOST
bet.Status = types.Bet_STATUS_RESULT_DECLARED
return nil
}