| 1 | [ |
| 2 | "autonomous agent", |
| 3 | { |
| 4 | "init": "{ |
| 5 | if (trigger.output[[asset!=base]].asset != 'none') |
| 6 | bounce('foreign coins'); |
| 7 | $min_stake = var['min_stake']; |
| 8 | $min_reward = var['min_reward']; |
| 9 | $coef = 1.5; |
| 10 | $overpayment_fee = 1000; |
| 11 | $period_length = 3600; |
| 12 | |
| 13 | $exchange = trigger.data.exchange; |
| 14 | $remove_wallet_id = trigger.data.remove_wallet_id; |
| 15 | $add_wallet_id = trigger.data.add_wallet_id; |
| 16 | $specified_key = trigger.data.operation_id; |
| 17 | $commit = trigger.data.commit; |
| 18 | $payment_amount = trigger.output[[asset=base]]; |
| 19 | |
| 20 | |
| 21 | if ($exchange AND length($exchange) > 40) |
| 22 | bounce('exchange cannot be over 40 chars'); |
| 23 | if ($exchange AND contains($exchange, '_')) |
| 24 | bounce('exchange cannot contain underscore'); |
| 25 | if ($remove_wallet_id AND $add_wallet_id) |
| 26 | bounce('cannot remove and add'); |
| 27 | $wallet_id = $remove_wallet_id ? $remove_wallet_id : $add_wallet_id; |
| 28 | if ($wallet_id AND !is_integer(+$wallet_id)) |
| 29 | bounce("wallet_id must be an integer"); |
| 30 | if ($wallet_id AND $wallet_id > 1e14) |
| 31 | bounce('wallet_id cannot be over 1e14'); |
| 32 | }", |
| 33 | "messages": { |
| 34 | "cases": [ |
| 35 | { |
| 36 | "if": "{$specified_key OR $exchange AND $wallet_id }", |
| 37 | "init": "{ |
| 38 | if ($specified_key AND !var[$specified_key]) |
| 39 | bounce("unknown operation"); |
| 40 | if ($specified_key AND var[$specified_key] != 'committed' AND !$commit) |
| 41 | bounce('this operation is not committed'); |
| 42 | if ($specified_key){ |
| 43 | $key = $specified_key; |
| 44 | $key_with_prefix_removed = substring($key, 10); |
| 45 | $extracted_exchange = substring($key_with_prefix_removed, 0, index_of($key_with_prefix_removed, '_')); |
| 46 | $key_with_prefix_and_exchange_removed = substring($key_with_prefix_removed, length($extracted_exchange) + 1); |
| 47 | $extracted_wallet_id = substring($key_with_prefix_and_exchange_removed, 0, index_of($key_with_prefix_and_exchange_removed, '_')); |
| 48 | $pair = 'pair_' || $extracted_exchange || '_' || $extracted_wallet_id; |
| 49 | } |
| 50 | else { |
| 51 | $pair = 'pair_' || $exchange || '_' || $wallet_id; |
| 52 | $num = var[$pair || '_number'] otherwise 0; |
| 53 | $old_key = 'operation_' || $exchange || '_' || $wallet_id || '_' || ($num); |
| 54 | if ((!$num OR var[$old_key] == 'committed') AND !$commit) |
| 55 | $key = 'operation_' || $exchange || '_' || $wallet_id || '_' || ($num + 1); |
| 56 | else |
| 57 | $key = $old_key; |
| 58 | } |
| 59 | }", |
| 60 | "messages": { |
| 61 | "cases": [ |
| 62 | { |
| 63 | "if": "{ |
| 64 | if ($commit) |
| 65 | return false; |
| 66 | if ($payment_amount <= 10000) |
| 67 | return false; |
| 68 | if ($specified_key) |
| 69 | bounce("you can specify operation id only for commit or withdrawal"); |
| 70 | $outcome = $add_wallet_id ? 'in' : 'out'; |
| 71 | $bInitialStake = !var[$key]; |
| 72 | |
| 73 | if ($bInitialStake){ |
| 74 | if ($payment_amount < $min_stake) |
| 75 | bounce("min stake is" || $min_stake); |
| 76 | if($remove_wallet_id AND (!var[$pair ||"_committed_outcome"] OR var[$pair||"_committed_outcome"] == $outcome)) |
| 77 | bounce("this wallet id is not active"); |
| 78 | if($add_wallet_id AND var[$pair||"_committed_outcome"] AND $outcome == var[$pair||"_committed_outcome"]) |
| 79 | bounce("committed outcome for this pair is already " || $outcome); |
| 80 | if (var['wallet_'|| $wallet_id||'_has_operation'] AND $add_wallet_id) |
| 81 | bounce("this wallet id belongs or is being added to another exchange"); |
| 82 | $pool_id = trigger.data.pool_id; |
| 83 | if ($pool_id){ |
| 84 | if (!is_integer(+$pool_id)) |
| 85 | bounce("pool_id must be an integer"); |
| 86 | $number_of_rewards = var['pool_' || $pool_id || '_number_of_rewards']; |
| 87 | if (!$number_of_rewards) |
| 88 | bounce("pool " || $pool_id || " doesn't exist or is empty"); |
| 89 | $pool_exchange = var['pool_' || $pool_id || '_exchange']; |
| 90 | if ($pool_exchange AND $pool_exchange != $exchange) |
| 91 | bounce("pool " || $pool_id || " is for exchange " || $pool_exchange || " only"); |
| 92 | } |
| 93 | return true; |
| 94 | } |
| 95 | |
| 96 | if (timestamp - var[$key || '_countdown_start'] > $period_length) |
| 97 | bounce('challenging period expired'); |
| 98 | $current_outcome = var[$key || '_outcome']; |
| 99 | if ($current_outcome == $outcome) |
| 100 | bounce('staking on the current outcome is not allowed'); |
| 101 | $stake_on_current_outcome = var[$key || '_total_staked_on_' || $current_outcome]; |
| 102 | $stake_on_proposed_outcome = var[$key || '_total_staked_on_' || $outcome]; |
| 103 | $required_to_challenge = round($coef * $stake_on_current_outcome); |
| 104 | $amount_left_to_challenge = $required_to_challenge - $stake_on_proposed_outcome - $payment_amount; |
| 105 | if ($amount_left_to_challenge <= 10000 AND $amount_left_to_challenge > 0) |
| 106 | bounce('amount left cannot be <= 10000'); |
| 107 | $would_override_current_outcome = $amount_left_to_challenge <= 0; |
| 108 | if ($would_override_current_outcome) |
| 109 | $excess = $stake_on_proposed_outcome + $payment_amount - $required_to_challenge; |
| 110 | true |
| 111 | }", |
| 112 | "messages": [ |
| 113 | { |
| 114 | "if": "{$excess}", |
| 115 | "app": "payment", |
| 116 | "payload": { |
| 117 | "asset": "base", |
| 118 | "outputs": [ |
| 119 | { |
| 120 | "address": "{trigger.address}", |
| 121 | "amount": "{$excess - $overpayment_fee}" |
| 122 | } |
| 123 | ] |
| 124 | } |
| 125 | }, |
| 126 | { |
| 127 | "app": "state", |
| 128 | "state": "{ |
| 129 | if ($bInitialStake){ |
| 130 | var[$pair || '_number'] += 1; |
| 131 | var[$key] = 'onreview'; |
| 132 | var[$key || '_initial_outcome'] = $outcome; |
| 133 | var[$key || '_initial_reporter'] = trigger.address; |
| 134 | var['wallet_'|| $wallet_id||'_has_operation'] = true; |
| 135 | if ($pool_id){ |
| 136 | var['pool_' || $pool_id || '_number_of_rewards'] -= 1; |
| 137 | var[$key || '_pool_id'] = $pool_id; |
| 138 | response['expected_reward'] = var['pool_' || $pool_id || '_reward_amount']; |
| 139 | } |
| 140 | } |
| 141 | response['proposed_outcome'] = $outcome; |
| 142 | if ($bInitialStake OR $would_override_current_outcome){ |
| 143 | var[$key || '_countdown_start'] = timestamp; |
| 144 | var[$key || '_outcome'] = $outcome; |
| 145 | response['resulting_outcome'] = $outcome; |
| 146 | } |
| 147 | else |
| 148 | response['resulting_outcome'] = $current_outcome; |
| 149 | |
| 150 | $accepted_amount = $payment_amount - $excess; |
| 151 | var[$key || '_total_staked_on_' || $outcome] += $accepted_amount; |
| 152 | var[$key || '_total_staked_on_' || $outcome || '_by_' || trigger.address] += $accepted_amount; |
| 153 | |
| 154 | $url_1 = trigger.data.url_1; |
| 155 | $url_2 = trigger.data.url_2; |
| 156 | $url_3 = trigger.data.url_3; |
| 157 | $url_4 = trigger.data.url_4; |
| 158 | $url_5 = trigger.data.url_5; |
| 159 | |
| 160 | if ($url_1 AND length($url_1) > 256) |
| 161 | bounce('url_1 cannot be over 256 chars'); |
| 162 | if ($url_2 AND length($url_2) > 256) |
| 163 | bounce('url_2 cannot be over 256 chars'); |
| 164 | if ($url_3 AND length($url_3) > 256) |
| 165 | bounce('url_3 cannot be over 256 chars'); |
| 166 | if ($url_4 AND length($url_4) > 256) |
| 167 | bounce('url_4 cannot be over 256 chars'); |
| 168 | if ($url_5 AND length($url_5) > 256) |
| 169 | bounce('url_5 cannot be over 256 chars'); |
| 170 | |
| 171 | if($url_1){ |
| 172 | var[$key || '_url_id_proof_for_' || $outcome] += 1; |
| 173 | $url_1_id = var[$key || '_url_id_proof_for_' || $outcome]; |
| 174 | var[$key || '_url_proof_for_' || $outcome ||'_' || $url_1_id] = $url_1; |
| 175 | } |
| 176 | if($url_2){ |
| 177 | var[$key || '_url_id_proof_for_' || $outcome] += 1; |
| 178 | $url_2_id = var[$key || '_url_id_proof_for_' || $outcome]; |
| 179 | var[$key || '_url_proof_for_' || $outcome ||'_' || $url_2_id] = $url_2; |
| 180 | } |
| 181 | if($url_3){ |
| 182 | var[$key || '_url_id_proof_for_' || $outcome] += 1; |
| 183 | $url_3_id = var[$key || '_url_id_proof_for_' || $outcome]; |
| 184 | var[$key || '_url_proof_for_' || $outcome ||'_' || $url_3_id] = $url_3; |
| 185 | } |
| 186 | if($url_4){ |
| 187 | var[$key || '_url_id_proof_for_' || $outcome] += 1; |
| 188 | $url_4_id = var[$key || '_url_id_proof_for_' || $outcome]; |
| 189 | var[$key || '_url_proof_for_' || $outcome ||'_' || $url_4_id] = $url_4; |
| 190 | } |
| 191 | if($url_5){ |
| 192 | var[$key || '_url_id_proof_for_' || $outcome] += 1; |
| 193 | $url_5_id = var[$key || '_url_id_proof_for_' || $outcome]; |
| 194 | var[$key || '_url_proof_for_' || $outcome ||'_' || $url_5_id] = $url_5; |
| 195 | } |
| 196 | response['operation_id'] = $key; |
| 197 | response['staked_on_in'] = var[$key || '_total_staked_on_in'] otherwise 0; |
| 198 | response['staked_on_out'] = var[$key || '_total_staked_on_out'] otherwise 0; |
| 199 | response['accepted_amount'] = $accepted_amount; |
| 200 | response['your_stake'] = var[$key || '_total_staked_on_' || $outcome || '_by_' || trigger.address]; |
| 201 | }" |
| 202 | } |
| 203 | ] |
| 204 | }, |
| 205 | { |
| 206 | "if": "{ |
| 207 | if (!$commit) |
| 208 | return false; |
| 209 | if (!var[$key]) |
| 210 | bounce('unknown operation'); |
| 211 | if (var[$key] == 'committed') |
| 212 | bounce('already committed'); |
| 213 | if (timestamp - var[$key || '_countdown_start'] <= $period_length) |
| 214 | bounce('challenge period is still running'); |
| 215 | $pool_id = var[$key || '_pool_id']; |
| 216 | $outcome = var[$key || '_outcome']; |
| 217 | |
| 218 | $address = var[$key || '_initial_reporter']; |
| 219 | $initial_reporter_stake = var[$key || '_total_staked_on_' || $outcome || '_by_' || $address]; |
| 220 | $require_datafeed = var[$key || '_initial_outcome'] == $outcome; |
| 221 | if ($initial_reporter_stake){ |
| 222 | $reward = var['pool_' || $pool_id || '_reward_amount'] otherwise 0; |
| 223 | $total_winning_stake = var[$key || '_total_staked_on_' || $outcome]; |
| 224 | $total_stake = var[$key || '_total_staked_on_in'] + var[$key || '_total_staked_on_out']; |
| 225 | $amount = round($initial_reporter_stake / $total_winning_stake * $total_stake); |
| 226 | $full_amount = $amount + $reward; |
| 227 | } |
| 228 | true |
| 229 | }", |
| 230 | "messages": [ |
| 231 | { |
| 232 | "if": "{$require_datafeed AND $outcome == 'in'}", |
| 233 | "app": "data_feed", |
| 234 | "payload": { |
| 235 | "{$exchange otherwise $extracted_exchange}": "{$wallet_id otherwise $extracted_wallet_id}" |
| 236 | } |
| 237 | }, |
| 238 | { |
| 239 | "if": "{$require_datafeed AND $outcome == 'out'}", |
| 240 | "app": "data_feed", |
| 241 | "payload": { |
| 242 | "{$exchange otherwise $extracted_exchange}": "{"-"||($wallet_id otherwise $extracted_wallet_id)}" |
| 243 | } |
| 244 | }, |
| 245 | { |
| 246 | "if": "{$initial_reporter_stake}", |
| 247 | "app": "payment", |
| 248 | "payload": { |
| 249 | "asset": "base", |
| 250 | "outputs": [ |
| 251 | { |
| 252 | "address": "{$address}", |
| 253 | "amount": "{$full_amount}" |
| 254 | } |
| 255 | ] |
| 256 | } |
| 257 | }, |
| 258 | { |
| 259 | "app": "state", |
| 260 | "state": "{ |
| 261 | var[$key] = 'committed'; |
| 262 | var[$pair||"_committed_outcome"] = $outcome; |
| 263 | if ($outcome == 'out') |
| 264 | var['wallet_'||($wallet_id otherwise $extracted_wallet_id)||'_has_operation'] = false; |
| 265 | if ($initial_reporter_stake){ |
| 266 | var[$key || '_total_staked_on_' || $outcome || '_by_' || $address] = false; |
| 267 | response['paid_out_amount'] = $full_amount; |
| 268 | response['paid_out_address'] = $address; |
| 269 | } |
| 270 | else if ($pool_id) |
| 271 | var['pool_' || $pool_id || '_number_of_rewards'] += 1; |
| 272 | response['pair'] = $pair; |
| 273 | response['operation_id'] = $key; |
| 274 | response['committed_outcome'] = $outcome; |
| 275 | }" |
| 276 | } |
| 277 | ] |
| 278 | }, |
| 279 | { |
| 280 | "if": "{ |
| 281 | if (!trigger.data.withdraw) |
| 282 | return false; |
| 283 | if (!var[$key]) |
| 284 | bounce('unknown feed'); |
| 285 | if (var[$key] != 'committed') |
| 286 | bounce('not committed yet'); |
| 287 | $address = trigger.data.address otherwise trigger.address; |
| 288 | $outcome = var[$key || '_outcome']; |
| 289 | $my_stake = var[$key || '_total_staked_on_' || $outcome || '_by_' || $address]; |
| 290 | if (!$my_stake) |
| 291 | bounce("you didn't stake on the winning outcome or you already withdrew"); |
| 292 | $total_winning_stake = var[$key || '_total_staked_on_' || $outcome]; |
| 293 | $total_stake = var[$key || '_total_staked_on_in'] + var[$key || '_total_staked_on_out']; |
| 294 | $amount = round($my_stake / $total_winning_stake * $total_stake); |
| 295 | true |
| 296 | }", |
| 297 | "messages": [ |
| 298 | { |
| 299 | "app": "payment", |
| 300 | "payload": { |
| 301 | "asset": "base", |
| 302 | "outputs": [ |
| 303 | { |
| 304 | "address": "{$address}", |
| 305 | "amount": "{$amount}" |
| 306 | } |
| 307 | ] |
| 308 | } |
| 309 | }, |
| 310 | { |
| 311 | "app": "state", |
| 312 | "state": "{ |
| 313 | var[$key || '_total_staked_on_' || $outcome || '_by_' || $address] = false; |
| 314 | response['message'] = "paid " || $amount || " bytes"; |
| 315 | response['pair'] = $pair; |
| 316 | response['operation_id'] = $key; |
| 317 | response['paid_out_amount'] = $amount; |
| 318 | response['paid_out_address'] = $address; |
| 319 | }" |
| 320 | } |
| 321 | ] |
| 322 | } |
| 323 | ] |
| 324 | } |
| 325 | }, |
| 326 | { |
| 327 | "if": "{trigger.data.reward_amount AND trigger.data.number_of_rewards}", |
| 328 | "init": "{ |
| 329 | $reward_amount = +trigger.data.reward_amount; |
| 330 | if (!is_integer($reward_amount)) |
| 331 | bounce("reward_amount must be an integer"); |
| 332 | if ($reward_amount < $min_reward) |
| 333 | bounce('reward_amount must be at least ' || $min_reward || ' bytes'); |
| 334 | $number_of_rewards = +trigger.data.number_of_rewards; |
| 335 | if (!is_integer($number_of_rewards)) |
| 336 | bounce("number_of_rewards must be an integer"); |
| 337 | $expected_amount = $reward_amount * $number_of_rewards; |
| 338 | if ($payment_amount != $expected_amount) |
| 339 | bounce('wrong amount received, expected: ' || $expected_amount); |
| 340 | }", |
| 341 | "messages": [ |
| 342 | { |
| 343 | "app": "state", |
| 344 | "state": "{ |
| 345 | var['pool_id'] += 1; |
| 346 | $pool_id = var['pool_id']; |
| 347 | var['pool_' || $pool_id || '_sponsor'] = trigger.address; |
| 348 | var['pool_' || $pool_id || '_reward_amount'] = $reward_amount; |
| 349 | var['pool_' || $pool_id || '_number_of_rewards'] = $number_of_rewards; |
| 350 | if ($exchange) |
| 351 | var['pool_' || $pool_id || '_exchange'] = $exchange; |
| 352 | response['created_pool'] = $pool_id; |
| 353 | response['amount'] = $number_of_rewards * $reward_amount; |
| 354 | response['message'] = "created a reward pool " || $pool_id || " of " || $expected_amount || " bytes"; |
| 355 | }" |
| 356 | } |
| 357 | ] |
| 358 | }, |
| 359 | { |
| 360 | "if": "{trigger.data.withdraw_pool AND trigger.data.pool_id}", |
| 361 | "init": "{ |
| 362 | $pool_id = trigger.data.pool_id; |
| 363 | $sponsor = var['pool_' || $pool_id || '_sponsor']; |
| 364 | if (!$sponsor) |
| 365 | bounce('no such pool: ' || $pool_id); |
| 366 | if ($sponsor != trigger.address) |
| 367 | bounce('not your pool: ' || $pool_id); |
| 368 | $number_of_rewards = var['pool_' || $pool_id || '_number_of_rewards']; |
| 369 | if (!$number_of_rewards) |
| 370 | bounce('pool ' || $pool_id || ' is already empty'); |
| 371 | $reward_amount = var['pool_' || $pool_id || '_reward_amount']; |
| 372 | }", |
| 373 | "messages": [ |
| 374 | { |
| 375 | "app": "payment", |
| 376 | "payload": { |
| 377 | "asset": "base", |
| 378 | "outputs": [ |
| 379 | { |
| 380 | "address": "{trigger.address}", |
| 381 | "amount": "{$number_of_rewards * $reward_amount}" |
| 382 | } |
| 383 | ] |
| 384 | } |
| 385 | }, |
| 386 | { |
| 387 | "app": "state", |
| 388 | "state": "{ |
| 389 | var['pool_' || $pool_id || '_number_of_rewards'] = false; |
| 390 | response['destroyed_pool'] = $pool_id; |
| 391 | response['amount'] = $number_of_rewards * $reward_amount; |
| 392 | response['message'] = "destroyed reward pool " || $pool_id; |
| 393 | }" |
| 394 | } |
| 395 | ] |
| 396 | }, |
| 397 | { |
| 398 | "if": "{trigger.data.nickname}", |
| 399 | "init": "{ |
| 400 | $nickname = trigger.data.nickname; |
| 401 | if ($nickname AND length($nickname) < 3) |
| 402 | bounce('Nickname must be over at least 3 chars'); |
| 403 | if ($nickname AND length($nickname) > 50) |
| 404 | bounce("Nickname can't be over 50 chars"); |
| 405 | if ($nickname AND contains($nickname, '_')) |
| 406 | bounce('Nickname cannot contain underscore'); |
| 407 | }", |
| 408 | "messages": [ |
| 409 | { |
| 410 | "app": "state", |
| 411 | "state": "{ |
| 412 | var['nickname_' || trigger.address] = trigger.data.nickname; |
| 413 | response['nickname'] = trigger.data.nickname; |
| 414 | response['message'] = "Nickname changed for " || trigger.data.nickname; |
| 415 | }" |
| 416 | } |
| 417 | ] |
| 418 | }, |
| 419 | { |
| 420 | "if": "{trigger.data.nickname}", |
| 421 | "init": "{ |
| 422 | $nickname = trigger.data.nickname; |
| 423 | if ($nickname AND length($nickname) < 3) |
| 424 | bounce('Nickname must be over at least 3 chars'); |
| 425 | if ($nickname AND length($nickname) > 50) |
| 426 | bounce("Nickname can't be over 50 chars"); |
| 427 | if ($nickname AND contains($nickname, '_')) |
| 428 | bounce('Nickname cannot contain underscore'); |
| 429 | }", |
| 430 | "messages": [ |
| 431 | { |
| 432 | "app": "state", |
| 433 | "state": "{ |
| 434 | var['nickname_' || trigger.address] = trigger.data.nickname; |
| 435 | response['nickname'] = trigger.data.nickname; |
| 436 | response['message'] = "Nickname changed for " || trigger.data.nickname; |
| 437 | }" |
| 438 | } |
| 439 | ] |
| 440 | }, |
| 441 | { |
| 442 | "if": "{trigger.data.control_address}", |
| 443 | "init": "{ |
| 444 | if (var['control_address'] AND var['control_address'] != trigger.address) |
| 445 | bounce('you are not control address'); |
| 446 | if(!is_valid_address(trigger.data.control_address)) |
| 447 | bounce('new control address is not a valid address'); |
| 448 | }", |
| 449 | "messages": [ |
| 450 | { |
| 451 | "app": "state", |
| 452 | "state": "{ |
| 453 | var['control_address'] = trigger.data.control_address; |
| 454 | response['new_control_address'] = trigger.data.control_address; |
| 455 | }" |
| 456 | } |
| 457 | ] |
| 458 | }, |
| 459 | { |
| 460 | "if": "{trigger.data.min_reward}", |
| 461 | "init": "{ |
| 462 | if (!var['control_address'] OR var['control_address'] != trigger.address) |
| 463 | bounce('you are not control address'); |
| 464 | if (!is_integer(+trigger.data.min_reward)) |
| 465 | bounce("min_reward must be an integer"); |
| 466 | }", |
| 467 | "messages": [ |
| 468 | { |
| 469 | "app": "state", |
| 470 | "state": "{ |
| 471 | var['min_reward'] = trigger.data.min_reward; |
| 472 | response['new_min_reward'] = trigger.data.min_reward; |
| 473 | }" |
| 474 | } |
| 475 | ] |
| 476 | }, |
| 477 | { |
| 478 | "if": "{trigger.data.min_stake}", |
| 479 | "init": "{ |
| 480 | if (!var['control_address'] OR var['control_address'] != trigger.address) |
| 481 | bounce('you are not control address'); |
| 482 | if (!is_integer(+trigger.data.min_stake)) |
| 483 | bounce("min_stake must be an integer"); |
| 484 | }", |
| 485 | "messages": [ |
| 486 | { |
| 487 | "app": "state", |
| 488 | "state": "{ |
| 489 | var['min_stake'] = trigger.data.min_stake; |
| 490 | response['new_min_stake'] = trigger.data.min_stake; |
| 491 | }" |
| 492 | } |
| 493 | ] |
| 494 | } |
| 495 | ] |
| 496 | } |
| 497 | } |
| 498 | ] |