1 | | - | {-# STDLIB_VERSION 6 #-} |
---|
2 | | - | {-# SCRIPT_TYPE ACCOUNT #-} |
---|
3 | | - | {-# CONTENT_TYPE DAPP #-} |
---|
4 | | - | let SEP = "__" |
---|
5 | | - | |
---|
6 | | - | let WAVESID = base58'WAVES' |
---|
7 | | - | |
---|
8 | | - | let WAVESD = 100000000 |
---|
9 | | - | |
---|
10 | | - | let GAME_NAME = "Card of the Day" |
---|
11 | | - | |
---|
12 | | - | let RANDOM_RANGE = 40 |
---|
13 | | - | |
---|
14 | | - | let NUM_BETS = 1 |
---|
15 | | - | |
---|
16 | | - | func getStrOrFail (address,key) = valueOrErrorMessage(getString(address, key), makeString(["mandatory ", toString(address), ".", key, " is not defined"], "")) |
---|
17 | | - | |
---|
18 | | - | |
---|
19 | | - | func getIntOrFail (address,key) = valueOrErrorMessage(getInteger(address, key), makeString(["mandatory ", toString(address), ".", key, " is not defined"], "")) |
---|
20 | | - | |
---|
21 | | - | |
---|
22 | | - | func getBoolOrFail (address,key) = valueOrErrorMessage(getBoolean(address, key), makeString(["mandatory ", toString(address), ".", key, " is not defined"], "")) |
---|
23 | | - | |
---|
24 | | - | |
---|
25 | | - | let allowedAssetsKey = "%s%s__cfg__allowedAssets" |
---|
26 | | - | |
---|
27 | | - | let assetsDecimalsKey = "%s%s__cfg__assetsDecimals" |
---|
28 | | - | |
---|
29 | | - | let betDividersKey = "%s%s__cfg__assetsBetDividers" |
---|
30 | | - | |
---|
31 | | - | let RSAPUBLIC64KEY = "%s%s__cfg__rsaPublic64" |
---|
32 | | - | |
---|
33 | | - | let SERVERADDRESSKEY = "%s%s__cfg__benzAddress" |
---|
34 | | - | |
---|
35 | | - | let RANDTIMEFRAMEKEY = "%s%s__cfg__withdrawTimeFrame" |
---|
36 | | - | |
---|
37 | | - | let GAMESCOUNTERKEY = "%s%s__runtime__gameNum" |
---|
38 | | - | |
---|
39 | | - | let blockedKey = "%s%s__runtime__contractIsBlocked" |
---|
40 | | - | |
---|
41 | | - | func getIntArray (key) = { |
---|
42 | | - | let a = getStrOrFail(this, key) |
---|
43 | | - | func filler (acc,el) = (acc :+ parseIntValue(el)) |
---|
44 | | - | |
---|
45 | | - | let $l = split(a, SEP) |
---|
46 | | - | let $s = size($l) |
---|
47 | | - | let $acc0 = nil |
---|
48 | | - | func $f0_1 ($a,$i) = if (($i >= $s)) |
---|
49 | | - | then $a |
---|
50 | | - | else filler($a, $l[$i]) |
---|
51 | | - | |
---|
52 | | - | func $f0_2 ($a,$i) = if (($i >= $s)) |
---|
53 | | - | then $a |
---|
54 | | - | else throw("List size exceeds 10") |
---|
55 | | - | |
---|
56 | | - | $f0_2($f0_1($f0_1($f0_1($f0_1($f0_1($f0_1($f0_1($f0_1($f0_1($f0_1($acc0, 0), 1), 2), 3), 4), 5), 6), 7), 8), 9), 10) |
---|
57 | | - | } |
---|
58 | | - | |
---|
59 | | - | |
---|
60 | | - | let ASSETS = split(getStrOrFail(this, allowedAssetsKey), SEP) |
---|
61 | | - | |
---|
62 | | - | let DECIMALS = getIntArray(assetsDecimalsKey) |
---|
63 | | - | |
---|
64 | | - | let BETDIVIDERS = getIntArray(betDividersKey) |
---|
65 | | - | |
---|
66 | | - | func keyReservationByAssetStr (assetStr) = ("$RESERVED_AMOUNT_" + assetStr) |
---|
67 | | - | |
---|
68 | | - | |
---|
69 | | - | func keyReservationByAssetIdx (assetIdx) = keyReservationByAssetStr(ASSETS[assetIdx]) |
---|
70 | | - | |
---|
71 | | - | |
---|
72 | | - | let MINFEEWAVES = ((5 * WAVESD) / 1000) |
---|
73 | | - | |
---|
74 | | - | let idxAssets = 0 |
---|
75 | | - | |
---|
76 | | - | let idxDecimals = 1 |
---|
77 | | - | |
---|
78 | | - | let idxDividers = 2 |
---|
79 | | - | |
---|
80 | | - | let BET1 = 1 |
---|
81 | | - | |
---|
82 | | - | let BET2 = 2 |
---|
83 | | - | |
---|
84 | | - | let BET4 = 4 |
---|
85 | | - | |
---|
86 | | - | let BET8 = 8 |
---|
87 | | - | |
---|
88 | | - | let BET14 = 14 |
---|
89 | | - | |
---|
90 | | - | let RATEMULT = 10000 |
---|
91 | | - | |
---|
92 | | - | let RATE = 100000 |
---|
93 | | - | |
---|
94 | | - | let BETS = [BET2] |
---|
95 | | - | |
---|
96 | | - | let IdxGameState = 0 |
---|
97 | | - | |
---|
98 | | - | let IdxPlayerChoice = 1 |
---|
99 | | - | |
---|
100 | | - | let IdxPlayerPubKey58 = 2 |
---|
101 | | - | |
---|
102 | | - | let IdxStartedHeight = 3 |
---|
103 | | - | |
---|
104 | | - | let IdxWinAmount = 4 |
---|
105 | | - | |
---|
106 | | - | let IdxAssetId = 5 |
---|
107 | | - | |
---|
108 | | - | let STATESUBMITTED = "SUBMITTED" |
---|
109 | | - | |
---|
110 | | - | let STATEWON = "WON" |
---|
111 | | - | |
---|
112 | | - | let STATELOST = "LOST" |
---|
113 | | - | |
---|
114 | | - | func getStringOrFail (key) = valueOrErrorMessage(getString(this, key), (key + " key is not specified in this.state")) |
---|
115 | | - | |
---|
116 | | - | |
---|
117 | | - | let RSAPUBLIC = fromBase64String(getStringOrFail(RSAPUBLIC64KEY)) |
---|
118 | | - | |
---|
119 | | - | let SERVER = addressFromStringValue(getStringOrFail(SERVERADDRESSKEY)) |
---|
120 | | - | |
---|
121 | | - | let RANDORACLETIMEFRAME = valueOrElse(getInteger(this, RANDTIMEFRAMEKEY), 7200) |
---|
122 | | - | |
---|
123 | | - | func getIntOr (key,default) = if (isDefined(getInteger(key))) |
---|
124 | | - | then getIntegerValue(key) |
---|
125 | | - | else default |
---|
126 | | - | |
---|
127 | | - | |
---|
128 | | - | func setInt (key,value) = IntegerEntry(key, value) |
---|
129 | | - | |
---|
130 | | - | |
---|
131 | | - | func incrementInt (key) = setInt(key, (getIntOr(key, -1) + 1)) |
---|
132 | | - | |
---|
133 | | - | |
---|
134 | | - | func changeInt (key,by) = setInt(key, (getIntOr(key, 0) + by)) |
---|
135 | | - | |
---|
136 | | - | |
---|
137 | | - | func assetIdToStr (assetIdOrUnit) = match assetIdOrUnit { |
---|
138 | | - | case b: ByteVector => |
---|
139 | | - | toBase58String(b) |
---|
140 | | - | case _ => |
---|
141 | | - | "WAVES" |
---|
142 | | - | } |
---|
143 | | - | |
---|
144 | | - | |
---|
145 | | - | func assetIdFromStr (assetIdStr) = if ((assetIdStr == "WAVES")) |
---|
146 | | - | then unit |
---|
147 | | - | else fromBase58String(assetIdStr) |
---|
148 | | - | |
---|
149 | | - | |
---|
150 | | - | func getAssetBalance (assetIdOrUnit) = match assetIdOrUnit { |
---|
151 | | - | case assetId: ByteVector => |
---|
152 | | - | assetBalance(this, assetId) |
---|
153 | | - | case _ => |
---|
154 | | - | wavesBalance(this).available |
---|
155 | | - | } |
---|
156 | | - | |
---|
157 | | - | |
---|
158 | | - | func increaseReserveAmount (winAmount,assetIdx) = { |
---|
159 | | - | let assetIdStr = ASSETS[assetIdx] |
---|
160 | | - | let newReservedAmount = (getIntOr(keyReservationByAssetIdx(assetIdx), 0) + winAmount) |
---|
161 | | - | if ((newReservedAmount > getAssetBalance(assetIdFromStr(assetIdStr)))) |
---|
162 | | - | then throw((("Insufficient funds on " + GAME_NAME) + " account. Transaction was rejected for your safety.")) |
---|
163 | | - | else newReservedAmount |
---|
164 | | - | } |
---|
165 | | - | |
---|
166 | | - | |
---|
167 | | - | func decreaseReservedAmount (gameId,assetIdx,winAmount) = if ((0 > (getIntOr(keyReservationByAssetIdx(assetIdx), 0) - winAmount))) |
---|
168 | | - | then throw((("Invalid " + GAME_NAME) + " account state - reserved amount is less than 0")) |
---|
169 | | - | else changeInt(keyReservationByAssetIdx(assetIdx), -(winAmount)) |
---|
170 | | - | |
---|
171 | | - | |
---|
172 | | - | func validateAndGetAssetIdx (assetIdStr) = { |
---|
173 | | - | let idx = indexOf(ASSETS, assetIdStr) |
---|
174 | | - | if (!(isDefined(idx))) |
---|
175 | | - | then throw("Invalid payment asset") |
---|
176 | | - | else value(idx) |
---|
177 | | - | } |
---|
178 | | - | |
---|
179 | | - | |
---|
180 | | - | func validateBetAndGetWinAmount (bet,internalAssetIdx,playerChoice) = { |
---|
181 | | - | let dicesCount = size(playerChoice) |
---|
182 | | - | func checkAmount (a,x) = if (a) |
---|
183 | | - | then true |
---|
184 | | - | else (bet == ((x * DECIMALS[internalAssetIdx]) / BETDIVIDERS[internalAssetIdx])) |
---|
185 | | - | |
---|
186 | | - | if (!({ |
---|
187 | | - | let $l = BETS |
---|
188 | | - | let $s = size($l) |
---|
189 | | - | let $acc0 = false |
---|
190 | | - | func $f0_1 ($a,$i) = if (($i >= $s)) |
---|
191 | | - | then $a |
---|
192 | | - | else checkAmount($a, $l[$i]) |
---|
193 | | - | |
---|
194 | | - | func $f0_2 ($a,$i) = if (($i >= $s)) |
---|
195 | | - | then $a |
---|
196 | | - | else throw("List size exceeds 5") |
---|
197 | | - | |
---|
198 | | - | $f0_2($f0_1($f0_1($f0_1($f0_1($f0_1($acc0, 0), 1), 2), 3), 4), 5) |
---|
199 | | - | })) |
---|
200 | | - | then throw("Bet amount is not valid") |
---|
201 | | - | else if ((parseInt(playerChoice) == unit)) |
---|
202 | | - | then throw("Invalid player's choice") |
---|
203 | | - | else if ((dicesCount != NUM_BETS)) |
---|
204 | | - | then throw("Invalid length of player's choice") |
---|
205 | | - | else fraction(bet, RATE, RATEMULT) |
---|
206 | | - | } |
---|
207 | | - | |
---|
208 | | - | |
---|
209 | | - | func generateRandChoice (gameId,rsaSign,playerChoice) = { |
---|
210 | | - | let rsaSigValid = rsaVerify_16Kb(SHA256, toBytes(gameId), rsaSign, RSAPUBLIC) |
---|
211 | | - | if (!(rsaSigValid)) |
---|
212 | | - | then throw("Invalid RSA signature") |
---|
213 | | - | else { |
---|
214 | | - | let rand = (toInt(sha256((rsaSign + toBytes(playerChoice)))) % RANDOM_RANGE) |
---|
215 | | - | toString(rand) |
---|
216 | | - | } |
---|
217 | | - | } |
---|
218 | | - | |
---|
219 | | - | |
---|
220 | | - | func isPlayerWin (playerChoice,randChoise) = if ((size(playerChoice) == NUM_BETS)) |
---|
221 | | - | then (playerChoice == randChoise) |
---|
222 | | - | else false |
---|
223 | | - | |
---|
224 | | - | |
---|
225 | | - | func formatGameDataS (gameStatus,playerChoice,playerPubKey58,startedHeight,winAmount,assetIdx,randOrEmpty) = makeString([gameStatus, playerChoice, playerPubKey58, startedHeight, winAmount, assetIdx, if ((randOrEmpty == "")) |
---|
226 | | - | then "" |
---|
227 | | - | else randOrEmpty], "_") |
---|
228 | | - | |
---|
229 | | - | |
---|
230 | | - | func formatGameData (gameStatus,playerChoice,playerPubKey58,startedHeight,winAmount,assetIdx,randOrEmpty) = formatGameDataS(gameStatus, playerChoice, playerPubKey58, toString(startedHeight), toString(winAmount), toString(assetIdx), randOrEmpty) |
---|
231 | | - | |
---|
232 | | - | |
---|
233 | | - | func finishGameData (origGameData,gameStatus,rand,winByTimeout) = { |
---|
234 | | - | let finishGameData = formatGameDataS(gameStatus, origGameData[IdxPlayerChoice], origGameData[IdxPlayerPubKey58], origGameData[IdxStartedHeight], origGameData[IdxWinAmount], origGameData[IdxAssetId], rand) |
---|
235 | | - | if (winByTimeout) |
---|
236 | | - | then (finishGameData + "_TIMEOUT") |
---|
237 | | - | else finishGameData |
---|
238 | | - | } |
---|
239 | | - | |
---|
240 | | - | |
---|
241 | | - | func extractGameData (gameId) = split(match getString(this, gameId) { |
---|
242 | | - | case str: String => |
---|
243 | | - | str |
---|
244 | | - | case _ => |
---|
245 | | - | throw((("Game: " + gameId) + " not found.")) |
---|
246 | | - | }, "_") |
---|
247 | | - | |
---|
248 | | - | |
---|
249 | | - | @Callable(i) |
---|
250 | | - | func constructorV1 (rsaPublic64,benzAddress,randOracleTimeFrame,tokensDescriptor) = if ((i.caller != SERVER)) |
---|
251 | | - | then throw("not authorized") |
---|
252 | | - | else { |
---|
253 | | - | func splitter (acc,elem) = { |
---|
254 | | - | let tokList = split(elem, ":") |
---|
255 | | - | if ((size(tokList) != 4)) |
---|
256 | | - | then throw("Invalid asset record") |
---|
257 | | - | else $Tuple3((acc._1 :+ tokList[idxAssets]), (acc._2 :+ tokList[idxDecimals]), (acc._3 :+ tokList[idxDividers])) |
---|
258 | | - | } |
---|
259 | | - | |
---|
260 | | - | let r = { |
---|
261 | | - | let $l = split_4C(tokensDescriptor, "_") |
---|
262 | | - | let $s = size($l) |
---|
263 | | - | let $acc0 = $Tuple3(nil, nil, nil) |
---|
264 | | - | func $f0_1 ($a,$i) = if (($i >= $s)) |
---|
265 | | - | then $a |
---|
266 | | - | else splitter($a, $l[$i]) |
---|
267 | | - | |
---|
268 | | - | func $f0_2 ($a,$i) = if (($i >= $s)) |
---|
269 | | - | then $a |
---|
270 | | - | else throw("List size exceeds 10") |
---|
271 | | - | |
---|
272 | | - | $f0_2($f0_1($f0_1($f0_1($f0_1($f0_1($f0_1($f0_1($f0_1($f0_1($f0_1($acc0, 0), 1), 2), 3), 4), 5), 6), 7), 8), 9), 10) |
---|
273 | | - | } |
---|
274 | | - | [StringEntry(RSAPUBLIC64KEY, rsaPublic64), StringEntry(SERVERADDRESSKEY, benzAddress), IntegerEntry(RANDTIMEFRAMEKEY, randOracleTimeFrame), StringEntry(allowedAssetsKey, makeString_2C(r._1, SEP)), StringEntry(assetsDecimalsKey, makeString(r._2, SEP)), StringEntry(betDividersKey, makeString(r._3, SEP))] |
---|
275 | | - | } |
---|
276 | | - | |
---|
277 | | - | |
---|
278 | | - | |
---|
279 | | - | @Callable(i) |
---|
280 | | - | func maintenance (blocked) = if ((i.caller != SERVER)) |
---|
281 | | - | then throw("not authorized") |
---|
282 | | - | else [BooleanEntry(blockedKey, blocked)] |
---|
283 | | - | |
---|
284 | | - | |
---|
285 | | - | |
---|
286 | | - | @Callable(i) |
---|
287 | | - | func bet (playerChoice) = if (valueOrElse(getBoolean(blockedKey), false)) |
---|
288 | | - | then throw("Game is stopped for maintenence") |
---|
289 | | - | else { |
---|
290 | | - | let gameId = toBase58String(i.transactionId) |
---|
291 | | - | if ((1 >= size(i.payments))) |
---|
292 | | - | then throw("2 payments must be attached") |
---|
293 | | - | else if (isDefined(getString(this, gameId))) |
---|
294 | | - | then throw((("Bet for: " + gameId) + " was already made.")) |
---|
295 | | - | else { |
---|
296 | | - | let betPmt = value(i.payments[0]) |
---|
297 | | - | let feePmt = value(i.payments[1]) |
---|
298 | | - | if (isDefined(feePmt.assetId)) |
---|
299 | | - | then throw("feePmt (2nd payment) assetId must be in Waves") |
---|
300 | | - | else if ((MINFEEWAVES > feePmt.amount)) |
---|
301 | | - | then throw("feePmt (2nd payment) must be >= 0.005 Waves") |
---|
302 | | - | else { |
---|
303 | | - | let assetIdStr = assetIdToStr(betPmt.assetId) |
---|
304 | | - | let internalAssetIdx = validateAndGetAssetIdx(assetIdStr) |
---|
305 | | - | let commission = feePmt.amount |
---|
306 | | - | let winAmount = validateBetAndGetWinAmount(betPmt.amount, internalAssetIdx, playerChoice) |
---|
307 | | - | let playerPubKey58 = toBase58String(i.callerPublicKey) |
---|
308 | | - | let gameData = formatGameData(STATESUBMITTED, playerChoice, playerPubKey58, height, winAmount, internalAssetIdx, "") |
---|
309 | | - | [IntegerEntry(keyReservationByAssetIdx(internalAssetIdx), increaseReserveAmount(winAmount, internalAssetIdx)), incrementInt(GAMESCOUNTERKEY), StringEntry(gameId, gameData), ScriptTransfer(SERVER, commission, feePmt.assetId)] |
---|
310 | | - | } |
---|
311 | | - | } |
---|
312 | | - | } |
---|
313 | | - | |
---|
314 | | - | |
---|
315 | | - | |
---|
316 | | - | @Callable(i) |
---|
317 | | - | func withdraw (gameId,rsaSign) = { |
---|
318 | | - | let gameData = extractGameData(gameId) |
---|
319 | | - | let gameState = gameData[IdxGameState] |
---|
320 | | - | let playerChoice = gameData[IdxPlayerChoice] |
---|
321 | | - | let startedHeight = parseIntValue(gameData[IdxStartedHeight]) |
---|
322 | | - | let winAmount = parseIntValue(gameData[IdxWinAmount]) |
---|
323 | | - | let assetIdx = parseIntValue(gameData[IdxAssetId]) |
---|
324 | | - | let playerPubKey58 = gameData[IdxPlayerPubKey58] |
---|
325 | | - | let playerAddress = addressFromPublicKey(fromBase58String(playerPubKey58)) |
---|
326 | | - | if ((gameState != STATESUBMITTED)) |
---|
327 | | - | then throw("Invalid game state for passed gameId") |
---|
328 | | - | else if ((i.caller != SERVER)) |
---|
329 | | - | then throw("Regular withdraw can be done by server only") |
---|
330 | | - | else { |
---|
331 | | - | let winByTimeout = ((height - startedHeight) > RANDORACLETIMEFRAME) |
---|
332 | | - | let randChoise = if (winByTimeout) |
---|
333 | | - | then playerChoice |
---|
334 | | - | else generateRandChoice(gameId, rsaSign, playerChoice) |
---|
335 | | - | let playerWin = isPlayerWin(playerChoice, randChoise) |
---|
336 | | - | let newGameStatus = if (playerWin) |
---|
337 | | - | then STATEWON |
---|
338 | | - | else STATELOST |
---|
339 | | - | let newGameData = finishGameData(gameData, newGameStatus, randChoise, winByTimeout) |
---|
340 | | - | ([StringEntry(gameId, newGameData), decreaseReservedAmount(gameId, assetIdx, winAmount)] ++ (if (playerWin) |
---|
341 | | - | then [ScriptTransfer(playerAddress, winAmount, assetIdFromStr(ASSETS[assetIdx]))] |
---|
342 | | - | else nil)) |
---|
343 | | - | } |
---|
344 | | - | } |
---|
345 | | - | |
---|
346 | | - | |
---|
| 1 | + | # no script |
---|