diff --git a/contracts/dice/README.md b/contracts/dice/README.md index d4f1930e5626dd5e1f8c4d248773341379748406..0edd7d5ca34f02fcad58f6fb2333eacb9845f531 100644 --- a/contracts/dice/README.md +++ b/contracts/dice/README.md @@ -43,4 +43,227 @@ Potential Vulnerabilities - under normal operation of DPOS chains there are few if any chain reorganizations +Example game session using cleos +------- +#### Prerequisites +* Wallet must be unlock and have at least the following private keys + + **5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3** + **5Jmsawgsp1tQ3GD6JyGCwy1dcvqKZgX6ugMVMdjirx85iv5VyPR** + +##### Upload bios contract +````bash +cleos set contract eosio build/contracts/eosio.bios -p eosio +```` + +##### Ceate eosio.token account +````bash +cleos create account eosio eosio.token EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4 EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4 +```` + +##### Set eosio.token contract to eosio.token account +````bash +cleos set contract eosio.token build/contracts/eosio.token -p eosio.token +```` + +##### Create dice account +````bash +cleos create account eosio dice EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4 EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4 +```` + +##### Set dice contract to dice account +````bash +cleos set contract dice build/contracts/dice -p dice +```` + +##### Create native EOS token +````bash +cleos push action eosio.token create '[ "eosio", "1000000000.0000 EOS", 0, 0, 0]' -p eosio.token +```` + +##### Create alice account +````bash +cleos create account eosio alice EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4 EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4 +```` + +##### Create bob account +````bash +cleos create account eosio bob EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4 EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4 +```` + +##### Issue 1000 EOS to alice +````bash +cleos push action eosio.token issue '[ "alice", "1000.0000 EOS", "" ]' -p eosio +```` + +##### Issue 1000 EOS to bob +````bash +cleos push action eosio.token issue '[ "bob", "1000.0000 EOS", "" ]' -p eosio +```` + +##### Allow dice contract to make transfers on alice behalf (deposit) +````bash +cleos set account permission alice active '{"threshold": 1,"keys": [{"key": "EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4","weight": 1}],"accounts": [{"permission":{"actor":"dice","permission":"active"},"weight":1}]}' owner -p alice +```` + +##### Allow dice contract to make transfers on bob behalf (deposit) +````bash +cleos set account permission bob active '{"threshold": 1,"keys": [{"key": "EOS7ijWCBmoXBi3CgtK7DJxentZZeTkeUnaSDvyro9dq7Sd1C3dC4","weight": 1}],"accounts": [{"permission":{"actor":"dice","permission":"active"},"weight":1}]}' owner -p bob +```` + +##### Alice deposits 100 EOS into the dice contract +````bash +cleos push action dice deposit '[ "alice", "100.0000 EOS" ]' -p alice +```` + +##### Bob deposits 100 EOS into the dice contract +````bash +cleos push action dice deposit '[ "bob", "100.0000 EOS" ]' -p bob +```` + +##### Alice generates a secret +````bash +openssl rand 32 -hex +28349b1d4bcdc9905e4ef9719019e55743c84efa0c5e9a0b077f0b54fcd84905 +```` + +##### Alice generates sha256(secret) +````bash +echo -n '28349b1d4bcdc9905e4ef9719019e55743c84efa0c5e9a0b077f0b54fcd84905' | xxd -r -p | sha256sum -b | awk '{print $1}' +d533f24d6f28ddcef3f066474f7b8355383e485681ba8e793e037f5cf36e4883 +```` + +##### Alice bets 3 EOS +````bash +cleos push action dice offerbet '[ "3.0000 EOS", "alice", "d533f24d6f28ddcef3f066474f7b8355383e485681ba8e793e037f5cf36e4883" ]' -p alice +```` + +##### Bob generates a secret +````bash +openssl rand 32 -hex +15fe76d25e124b08feb835f12e00a879bd15666a33786e64b655891fba7d6c12 +```` + +##### Bob generates sha256(secret) +````bash +echo -n '15fe76d25e124b08feb835f12e00a879bd15666a33786e64b655891fba7d6c12' | xxd -r -p | sha256sum -b | awk '{print $1}' +50ed53fcdaf27f88d51ea4e835b1055efe779bb87e6cfdff47d28c88ffb27129 +```` + +##### Bob also bets 3 EOS (a game is started) +````bash +cleos push action dice offerbet '[ "3.0000 EOS", "bob", "50ed53fcdaf27f88d51ea4e835b1055efe779bb87e6cfdff47d28c88ffb27129" ]' -p bob +```` + +##### Dice contract tables right after the game started +````bash +cleos get table dice dice account +```` +````json +{ + "rows": [{ + "owner": "alice", + "eos_balance": "97.0000 EOS", + "open_offers": 0, + "open_games": 1 + },{ + "owner": "bob", + "eos_balance": "97.0000 EOS", + "open_offers": 0, + "open_games": 1 + } + ], + "more": false +} +```` + +````bash +cleos get table dice dice game +```` +````json +{ + "rows": [{ + "id": 1, + "bet": "3.0000 EOS", + "deadline": "1970-01-01T00:00:00", + "player1": { + "commitment": "d533f24d6f28ddcef3f066474f7b8355383e485681ba8e793e037f5cf36e4883", + "reveal": "0000000000000000000000000000000000000000000000000000000000000000" + }, + "player2": { + "commitment": "50ed53fcdaf27f88d51ea4e835b1055efe779bb87e6cfdff47d28c88ffb27129", + "reveal": "0000000000000000000000000000000000000000000000000000000000000000" + } + } + ], + "more": false +} +```` + +##### Bob reveals his secret +````bash +cleos push action dice reveal '[ "50ed53fcdaf27f88d51ea4e835b1055efe779bb87e6cfdff47d28c88ffb27129", "15fe76d25e124b08feb835f12e00a879bd15666a33786e64b655891fba7d6c12" ]' -p bob +```` + +##### Game table after bob revealed (now the game has a deadline for alice to reveal) +````bash +cleos get table dice dice game +```` +````json +{ + "rows": [{ + "id": 1, + "bet": "3.0000 EOS", + "deadline": "2018-04-17T07:45:49", + "player1": { + "commitment": "d533f24d6f28ddcef3f066474f7b8355383e485681ba8e793e037f5cf36e4883", + "reveal": "0000000000000000000000000000000000000000000000000000000000000000" + }, + "player2": { + "commitment": "50ed53fcdaf27f88d51ea4e835b1055efe779bb87e6cfdff47d28c88ffb27129", + "reveal": "15fe76d25e124b08feb835f12e00a879bd15666a33786e64b655891fba7d6c12" + } + } + ], + "more": false +} +```` + +##### Alice reveals her secret (the winner is determined, the game is removed) +````bash +cleos push action dice reveal '[ "d533f24d6f28ddcef3f066474f7b8355383e485681ba8e793e037f5cf36e4883", "28349b1d4bcdc9905e4ef9719019e55743c84efa0c5e9a0b077f0b54fcd84905" ]' -p alice +```` + +##### Balance of the accounts after game ends +````bash +cleos get table dice dice account +```` +````json +{ + "rows": [{ + "owner": "alice", + "eos_balance": "103.0000 EOS", + "open_offers": 0, + "open_games": 0 + },{ + "owner": "bob", + "eos_balance": "97.0000 EOS", + "open_offers": 0, + "open_games": 0 + } + ], + "more": false +} +```` + +##### Alice withdraw from her dice account 103 EOS +````bash +cleos push action dice withdraw '[ "alice", "103.0000 EOS" ]' -p alice +```` + +##### Balance of alice after withdraw +````bash +cleos get currency balance eosio.token alice eos +1003.0000 EOS +```` diff --git a/contracts/dice/dice.cpp b/contracts/dice/dice.cpp index 6665d939bd57614f0ccef08b0f39ac39eabd44d5..2100ffcfe3e086e9d23d650a913ff4556449b73c 100644 --- a/contracts/dice/dice.cpp +++ b/contracts/dice/dice.cpp @@ -5,22 +5,20 @@ #include #include -#include -#include -#include -#include -#include -#include +#include +#include +#include #include - -#include - -using eos_currency = eosiosystem::contract::currency; +#include using eosio::key256; using eosio::indexed_by; using eosio::const_mem_fun; using eosio::asset; +using eosio::permission_level; +using eosio::action; +using eosio::print; +using eosio::name; class dice : public eosio::contract { public: @@ -37,9 +35,10 @@ class dice : public eosio::contract { //@abi action void offerbet(const asset& bet, const account_name player, const checksum256& commitment) { - auto amount = eos_currency::token_type(bet); + eosio_assert( bet.symbol == S(4,EOS) , "only EOS token allowed" ); + eosio_assert( bet.is_valid(), "invalid bet" ); + eosio_assert( bet.amount > 0, "must bet positive quantity" ); - eosio_assert( amount.quantity > 0, "invalid bet" ); eosio_assert( !has_offer( commitment ), "offer with this commitment already exist" ); require_auth( player ); @@ -60,12 +59,13 @@ class dice : public eosio::contract { auto matched_offer_itr = idx.lower_bound( (uint64_t)new_offer_itr->bet.amount ); if( matched_offer_itr == idx.end() - || matched_offer_itr->bet.amount != new_offer_itr->bet.amount + || matched_offer_itr->bet != new_offer_itr->bet || matched_offer_itr->owner == new_offer_itr->owner ) { // No matching bet found, update player's account accounts.modify( cur_player_itr, 0, [&](auto& acnt) { - acnt.eos_balance = (asset)(eos_currency::token_type(acnt.eos_balance) - amount); + eosio_assert( acnt.eos_balance >= bet, "insufficient balance" ); + acnt.eos_balance -= bet; acnt.open_offers++; }); @@ -114,7 +114,8 @@ class dice : public eosio::contract { }); accounts.modify( cur_player_itr, 0, [&](auto& acnt) { - acnt.eos_balance = (asset)(eos_currency::token_type(acnt.eos_balance) - amount); + eosio_assert( acnt.eos_balance >= bet, "insufficient balance" ); + acnt.eos_balance -= bet; acnt.open_games++; }); } @@ -133,7 +134,7 @@ class dice : public eosio::contract { auto acnt_itr = accounts.find(offer_itr->owner); accounts.modify(acnt_itr, 0, [&](auto& acnt){ acnt.open_offers--; - acnt.eos_balance.amount += offer_itr->bet.amount; + acnt.eos_balance += offer_itr->bet; }); idx.erase(offer_itr); @@ -212,7 +213,10 @@ class dice : public eosio::contract { } //@abi action - void deposit( const account_name from, const asset& a ) { + void deposit( const account_name from, const asset& quantity ) { + + eosio_assert( quantity.is_valid(), "invalid quantity" ); + eosio_assert( quantity.amount > 0, "must deposit positive quantity" ); auto itr = accounts.find(from); if( itr == accounts.end() ) { @@ -221,27 +225,37 @@ class dice : public eosio::contract { }); } - auto amount = eos_currency::token_type(a); + action( + permission_level{ from, N(active) }, + N(eosio.token), N(transfer), + std::make_tuple(from, _self, quantity, std::string("")) + ).send(); - eos_currency::inline_transfer( from, _self, amount ); accounts.modify( itr, 0, [&]( auto& acnt ) { - acnt.eos_balance = (asset)(eos_currency::token_type(acnt.eos_balance) + amount); + acnt.eos_balance += quantity; }); } //@abi action - void withdraw( const account_name to, const asset& a ) { + void withdraw( const account_name to, const asset& quantity ) { require_auth( to ); + eosio_assert( quantity.is_valid(), "invalid quantity" ); + eosio_assert( quantity.amount > 0, "must withdraw positive quantity" ); + auto itr = accounts.find( to ); eosio_assert(itr != accounts.end(), "unknown account"); - auto amount = eos_currency::token_type(a); accounts.modify( itr, 0, [&]( auto& acnt ) { - acnt.eos_balance = (asset)(eos_currency::token_type(acnt.eos_balance) - amount); + eosio_assert( acnt.eos_balance >= quantity, "insufficient balance" ); + acnt.eos_balance -= quantity; }); - eos_currency::inline_transfer( _self, to, amount ); + action( + permission_level{ _self, N(active) }, + N(eosio.token), N(transfer), + std::make_tuple(_self, to, quantity, std::string("")) + ).send(); if( itr->is_empty() ) { accounts.erase(itr); @@ -354,7 +368,7 @@ class dice : public eosio::contract { // Update winner account balance and game count auto winner_account = accounts.find(winner_offer.owner); accounts.modify( winner_account, 0, [&]( auto& acnt ) { - acnt.eos_balance.amount += 2*g.bet.amount; + acnt.eos_balance += 2*g.bet; acnt.open_games--; }); diff --git a/contracts/eosiolib/asset.hpp b/contracts/eosiolib/asset.hpp index 465ccf0b5fbabd304f8338f265db31d37c0d1d23..26bc6a62d4413f921d902653bb1f3815bf46fd4f 100644 --- a/contracts/eosiolib/asset.hpp +++ b/contracts/eosiolib/asset.hpp @@ -199,7 +199,7 @@ namespace eosio { friend bool operator==( const asset& a, const asset& b ) { eosio_assert( a.symbol == b.symbol, "comparison of assets with different symbols is not allowed" ); - return a.amount < b.amount; + return a.amount == b.amount; } friend bool operator!=( const asset& a, const asset& b ) { diff --git a/tests/wasm_tests/dice_tests.cpp b/tests/wasm_tests/dice_tests.cpp index 3608000c27a34b7e54d44407dbf25270261fc174..12cb732a3adfdb5c2f6d87cd1e9cdb3000127e1b 100644 --- a/tests/wasm_tests/dice_tests.cpp +++ b/tests/wasm_tests/dice_tests.cpp @@ -219,33 +219,34 @@ BOOST_AUTO_TEST_SUITE(dice_tests) BOOST_FIXTURE_TEST_CASE( dice_test, dice_tester ) try { - set_code(config::system_account_name, eosio_token_wast); - set_abi(config::system_account_name, eosio_token_abi); + create_accounts( {N(eosio.token), N(dice),N(alice),N(bob),N(carol),N(david)}, false); + + set_code(N(eosio.token), eosio_token_wast); + set_abi(N(eosio.token), eosio_token_abi); - create_accounts( {N(dice),N(alice),N(bob),N(carol),N(david)}, false); produce_block(); add_dice_authority(N(alice)); add_dice_authority(N(bob)); add_dice_authority(N(carol)); - push_action(N(eosio), N(create), N(eosio), mvo() - ("issuer", "eosio") - ("maximum_supply", "1000000000000.0000 EOS") + push_action(N(eosio.token), N(create), N(eosio.token), mvo() + ("issuer", "eosio.token") + ("maximum_supply", "1000000000.0000 EOS") ("can_freeze", "0") ("can_recall", "0") ("can_whitelist", "0") ); - push_action(N(eosio), N(issue), N(eosio), mvo() + push_action(N(eosio.token), N(issue), N(eosio.token), mvo() ("to", "eosio") - ("quantity", "1000000.0000 EOS") + ("quantity", "1000000000.0000 EOS") ("memo", "") ); - transfer( N(eosio), N(alice), "10000.0000 EOS", "", N(eosio) ); - transfer( N(eosio), N(bob), "10000.0000 EOS", "", N(eosio) ); - transfer( N(eosio), N(carol), "10000.0000 EOS", "", N(eosio) ); + transfer( N(eosio), N(alice), "10000.0000 EOS", "", N(eosio.token) ); + transfer( N(eosio), N(bob), "10000.0000 EOS", "", N(eosio.token) ); + transfer( N(eosio), N(carol), "10000.0000 EOS", "", N(eosio.token) ); produce_block(); @@ -283,7 +284,7 @@ BOOST_FIXTURE_TEST_CASE( dice_test, dice_tester ) try { // Alice tries to bet 1000 EOS (fail) // secret : a512f6b1b589a8906d574e9de74a529e504a5c53a760f0991a3e00256c027971 - BOOST_CHECK_THROW( offer_bet( N(alice), asset::from_string("10000.0000 EOS"), + BOOST_CHECK_THROW( offer_bet( N(alice), asset::from_string("1000.0000 EOS"), commitment_for("a512f6b1b589a8906d574e9de74a529e504a5c53a760f0991a3e00256c027971") ), fc::exception); produce_block(); @@ -384,7 +385,7 @@ BOOST_FIXTURE_TEST_CASE( dice_test, dice_tester ) try { BOOST_REQUIRE_EQUAL( balance_of(N(alice)), asset::from_string("1.0000 EOS")); BOOST_REQUIRE_EQUAL( - get_currency_balance(N(eosio), EOS_SYMBOL, N(alice)), + get_currency_balance(N(eosio.token), EOS_SYMBOL, N(alice)), asset::from_string("10009.0000 EOS") ); @@ -396,7 +397,7 @@ BOOST_FIXTURE_TEST_CASE( dice_test, dice_tester ) try { withdraw( N(alice), asset::from_string("1.0000 EOS")); BOOST_REQUIRE_EQUAL( - get_currency_balance(N(eosio), EOS_SYMBOL, N(alice)), + get_currency_balance(N(eosio.token), EOS_SYMBOL, N(alice)), asset::from_string("10010.0000 EOS") );