├── LICENSE.md ├── README.md ├── i ├── dappradar-highrisk.png └── trolly-ponzi.png ├── ponzi_governmental.rb ├── ponzi_gradual.rb ├── ponzi_simple.rb ├── pyramid_simple.rb ├── run_ponzi_governmental.rb ├── run_ponzi_gradual.rb ├── run_ponzi_simple.rb ├── run_pyramid_simple.rb ├── run_satoshi_dice.rb ├── satoshi_dice.rb └── satoshi_dice_payout.rb /LICENSE.md: -------------------------------------------------------------------------------- 1 | CC0 1.0 Universal 2 | 3 | Statement of Purpose 4 | 5 | The laws of most jurisdictions throughout the world automatically confer 6 | exclusive Copyright and Related Rights (defined below) upon the creator and 7 | subsequent owner(s) (each and all, an "owner") of an original work of 8 | authorship and/or a database (each, a "Work"). 9 | 10 | Certain owners wish to permanently relinquish those rights to a Work for the 11 | purpose of contributing to a commons of creative, cultural and scientific 12 | works ("Commons") that the public can reliably and without fear of later 13 | claims of infringement build upon, modify, incorporate in other works, reuse 14 | and redistribute as freely as possible in any form whatsoever and for any 15 | purposes, including without limitation commercial purposes. These owners may 16 | contribute to the Commons to promote the ideal of a free culture and the 17 | further production of creative, cultural and scientific works, or to gain 18 | reputation or greater distribution for their Work in part through the use and 19 | efforts of others. 20 | 21 | For these and/or other purposes and motivations, and without any expectation 22 | of additional consideration or compensation, the person associating CC0 with a 23 | Work (the "Affirmer"), to the extent that he or she is an owner of Copyright 24 | and Related Rights in the Work, voluntarily elects to apply CC0 to the Work 25 | and publicly distribute the Work under its terms, with knowledge of his or her 26 | Copyright and Related Rights in the Work and the meaning and intended legal 27 | effect of CC0 on those rights. 28 | 29 | 1. Copyright and Related Rights. A Work made available under CC0 may be 30 | protected by copyright and related or neighboring rights ("Copyright and 31 | Related Rights"). Copyright and Related Rights include, but are not limited 32 | to, the following: 33 | 34 | i. the right to reproduce, adapt, distribute, perform, display, communicate, 35 | and translate a Work; 36 | 37 | ii. moral rights retained by the original author(s) and/or performer(s); 38 | 39 | iii. publicity and privacy rights pertaining to a person's image or likeness 40 | depicted in a Work; 41 | 42 | iv. rights protecting against unfair competition in regards to a Work, 43 | subject to the limitations in paragraph 4(a), below; 44 | 45 | v. rights protecting the extraction, dissemination, use and reuse of data in 46 | a Work; 47 | 48 | vi. database rights (such as those arising under Directive 96/9/EC of the 49 | European Parliament and of the Council of 11 March 1996 on the legal 50 | protection of databases, and under any national implementation thereof, 51 | including any amended or successor version of such directive); and 52 | 53 | vii. other similar, equivalent or corresponding rights throughout the world 54 | based on applicable law or treaty, and any national implementations thereof. 55 | 56 | 2. Waiver. To the greatest extent permitted by, but not in contravention of, 57 | applicable law, Affirmer hereby overtly, fully, permanently, irrevocably and 58 | unconditionally waives, abandons, and surrenders all of Affirmer's Copyright 59 | and Related Rights and associated claims and causes of action, whether now 60 | known or unknown (including existing as well as future claims and causes of 61 | action), in the Work (i) in all territories worldwide, (ii) for the maximum 62 | duration provided by applicable law or treaty (including future time 63 | extensions), (iii) in any current or future medium and for any number of 64 | copies, and (iv) for any purpose whatsoever, including without limitation 65 | commercial, advertising or promotional purposes (the "Waiver"). Affirmer makes 66 | the Waiver for the benefit of each member of the public at large and to the 67 | detriment of Affirmer's heirs and successors, fully intending that such Waiver 68 | shall not be subject to revocation, rescission, cancellation, termination, or 69 | any other legal or equitable action to disrupt the quiet enjoyment of the Work 70 | by the public as contemplated by Affirmer's express Statement of Purpose. 71 | 72 | 3. Public License Fallback. Should any part of the Waiver for any reason be 73 | judged legally invalid or ineffective under applicable law, then the Waiver 74 | shall be preserved to the maximum extent permitted taking into account 75 | Affirmer's express Statement of Purpose. In addition, to the extent the Waiver 76 | is so judged Affirmer hereby grants to each affected person a royalty-free, 77 | non transferable, non sublicensable, non exclusive, irrevocable and 78 | unconditional license to exercise Affirmer's Copyright and Related Rights in 79 | the Work (i) in all territories worldwide, (ii) for the maximum duration 80 | provided by applicable law or treaty (including future time extensions), (iii) 81 | in any current or future medium and for any number of copies, and (iv) for any 82 | purpose whatsoever, including without limitation commercial, advertising or 83 | promotional purposes (the "License"). The License shall be deemed effective as 84 | of the date CC0 was applied by Affirmer to the Work. Should any part of the 85 | License for any reason be judged legally invalid or ineffective under 86 | applicable law, such partial invalidity or ineffectiveness shall not 87 | invalidate the remainder of the License, and in such case Affirmer hereby 88 | affirms that he or she will not (i) exercise any of his or her remaining 89 | Copyright and Related Rights in the Work or (ii) assert any associated claims 90 | and causes of action with respect to the Work, in either case contrary to 91 | Affirmer's express Statement of Purpose. 92 | 93 | 4. Limitations and Disclaimers. 94 | 95 | a. No trademark or patent rights held by Affirmer are waived, abandoned, 96 | surrendered, licensed or otherwise affected by this document. 97 | 98 | b. Affirmer offers the Work as-is and makes no representations or warranties 99 | of any kind concerning the Work, express, implied, statutory or otherwise, 100 | including without limitation warranties of title, merchantability, fitness 101 | for a particular purpose, non infringement, or the absence of latent or 102 | other defects, accuracy, or the present or absence of errors, whether or not 103 | discoverable, all to the greatest extent permissible under applicable law. 104 | 105 | c. Affirmer disclaims responsibility for clearing rights of other persons 106 | that may apply to the Work or any use thereof, including without limitation 107 | any person's Copyright and Related Rights in the Work. Further, Affirmer 108 | disclaims responsibility for obtaining any necessary consents, permissions 109 | or other rights required for any use of the Work. 110 | 111 | d. Affirmer understands and acknowledges that Creative Commons is not a 112 | party to this document and has no duty or obligation with respect to this 113 | CC0 or use of the Work. 114 | 115 | For more information, please see 116 | minimum_investment ) 37 | 38 | # record new investor 39 | previous_investor = @current_investor 40 | @current_investor = msg.sender 41 | @current_investment = msg.value 42 | 43 | # pay out previous investor 44 | previous_investor.send( msg.value ) 45 | end 46 | 47 | end # class SimplePonzi 48 | ``` 49 | 50 | (Source: [`ponzi_simple.rb`](ponzi_simple.rb)) 51 | 52 | 53 | 54 | What's a ponzi scheme? 55 | 56 | ![](i/trolly-ponzi.png) 57 | 58 | (Source: [Best of Bitcoin Maximalist - Scammers, Morons, Clowns, Shills & BagHODLers - Inside The New New Crypto Ponzi Economics](https://bitsblocks.github.io/bitcoin-maximalist)) 59 | 60 | 61 | > A Ponzi scheme is a form of fraud which lures investors and pays profits to earlier investors by using funds obtained from more recent investors. The victims are led to believe that the profits are coming from product sales or other means, and they remain unaware that other investors are the source of profits. A Ponzi scheme is able to maintain the illusion of a sustainable business as long as there continue to be new investors willing to contribute new funds, and as long as most of the investors do not demand full repayment and are willing to believe in the non-existent assets that they are purported to own. 62 | The scheme is named after Charles Ponzi who became notorious for using the technique in the 1920s. 63 | > 64 | > -- [Ponzi Scheme @ Wikipedia](https://en.wikipedia.org/wiki/Ponzi_scheme) 65 | 66 | 67 | 68 | Back to the future. Now in 2019 ponzi schemes are as popular as ever. For 69 | modern Get-Rich-Quick "verifiable corrupt" or "honest" ponzi schemes 70 | running on the blockchain 71 | browse the ["high-risk" category](https://dappradar.com/rankings/protocol/ethereum/category/high-risk) of contract scripts 72 | running on Ethereum, for example. 73 | 74 | ![](i/dappradar-highrisk.png) 75 | 76 | 77 | Anyways, let's look at the first simple ponzi contract script. 78 | The idea is: 79 | 80 | The money sent in by the latest investor 81 | gets paid out to the previous investor and because every 82 | new investment must be at least 10% larger than the last 83 | investment - EVERY INVESTOR WINS! (*) 84 | 85 | (*): Except the last "sucker" is HODLing the bag waiting for a greater fool. 86 | 87 | 88 | Let's setup some test accounts with funny money: 89 | 90 | ``` ruby 91 | ## setup test accounts with starter balance 92 | Account[ '0x1111' ].balance = 0 93 | Account[ '0xaaaa' ].balance = 1_000_000 94 | Account[ '0xbbbb' ].balance = 1_200_000 95 | Account[ '0xcccc' ].balance = 1_400_000 96 | 97 | ## (pp) pretty print all known accounts with balance 98 | pp Uni.accounts # Uni (short for) Universum 99 | ``` 100 | 101 | (Source: [`run_ponzi_simple.rb`](run_ponzi_simple.rb)) 102 | 103 | 104 | printing: 105 | 106 | ``` 107 | [#, 108 | #, 109 | #, 110 | #] 111 | ``` 112 | 113 | Note: The contract scripts run on Universum - a 3rd generation blockchain / world computer. New to Universum? See the [Universum (World Computer) White Paper](https://github.com/openblockchains/universum/blob/master/WHITEPAPER.md)! 114 | 115 | 116 | 117 | And let's invest: 118 | 119 | ``` ruby 120 | ## genesis - create contract 121 | ponzi = Uni.send_transaction( from: '0x1111', data: SimplePonzi ).contract 122 | pp ponzi 123 | #=> # 124 | 125 | Uni.send_transaction( from: '0xaaaa', to: ponzi, value: 1_000_000 ) 126 | pp ponzi 127 | #=> # 128 | 129 | Uni.send_transaction( from: '0xbbbb', to: ponzi, value: 1_200_000 ) 130 | pp ponzi 131 | #=> # 132 | 133 | Uni.send_transaction( from: '0xcccc', to: ponzi, value: 1_400_000 ) 134 | pp ponzi 135 | #=> # 136 | 137 | ## (pp) pretty print all known accounts with balance 138 | pp Uni.accounts 139 | ``` 140 | 141 | Resulting in: 142 | 143 | ``` 144 | [#, 145 | #, 146 | #, 147 | #] 148 | ``` 149 | 150 | The "Genesis" `0x1111` account made a 100% profit of 1_000_000. 151 | The `0xaaaa` account made an investment of 1_000_000 and got 1_200_000. 200_000 profit! Yes, it works! 152 | The `0xbbbb` account made an investment of 1_200_000 and got 1_400_000. 200_000 profit! Yes, it works! 153 | The `0xcccc` account is still waiting for a greater fool and is HODLing the bag. 154 | To the moon! 155 | 156 | 157 | ### Gradual Ponzi - May the Brave be Rewared with Riches! 158 | 159 | Let's put together a more realistic ponzi 160 | where investor get above-average returns gradually 161 | as more investors join in: 162 | 163 | 164 | ``` ruby 165 | class GradualPonzi < Contract 166 | 167 | MINIMUM_INVESTMENT = 1_000_000 168 | 169 | def initialize 170 | @investors = [] # Array.of( Address ) 171 | @investors.push( msg.sender ) 172 | 173 | @balances = Mapping.of( Address => Money ) 174 | end 175 | 176 | 177 | def receive 178 | assert( msg.value >= MINIMUM_INVESTMENT ) 179 | 180 | investor_share = msg.value / @investors.size 181 | @investors.each do |investor| 182 | @balances[investor] += investor_share 183 | end 184 | 185 | @investors.push( msg.sender ) 186 | end 187 | 188 | 189 | def withdraw 190 | payout = @balances[ msg.sender ] 191 | @balances[ msg.sender ] = 0 192 | msg.sender.transfer( payout ) 193 | end 194 | end # class GradualPonzi 195 | ``` 196 | 197 | (Source: [`ponzi_gradual.rb`](ponzi_gradual.rb)) 198 | 199 | 200 | 201 | The improved ponzi formula is: 202 | 203 | The investment scheme has a 1_000_000 minimum 204 | to keep out "free loaders" 205 | and every new investment gets shared evenly 206 | between all previous investors. 207 | At any time investors can withdraw all profits made 208 | BUT not the initial investment. 209 | 210 | 211 | Let's setup some test accounts with funny money: 212 | 213 | ``` ruby 214 | ## setup test accounts with starter balance 215 | Account[ '0x1111' ].balance = 0 216 | Account[ '0xaaaa' ].balance = 1_000_000 217 | Account[ '0xbbbb' ].balance = 1_000_000 218 | Account[ '0xcccc' ].balance = 1_000_000 219 | Account[ '0xdddd' ].balance = 1_000_000 220 | 221 | ## (pp) pretty print all known accounts with balance 222 | pp Uni.accounts # Uni (short for) Universum 223 | ``` 224 | 225 | (Source: [`run_ponzi_gradual.rb`](run_ponzi_gradual.rb)) 226 | 227 | printing: 228 | 229 | ``` 230 | [#, 231 | #, 232 | #, 233 | #, 234 | #] 235 | ``` 236 | 237 | And let's invest: 238 | 239 | ``` ruby 240 | ## genesis - create contract 241 | ponzi = Uni.send_transaction( from: '0x1111', data: GradualPonzi ).contract 242 | pp ponzi 243 | #=> # 246 | 247 | Uni.send_transaction( from: '0xaaaa', to: ponzi, value: 1_000_000 ) 248 | pp ponzi 249 | #=> #1000000}, 251 | # @investors=["0x1111", "0xaaaa"]> 252 | 253 | Uni.send_transaction( from: '0xbbbb', to: ponzi, value: 1_000_000 ) 254 | pp ponzi 255 | #=> #1500000, "0xaaaa"=>500000}, 257 | # @investors=["0x1111", "0xaaaa", "0xbbbb"]> 258 | 259 | Uni.send_transaction( from: '0xcccc', to: ponzi, value: 1_000_000 ) 260 | pp ponzi 261 | #=> #1833333, "0xaaaa"=>833333, "0xbbbb"=>333333}, 263 | # @investors=["0x1111", "0xaaaa", "0xbbbb", "0xcccc"]> 264 | 265 | Uni.send_transaction( from: '0xdddd', to: ponzi, value: 1_000_000 ) 266 | pp ponzi 267 | #=> #2083333, "0xaaaa"=>1083333, 269 | # "0xbbbb"=>583333, "0xcccc"=>250000}, 270 | # @investors=["0x1111", "0xaaaa", "0xbbbb", "0xcccc", "0xdddd"]> 271 | 272 | ## (pp) pretty print all known accounts with balance 273 | pp Uni.accounts 274 | ``` 275 | 276 | Resulting in: 277 | 278 | ``` 279 | [#, 280 | #, 281 | #, 282 | #, 283 | #] 284 | ``` 285 | 286 | Note: All accounts have a balance of 0 because 287 | the investment (and profits) are this time safely kept in the ponzi! 288 | 289 | The "Genesis" `0x1111` account made a 100% profit of 2_083_333. 290 | The `0xaaaa` account made an investment of 1_000_000 and got 1_083_333. 83_333 profit! Yes, it works! 291 | The `0xbbbb` account made an investment of 1_000_000 and got 583_333 so far. HODL! HODL! 292 | The `0xcccc` got 250_000 so far. HODL! HODL! 293 | The `0xdddd` account is still waiting for greater fools and is HODLing the bag. 294 | To the moon! 295 | 296 | 297 | Let's withdraw and pocket the profits: 298 | 299 | ``` ruby 300 | Uni.send_transaction( from: '0xaaaa', to: ponzi, data: [:withdraw] ) 301 | pp ponzi 302 | #=> #2083333, "0xaaaa"=>0, 304 | # "0xbbbb"=>583333, "0xcccc"=>250000}, 305 | # @investors=["0x1111", "0xaaaa", "0xbbbb", "0xcccc", "0xdddd"]> 306 | 307 | ## (pp) pretty print all known accounts with balance 308 | pp Uni.accounts 309 | ``` 310 | 311 | resulting in: 312 | 313 | ``` 314 | [#, 315 | #, 316 | #, 317 | #, 318 | #] 319 | ``` 320 | 321 | Yes, it's real! 322 | The `0xaaaa` account keeps getting more (and more) profits in the ponzi 323 | (see the `@investors` list) and 324 | got already ALL the investment back with 83_333 profits! 325 | 326 | 327 | 328 | 329 | 330 | 331 | ## Ponzi Governmental - Real World Case Study - Last Creditor (Before Collapse) Wins the Jackpot! 332 | 333 | Let's look at a real world ponzi (scheme) contract called Governmental 334 | put together by an Austrian School of Economics fan boy or girl. 335 | 336 | 337 | What's Governmental? 338 | 339 | The gambling website says: 340 | 341 | > This is an educational game which simulates the finances of a government - 342 | > in other words: It's a Ponzi scheme 343 | > 344 | > Rule 1 345 | > 346 | > You can lend the government money - they promise to pay it back +10% interest. 347 | > Minimum contribution is 1 ETH. 348 | > 349 | > Rule 2 350 | > 351 | > If the government does not receive new money for 12h the system breaks down. The latest 352 | > creditor saw the crash coming and receives the jackpot. All others will lose their claims. 353 | > 354 | > Rule 3 355 | > 356 | > All incoming money is used in the following way: 5% goes into the "jackpot" (capped at 10 000 ETH), 357 | > 5% goes to the corrupt elite that runs the government, 358 | > 90% are used to pay out creditors in order of their date of credit. 359 | > When the jackpot is full, 95% go toward the payout of creditors. 360 | > 361 | > Rule 4 362 | > 363 | > Creditors can share an affiliate link. Money deposited this way is distributed 364 | > as follows: 5% go toward the linker directly, 5% to the corrupt elite, 365 | > 5% into the jackpot (until full). The rest is used for payouts. 366 | > 367 | > (Source: [`governmental.github.io/GovernMental`](http://governmental.github.io/GovernMental/) 368 | 369 | 370 | Believe it or not the contract ran in early 2016 for about 40 days on 371 | the Ethereum blockchain and 372 | was so popular that the payout money got stuck because of too many investors 373 | and the payout function was hitting the gas limit 374 | (that is, maximum number of instructions). 375 | Read the official announcement titled 376 | [">1000 ETH Jackpot! New Contract - "Governmental" Government Simulation"](https://bitcointalk.org/index.php?topic=1424324.0) 377 | 378 | Anyways, let's look at the code (in JavaScript-like Solidity), 379 | see [etherscan.io/address/0xF45717552f12Ef7cb65e95476F217Ea008167Ae3#code](https://etherscan.io/address/0xF45717552f12Ef7cb65e95476F217Ea008167Ae3#code): 380 | 381 | ``` solidity 382 | contract Government { 383 | 384 | // Global Variables 385 | uint32 public lastCreditorPayedOut; 386 | uint public lastTimeOfNewCredit; 387 | uint public profitFromCrash; 388 | address[] public creditorAddresses; 389 | uint[] public creditorAmounts; 390 | address public corruptElite; 391 | mapping (address => uint) buddies; 392 | uint constant TWELVE_HOURS = 43200; 393 | uint8 public round; 394 | 395 | function Government() { 396 | // The corrupt elite establishes a new government 397 | // this is the commitment of the corrupt Elite - everything that can not be saved from a crash 398 | profitFromCrash = msg.value; 399 | corruptElite = msg.sender; 400 | lastTimeOfNewCredit = block.timestamp; 401 | } 402 | 403 | function lendGovernmentMoney(address buddy) returns (bool) { 404 | uint amount = msg.value; 405 | // check if the system already broke down. 406 | // If for 12h no new creditor gives new credit to the system it will brake down. 407 | // 12h are on average = 60*60*12/12.5 = 3456 408 | if (lastTimeOfNewCredit + TWELVE_HOURS < block.timestamp) { 409 | // Return money to sender 410 | msg.sender.send(amount); 411 | // Sends all contract money to the last creditor 412 | creditorAddresses[creditorAddresses.length - 1].send(profitFromCrash); 413 | corruptElite.send(this.balance); 414 | // Reset contract state 415 | lastCreditorPayedOut = 0; 416 | lastTimeOfNewCredit = block.timestamp; 417 | profitFromCrash = 0; 418 | creditorAddresses = new address[](0); 419 | creditorAmounts = new uint[](0); 420 | round += 1; 421 | return false; 422 | } 423 | else { 424 | // the system needs to collect at least 1% of the profit from a crash to stay alive 425 | if (amount >= 10 ** 18) { 426 | // the System has received fresh money, it will survive at leat 12h more 427 | lastTimeOfNewCredit = block.timestamp; 428 | // register the new creditor and his amount with 10% interest rate 429 | creditorAddresses.push(msg.sender); 430 | creditorAmounts.push(amount * 110 / 100); 431 | // now the money is distributed 432 | // first the corrupt elite grabs 5% - thieves! 433 | corruptElite.send(amount * 5/100); 434 | // 5% are going into the economy 435 | // (they will increase the value for the person seeing the crash comming) 436 | if (profitFromCrash < 10000 * 10**18) { 437 | profitFromCrash += amount * 5/100; 438 | } 439 | // if you have a buddy in the government (and he is in the creditor list) 440 | // he can get 5% of your credits. Make a deal with him. 441 | if(buddies[buddy] >= amount) { 442 | buddy.send(amount * 5/100); 443 | } 444 | buddies[msg.sender] += amount * 110 / 100; 445 | // 90% of the money will be used to pay out old creditors 446 | if (creditorAmounts[lastCreditorPayedOut] <= address(this).balance - profitFromCrash) { 447 | creditorAddresses[lastCreditorPayedOut].send(creditorAmounts[lastCreditorPayedOut]); 448 | buddies[creditorAddresses[lastCreditorPayedOut]] -= creditorAmounts[lastCreditorPayedOut]; 449 | lastCreditorPayedOut += 1; 450 | } 451 | return true; 452 | } 453 | else { 454 | msg.sender.send(amount); 455 | return false; 456 | } 457 | } 458 | } 459 | 460 | // fallback function 461 | function() { 462 | lendGovernmentMoney(0); 463 | } 464 | 465 | function totalDebt() returns (uint debt) { 466 | for(uint i=lastCreditorPayedOut; i Bool 498 | payable :invest_in_the_system 499 | 500 | MINIMUM_INVESTMENT = 1_000_000 501 | TWELVE_HOURS = 43_200 ## in seconds e.g. 12h*60min*60secs 502 | 503 | def initialize 504 | ## The corrupt elite establishes a new government 505 | ## this is the commitment of the corrupt Elite - everything that can not be saved from a crash 506 | @profit_from_crash = msg.value 507 | @corrupt_elite = msg.sender 508 | @last_time_of_new_credit = block.timestamp 509 | 510 | @creditor_addresses = [] # Array.of( Address ) 511 | @creditor_amounts = [] # Array.of( Money ) 512 | @buddies = Mapping.of( Address => Money ) 513 | 514 | @round = 0 515 | @last_creditor_paid_out = 0 516 | end 517 | 518 | def lend_government_money( buddy ) 519 | amount = msg.value 520 | 521 | ## check if the system already broke down. 522 | ## If for 12h no new creditor gives new credit to the system it will brake down. 523 | ## 12h are on average = 12h*60min*60secs/12.5 = 3456 blocks 524 | if @last_time_of_new_credit + TWELVE_HOURS < block.timestamp 525 | ## Return money to sender 526 | msg.sender.send( amount ) 527 | ## Sends all contract money to the last creditor 528 | @creditor_addresses[ @creditor_addresses.length-1].send( @profit_from_crash ) 529 | @corrupt_elite.send( balance ) 530 | ## Reset contract state 531 | @last_creditor_paid_out = 0 532 | @last_time_of_new_credit = block.timestamp 533 | @profit_from_crash = 0 534 | @creditor_addresses = [] 535 | @creditor_amounts = [] 536 | @round += 1 537 | return false 538 | else 539 | ## the system needs to collect at least 1% of the profit from a crash to stay alive 540 | if amount >= MINIMUM_INVESTMENT 541 | ## the System has received fresh money, it will survive at leat 12h more 542 | @last_time_of_new_credit = block.timestamp; 543 | ## register the new creditor and his amount with 10% interest rate 544 | @creditor_addresses.push( msg.sender ) 545 | @creditor_amounts.push( amount * 110 / 100 ) 546 | 547 | ## now the money is distributed 548 | ## first the corrupt elite grabs 5% - thieves! 549 | @corrupt_elite.send( amount * 5/100 ) 550 | 551 | ## 5% are going into the economy (they will increase the value for the person seeing the crash comming) 552 | if @profit_from_crash < 10_000 * MINIMUM_INVESTMENT 553 | @profit_from_crash += amount * 5/100 554 | end 555 | 556 | ## if you have a buddy in the government (and he is in the creditor list) he can get 5% of your credits. 557 | ## Make a deal with him. 558 | if @buddies[buddy] >= amount 559 | buddy.send( amount * 5/100 ) 560 | end 561 | 562 | @buddies[ msg.sender ] += amount * 110 / 100 563 | 564 | ## 90% of the money will be used to pay out old creditors 565 | if @creditor_amounts[ @last_creditor_paid_out] <= balance - @profit_from_crash 566 | @creditor_addresses[ @last_creditor_paid_out ].send( @creditor_amounts[@last_creditor_paid_out] ) 567 | 568 | @buddies[ @creditor_addresses[ @last_creditor_paid_out ]] = 0 569 | @last_creditor_paid_out += 1 570 | end 571 | return true 572 | else 573 | msg.sender.send( amount ) 574 | return false 575 | end 576 | end 577 | end # method lend_government_money 578 | 579 | def receive 580 | lend_government_money( '0x0000' ) 581 | end 582 | 583 | def total_debt 584 | debt = 0 585 | (@last_creditor_paid_out...@creditor_amounts.length).each do |i| 586 | debt += @creditor_amounts[i] 587 | end 588 | debt 589 | end 590 | 591 | def total_paid_out 592 | payout = 0 593 | (0...@last_creditor_paid_out).each do |i| 594 | payout += @creditor_amounts[i] 595 | end 596 | payout 597 | end 598 | 599 | ## better don't do it (unless you are the corrupt elite and you want to establish trust in the system) 600 | def invest_in_the_system 601 | @profit_from_crash += msg.value 602 | end 603 | 604 | ## From time to time the corrupt elite inherits it's power to the next generation 605 | def inherit_to_next_generation( next_generation ) 606 | if msg.sender == @corrupt_elite 607 | @corrupt_elite = next_generation 608 | end 609 | end 610 | end # class Governmental 611 | ``` 612 | 613 | (Source: [`ponzi_governmental.rb`](ponzi_governmental.rb)) 614 | 615 | 616 | Now doesn't the new contract script look better, that is, more understandable? 617 | Let's go through the 618 | state (storage) variables setup in the constructor: 619 | 620 | `@last_creditor_paid_out` - Stores the array index of the first creditor 621 | that hasn't been paid out. Starting with `0`. The index is used 622 | with the `@creditor_addresses` 623 | and `@creditor_amounts` arrays. 624 | 625 | `@last_time_of_new_credit` - Stores the `block.timestamp`, that is, 626 | the last timestamp of the last creditor's block timestamp. 627 | The timestamp is in seconds since Jan 1st, 1970 628 | (also known as Unix Epoch Time - e.g. print the current Epoch 629 | Time with `Time.now.to_i` resulting in `1549040124` on Feb 1st, 2019 @ 16:55.24). 630 | 631 | `@profit_from_crash` - Store the jackpot value! Starting with a 632 | seed down payment / investment of the contract owner 633 | (also known as `@corrupt_elite`) 634 | and gets bigger by 5% for every new creditor. 635 | 636 | `@creditor_addresses` and 637 | `@creditor_amounts` - Store the creditors account addresses (e.g. `0xaaaa`, `0xbbbb`, etc.) 638 | and the investment with the 10% promised interest / profit. 639 | 640 | `@corrupt_elite` - Holds the contract's owners account address. 641 | Gets paid out 5% from every new investment by new creditors. 642 | 643 | `@round` - Starts with `0` and every time 644 | the scheme collapses and the jackpot gets paid out 645 | and new round begins. 646 | 647 | 648 | Enough theory. Let's run the contract. 649 | Let's setup test accounts with a starter balance: 650 | 651 | ``` ruby 652 | Account[ '0x1111' ].balance = 1_000_000 653 | Account[ '0xaaaa' ].balance = 1_000_000 654 | Account[ '0xbbbb' ].balance = 1_000_000 655 | Account[ '0xcccc' ].balance = 1_000_000 656 | Account[ '0xdddd' ].balance = 1_000_000 657 | Account[ '0xeeee' ].balance = 1_000_000 658 | 659 | ## (pp) pretty print all known accounts with balance 660 | pp Uni.accounts 661 | ``` 662 | 663 | (Source: [`run_ponzi_governmental.rb`](run_ponzi_governmental.rb)) 664 | 665 | 666 | resulting in: 667 | 668 | ``` 669 | [#, 670 | #, 671 | #, 672 | #, 673 | #, 674 | #] 675 | ``` 676 | 677 | Let's start-up the contract with a 1 million seed jackpot! 678 | 679 | ``` ruby 680 | ## genesis - create contract 681 | gov = Uni.send_transaction( from: '0x1111', value: 1_000_000, data: Governmental ).contract 682 | pp gov 683 | ``` 684 | 685 | resulting in: 686 | 687 | ``` 688 | # 698 | ``` 699 | 700 | Now let's add the first four investments: 701 | 702 | ``` ruby 703 | Uni.send_transaction( from: '0xaaaa', to: gov, value: 1_000_000 ) 704 | pp gov 705 | Uni.send_transaction( from: '0xbbbb', to: gov, value: 1_000_000, data: [:lend_government_money, '0xaaaa'] ) 706 | pp gov 707 | Uni.send_transaction( from: '0xcccc', to: gov, value: 1_000_000 ) 708 | pp gov 709 | Uni.send_transaction( from: '0xdddd', to: gov, value: 1_000_000 ) 710 | pp gov 711 | 712 | ## (pp) pretty print all known accounts with balance 713 | pp Uni.accounts 714 | ``` 715 | 716 | resulting in: 717 | 718 | ``` 719 | ... 720 | #0, "0xbbbb"=>0, "0xcccc"=>0, "0xdddd"=>1100000}, 723 | @corrupt_elite="0x1111", 724 | @creditor_addresses=["0xaaaa", "0xbbbb", "0xcccc", "0xdddd"], 725 | @creditor_amounts=[1100000, 1100000, 1100000, 1100000], 726 | @last_creditor_paid_out=3, 727 | @last_time_of_new_credit=1548880299, 728 | @profit_from_crash=1200000, 729 | @round=0> 730 | [#, 731 | #, 732 | #, 733 | #, 734 | #, 735 | #] 736 | ``` 737 | 738 | It's working! 739 | The account `0xaaaa` invested 1_000_000 and got a payout of 1_150_000. 740 | 15_000 profit! 10% interest for the investment and a 5% bonus for the buddy referral fee. 741 | The accounts `0xbbbb` and `0xcccc` got the "standard" payout of 742 | 1_100_000 for the 1_000_000 investment. 10% profit! 743 | The last investor `0xdddd` is HODLing the bag and is all in 744 | with a balance of 0 and waiting for the collapse / jackpot! 745 | 746 | 747 | Let's move the blocktime ahead 13 hours :-) and mine a new block: 748 | 749 | ``` ruby 750 | ## mine - move block time ahead more than 12h (use 13h) 751 | Uni.block = { number: 1, timestamp: Time.now.to_i + 60*60*13 } 752 | ``` 753 | 754 | Now let's send in a new investment 755 | and let's see how the economy will collapse 756 | and new (ponzi) round starts: 757 | 758 | ``` ruby 759 | Uni.send_transaction( from: '0xeeee', to: gov, value: 1_000_000 ) 760 | pp gov 761 | 762 | ## (pp) pretty print all known accounts with balance 763 | pp Uni.accounts 764 | ``` 765 | 766 | resulting in: 767 | 768 | ``` 769 | #0, "0xbbbb"=>0, "0xcccc"=>0, "0xdddd"=>1100000}, 772 | @corrupt_elite="0x1111", 773 | @creditor_addresses=[], 774 | @creditor_amounts=[], 775 | @last_creditor_paid_out=0, 776 | @last_time_of_new_credit=1548927099, 777 | @profit_from_crash=0, 778 | @round=1> 779 | [#, 780 | #, 781 | #, 782 | #, 783 | #, 784 | #] 785 | ``` 786 | 787 | Not bad. EVERYONE WINS! Except the corrupt elite - 788 | only getting back 450_000 for the 1_000_000 seed investment. 789 | 790 | 791 | 792 | To be continued... 793 | 794 | 795 | 796 | -------------------------------------------------------------------------------- /i/dappradar-highrisk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s6ruby/programming-crypto-contracts/431d756200b8d0661e7ca2ca8c0b844b91820bf5/i/dappradar-highrisk.png -------------------------------------------------------------------------------- /i/trolly-ponzi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/s6ruby/programming-crypto-contracts/431d756200b8d0661e7ca2ca8c0b844b91820bf5/i/trolly-ponzi.png -------------------------------------------------------------------------------- /ponzi_governmental.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | ## 4 | # a "real world" ponzi (scheme) contract 5 | # see http://governmental.github.io/GovernMental 6 | # last investor wins the jackpot! 7 | # 8 | # for live installation on ethereum with code (in solidity) 9 | # see https://etherscan.io/address/0xF45717552f12Ef7cb65e95476F217Ea008167Ae3#code 10 | # 11 | # to run type: 12 | # $ ruby ./run_ponzi_governmental.rb 13 | 14 | ## 15 | # note: comments are the "original" anti-government rant comments 16 | # from the solidity source code 17 | 18 | 19 | class Governmental < Contract 20 | 21 | MINIMUM_INVESTMENT = 1_000_000 22 | 23 | TWELVE_HOURS = 43_200 ## in seconds e.g. 12h*60min*60secs 24 | 25 | 26 | def initialize 27 | ## The corrupt elite establishes a new government 28 | ## this is the commitment of the corrupt Elite - everything that can not be saved from a crash 29 | @profit_from_crash = msg.value ## type uint public 30 | @corrupt_elite = msg.sender ## type address public 31 | @last_time_of_new_credit = block.timestamp ## type uint public 32 | 33 | @creditor_addresses = [] ## type address[] public 34 | @creditor_amounts = [] ## type uint[] public 35 | @buddies = Mapping.of( Address => Money ) ## type mapping (address => uint) buddies 36 | 37 | @round = 0 ## type uint8 public 38 | @last_creditor_paid_out = 0 ## type uint32 public 39 | end 40 | 41 | 42 | def lend_government_money( buddy ) 43 | amount = msg.value 44 | 45 | ## check if the system already broke down. If for 12h no new creditor gives new credit to the system it will brake down. 46 | ## 12h are on average = 12h*60min*60secs/12.5 = 3456 blocks 47 | if @last_time_of_new_credit + TWELVE_HOURS < block.timestamp 48 | ## Return money to sender 49 | msg.sender.send( amount ) 50 | ## Sends all contract money to the last creditor 51 | @creditor_addresses[ @creditor_addresses.length-1].send( @profit_from_crash ) 52 | @corrupt_elite.send( balance ) 53 | ## Reset contract state 54 | @last_creditor_paid_out = 0 55 | @last_time_of_new_credit = block.timestamp 56 | @profit_from_crash = 0 57 | @creditor_addresses = [] 58 | @creditor_amounts = [] 59 | @round += 1 60 | return false 61 | else 62 | ## the system needs to collect at least 1% of the profit from a crash to stay alive 63 | if amount >= MINIMUM_INVESTMENT 64 | ## the System has received fresh money, it will survive at leat 12h more 65 | @last_time_of_new_credit = block.timestamp; 66 | ## register the new creditor and his amount with 10% interest rate 67 | @creditor_addresses.push( msg.sender ) 68 | @creditor_amounts.push( amount * 110 / 100 ) 69 | 70 | ## now the money is distributed 71 | 72 | ## first the corrupt elite grabs 5% - thieves! 73 | @corrupt_elite.send( amount * 5/100 ) 74 | 75 | ## 5% are going into the economy (they will increase the value for the person seeing the crash comming) 76 | if @profit_from_crash < 10_000 * MINIMUM_INVESTMENT 77 | @profit_from_crash += amount * 5/100 78 | end 79 | 80 | ## if you have a buddy in the government (and he is in the creditor list) he can get 5% of your credits. 81 | ## Make a deal with him. 82 | if @buddies[buddy] >= amount 83 | buddy.send( amount * 5/100 ) 84 | end 85 | 86 | @buddies[ msg.sender ] += amount * 110 / 100 87 | 88 | ## 90% of the money will be used to pay out old creditors 89 | if @creditor_amounts[ @last_creditor_paid_out] <= balance - @profit_from_crash 90 | @creditor_addresses[ @last_creditor_paid_out ].send( @creditor_amounts[@last_creditor_paid_out] ) 91 | 92 | @buddies[ @creditor_addresses[ @last_creditor_paid_out ]] = 0 93 | @last_creditor_paid_out += 1 94 | end 95 | return true 96 | else 97 | msg.sender.send( amount ) 98 | return false 99 | end 100 | end 101 | end # method lend_government_money 102 | 103 | 104 | def receive 105 | lend_government_money( '0x0000' ) ## sorry - no buddy (e.g. '0x0000') in government gets 5% "referral" fee 106 | end 107 | 108 | 109 | def total_debt ## returns type uint debt 110 | debt = 0 111 | (@last_creditor_paid_out...@creditor_amounts.length).each do |i| 112 | debt += @creditor_amounts[i] 113 | end 114 | debt 115 | end 116 | 117 | def total_paid_out ## returns type uint payout 118 | payout = 0 119 | (0...@last_creditor_paid_out).each do |i| 120 | payout += @creditor_amounts[i] 121 | end 122 | payout 123 | end 124 | 125 | ## better don't do it (unless you are the corrupt elite and you want to establish trust in the system) 126 | def invest_in_the_system 127 | @profit_from_crash += msg.value 128 | end 129 | 130 | ## From time to time the corrupt elite inherits it's power to the next generation 131 | def inherit_to_next_generation( next_generation ) 132 | if msg.sender == @corrupt_elite 133 | @corrupt_elite = next_generation 134 | end 135 | end 136 | end # class Governmental 137 | -------------------------------------------------------------------------------- /ponzi_gradual.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | ## 4 | # a more realistic gradual ponzy (scheme) contract 5 | # last investors (or suckers) HODLing the bag 6 | # 7 | # to run type: 8 | # $ ruby ./run_ponzi_gradual.rb 9 | 10 | 11 | 12 | 13 | class GradualPonzi < Contract 14 | 15 | MINIMUM_INVESTMENT = 1_000_000 16 | 17 | def initialize 18 | @investors = [] # type address[] - array of address 19 | @investors.push( msg.sender ) 20 | 21 | @balances = Mapping.of( Address => Money ) # type mapping( address => unit ) 22 | end 23 | 24 | 25 | def receive # @payable default function 26 | assert( msg.value >= MINIMUM_INVESTMENT ) 27 | 28 | investor_share = msg.value / @investors.size 29 | @investors.each do |investor| 30 | @balances[investor] += investor_share 31 | end 32 | 33 | @investors.push( msg.sender ) 34 | end 35 | 36 | 37 | def withdraw 38 | payout = @balances[msg.sender] 39 | @balances[msg.sender] = 0 40 | msg.sender.transfer( payout ) 41 | end 42 | 43 | end # class GradualPonzi 44 | -------------------------------------------------------------------------------- /ponzi_simple.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | ## 4 | # a simple ponzy (scheme) contract 5 | # last investor (or sucker) HODLing the bag 6 | # 7 | # to run type: 8 | # $ ruby ./run_ponzi_simple.rb 9 | 10 | 11 | 12 | class SimplePonzi < Contract 13 | 14 | def initialize 15 | @current_investor = msg.sender # type address - (hex) string starts with 0x 16 | @current_investment = 0 # type uint 17 | end 18 | 19 | def receive ## @payable default function 20 | # note: new investments must be 10% greater than current 21 | minimum_investment = @current_investment * 11/10 22 | assert( msg.value > minimum_investment ) 23 | 24 | # record new investor 25 | previous_investor = @current_investor 26 | @current_investor = msg.sender 27 | @current_investment = msg.value 28 | 29 | # pay out previous investor 30 | previous_investor.send( msg.value ) 31 | end 32 | 33 | end # class SimplPonzi 34 | -------------------------------------------------------------------------------- /pyramid_simple.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | ## 4 | # a simple pyramid (scheme) contract 5 | # last investors (or suckers) HODLing the bag 6 | # 7 | # to run type: 8 | # $ ruby ./run_pyramid_simple.rb 9 | 10 | 11 | 12 | class SimplePyramid < Contract 13 | 14 | MINIMUM_INVESTMENT = 1_000_000 15 | 16 | def initialize 17 | assert( msg.value >= MINIMUM_INVESTMENT ) 18 | 19 | @depth = 1 # current depth / pyramid level 20 | @next_payout = 3 # start first payout with 3 investors (1 / 2,3) on depth+1 (e.g. 2nd) level 21 | 22 | @investors = [] # type address[] - array of address 23 | @investors.push( msg.sender ) 24 | 25 | @balances = Mapping.of( Address => Money ) # hash mapping with default value 0 26 | @balances[ address ] = msg.value 27 | end 28 | 29 | def receive # @payable 30 | assert( msg.value >= MINIMUM_INVESTMENT ) 31 | @balances[ address ] += msg.value 32 | 33 | @investors.push( msg.sender ) 34 | 35 | if( @investors.size == @next_payout ) 36 | # payout previous layer in pyramid 37 | ## e.g. with depth=1 (investors=3 e.g. 1+2) - 2**1=2 // 2**0=1 38 | ## - end_index: 1 = 3 - 2 39 | ## - start_index: 0 = 1 - 1 40 | ## with depth=2 (investors=7 e.g. 1+2+4) - 2**2=4 // 2**1=2 41 | ## - end_index: 3 = 7 - 4 42 | ## - start_index: 1 = 3 - 2 43 | ## with depth=3 (inverstors=15 e.g. 1+2+4+8) - 2**3=8 // 2**2=4 44 | ## - end_index: 7 = 15 - 8 45 | ## - start_index: 3 = 7 - 4 46 | ## ... 47 | end_index = @investors.size - 2**@depth 48 | start_index = end_index - 2**(@depth-1) 49 | 50 | puts "depth: #{@depth}, investors: #{@investors.size}, start_index: #{start_index}, end_index: #{end_index}" 51 | 52 | # note: use ... (three dots) NOT .. (two dots) to exclude end index 53 | (start_index...end_index).each do |index| 54 | @balances[ @investors[index] ] += MINIMUM_INVESTMENT 55 | puts "payout investment for ##{index+1} - yes, it works!" 56 | end 57 | 58 | ## share remaining balance among all 59 | paid = MINIMUM_INVESTMENT * 2**(@depth-1) 60 | share = (@balances[ address ] - paid) / @investors.size 61 | puts "paid out: #{paid}, remaining share / investor: #{share}" 62 | 63 | @investors.each do |investor| 64 | @balances[investor] += share 65 | end 66 | 67 | ## update pyramid state 68 | @balances[ address ] = 0 69 | @depth += 1 70 | @next_payout += 2**@depth 71 | end 72 | end 73 | 74 | def withdraw 75 | payout = @balances[ msg.sender ] 76 | @balances[ msg.sender ] = 0 77 | msg.sender.transfer( payout ) 78 | end 79 | 80 | end # class SimplePyramid 81 | -------------------------------------------------------------------------------- /run_ponzi_governmental.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'universum' 4 | 5 | require_relative './ponzi_governmental' 6 | 7 | 8 | ### 9 | # test contract 10 | 11 | ## setup test accounts with starter balance 12 | Account[ '0x1111' ].balance = 1_000_000 13 | Account[ '0xaaaa' ].balance = 1_000_000 14 | Account[ '0xbbbb' ].balance = 1_000_000 15 | Account[ '0xcccc' ].balance = 1_000_000 16 | Account[ '0xdddd' ].balance = 1_000_000 17 | Account[ '0xeeee' ].balance = 1_000_000 18 | 19 | ## (pp) pretty print all known accounts with balance 20 | pp Uni.accounts 21 | 22 | ## genesis - create contract 23 | gov = Uni.send_transaction( from: '0x1111', value: 1_000_000, data: Governmental ).contract 24 | pp gov 25 | 26 | Uni.send_transaction( from: '0xaaaa', to: gov, value: 1_000_000 ) 27 | pp gov 28 | Uni.send_transaction( from: '0xbbbb', to: gov, value: 1_000_000, data: [:lend_government_money, '0xaaaa'] ) 29 | pp gov 30 | Uni.send_transaction( from: '0xcccc', to: gov, value: 1_000_000 ) 31 | pp gov 32 | Uni.send_transaction( from: '0xdddd', to: gov, value: 1_000_000 ) 33 | pp gov 34 | 35 | ## (pp) pretty print all known accounts with balance 36 | pp Uni.accounts 37 | 38 | ## mine - move block time ahead more than 12h (use 13h) 39 | Uni.block = { number: 1, timestamp: Time.now.to_i + 60*60*13 } 40 | 41 | ## economy will collapse and new (ponzi) round starts 42 | Uni.send_transaction( from: '0xeeee', to: gov, value: 1_000_000 ) 43 | pp gov 44 | 45 | ## (pp) pretty print all known accounts with balance 46 | pp Uni.accounts 47 | -------------------------------------------------------------------------------- /run_ponzi_gradual.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'universum' 4 | 5 | require_relative './ponzi_gradual' 6 | 7 | 8 | ### 9 | # test contract 10 | 11 | ## setup test accounts with starter balance 12 | Account[ '0x1111' ].balance = 0 13 | Account[ '0xaaaa' ].balance = 1_000_000 14 | Account[ '0xbbbb' ].balance = 1_000_000 15 | Account[ '0xcccc' ].balance = 1_000_000 16 | Account[ '0xdddd' ].balance = 1_000_000 17 | 18 | ## pretty print (pp) all known accounts with balance 19 | pp Uni.accounts 20 | 21 | ## genesis - create contract 22 | ponzi = Uni.send_transaction( from: '0x1111', data: GradualPonzi ).contract 23 | pp ponzi 24 | 25 | Uni.send_transaction( from: '0xaaaa', to: ponzi, value: 1_000_000 ) 26 | pp ponzi 27 | 28 | Uni.send_transaction( from: '0xbbbb', to: ponzi, value: 1_000_000 ) 29 | pp ponzi 30 | 31 | Uni.send_transaction( from: '0xcccc', to: ponzi, value: 1_000_000 ) 32 | pp ponzi 33 | 34 | Uni.send_transaction( from: '0xdddd', to: ponzi, value: 1_000_000 ) 35 | pp ponzi 36 | 37 | ## pretty print (pp) all known accounts with balance 38 | pp Uni.accounts 39 | 40 | Uni.send_transaction( from: '0xaaaa', to: ponzi, data: [:withdraw] ) 41 | pp ponzi 42 | 43 | ## pretty print (pp) all known accounts with balance 44 | pp Uni.accounts 45 | -------------------------------------------------------------------------------- /run_ponzi_simple.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'universum' 4 | 5 | require_relative './ponzi_simple' 6 | 7 | 8 | ### 9 | # test contract 10 | 11 | ## setup test accounts with starter balance 12 | Account[ '0x1111' ].balance = 0 13 | Account[ '0xaaaa' ].balance = 1_000_000 14 | Account[ '0xbbbb' ].balance = 1_200_000 15 | Account[ '0xcccc' ].balance = 1_400_000 16 | 17 | ## pretty print (pp) all known accounts with balance 18 | pp Uni.accounts 19 | 20 | ## genesis - create contract 21 | ponzi = Uni.send_transaction( from: '0x1111', data: SimplePonzi ).contract 22 | pp ponzi 23 | 24 | Uni.send_transaction( from: '0xaaaa', to: ponzi, value: 1_000_000 ) 25 | pp ponzi 26 | 27 | Uni.send_transaction( from: '0xbbbb', to: ponzi, value: 1_200_000 ) 28 | pp ponzi 29 | 30 | Uni.send_transaction( from: '0xcccc', to: ponzi, value: 1_400_000 ) 31 | pp ponzi 32 | 33 | ## pretty print (pp) all known accounts with balance 34 | pp Uni.accounts 35 | -------------------------------------------------------------------------------- /run_pyramid_simple.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'universum' 4 | 5 | require_relative './pyramid_simple' 6 | 7 | 8 | ### 9 | # test contract 10 | 11 | ## setup test accounts with starter balance 12 | Account[ '0x1111' ].balance = 1_000_000 13 | Account[ '0xaa11' ].balance = 1_000_000 14 | Account[ '0xaa22' ].balance = 1_000_000 15 | Account[ '0xbb11' ].balance = 1_000_000 16 | Account[ '0xbb22' ].balance = 1_000_000 17 | Account[ '0xbb33' ].balance = 1_000_000 18 | Account[ '0xbb44' ].balance = 1_000_000 19 | Account[ '0xcc11' ].balance = 1_000_000 20 | Account[ '0xcc22' ].balance = 1_000_000 21 | 22 | ## pretty print (pp) all known accounts with balance 23 | pp Uni.accounts 24 | 25 | ## genesis - create contract 26 | pyramid = Uni.send_transaction( from: '0x1111', value: 1_000_000, data: SimplePyramid ).contract 27 | pp pyramid 28 | 29 | ## level 1 30 | Uni.send_transaction( from: '0xaa11', to: pyramid, value: 1_000_000 ) 31 | pp pyramid 32 | Uni.send_transaction( from: '0xaa22', to: pyramid, value: 1_000_000 ) 33 | pp pyramid 34 | 35 | ## level 2 36 | Uni.send_transaction( from: '0xbb11', to: pyramid, value: 1_000_000 ) 37 | pp pyramid 38 | Uni.send_transaction( from: '0xbb22', to: pyramid, value: 1_000_000 ) 39 | pp pyramid 40 | Uni.send_transaction( from: '0xbb33', to: pyramid, value: 1_000_000 ) 41 | pp pyramid 42 | Uni.send_transaction( from: '0xbb44', to: pyramid, value: 1_000_000 ) 43 | pp pyramid 44 | 45 | # level 3 46 | Uni.send_transaction( from: '0xcc11', to: pyramid, value: 1_000_000 ) 47 | pp pyramid 48 | Uni.send_transaction( from: '0xcc22', to: pyramid, value: 1_000_000 ) 49 | pp pyramid 50 | 51 | ## pretty print (pp) all known accounts with balance 52 | pp Uni.accounts 53 | 54 | Uni.send_transaction( from: '0x1111', to: pyramid, data: [:withdraw] ) 55 | pp pyramid 56 | 57 | ## pretty print (pp) all known accounts with balance 58 | pp Uni.accounts 59 | -------------------------------------------------------------------------------- /run_satoshi_dice.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | require 'universum' 4 | 5 | require_relative './satoshi_dice' 6 | 7 | 8 | ## setup test accounts with starter balance 9 | Account[ '0x1111' ].balance = 1_000 10 | Account[ '0xaaaa' ].balance = 1_000 11 | Account[ '0xbbbb' ].balance = 1_000 12 | Account[ '0xcccc' ].balance = 1_000 13 | 14 | ## pretty print (pp) all known accounts with balance 15 | pp Uni.accounts 16 | 17 | 18 | ## genesis - create contract 19 | dice = Uni.send_transaction( from: '0x1111', data: SatoshiDice ).contract 20 | pp dice 21 | 22 | Uni.send_transaction( from: '0x1111', to: dice, value: 1_000, data: [:fund] ) 23 | pp dice 24 | 25 | Uni.send_transaction( from: '0xaaaa', to: dice, value: 100, data: [:bet, 20_000] ) 26 | pp dice 27 | 28 | Uni.send_transaction( from: '0xbbbb', to: dice, value: 100, data: [:bet, 30_000] ) 29 | pp dice 30 | 31 | ## "mine" some blocks - block number (height) set to 3 and timestamp to Time.now 32 | Uni.block = { timestamp: Time.now.to_i, number: 3 } 33 | 34 | Uni.send_transaction( from: '0xaaaa', to: dice, data: [:roll, 1] ) 35 | pp dice 36 | 37 | Uni.send_transaction( from: '0xbbbb', to: dice, data: [:roll, 2] ) 38 | pp dice 39 | 40 | ## pretty print (pp) all known accounts with balance 41 | pp Uni.accounts 42 | -------------------------------------------------------------------------------- /satoshi_dice.rb: -------------------------------------------------------------------------------- 1 | # encoding: utf-8 2 | 3 | ## 4 | # satoshi dice gambling - up to 65 000x returns on your bet! 5 | # 6 | # to run type: 7 | # $ ruby ./run_satoshi_dice.rb 8 | 9 | 10 | class SatoshiDice < Contract 11 | 12 | ## type struct - address user; uint block; uint cap; uint amount; 13 | Bet = Struct.new( :user, :block, :cap, :amount ) 14 | 15 | ## Fee (Casino House Edge) is 1.9%, that is, 19 / 1000 16 | FEE_NUMERATOR = 19 17 | FEE_DENOMINATOR = 1000 18 | 19 | 20 | MAXIMUM_CAP = 2**16 # 65_536 = 2^16 = 2 byte/16 bit 21 | MAXIMUM_BET = 100_000_000 22 | MINIMUM_BET = 100 23 | 24 | BetPlaced = Event.new( :id, :user, :cap, :amount ) ## type uint id, address user, uint cap, uint amount 25 | Roll = Event.new( :id, :rolled ) ## type uint id, uint rolled 26 | 27 | 28 | def initialize 29 | @owner = msg.sender 30 | @counter = 0 31 | @bets = Mapping.of( Integer => Bet ) ## type mapping( uint => Bet ) 32 | end 33 | 34 | def bet( cap ) 35 | assert( cap >= 1 && cap <= MAXIMUM_CAP ) 36 | assert( msg.value >= MINIMUM_BET && msg.value <= MAXIMUM_BET ) 37 | 38 | @counter += 1 39 | @bets[@counter] = Bet.new( msg.sender, block.number+3, cap, msg.value ) 40 | log BetPlaced.new( @counter, msg.sender, cap, msg.value ) 41 | end 42 | 43 | def roll( id ) 44 | bet = @bets[id] 45 | 46 | assert( msg.sender == bet.user ) 47 | assert( block.number >= bet.block ) 48 | assert( block.number <= bet.block + 255 ) 49 | 50 | ## "provable" fair - random number depends on 51 | ## - blockhash (of block in the future - t+3) 52 | ## - nonce (that is, bet counter id) 53 | hex = sha256( "#{blockhash( bet.block )} #{id}" ) 54 | ## get first 2 bytes (4 chars in hex string) and convert to integer number 55 | ## results in a number between 0 and 65_535 56 | rolled = hex[0,4].to_i(16) 57 | puts "rolled: #{rolled} - hex: #{hex[0,4]}" 58 | 59 | if rolled < bet.cap 60 | payout = bet.amount * MAXIMUM_CAP / bet.cap 61 | fee = payout * FEE_NUMERATOR / FEE_DENOMINATOR 62 | payout -= fee 63 | puts "bingo! payout: #{payout}, fee: #{fee}" 64 | 65 | msg.sender.transfer( payout ) 66 | end 67 | 68 | log Roll.new( id, rolled ) 69 | @bets.delete( id ) 70 | end 71 | 72 | def fund 73 | end 74 | 75 | def kill 76 | assert( msg.sender == @owner ) 77 | selfdestruct( @owner ) 78 | end 79 | end 80 | -------------------------------------------------------------------------------- /satoshi_dice_payout.rb: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Fee (Casino House Edge) is 1.9%, that is, 19 / 1000 4 | FEE_NUMERATOR = 19 5 | FEE_DENOMINATOR = 1000 6 | 7 | 8 | MAXIMUM_CAP = 2**16 # 65_536 = 2^16 = 2 byte/16 bit 9 | 10 | 11 | (1..MAXIMUM_CAP).each do |bet| 12 | if bet > 200 && bet < 65530 13 | next unless bet % 100 == 0 14 | end 15 | 16 | print "%5d" % bet 17 | 18 | payout = MAXIMUM_CAP / bet.to_f 19 | fee = payout * FEE_NUMERATOR / FEE_DENOMINATOR.to_f 20 | payout -= fee 21 | print " - %f x " % payout 22 | print " (%f %%) " % (bet*100 / MAXIMUM_CAP.to_f ) 23 | print " (+ #{FEE_NUMERATOR}/#{FEE_DENOMINATOR} fee %f) " % fee 24 | 25 | print "\n" 26 | 27 | end 28 | --------------------------------------------------------------------------------