-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathpotato.mligo
326 lines (296 loc) · 16 KB
/
potato.mligo
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
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
#if !POTATO
#define POTATO
#include "types.mligo"
type game_data =
[@layout:comb]
{
game_id: TicketBook.game_id; (* ID of this game *)
admin: address; (* tz address of person who made this particular game *)
start_time: timestamp; (* when the game will start NOT USED YET *)
in_progress: bool; (* is the game in progress *)
num_players: nat; (* number of currently registered players *)
winner: address option; (* most recent person to send potato back *)
game_over: bool; (* is the game ended *)
}
type all_game_data = (TicketBook.game_id, game_data) big_map
type game_storage =
[@layout:comb]
{
(* just pure game data by game_id, no tickets in here *)
data: all_game_data;
(* ticket books by game_id, each of which is holding N tickets per game *)
tickets: TicketBook.t;
(* counter of unique game IDs *)
next_game_id : TicketBook.game_id;
}
type parameter =
(* potato ticket stuff *)
| New_game of new_game_param (* admin opens a new game for people to register up to *)
| Buy_potato_for_game of buy_potato_param (* non-admin register for a game by buying a potato *)
| Start_game of TicketBook.game_id (* admin starts the game *)
| Pass_potato of pass_potato_param (* non-admin passes the potato (ticket) back *)
| End_game of TicketBook.game_id (* admin ends game, winner is last person to give back before end of game *)
(* FA2 stuff *)
| Transfer of transfer list
| Balance_of of balance_of_param
| Update_operators of update_operator list
type return = operation list * game_storage
type flattened_key = address*address*TicketBook.game_id
type flattened = (flattened_key, nat) map
let main (action, store: parameter * game_storage) : return =
begin
let {data = data; tickets = tickets; next_game_id = next_game_id} = store in
( match action with
| New_game new_game_param ->
(*let now = Tezos.now in*)
(*assert (now < start_time);*)
if (Big_map.mem next_game_id data) then (failwith "Game already created" : return) else
let {admin = admin; start_time = start_time; max_players = max_players; } = new_game_param in
if (max_players < 2n) then (failwith "Games should have at least two players" : return) else
let winner : address option = None in
let game_data : game_data = {
game_id = next_game_id;
admin = admin;
start_time = start_time;
in_progress = false;
num_players = 0n;
winner = winner;
game_over = false;
} in
let data = Big_map.add next_game_id game_data data in
let tickets = TicketBook.create_with next_game_id max_players tickets in
( ([] : operation list), { data = data; tickets=tickets; next_game_id = (next_game_id + 1n) } )
| Buy_potato_for_game buy_potato_param -> (
match (Big_map.find_opt buy_potato_param.game_id data) with
| None -> (failwith Errors.potato_NO_GAME_DATA : return)
| Some game_data ->
(*let now = Tezos.now in*)
if (buy_potato_param.game_id <> game_data.game_id) then (failwith Errors.potato_WRONG_GAME : return) else
let purchase_price = Tezos.amount in
if (purchase_price <> 10tez) then (failwith "Games cost 10tez" : return) else
let addr = Tezos.sender in
if (addr = game_data.admin) then (failwith "Game owner cannot buy a potato" : return) else
(*assert (now < game_data.start_time);*)
if game_data.game_over then (failwith "Game already finished" : return) else
if game_data.in_progress then (failwith "Game already in progress" : return) else
let (tkt, tickets) = TicketBook.get game_data.game_id tickets in (
match tkt with
| None -> (failwith Errors.potato_NO_TICKET : return)
| Some tkt ->
let ((_addr, (game, amt)), tkt) = Tezos.read_ticket tkt in (
match (game.game_id = game_data.game_id, Tezos.split_ticket tkt (1n, abs(amt-1n))) with
| (false, _) -> (failwith Errors.potato_WRONG_GAME : return)
| (true, None) -> (failwith Errors.potato_EMPTY_TICKETBOOK : return)
| (true, Some (tkt, book)) ->
let op = Tezos.transaction tkt 0mutez buy_potato_param.dest in
let (_, tickets) = Big_map.get_and_update game_data.game_id (Some book) tickets in
let game_data = {game_data with num_players = game_data.num_players + 1n} in
let data = Big_map.update game_data.game_id (Some game_data) data in
([op], {data = data; tickets = tickets; next_game_id = next_game_id})
)
)
)
| Start_game game_id -> (
match (Big_map.find_opt game_id data) with
| None -> (failwith Errors.potato_NO_GAME_DATA : return)
| Some game_data ->
if (game_id <> game_data.game_id) then (failwith Errors.potato_WRONG_GAME : return) else
(*let now = Tezos.now in *)
(*assert (now >= game_data.start_time);*)
let addr = Tezos.sender in
if (addr <> game_data.admin) then (failwith "Game owner has to start the game" : return) else
if game_data.in_progress then (failwith "Game already in progress" : return) else
if (game_data.num_players = 0n) then (failwith "No players for game" : return) else
if game_data.game_over then (failwith "Game already finished" : return) else
let game_data = {game_data with in_progress = true} in
let data = Big_map.update game_id (Some game_data) data in
( ([] : operation list), {data = data ; tickets = tickets; next_game_id = next_game_id} )
)
| Pass_potato potato ->
let {game_id = game_id; ticket=ticket; winner=winner} = potato in (
match (Big_map.find_opt game_id data) with
| None -> (failwith Errors.potato_NO_GAME_DATA : return)
| Some game_data ->
if (game_id <> game_data.game_id) then (failwith Errors.potato_WRONG_GAME : return) else
(*let now = Tezos.now in*)
if (winner = game_data.admin) then (failwith "Game owner cannot win own game" : return) else
(*assert (now >= game_data.start_time);*)
if not game_data.in_progress then (failwith "Game not in progress" : return) else
if game_data.game_over then (failwith "Game already finished" : return) else
let (tkt, tickets) = TicketBook.get game_id tickets in (
match tkt with
| None -> (failwith Errors.potato_NO_TICKET : return)
| Some tkt ->
let ((_addr, (game, _amt)), tkt) = Tezos.read_ticket tkt in (
match (game_id = game.game_id, Tezos.join_tickets (ticket, tkt)) with
| (false, _) -> (failwith Errors.potato_WRONG_GAME : return)
| (true, None) -> (failwith Errors.potato_WRONG_GAME : return)
| (true, Some book) ->
let (_, tickets) = Big_map.get_and_update game_id (Some book) tickets in
let game_data = {game_data with winner = (Some winner)} in
let data = Big_map.update game_id (Some game_data) data in
( ([] : operation list), {data = data ; tickets = tickets; next_game_id = next_game_id} )
)
)
)
| End_game game_id -> (
match (Big_map.find_opt game_id data) with
| None -> (failwith Errors.potato_NO_GAME_DATA : return)
| Some game_data ->
if (game_id <> game_data.game_id) then (failwith Errors.potato_WRONG_GAME : return) else
(*let now = Tezos.now in*)
(*assert (now >= game_data.start_time);*)
let addr = Tezos.sender in
if (addr <> game_data.admin) then (failwith "Game owner has to end the game" : return) else
if not game_data.in_progress then (failwith "Game not in progress" : return) else
if game_data.game_over then (failwith "Game already finished" : return) else
let game_data = {game_data with game_over = true} in
let data = Big_map.update game_id (Some game_data) data in
let (_, tickets) = TicketBook.get game_id tickets in (
match (game_data.winner) with
| None -> ( ([] : operation list), {data = data ; tickets = tickets; next_game_id = next_game_id} )
| Some winner -> (
match ((Tezos.get_contract_opt winner) : unit contract option) with
| None -> (failwith Errors.potato_BAD_CONTRACT : return)
| Some c ->
let winnings = game_data.num_players * 10tez in
let op1 = Tezos.transaction () winnings c in
( [op1], {data = data ; tickets = tickets; next_game_id = next_game_id} )
)
)
)
(* FA2 spec relates to games ie balance is number of games, transfers are of games *)
| Transfer transfers -> begin
(* NOTE it is important not to modify any of data - a big_map - until
we know that it is ok to do so.
Hence strategy is to check everything up front first then
do a final edit pass on the transfers
*)
(* some quick checks before bigger stuff *)
let _all_token_ids_exist (acc, tdest : bool*transfer_destination) : bool =
acc && (Big_map.mem tdest.token_id data)
in
(* have to be ==0 or 1 as the game is like an NFT too *)
let _all_amounts_ok_pass1 (acc, tdest : bool*transfer_destination) : bool =
acc && (tdest.amount < 2n)
in
(* this function applies the two quick checkers to the inner lists of each transfer *)
let quick_check (t : transfer) : unit =
let token_ids_ok = List.fold _all_token_ids_exist t.txs true in
let amounts_ok = List.fold _all_amounts_ok_pass1 t.txs true in
match (token_ids_ok, amounts_ok) with
| (true, true) -> () (* NOTE this needs to come first *)
| (false, _) -> (failwith Errors.fa2_TOKEN_UNDEFINED : unit)
| (_, false) -> (failwith Errors.fa2_INSUFFICIENT_BALANCE : unit)
in
let _u : unit = List.iter quick_check transfers in
(* bigger stuff - flatten down to (from, to, token_id) -> accumulated amount map *)
let _flatten (from_ : address) (acc, tdest : flattened*transfer_destination) : flattened =
let key = (from_, tdest.to_, tdest.token_id) in
let amount_ = match Map.find_opt key acc with
| Some amount_ -> amount_ + tdest.amount
| None -> 0n
in
Map.update key (Some amount_) acc
in
let all_amounts = List.fold (
fun (acc, t : flattened*transfer) -> List.fold (_flatten t.from_) t.txs acc
) transfers (Map.empty : flattened)
in
(* can now check accumulated amounts for each to/from/game_id tuple
and also that the from address is the current owner of the game
*)
let full_check ((from_, _to, game_id), amount_ : flattened_key*nat) : unit =
(* not quite same as before as this is an accumulated total now *)
if amount_ > 1n then (failwith Errors.fa2_INSUFFICIENT_BALANCE : unit) else
match Big_map.find_opt game_id data with
| None -> (failwith Errors.fa2_TOKEN_UNDEFINED : unit) (* same as before but have to deal with it again *)
| Some game ->
(* only allow current admin to send non-zero amounts of their own stuff *)
if from_ <> game.admin then
if amount_ > 0n then (failwith Errors.fa2_NOT_OPERATOR : unit) else ()
else ()
in
let _u : unit = Map.iter full_check all_amounts in
(* Now is safe to edit data - go over one more time to modify admin address if amount_ == 1
NOTE have to do this after checking everything as dont want to partially change data *)
let change_admin (acc, ((_from, to_, game_id), amount_) : all_game_data*(flattened_key*nat)) : all_game_data =
if amount_ = 0n then acc else
match Big_map.find_opt game_id acc with
| None -> acc
| Some game ->
let game = {game with admin = to_} in
Big_map.update game_id (Some game) acc
in
let data = Map.fold change_admin all_amounts data in
(([] : operation list), {data = data; tickets = tickets; next_game_id = next_game_id})
end
| Balance_of bp -> begin
let _all_token_ids_exist (acc, req : bool*balance_of_request) : bool =
acc && (Big_map.mem req.token_id data)
in
let _get_balance (req : balance_of_request) : balance_of_response =
let zero_balance = {request=req; balance=0n} in
let one_balance = {request=req; balance=1n} in
match Big_map.find_opt req.token_id data with
| Some game -> if req.owner = game.admin then one_balance else zero_balance
| None -> zero_balance
in
match (List.fold _all_token_ids_exist bp.requests true) with
| false -> (failwith Errors.fa2_TOKEN_UNDEFINED : return)
| true ->
let resps = List.map _get_balance bp.requests in
let op = Tezos.transaction resps 0mutez bp.callback in
([op], {data = data; tickets = tickets; next_game_id = next_game_id})
end
| Update_operators _ -> (failwith Errors.fa2_NOT_OPERATOR : return)
)
end
(* (Pair {} {} 0) *)
let sample_storage : game_storage = {
data = (Big_map.empty : (TicketBook.game_id, game_data) big_map);
tickets = TicketBook.empty;
next_game_id = 0n;
}
(*
let test =
let _check_game_data = fun (actual : game_data) (expected : game_data) ->
let _ = assert (actual.game_id = expected.game_id) in
let _ = assert (actual.admin = expected.admin) in
let _ = assert (actual.start_time = expected.start_time) in
let _ = assert (actual.in_progress = expected.in_progress) in
let _ = assert (actual.num_players = expected.num_players) in
let _ = assert (actual.winner = expected.winner) in
let _ = assert (actual.game_over = expected.game_over) in
()
in
let admin = ("tz1KqTpEZ7Yob7QbPE4Hy4Wo8fHG8LhKxZSx" : address) in
let _ = Test.set_source admin in
let init_data = {
game_id = "";
admin = admin;
start_time = ("2021-07-26t15:45:10Z" : timestamp);
in_progress = false;
num_players = 0n;
winner = (None : address option);
game_over = false;
} in
let init_tickets : (game_id, TicketBook.tkt) big_map = Big_map.empty in
let init_storage = { data = (None : game_data option); tickets = init_tickets } in
let (taddr, _, _) = Test.originate main init_storage 0tez in
let actual = Test.get_storage taddr in
let _ = _check_game_data actual.data init_data in
let _ = assert (false = Big_map.mem "game01" actual.tickets) in
let c = Test.to_contract taddr in
let new_game_param = {
game_id = "game01";
admin = admin;
start_time = ("2021-07-26t16:45:10Z" : timestamp);
max_players = 10n;
} in
let () = Test.transfer_to_contract_exn c (New_game new_game_param) 1mutez in
let actual = Test.get_storage taddr in
let _ = _check_game_data actual.data init_data in
()
*)
#endif