未验证 提交 6aa302d1 编写于 作者: D Daniel Larimer 提交者: GitHub

Merge pull request #1663 from EOSIO/dice-contract

Enhance ABI generator
......@@ -29,6 +29,7 @@ add_subdirectory(test_api_multi_index)
#add_subdirectory(social)
add_subdirectory(eosio.bios)
add_subdirectory(noop)
add_subdirectory(dice)
file(GLOB SKELETONS RELATIVE ${CMAKE_SOURCE_DIR}/contracts "skeleton/*")
......@@ -43,6 +44,7 @@ add_custom_command(OUTPUT share/eosio/skeleton/skeleton.cpp
add_custom_target(copy_skeleton_contract ALL DEPENDS share/eosio/skeleton/skeleton.cpp)
install(DIRECTORY eosiolib DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR})
install(DIRECTORY eosio.system DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR})
install(DIRECTORY musl DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR})
install(DIRECTORY libc++ DESTINATION ${CMAKE_INSTALL_FULL_INCLUDEDIR})
install(DIRECTORY skeleton DESTINATION ${CMAKE_INSTALL_FULL_DATAROOTDIR}/eosio)
......
file(GLOB ABI_FILES "*.abi")
configure_file("${ABI_FILES}" "${CMAKE_CURRENT_BINARY_DIR}" COPYONLY)
add_wast_executable(TARGET dice
INCLUDE_FOLDERS "${STANDARD_INCLUDE_FOLDERS}"
LIBRARIES libc++ libc eosiolib
DESTINATION_FOLDER ${CMAKE_CURRENT_BINARY_DIR}
)
{
"____comment": "This file was generated by eosio-abigen. DO NOT EDIT - 2018-03-29T02:09:11",
"types": [],
"structs": [{
"name": "offer",
"base": "",
"fields": [{
"name": "id",
"type": "uint64"
},{
"name": "owner",
"type": "account_name"
},{
"name": "bet",
"type": "asset"
},{
"name": "commitment",
"type": "checksum256"
},{
"name": "gameid",
"type": "uint64"
}
]
},{
"name": "player",
"base": "",
"fields": [{
"name": "commitment",
"type": "checksum256"
},{
"name": "reveal",
"type": "checksum256"
}
]
},{
"name": "game",
"base": "",
"fields": [{
"name": "id",
"type": "uint64"
},{
"name": "bet",
"type": "asset"
},{
"name": "deadline",
"type": "time"
},{
"name": "player1",
"type": "player"
},{
"name": "player2",
"type": "player"
}
]
},{
"name": "global_dice",
"base": "",
"fields": [{
"name": "id",
"type": "uint64"
},{
"name": "nextgameid",
"type": "uint64"
}
]
},{
"name": "account",
"base": "",
"fields": [{
"name": "owner",
"type": "account_name"
},{
"name": "eos_balance",
"type": "asset"
},{
"name": "open_offers",
"type": "uint32"
},{
"name": "open_games",
"type": "uint32"
}
]
},{
"name": "offerbet",
"base": "",
"fields": [{
"name": "bet",
"type": "asset"
},{
"name": "player",
"type": "account_name"
},{
"name": "commitment",
"type": "checksum256"
}
]
},{
"name": "canceloffer",
"base": "",
"fields": [{
"name": "commitment",
"type": "checksum256"
}
]
},{
"name": "reveal",
"base": "",
"fields": [{
"name": "commitment",
"type": "checksum256"
},{
"name": "source",
"type": "checksum256"
}
]
},{
"name": "claimexpired",
"base": "",
"fields": [{
"name": "gameid",
"type": "uint64"
}
]
},{
"name": "deposit",
"base": "",
"fields": [{
"name": "from",
"type": "account_name"
},{
"name": "a",
"type": "asset"
}
]
},{
"name": "withdraw",
"base": "",
"fields": [{
"name": "to",
"type": "account_name"
},{
"name": "a",
"type": "asset"
}
]
}
],
"actions": [{
"name": "offerbet",
"type": "offerbet"
},{
"name": "canceloffer",
"type": "canceloffer"
},{
"name": "reveal",
"type": "reveal"
},{
"name": "claimexpired",
"type": "claimexpired"
},{
"name": "deposit",
"type": "deposit"
},{
"name": "withdraw",
"type": "withdraw"
}
],
"tables": [{
"name": "offer",
"index_type": "i64",
"key_names": [
"id"
],
"key_types": [
"uint64"
],
"type": "offer"
},{
"name": "game",
"index_type": "i64",
"key_names": [
"id"
],
"key_types": [
"uint64"
],
"type": "game"
},{
"name": "global",
"index_type": "i64",
"key_names": [
"id"
],
"key_types": [
"uint64"
],
"type": "global_dice"
},{
"name": "account",
"index_type": "i64",
"key_names": [
"owner"
],
"key_types": [
"account_name"
],
"type": "account"
}
]
}
\ No newline at end of file
......@@ -3,80 +3,375 @@
* @copyright defined in eos/LICENSE.txt
*/
namespace dice {
void apply_offer( const offer_bet& offer ) {
eosio_assert( offer.amount > 0, "insufficient bet" );
eosio_assert( !hasOffer( offer.commitment ), "offer with this commitment already exist" );
require_auth( offer.player );
auto acnt = get_account( offer.player );
acnt.balance -= offer.amount;
acnt.open_offers++;
save( acnt );
/**
* 1. Lookup lowerbound on offer's by offer_primary_key
* if lowerbound.primary.bet == offer.bet
* global_dice.nextgameid++ (load and save to DB)
* create new game and set game.bet == offer.bet
* set player1 == lowerbound player
* set player2 == offer.player
* update lowerbound.primary.bet = 0 and lwoerbound.gameid = global_dice.nextgameid
* Create offer entry in offers table with bet = 0 and gameid == --^
* else
* Create offer entry in offers table with bet = offer.bet and gameid = 0
*/
}
void apply_cancel( const cancel_offer& offer ) {
/**
* Lookup offer in offer's table and assert that gameid == 0
* Lookup account and increment balance by bet
* Deelte offer from offers table
*/
}
#include <utility>
#include <vector>
#include <eosiolib/crypto.h>
#include <eosiolib/types.hpp>
#include <eosiolib/token.hpp>
#include <eosiolib/print.hpp>
#include <eosiolib/action.hpp>
#include <eosiolib/multi_index.hpp>
#include <eosiolib/contract.hpp>
/**
* No authority required on this message so long as it is properly revealed
*/
void apply_reveal( const reveal& offer ) {
assert_sha256( &offer.source, sizeof(offer.source), &offer.commitment );
/**
* assert offer already exists
* assert offer has a gameid > 0
*
* Lookup game by gameid
* assert game.player[x].reveal is 0 hasn't already been revealed
* set game.player[x].reveal to revealed
*
* if( game.player[!x].reveal != 0 ) {
* uint256_t result;
* sha256( &game.player1, sizeof(player)*2, result);
* if( result.words[1] < result.words[0] ) {
* pay_player1 by incrementing account by bet and decrement open games
* }
* else
* pay_player2 by incrmenting account by bet and decrement open games
* delete game, and both offers
* }
* else {
* set game.deadline = now() + timeout (5 minutes)
* }
*
*/
}
#include <eosio.system/eosio.system.hpp>
/**
* No authority required by this so long as
*/
void apply_claim( const claim_expired& claim ) {
/// lookup game by id, assert now() > deadline and deadline != 0
/// pay the player that revealed
/// delete game and open offers
}
using eos_currency = eosiosystem::contract<N(eosio)>::currency;
using eosio::key256;
using eosio::indexed_by;
using eosio::const_mem_fun;
using eosio::asset;
class dice : public eosio::contract {
public:
const uint32_t FIVE_MINUTES = 5*60;
dice(account_name self)
:eosio::contract(self),
offers(_self, _self),
games(_self, _self),
global_dices(_self, _self),
accounts(_self, _self)
{}
//@abi action
void offerbet(const asset& bet, const account_name player, const checksum256& commitment) {
auto amount = eos_currency::token_type(bet);
eosio_assert( amount.quantity > 0, "invalid bet" );
eosio_assert( !has_offer( commitment ), "offer with this commitment already exist" );
require_auth( player );
auto cur_player_itr = accounts.find( player );
eosio_assert(cur_player_itr != accounts.end(), "unknown account");
// Store new offer
auto new_offer_itr = offers.emplace(_self, [&](auto& offer){
offer.id = offers.available_primary_key();
offer.bet = bet;
offer.owner = player;
offer.commitment = commitment;
offer.gameid = 0;
});
// Try to find a matching bet
auto idx = offers.template get_index<N(bet)>();
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->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);
acnt.open_offers++;
});
} else {
// Create global game counter if not exists
auto gdice_itr = global_dices.begin();
if( gdice_itr == global_dices.end() ) {
gdice_itr = global_dices.emplace(_self, [&](auto& gdice){
gdice.nextgameid=0;
});
}
// Increment global game counter
global_dices.modify(gdice_itr, 0, [&](auto& gdice){
gdice.nextgameid++;
});
// Create a new game
auto game_itr = games.emplace(_self, [&](auto& new_game){
new_game.id = gdice_itr->nextgameid;
new_game.bet = new_offer_itr->bet;
new_game.deadline = 0;
new_game.player1.commitment = matched_offer_itr->commitment;
memset(&new_game.player1.reveal, 0, sizeof(checksum256));
new_game.player2.commitment = new_offer_itr->commitment;
memset(&new_game.player2.reveal, 0, sizeof(checksum256));
});
// Update player's offers
idx.modify(matched_offer_itr, 0, [&](auto& offer){
offer.bet.amount = 0;
offer.gameid = game_itr->id;
});
offers.modify(new_offer_itr, 0, [&](auto& offer){
offer.bet.amount = 0;
offer.gameid = game_itr->id;
});
// Update player's accounts
accounts.modify( accounts.find( matched_offer_itr->owner ), 0, [&](auto& acnt) {
acnt.open_offers--;
acnt.open_games++;
});
accounts.modify( cur_player_itr, 0, [&](auto& acnt) {
acnt.eos_balance = (asset)(eos_currency::token_type(acnt.eos_balance) - amount);
acnt.open_games++;
});
}
}
//@abi action
void canceloffer( const checksum256& commitment ) {
auto idx = offers.template get_index<N(commitment)>();
auto offer_itr = idx.find( offer::get_commitment(commitment) );
eosio_assert( offer_itr != idx.end(), "offer does not exists" );
eosio_assert( offer_itr->gameid == 0, "unable to cancel offer" );
require_auth( offer_itr->owner );
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;
});
idx.erase(offer_itr);
}
//@abi action
void reveal( const checksum256& commitment, const checksum256& source ) {
assert_sha256( (char *)&source, sizeof(source), (const checksum256 *)&commitment );
auto idx = offers.template get_index<N(commitment)>();
auto curr_revealer_offer = idx.find( offer::get_commitment(commitment) );
eosio_assert(curr_revealer_offer != idx.end(), "offer not found");
eosio_assert(curr_revealer_offer->gameid > 0, "unable to reveal");
auto game_itr = games.find( curr_revealer_offer->gameid );
player curr_reveal = game_itr->player1;
player prev_reveal = game_itr->player2;
if( !is_equal(curr_reveal.commitment, commitment) ) {
std::swap(curr_reveal, prev_reveal);
}
eosio_assert( is_zero(curr_reveal.reveal) == true, "player already revealed");
if( !is_zero(prev_reveal.reveal) ) {
checksum256 result;
sha256( (char *)&game_itr->player1, sizeof(player)*2, &result);
auto prev_revealer_offer = idx.find( offer::get_commitment(prev_reveal.commitment) );
int winner = result.hash[1] < result.hash[0] ? 0 : 1;
if( winner ) {
pay_and_clean(*game_itr, *curr_revealer_offer, *prev_revealer_offer);
} else {
pay_and_clean(*game_itr, *prev_revealer_offer, *curr_revealer_offer);
}
} else {
games.modify(game_itr, 0, [&](auto& game){
if( is_equal(curr_reveal.commitment, game.player1.commitment) )
game.player1.reveal = source;
else
game.player2.reveal = source;
game.deadline = now() + FIVE_MINUTES;
});
}
}
//@abi action
void claimexpired( const uint64_t gameid ) {
auto game_itr = games.find(gameid);
eosio_assert(game_itr != games.end(), "game not found");
eosio_assert(game_itr->deadline != 0 && now() > game_itr->deadline, "game not expired");
auto idx = offers.template get_index<N(commitment)>();
auto player1_offer = idx.find( offer::get_commitment(game_itr->player1.commitment) );
auto player2_offer = idx.find( offer::get_commitment(game_itr->player2.commitment) );
if( !is_zero(game_itr->player1.reveal) ) {
eosio_assert( is_zero(game_itr->player2.reveal), "game error");
pay_and_clean(*game_itr, *player1_offer, *player2_offer);
} else {
eosio_assert( is_zero(game_itr->player1.reveal), "game error");
pay_and_clean(*game_itr, *player2_offer, *player1_offer);
}
}
//@abi action
void deposit( const account_name from, const asset& a ) {
auto itr = accounts.find(from);
if( itr == accounts.end() ) {
itr = accounts.emplace(_self, [&](auto& acnt){
acnt.owner = from;
});
}
auto amount = eos_currency::token_type(a);
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);
});
}
//@abi action
void withdraw( const account_name to, const asset& a ) {
require_auth( to );
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);
});
eos_currency::inline_transfer( _self, to, amount );
if( itr->is_empty() ) {
accounts.erase(itr);
}
}
private:
//@abi table offer i64
struct offer {
uint64_t id;
account_name owner;
asset bet;
checksum256 commitment;
uint64_t gameid = 0;
uint64_t primary_key()const { return id; }
uint64_t by_bet()const { return (uint64_t)bet.amount; }
key256 by_commitment()const { return get_commitment(commitment); }
static key256 get_commitment(const checksum256& commitment) {
const uint64_t *p64 = reinterpret_cast<const uint64_t *>(&commitment);
return key256::make_from_word_sequence<uint64_t>(p64[0], p64[1], p64[2], p64[3]);
}
EOSLIB_SERIALIZE( offer, (id)(owner)(bet)(commitment)(gameid) )
};
typedef eosio::multi_index< N(offer), offer,
indexed_by< N(bet), const_mem_fun<offer, uint64_t, &offer::by_bet > >,
indexed_by< N(commitment), const_mem_fun<offer, key256, &offer::by_commitment> >
> offer_index;
struct player {
checksum256 commitment;
checksum256 reveal;
EOSLIB_SERIALIZE( player, (commitment)(reveal) )
};
//@abi table game i64
struct game {
uint64_t id;
asset bet;
time deadline;
player player1;
player player2;
uint64_t primary_key()const { return id; }
EOSLIB_SERIALIZE( game, (id)(bet)(deadline)(player1)(player2) )
};
typedef eosio::multi_index< N(game), game> game_index;
//@abi table global i64
struct global_dice {
uint64_t id = 0;
uint64_t nextgameid = 0;
uint64_t primary_key()const { return id; }
EOSLIB_SERIALIZE( global_dice, (id)(nextgameid) )
};
typedef eosio::multi_index< N(global), global_dice> global_dice_index;
//@abi table account i64
struct account {
account( account_name o = account_name() ):owner(o){}
account_name owner;
asset eos_balance;
uint32_t open_offers = 0;
uint32_t open_games = 0;
bool is_empty()const { return !( eos_balance.amount | open_offers | open_games ); }
uint64_t primary_key()const { return owner; }
EOSLIB_SERIALIZE( account, (owner)(eos_balance)(open_offers)(open_games) )
};
typedef eosio::multi_index< N(account), account> account_index;
offer_index offers;
game_index games;
global_dice_index global_dices;
account_index accounts;
bool has_offer( const checksum256& commitment )const {
auto idx = offers.template get_index<N(commitment)>();
auto itr = idx.find( offer::get_commitment(commitment) );
return itr != idx.end();
}
bool is_equal(const checksum256& a, const checksum256& b)const {
return memcmp((void *)&a, (const void *)&b, sizeof(checksum256)) == 0;
}
bool is_zero(const checksum256& a)const {
const uint64_t *p64 = reinterpret_cast<const uint64_t*>(&a);
return p64[0] == 0 && p64[1] == 0 && p64[2] == 0 && p64[3] == 0;
}
void pay_and_clean(const game& g, const offer& winner_offer,
const offer& loser_offer) {
// 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.open_games--;
});
// Update losser account game count
auto loser_account = accounts.find(loser_offer.owner);
accounts.modify( loser_account, 0, [&]( auto& acnt ) {
acnt.open_games--;
});
if( loser_account->is_empty() ) {
accounts.erase(loser_account);
}
}
games.erase(g);
offers.erase(winner_offer);
offers.erase(loser_offer);
}
};
EOSIO_ABI( dice, (offerbet)(canceloffer)(reveal)(claimexpired)(deposit)(withdraw) )
/**
* @file
* @copyright defined in eos/LICENSE.txt
*/
namespace dice {
struct offer_bet {
eosio::tokens amount;
account_name player;
uint256 commitment;
};
struct cancel_offer {
uint256 commitment;
};
struct reveal {
uint256 commitment;
uint256 source;
};
struct claim_expired {
uint64_t gameid = 0;
};
using eos_tokens = eosio::tokens;
struct offer_primary_key {
eos_tokens bet;
account_name owner;
};
/**
* This struct is used with i128xi128 index which allows us to lookup and
* iterate over offers by either {bet,owner,commitment} or {commitment,bet,owner}
*
* Only the first 128 bits of commitment ID are used in the index, but there will
* be a requirement that these first 128 bits be unique and any collisions will
* be rejected.
*
* If there is no existing offer of equal bet size then gameid is set as 0, if it is
* matched then a new game record is created and the offer is modified so that bet
* goes to 0 (and therefore moves to end of queue and not matched with other bets) and
* the gameid of both offers are updated.
*/
struct PACKED( offer ) {
offer_primary_key primary;
uint256 commitment;
uint32_t gameid = 0;
};
struct player {
uint256 commitment;
uint256 reveal;
};
/**
*
*/
struct PACKED( game ) {
uint32_t gameid;
eos_tokens bet;
time deadline;
player player1;
player player2;
};
struct Packed( global_dice ) {
uint32_t nextgameid = 0;
};
struct PACKED( account ) {
account( account_name o = account_name() ):owner(o){}
account_name owner;
eos_tokens eos_balance;
uint32_t open_offers = 0;
bool is_empty()const { return ! ( bool(eos_balance) | open_offers); }
};
using accounts = eosio::table<N(dice),N(dice),N(account),account,uint64_t>;
using global_dices = eosio::table<N(dice),N(dice),N(global),global_dice,uint64_t>;
using offers = eosio::table<N(dice),N(dice),N(global),global_dice,offer_primary_key,uint128_t>;
inline account get_account( account_name owner ) {
account owned_account(owner);
accounts::get( owned_account );
return owned_account;
}
};
{
"types": [{
"new_type_name": "account_name",
"type": "name"
}
],
"types": [],
"structs": [{
"name": "transfer",
"base": "",
......
......@@ -37,18 +37,16 @@ namespace eosio {
return eosio::dispatch<Contract,SecondAction,Actions...>( code, act );
}
template<typename T, typename... Args>
bool execute_action( T* obj, void (T::*func)(Args...) ) {
char buffer[action_data_size()];
read_action_data( buffer, sizeof(buffer) );
auto args = unpack<std::tuple<Args...>>( buffer, sizeof(buffer) );
auto args = unpack<std::tuple<std::decay_t<Args>...>>( buffer, sizeof(buffer) );
auto f2 = [&]( auto... a ){
(obj->*func)( a... );
};
// apply( obj, func, args );
boost::mp11::tuple_apply( f2, args );
return true;
......
......@@ -68,7 +68,7 @@ string abi_generator::translate_type(const string& type_name) {
else if (type_name == "unsigned long" || type_name == "uint32_t") built_in_type = "uint32";
else if (type_name == "unsigned short" || type_name == "uint16_t") built_in_type = "uint16";
else if (type_name == "unsigned char" || type_name == "uint8_t") built_in_type = "uint8";
else if (type_name == "long long" || type_name == "int64_t") built_in_type = "int64";
else if (type_name == "long" || type_name == "int32_t") built_in_type = "int32";
else if (type_name == "short" || type_name == "int16_t") built_in_type = "int16";
......@@ -77,23 +77,89 @@ string abi_generator::translate_type(const string& type_name) {
return built_in_type;
}
bool abi_generator::inspect_type_methods_for_actions(const Decl* decl) { try {
const auto* rec_decl = dyn_cast<CXXRecordDecl>(decl);
if(rec_decl == nullptr) return false;
const auto* type = rec_decl->getTypeForDecl();
ABI_ASSERT(type != nullptr);
if( !type->isStructureOrClassType() ) {
return false;
}
bool at_least_one_action = false;
for(const auto* method : rec_decl->methods()) {
const RawComment* raw_comment = ast_context->getRawCommentForDeclNoCache(method);
if( raw_comment == nullptr) continue;
SourceManager& source_manager = ast_context->getSourceManager();
string raw_text = raw_comment->getRawText(source_manager);
regex r(R"(@abi (action))");
smatch smatch;
if(regex_search(raw_text, smatch, r)){
auto method_name = method->getNameAsString();
ABI_ASSERT(find_struct(method_name) == nullptr, "action already exists ${method_name}", ("method_name",method_name));
struct_def abi_struct;
for(const auto* p : method->parameters() ) {
clang::QualType qt = p->getOriginalType().getNonReferenceType();
qt.setLocalFastQualifiers(0);
string field_name = p->getNameAsString();
string field_type_name = add_type(qt);
field_def struct_field{field_name, field_type_name};
ABI_ASSERT(is_builtin_type(get_vector_element_type(struct_field.type))
|| find_struct(get_vector_element_type(struct_field.type))
|| find_type(get_vector_element_type(struct_field.type))
, "Unknown type ${type} [${abi}]",("type",struct_field.type)("abi",*output));
type_size[string(struct_field.type)] = is_vector(struct_field.type) ? 0 : ast_context->getTypeSize(qt);
abi_struct.fields.push_back(struct_field);
}
abi_struct.name = method_name;
abi_struct.base = "";
output->structs.push_back(abi_struct);
full_types[method_name] = method_name;
output->actions.push_back({method_name, method_name});
at_least_one_action = true;
}
}
return at_least_one_action;
} FC_CAPTURE_AND_RETHROW() }
void abi_generator::handle_decl(const Decl* decl) { try {
ABI_ASSERT(decl != nullptr);
ABI_ASSERT(output != nullptr);
ABI_ASSERT(ast_context != nullptr);
//ASTContext& ctx = decl->getASTContext();
const RawComment* raw_comment = ast_context->getRawCommentForDeclNoCache(decl);
if(!raw_comment) return;
SourceManager& source_manager = ast_context->getSourceManager();
auto file_name = source_manager.getFilename(raw_comment->getLocStart());
auto file_name = source_manager.getFilename(decl->getLocStart());
if ( !abi_context.empty() && !file_name.startswith(abi_context) ) {
return;
}
if(inspect_type_methods_for_actions(decl)) {
return;
}
const RawComment* raw_comment = ast_context->getRawCommentForDeclNoCache(decl);
if(raw_comment == nullptr) {
return;
}
string raw_text = raw_comment->getRawText(source_manager);
regex r(R"(@abi (action|table)((?: [a-z0-9]+)*))");
......@@ -103,7 +169,7 @@ void abi_generator::handle_decl(const Decl* decl) { try {
if(smatch.size() == 3) {
auto type = smatch[1].str();
vector<string> params;
auto string_params = smatch[2].str();
boost::trim(string_params);
......@@ -116,7 +182,7 @@ void abi_generator::handle_decl(const Decl* decl) { try {
ABI_ASSERT(action_decl != nullptr);
auto qt = action_decl->getTypeForDecl()->getCanonicalTypeInternal();
auto type_name = add_struct(qt);
ABI_ASSERT(!is_builtin_type(type_name),
"A built-in type with the same name exists, try using another name: ${type_name}", ("type_name",type_name));
......@@ -151,7 +217,7 @@ void abi_generator::handle_decl(const Decl* decl) { try {
table_def table;
table.name = boost::algorithm::to_lower_copy(boost::erase_all_copy(type_name, "_"));
table.type = type_name;
if(params.size() >= 1) {
table.name = params[0];
}
......@@ -173,9 +239,9 @@ void abi_generator::handle_decl(const Decl* decl) { try {
}
}
}
raw_text = smatch.suffix();
}
}
} FC_CAPTURE_AND_RETHROW() }
......@@ -193,11 +259,11 @@ bool abi_generator::is_string(const string& type) {
void abi_generator::get_all_fields(const struct_def& s, vector<field_def>& fields) {
abi_serializer abis(*output);
for(const auto& field : s.fields) {
fields.push_back(field);
}
if(s.base.size()) {
const auto* base = find_struct(s.base);
ABI_ASSERT(base, "Unable to find base type ${type}",("type",s.base));
......@@ -246,7 +312,7 @@ void abi_generator::guess_key_names(table_def& table, const struct_def s) {
if( table.index_type == "i64i64i64" || table.index_type == "i128i128"
|| table.index_type == "i64") {
table.key_names.clear();
table.key_types.clear();
......@@ -342,7 +408,7 @@ bool abi_generator::is_elaborated(const clang::QualType& qt) {
}
bool abi_generator::is_vector(const clang::QualType& vqt) {
QualType qt(vqt);
if ( is_elaborated(qt) )
......@@ -404,7 +470,7 @@ clang::QualType abi_generator::add_typedef(const clang::QualType& tqt) {
abi_typedef.new_type_name = new_type_name;
abi_typedef.type = translate_type(underlying_type_name);
const auto* td = find_type(abi_typedef.new_type_name);
if(!td && !is_struct_specialization(underlying_type) ) {
output->types.push_back(abi_typedef);
} else {
......@@ -424,7 +490,7 @@ clang::CXXRecordDecl::base_class_range abi_generator::get_struct_bases(const cla
const auto* td_decl = qt->getAs<clang::TypedefType>()->getDecl();
qt = td_decl->getUnderlyingType().getUnqualifiedType();
}
const auto* record_type = qt->getAs<clang::RecordType>();
ABI_ASSERT(record_type != nullptr);
auto cxxrecord_decl = clang::dyn_cast<CXXRecordDecl>(record_type->getDecl());
......@@ -455,7 +521,7 @@ string abi_generator::add_vector(const clang::QualType& vqt) {
auto vector_element_type = get_vector_element_type(qt);
ABI_ASSERT(!is_vector(vector_element_type), "Only one-dimensional arrays are supported");
add_type(vector_element_type);
auto vector_element_type_str = translate_type(get_type_name(vector_element_type));
......@@ -492,7 +558,7 @@ string abi_generator::add_type(const clang::QualType& tqt) {
if( is_struct(qt) ) {
return add_struct(qt, full_type_name);
}
ABI_ASSERT(false, "types can only be: vector, struct, class or a built-in type. (${type}) ", ("type",get_type_name(qt)));
return type_name;
}
......@@ -513,7 +579,7 @@ string abi_generator::add_struct(const clang::QualType& sqt, string full_name) {
}
auto name = remove_namespace(full_name);
ABI_ASSERT(is_struct(qt), "Only struct and class are supported. ${full_name}",("full_name",full_name));
if( find_struct(name) ) {
......@@ -543,7 +609,7 @@ string abi_generator::add_struct(const clang::QualType& sqt, string full_name) {
struct_def abi_struct;
for (const clang::FieldDecl* field : get_struct_fields(qt) ) {
clang::QualType qt = field->getType();
string field_name = field->getNameAsString();
string field_type_name = add_type(qt);
......@@ -561,7 +627,7 @@ string abi_generator::add_struct(const clang::QualType& sqt, string full_name) {
abi_struct.base = base_name;
output->structs.push_back(abi_struct);
full_types[name] = full_name;
return name;
}
......
......@@ -124,6 +124,7 @@ namespace eosio {
void handle_tagdecl_definition(TagDecl* tag_decl);
private:
bool inspect_type_methods_for_actions(const Decl* decl);
string remove_namespace(const string& full_name);
......
#include <fc/exception/exception.hpp>
#include <fc/io/json.hpp>
#include <eosio/abi_generator/abi_generator.hpp>
#include <fc/variant_object.hpp>
using namespace eosio;
using namespace eosio::chain::contracts;
using mvo = fc::mutable_variant_object;
std::unique_ptr<FrontendActionFactory> create_factory(bool verbose, bool opt_sfs, string abi_context, abi_def& output) {
class abi_frontend_action_factory : public FrontendActionFactory {
bool verbose;
......@@ -49,7 +52,17 @@ int main(int argc, const char **argv) { abi_def output; try {
int result = Tool.run(create_factory(abi_verbose, abi_opt_sfs, abi_context, output).get());
if(!result) {
abi_serializer(output).validate();
fc::json::save_to_file<abi_def>(output, abi_destination, true);
fc::variant vabi;
to_variant(output, vabi);
auto comment = fc::format_string(
"This file was generated by eosio-abigen. DO NOT EDIT - ${ts}",
mvo("ts",fc::time_point_sec(fc::time_point::now()).to_iso_string()));
auto abi_with_comment = mvo("____comment", comment)(mvo(vabi));
fc::json::save_to_file(abi_with_comment, abi_destination, true);
}
return result;
} FC_CAPTURE_AND_LOG((output)); return -1; }
......@@ -28,7 +28,7 @@ target_include_directories( chain_test PUBLIC ${CMAKE_BINARY_DIR}/contracts ${CM
target_include_directories( chain_test PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/wasm_tests )
target_include_directories( chain_test PUBLIC ${CMAKE_SOURCE_DIR}/plugins/net_plugin/include )
target_include_directories( chain_test PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include )
add_dependencies(chain_test asserter test_api test_api_mem test_api_db test_api_multi_index exchange currency proxy identity identity_test stltest infinite eosio.system eosio.token eosio.bios test.inline multi_index_test noop )
add_dependencies(chain_test asserter test_api test_api_mem test_api_db test_api_multi_index exchange currency proxy identity identity_test stltest infinite eosio.system eosio.token eosio.bios test.inline multi_index_test noop dice)
#
configure_file(${CMAKE_CURRENT_SOURCE_DIR}/p2p_tests/sync/test.sh ${CMAKE_CURRENT_BINARY_DIR}/p2p_tests/sync/test.sh COPYONLY)
......
#include <boost/test/unit_test.hpp>
#include <eosio/testing/tester.hpp>
#include <eosio/chain/contracts/abi_serializer.hpp>
#include <dice/dice.wast.hpp>
#include <dice/dice.abi.hpp>
#include <eosio.token/eosio.token.wast.hpp>
#include <eosio.token/eosio.token.abi.hpp>
#include <Runtime/Runtime.h>
#include <fc/variant_object.hpp>
using namespace eosio;
using namespace eosio::chain;
using namespace eosio::chain::contracts;
using namespace eosio::testing;
using namespace fc;
using namespace std;
using mvo = fc::mutable_variant_object;
struct offer_bet_t {
asset bet;
account_name player;
checksum256_type commitment;
static account_name get_account() { return N(dice); }
static action_name get_name() {return N(offerbet); }
};
FC_REFLECT(offer_bet_t, (bet)(player)(commitment));
struct cancel_offer_t {
checksum256_type commitment;
static account_name get_account() { return N(dice); }
static action_name get_name() {return N(canceloffer); }
};
FC_REFLECT(cancel_offer_t, (commitment));
struct reveal_t {
checksum256_type commitment;
checksum256_type source;
static account_name get_account() { return N(dice); }
static action_name get_name() {return N(reveal); }
};
FC_REFLECT(reveal_t, (commitment)(source));
struct deposit_t {
account_name from;
asset amount;
static account_name get_account() { return N(dice); }
static action_name get_name() {return N(deposit); }
};
FC_REFLECT( deposit_t, (from)(amount) );
struct withdraw_t {
account_name to;
asset amount;
static account_name get_account() { return N(dice); }
static action_name get_name() {return N(withdraw); }
};
FC_REFLECT( withdraw_t, (to)(amount) );
struct __attribute((packed)) account_t {
account_name owner;
asset eos_balance;
uint32_t open_offers;
uint32_t open_games;
};
FC_REFLECT(account_t, (owner)(eos_balance)(open_offers)(open_games));
struct player_t {
checksum_type commitment;
checksum_type reveal;
};
FC_REFLECT(player_t, (commitment)(reveal));
struct __attribute((packed)) game_t {
uint64_t gameid;
asset bet;
fc::time_point_sec deadline;
player_t player1;
player_t player2;
};
FC_REFLECT(game_t, (gameid)(bet)(deadline)(player1)(player2));
struct dice_tester : tester {
const contracts::table_id_object* find_table( name code, name scope, name table ) {
auto tid = control->get_database().find<table_id_object, by_code_scope_table>(boost::make_tuple(code, scope, table));
return tid;
}
template<typename IndexType, typename Scope>
const auto& get_index() {
return control->get_database().get_index<IndexType,Scope>();
}
void offer_bet(account_name account, asset amount, const checksum_type& commitment) {
signed_transaction trx;
action act( {{account, config::active_name}},
offer_bet_t{amount, account, commitment} );
trx.actions.push_back(act);
set_transaction_headers(trx);
trx.sign(get_private_key( account, "active" ), chain_id_type());
control->push_transaction(packed_transaction(trx,packed_transaction::none));
}
void cancel_offer(account_name account, const checksum_type& commitment) {
signed_transaction trx;
action act( {{account, config::active_name}},
cancel_offer_t{commitment} );
trx.actions.push_back(act);
set_transaction_headers(trx);
trx.sign(get_private_key( account, "active" ), chain_id_type());
control->push_transaction(packed_transaction(trx,packed_transaction::none));
}
void deposit(account_name account, asset amount) {
signed_transaction trx;
action act( {{account, config::active_name}},
deposit_t{account, amount} );
trx.actions.push_back(act);
set_transaction_headers(trx);
trx.sign(get_private_key( account, "active" ), chain_id_type());
control->push_transaction(packed_transaction(trx,packed_transaction::none));
}
void withdraw(account_name account, asset amount) {
signed_transaction trx;
action act( {{account, config::active_name}},
withdraw_t{account, amount} );
trx.actions.push_back(act);
set_transaction_headers(trx);
trx.sign(get_private_key( account, "active" ), chain_id_type());
control->push_transaction(packed_transaction(trx,packed_transaction::none));
}
void reveal(account_name account, const checksum_type& commitment, const checksum_type& source ) {
signed_transaction trx;
action act( {{account, config::active_name}},
reveal_t{commitment, source} );
trx.actions.push_back(act);
set_transaction_headers(trx);
trx.sign(get_private_key( account, "active" ), chain_id_type());
control->push_transaction(packed_transaction(trx,packed_transaction::none));
}
bool dice_account(account_name account, account_t& acnt) {
auto* maybe_tid = find_table(N(dice), N(dice), N(account));
if(maybe_tid == nullptr) return false;
auto* o = control->get_database().find<contracts::key_value_object, contracts::by_scope_primary>(boost::make_tuple(maybe_tid->id, account));
if(o == nullptr) {
return false;
}
fc::raw::unpack(o->value.data(), o->value.size(), acnt);
return true;
}
bool dice_game(uint64_t game_id, game_t& game) {
auto* maybe_tid = find_table(N(dice), N(dice), N(game));
if(maybe_tid == nullptr) return false;
auto* o = control->get_database().find<contracts::key_value_object, contracts::by_scope_primary>(boost::make_tuple(maybe_tid->id, game_id));
if(o == nullptr) return false;
fc::raw::unpack(o->value.data(), o->value.size(), game);
return true;
}
uint32_t open_games(account_name account) {
account_t acnt;
if(!dice_account(account, acnt)) return 0;
return acnt.open_games;
}
asset game_bet(uint64_t game_id) {
game_t game;
if(!dice_game(game_id, game)) return asset();
return game.bet;
}
uint32_t open_offers(account_name account) {
account_t acnt;
if(!dice_account(account, acnt)) return 0;
return acnt.open_offers;
}
asset balance_of(account_name account) {
account_t acnt;
if(!dice_account(account, acnt)) return asset();
return acnt.eos_balance;
}
checksum_type commitment_for( const char* secret ) {
return commitment_for(checksum_type(secret));
}
checksum_type commitment_for( const checksum_type& secret ) {
return fc::sha256::hash( secret.data(), sizeof(secret) );
}
void add_dice_authority(account_name account) {
auto auth = authority{
1,
{
{.key = get_public_key(account,"active"), .weight = 1}
},
{
{.permission = {N(dice),N(active)}, .weight = 1}
}
};
set_authority(account, N(active), auth, N(owner) );
}
};
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(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")
("can_freeze", "0")
("can_recall", "0")
("can_whitelist", "0")
);
push_action(N(eosio), N(issue), N(eosio), mvo()
("to", "eosio")
("quantity", "1000000.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) );
produce_block();
set_code(N(dice), dice_wast);
set_abi(N(dice), dice_abi);
produce_block();
// Alice deposits 1000 EOS
deposit( N(alice), asset::from_string("1000.0000 EOS"));
produce_block();
BOOST_REQUIRE_EQUAL( balance_of(N(alice)), asset::from_string("1000.0000 EOS"));
BOOST_REQUIRE_EQUAL( open_games(N(alice)), 0);
// Alice tries to bet 0 EOS (fail)
// secret : 9b886346e1351d4144d0b8392a975612eb0f8b6de7eae1cc9bcc55eb52be343c
BOOST_CHECK_THROW( offer_bet( N(alice), asset::from_string("0.0000 EOS"),
commitment_for("9b886346e1351d4144d0b8392a975612eb0f8b6de7eae1cc9bcc55eb52be343c")
), fc::exception);
// Alice bets 10 EOS (success)
// secret : 0ba044d2833758ee2c8f24d8a3f70c82c334abe6ce13219a4cf3b862abb03c46
offer_bet( N(alice), asset::from_string("10.0000 EOS"),
commitment_for("0ba044d2833758ee2c8f24d8a3f70c82c334abe6ce13219a4cf3b862abb03c46")
);
produce_block();
// Bob tries to bet using a secret previously used by Alice (fail)
// secret : 00000000000000000000000000000002c334abe6ce13219a4cf3b862abb03c46
BOOST_CHECK_THROW( offer_bet( N(bob), asset::from_string("10.0000 EOS"),
commitment_for("0ba044d2833758ee2c8f24d8a3f70c82c334abe6ce13219a4cf3b862abb03c46")
), fc::exception);
produce_block();
// Alice tries to bet 1000 EOS (fail)
// secret : a512f6b1b589a8906d574e9de74a529e504a5c53a760f0991a3e00256c027971
BOOST_CHECK_THROW( offer_bet( N(alice), asset::from_string("10000.0000 EOS"),
commitment_for("a512f6b1b589a8906d574e9de74a529e504a5c53a760f0991a3e00256c027971")
), fc::exception);
produce_block();
// Bob tries to bet 90 EOS without deposit
// secret : 4facfc98932dde46fdc4403125a16337f6879a842a7ff8b0dc8e1ecddd59f3c8
BOOST_CHECK_THROW( offer_bet( N(bob), asset::from_string("90.0000 EOS"),
commitment_for("4facfc98932dde46fdc4403125a16337f6879a842a7ff8b0dc8e1ecddd59f3c8")
), fc::exception);
produce_block();
// Bob deposits 500 EOS
deposit( N(bob), asset::from_string("500.0000 EOS"));
BOOST_REQUIRE_EQUAL( balance_of(N(bob)), asset::from_string("500.0000 EOS"));
// Bob bets 11 EOS (success)
// secret : eec3272712d974c474a3e7b4028b53081344a5f50008e9ccf918ba0725a8d784
offer_bet( N(bob), asset::from_string("11.0000 EOS"),
commitment_for("eec3272712d974c474a3e7b4028b53081344a5f50008e9ccf918ba0725a8d784")
);
produce_block();
// Bob cancels (success)
BOOST_REQUIRE_EQUAL( open_offers(N(bob)), 1);
cancel_offer( N(bob), commitment_for("eec3272712d974c474a3e7b4028b53081344a5f50008e9ccf918ba0725a8d784") );
BOOST_REQUIRE_EQUAL( open_offers(N(bob)), 0);
// Carol deposits 300 EOS
deposit( N(carol), asset::from_string("300.0000 EOS"));
// Carol bets 10 EOS (success)
// secret : 3efb4bd5e19b780f4980c919330c0306f8157f93db1fc72c7cefec63e0e7f37a
offer_bet( N(carol), asset::from_string("10.0000 EOS"),
commitment_for("3efb4bd5e19b780f4980c919330c0306f8157f93db1fc72c7cefec63e0e7f37a")
);
produce_block();
BOOST_REQUIRE_EQUAL( open_games(N(alice)), 1);
BOOST_REQUIRE_EQUAL( open_offers(N(alice)), 0);
BOOST_REQUIRE_EQUAL( open_games(N(carol)), 1);
BOOST_REQUIRE_EQUAL( open_offers(N(carol)), 0);
BOOST_REQUIRE_EQUAL( game_bet(1), asset::from_string("10.0000 EOS"));
// Alice tries to cancel a nonexistent bet (fail)
BOOST_CHECK_THROW( cancel_offer( N(alice),
commitment_for("00000000000000000000000000000000000000000000000000000000abb03c46")
), fc::exception);
// Alice tries to cancel an in-game bet (fail)
BOOST_CHECK_THROW( cancel_offer( N(alice),
commitment_for("0ba044d2833758ee2c8f24d8a3f70c82c334abe6ce13219a4cf3b862abb03c46")
), fc::exception);
// Alice reveals secret (success)
reveal( N(alice),
commitment_for("0ba044d2833758ee2c8f24d8a3f70c82c334abe6ce13219a4cf3b862abb03c46"),
checksum_type("0ba044d2833758ee2c8f24d8a3f70c82c334abe6ce13219a4cf3b862abb03c46")
);
produce_block();
// Alice tries to reveal again (fail)
BOOST_CHECK_THROW( reveal( N(alice),
commitment_for("0ba044d2833758ee2c8f24d8a3f70c82c334abe6ce13219a4cf3b862abb03c46"),
checksum_type("0ba044d2833758ee2c8f24d8a3f70c82c334abe6ce13219a4cf3b862abb03c46")
), fc::exception);
// Bob tries to reveal an invalid (secret,commitment) pair (fail)
BOOST_CHECK_THROW( reveal( N(bob),
commitment_for("121344d2833758ee2c8f24d8a3f70c82c334abe6ce13219a4cf3b862abb03c46"),
checksum_type("141544d2833758ee2c8f24d8a3f70c82c334abe6ce13219a4cf3b862abb03c46")
), fc::exception);
// Bob tries to reveal a valid (secret,commitment) pair that has no offer/game (fail)
BOOST_CHECK_THROW( reveal( N(bob),
commitment_for("e48c6884bb97ac5f5951df6012ce79f63bb8549ad0111315ad9ecbaf4c9b1eb8"),
checksum_type("e48c6884bb97ac5f5951df6012ce79f63bb8549ad0111315ad9ecbaf4c9b1eb8")
), fc::exception);
// Bob reveals Carol's secret (success)
reveal( N(bob),
commitment_for("3efb4bd5e19b780f4980c919330c0306f8157f93db1fc72c7cefec63e0e7f37a"),
checksum_type("3efb4bd5e19b780f4980c919330c0306f8157f93db1fc72c7cefec63e0e7f37a")
);
BOOST_REQUIRE_EQUAL( open_games(N(alice)), 0);
BOOST_REQUIRE_EQUAL( open_offers(N(alice)), 0);
BOOST_REQUIRE_EQUAL( balance_of(N(alice)), asset::from_string("1010.0000 EOS"));
BOOST_REQUIRE_EQUAL( open_games(N(carol)), 0);
BOOST_REQUIRE_EQUAL( open_offers(N(carol)), 0);
BOOST_REQUIRE_EQUAL( balance_of(N(carol)), asset::from_string("290.0000 EOS"));
// Alice withdraw 1009 EOS (success)
withdraw( N(alice), asset::from_string("1009.0000 EOS"));
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)),
asset::from_string("10009.0000 EOS")
);
// Alice withdraw 2 EOS (fail)
BOOST_CHECK_THROW( withdraw( N(alice), asset::from_string("2.0000 EOS")),
fc::exception);
// Alice withdraw 1 EOS (success)
withdraw( N(alice), asset::from_string("1.0000 EOS"));
BOOST_REQUIRE_EQUAL(
get_currency_balance(N(eosio), EOS_SYMBOL, N(alice)),
asset::from_string("10010.0000 EOS")
);
// Verify alice account was deleted
account_t alice_account;
BOOST_CHECK(dice_account(N(alice), alice_account) == false);
// No games in table
auto* game_tid = find_table(N(dice), N(dice), N(game));
BOOST_CHECK(game_tid != nullptr);
BOOST_CHECK(game_tid->count == 0);
// No offers in table
auto* offer_tid = find_table(N(dice), N(dice), N(offer));
BOOST_CHECK(offer_tid != nullptr);
BOOST_CHECK(offer_tid->count == 0);
// 2 records in account table (Bob & Carol)
auto* account_tid = find_table(N(dice), N(dice), N(account));
BOOST_CHECK(account_tid != nullptr);
BOOST_CHECK(account_tid->count == 2);
} FC_LOG_AND_RETHROW() /// basic_test
BOOST_AUTO_TEST_SUITE_END()
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册