提交 e3dc3082 编写于 作者: C Ciju John

Merge remote-tracking branch 'upstream/master' into improveTestingFixture-stat576

......@@ -29,7 +29,22 @@
"fields": [
{"name":"value", "type":"string"}
]
},{
"name": "regproducer",
"base": "",
"fields": [
{"name":"producer", "type":"account_name"}
{"name":"producer_key", "type":"bytes"}
]
},{
"name": "stakevote",
"base": "",
"fields": [
{"name":"voter", "type":"account_name"}
{"name":"amount", "type":"asset"}
]
}
],
"actions": [{
"name": "transfer",
......@@ -40,6 +55,12 @@
},{
"name": "nonce",
"type": "nonce"
},{
"name": "regproducer",
"type": "regproducer"
},{
"name": "stakevote",
"type": "stakevote"
}
],
"tables": [{
......@@ -50,4 +71,4 @@
"key_types" : ["name"]
}
]
}
\ No newline at end of file
}
......@@ -11,6 +11,7 @@ extern "C" {
/// The apply method implements the dispatch of events to this contract
void apply( uint64_t code, uint64_t act ) {
print( eosio::name(code), "::", eosio::name(act) );
eosiosystem::contract<N(eosio)>::apply( code, act );
}
}
......@@ -14,13 +14,59 @@
#include <eosiolib/multi_index.hpp>
#include <eosiolib/privileged.h>
#include <algorithm>
#include <map>
namespace eosiosystem {
using eosio::indexed_by;
using eosio::const_mem_fun;
using eosio::bytes;
using std::map;
using std::pair;
using eosio::print;
template<account_name SystemAccount>
class contract {
public:
static const account_name system_account = SystemAccount;
typedef eosio::generic_currency< eosio::token<system_account,S(4,EOS)> > currency;
typedef typename currency::token_type system_token_type;
struct producer_votes {
account_name owner;
uint128_t total_votes;
uint64_t primary_key()const { return owner; }
uint128_t by_votes()const { return total_votes; }
EOSLIB_SERIALIZE( producer_votes, (owner)(total_votes) );
};
typedef eosio::multi_index< N(producervote), producer_votes,
indexed_by<N(prototalvote), const_mem_fun<producer_votes, uint128_t, &producer_votes::by_votes> >
> producer_votes_index_type;
struct account_votes {
account_name owner;
account_name proxy;
uint32_t last_update;
system_token_type staked;
std::vector<account_name> producers;
uint64_t primary_key()const { return owner; }
EOSLIB_SERIALIZE( account_votes, (owner)(proxy)(last_update)(staked)(producers) );
};
typedef eosio::multi_index< N(accountvotes), account_votes> account_votes_index_type;
struct producer_config {
account_name owner;
eosio::bytes packed_key; /// a packed public key object
uint64_t primary_key()const { return owner; }
EOSLIB_SERIALIZE( producer_config, (owner)(packed_key) );
};
typedef eosio::multi_index< N(producercfg), producer_config> producer_config_index_type;
struct total_resources {
account_name owner;
......@@ -34,7 +80,6 @@ namespace eosiosystem {
};
/**
* Every user 'from' has a scope/table that uses every receipient 'to' as the primary key.
*/
......@@ -72,9 +117,10 @@ namespace eosiosystem {
};
ACTION( SystemAccount, regproducer ) {
account_name producer_to_register;
account_name producer;
bytes producer_key;
EOSLIB_SERIALIZE( regproducer, (producer_to_register) );
EOSLIB_SERIALIZE( regproducer, (producer)(producer_key) );
};
ACTION( SystemAccount, regproxy ) {
......@@ -83,21 +129,23 @@ namespace eosiosystem {
EOSLIB_SERIALIZE( regproxy, (proxy_to_register) );
};
ACTION( SystemAccount, delnetbw ) {
ACTION( SystemAccount, delegatebw ) {
account_name from;
account_name receiver;
typename currency::token_type stake_quantity;
typename currency::token_type stake_net_quantity;
typename currency::token_type stake_cpu_quantity;
EOSLIB_SERIALIZE( delnetbw, (from)(receiver)(stake_quantity) )
EOSLIB_SERIALIZE( delegatebw, (from)(receiver)(stake_net_quantity)(stake_cpu_quantity) )
};
ACTION( SystemAccount, undelnetbw ) {
ACTION( SystemAccount, undelegatebw ) {
account_name from;
account_name receiver;
typename currency::token_type stake_quantity;
typename currency::token_type unstake_net_quantity;
typename currency::token_type unstake_cpu_quantity;
EOSLIB_SERIALIZE( delnetbw, (delegator)(receiver)(stake_quantity) )
EOSLIB_SERIALIZE( undelegatebw, (from)(receiver)(unstake_net_quantity)(unstake_cpu_quantity) )
};
ACTION( SystemAccount, nonce ) {
......@@ -110,7 +158,14 @@ namespace eosiosystem {
// 1. hash + collision
// 2. incrementing count (key=> tablename
static void on( const delnetbw& del ) {
static void on( const delegatebw& del ) {
eosio_assert( del.stake_cpu_quantity.quantity >= 0, "must stake a positive amount" );
eosio_assert( del.stake_net_quantity.quantity >= 0, "must stake a positive amount" );
auto total_stake = del.stake_cpu_quantity + del.stake_net_quantity;
eosio_assert( total_stake.quantity >= 0, "must stake a positive amount" );
require_auth( del.from );
del_bandwidth_index_type del_index( SystemAccount, del.from );
......@@ -123,12 +178,14 @@ namespace eosiosystem {
del_index.emplace( del.from, [&]( auto& dbo ){
dbo.from = del.from;
dbo.to = del.receiver;
dbo.net_weight = del.stake_quantity;
dbo.net_weight = del.stake_net_quantity;
dbo.cpu_weight = del.stake_cpu_quantity;
});
}
else {
del_index.update( *itr, del.from, [&]( auto& dbo ){
dbo.net_weight = del.stake_quantity;
dbo.net_weight = del.stake_net_quantity;
dbo.cpu_weight = del.stake_cpu_quantity;
});
}
......@@ -136,25 +193,190 @@ namespace eosiosystem {
if( tot_itr == nullptr ) {
tot_itr = &total_index.emplace( del.from, [&]( auto& tot ) {
tot.owner = del.receiver;
tot.total_net_weight += del.stake_quantity;
tot.total_net_weight += del.stake_net_quantity;
tot.total_cpu_weight += del.stake_cpu_quantity;
});
} else {
total_index.update( *tot_itr, 0, [&]( auto& tot ) {
tot.total_net_weight += del.stake_quantity;
tot.total_net_weight += del.stake_net_quantity;
tot.total_cpu_weight += del.stake_cpu_quantity;
});
}
set_resource_limits( tot_itr->owner, tot_itr->total_ram, tot_itr->total_net_weight.quantity, tot_itr->total_cpu_weight.quantity, 0 );
currency::inline_transfer( del.from, SystemAccount, del.stake_quantity, "stake bandwidth" );
} // delnetbw
currency::inline_transfer( del.from, SystemAccount, total_stake, "stake bandwidth" );
} // delegatebw
static void on( const undelegatebw& del ) {
eosio_assert( del.unstake_cpu_quantity.quantity >= 0, "must stake a positive amount" );
eosio_assert( del.unstake_net_quantity.quantity >= 0, "must stake a positive amount" );
auto total_stake = del.unstake_cpu_quantity + del.unstake_net_quantity;
eosio_assert( total_stake.quantity >= 0, "must stake a positive amount" );
require_auth( del.from );
del_bandwidth_index_type del_index( SystemAccount, del.from );
total_resources_index_type total_index( SystemAccount, del.receiver );
//eosio_assert( is_account( del.receiver ), "can only delegate resources to an existing account" );
const auto& dbw = del_index.get(del.receiver);
eosio_assert( dbw.net_weight >= del.unstake_net_quantity, "insufficient staked net bandwidth" );
eosio_assert( dbw.cpu_weight >= del.unstake_cpu_quantity, "insufficient staked cpu bandwidth" );
del_index.update( dbw, del.from, [&]( auto& dbo ){
dbo.net_weight -= del.unstake_net_quantity;
dbo.cpu_weight -= del.unstake_cpu_quantity;
});
const auto& totals = total_index.get( del.receiver );
total_index.update( totals, 0, [&]( auto& tot ) {
tot.total_net_weight -= del.unstake_net_quantity;
tot.total_cpu_weight -= del.unstake_cpu_quantity;
});
set_resource_limits( totals.owner, totals.total_ram, totals.total_net_weight.quantity, totals.total_cpu_weight.quantity, 0 );
/// TODO: implement / enforce time delays on withdrawing
currency::inline_transfer( SystemAccount, del.from, total_stake, "unstake bandwidth" );
} // undelegatebw
/**
* This method will create a producr_config and producer_votes object for 'producer'
*
* @pre producer is not already registered
* @pre producer to register is an account
* @pre authority of producer to register
*
*/
static void on( const regproducer& reg ) {
require_auth( reg.producer_to_register );
auto producer = reg.producer;
require_auth( producer );
producer_votes_index_type votes( SystemAccount, SystemAccount );
const auto* existing = votes.find( producer );
eosio_assert( !existing, "producer already registered" );
votes.emplace( producer, [&]( auto& pv ){
pv.owner = producer;
pv.total_votes = 0;
});
producer_config_index_type proconfig( SystemAccount, SystemAccount );
proconfig.emplace( producer, [&]( auto& pc ) {
pc.owner = producer;
pc.packed_key = reg.producer_key;
});
}
ACTION( SystemAccount, stakevote ) {
account_name voter;
system_token_type amount;
EOSLIB_SERIALIZE( stakevote, (voter)(amount) )
};
static void on( const stakevote& sv ) {
print( "on stake vote\n" );
eosio_assert( sv.amount.quantity > 0, "must stake some tokens" );
require_auth( sv.voter );
account_votes_index_type avotes( SystemAccount, SystemAccount );
const auto* acv = avotes.find( sv.voter );
if( !acv ) {
acv = &avotes.emplace( sv.voter, [&]( auto& av ) {
av.owner = sv.voter;
av.last_update = now();
av.proxy = 0;
});
}
uint128_t old_weight = acv->staked.quantity;
uint128_t new_weight = old_weight + sv.amount.quantity;
producer_votes_index_type votes( SystemAccount, SystemAccount );
for( auto p : acv->producers ) {
votes.update( votes.get( p ), 0, [&]( auto& v ) {
v.total_votes -= old_weight;
v.total_votes += new_weight;
});
}
avotes.update( *acv, 0, [&]( auto av ) {
av.last_update = now();
av.staked += sv.amount;
});
currency::inline_transfer( sv.voter, SystemAccount, sv.amount, "stake for voting" );
};
ACTION( SystemAccount, voteproducer ) {
account_name voter;
account_name proxy;
std::vector<account_name> producers;
EOSLIB_SERIALIZE( voteproducer, (voter)(proxy)(producers) )
};
/**
* @pre vp.producers must be sorted from lowest to highest
* @pre if proxy is set then no producers can be voted for
* @pre every listed producer or proxy must have been previously registered
* @pre vp.voter must authorize this action
* @pre voter must have previously staked some EOS for voting
*/
static void on( const voteproducer& vp ) {
eosio_assert( std::is_sorted( vp.producers.begin(), vp.producers.end() ), "producer votes must be sorted" );
eosio_assert( vp.producers.size() <= 30, "attempt to vote for too many producers" );
if( vp.proxy != 0 ) eosio_assert( vp.producers.size() == 0, "cannot vote for producers and proxy at same time" );
require_auth( vp.voter );
account_votes_index_type avotes( SystemAccount, SystemAccount );
const auto& existing = avotes.get( vp.voter );
std::map<account_name, pair<uint128_t, uint128_t> > producer_vote_changes;
uint128_t old_weight = existing.staked.quantity; /// old time
uint128_t new_weight = old_weight; /// TODO: update for current weight
for( const auto& p : existing.producers )
producer_vote_changes[p].first = old_weight;
for( const auto& p : vp.producers )
producer_vote_changes[p].second = new_weight;
producer_votes_index_type votes( SystemAccount, SystemAccount );
for( const auto& delta : producer_vote_changes ) {
if( delta.second.first != delta.second.second ) {
const auto& provote = votes.get( delta.first );
votes.update( provote, 0, [&]( auto& pv ){
pv.total_votes -= delta.second.first;
pv.total_votes += delta.second.second;
});
}
}
avotes.update( existing, 0, [&]( auto& av ) {
av.proxy = vp.proxy;
av.last_update = now();
av.producers = vp.producers;
});
}
static void on( const regproxy& reg ) {
require_auth( reg.proxy_to_register );
}
static void on( const nonce& ) {
......@@ -162,7 +384,11 @@ namespace eosiosystem {
static void apply( account_name code, action_name act ) {
if( !eosio::dispatch<contract, regproducer, regproxy, delnetbw, nonce>( code, act) ) {
if( !eosio::dispatch<contract,
regproducer, regproxy,
delegatebw, undelegatebw,
regproducer, voteproducer, stakevote,
nonce>( code, act) ) {
if ( !eosio::dispatch<currency, typename currency::transfer, typename currency::issue>( code, act ) ) {
eosio::print("Unexpected action: ", eosio::name(act), "\n");
eosio_assert( false, "received unexpected action");
......
/**
* @file
* @copyright defined in eos/LICENSE.txt
*/
namespace native {
/**
@defgroup eoscontract EOS Contract
@brief Documents the interface to the EOS currency contract
@ingroup contracts
@{
*/
/**
* @ingroup contracts
* @brief Defines the base class for all contracts
*/
struct contract {
/**
* @brief updates the code that will be executed for this contract
*
* <h3> Required Authority </h3>
*
* Requires authority of *this* contract.
*
* <h3> Required Scope </h3>
*
* Requires scope of *this* contract.
*
* @note the change in code does not take effect until the start of the next block
*/
void setcode( Bytes code,
Abi abi,
uint8_t vm = 0,
uint8_t vm_version = 0 ) final;
/**
* @brief updates the authority required for a named permission
*
* <h3> Required Authority </h3>
*
* Requires authority of *this* contract.
*
* <h3> Required Scope </h3>
*
* Requires scope of *this* contract.
*/
void setauth( Name permission, ///< the name for the permission being set
Name parent, ///< the parent permission to this permission
Authority auth ///< the set of keys/accounts and threshold );
) final;
/**
* @brief set the local named permission required for `this` account/contract to
* call `con::act(...)`
*
* <h3> Required Authority </h3>
*
* Requires authority of *this* contract.
*
* <h3> Required Scope </h3>
*
* Requires scope of *this* contract.
*
* @pre myperm must be defined by prior call to @ref setauth
*
* @param con - the name of the contract this permission applies
* @param act - the name of the action on @ref con this permission applies to
* @param myperm - the name of a permission set by @ref setauth on `this` contract
*/
void setperm( Name con, Name act, Name myperm );
};
/**
* @class eos
* @brief A *native* currency contract implemented with account named `eos`
* @ingroup contracts
*
* @details The EOS contract is a *native* currency contract implemented with account named `eos`. This contract enables
* users to transfer EOS tokens to each other. This contract is designed to work the @ref stakedcontract and
* @ref systemcontract when creating new accounts, claiming staked EOS.
*/
struct eos : public contract {
/**
@brief This action will transfer funds from one account to another.
@pre `from`'s balance must be greaterthan or equal to `amount` transferred.
@pre The amount transferred must be greater than 0
@pre `to` and `from` may not be the same account.
<h3> Required Authority </h3>
This action requires the authority of the `from` account.
<h3>Required Scope </h3>
This action requires access to `from` and `to` account scopes. It does not require
access to the `eos` scope which means that multiple transfers can execute in parallel
as long as they don't have any overlapping scopes.
<h3> Required Recipients </h3>
This action requires that the accounts `from` and `to` are listed in the required recipients. This ensures
other contracts are notified anytime EOS tokens are transferred.
*/
void transfer (
account_name from, ///< account from which EOS will be withdrawn
account_name to, ///< account to receive EOS, may not be same as `from`
uint64_t amount ///< must be greater than 0 and less or equal to `from`'s balance
);
}; /// class EOS
/// @}
}
......@@ -110,7 +110,7 @@ namespace eosio {
static void inline_transfer( account_name from, account_name to, token_type quantity,
string memo = string() )
{
action act( permission_level(code,N(active)), transfer_memo( from, to, asset(quantity), move(memo) ));
action act( permission_level(from,N(active)), transfer_memo( from, to, asset(quantity), move(memo) ));
act.send();
}
......
......@@ -29,6 +29,9 @@ struct secondary_iterator<uint64_t> {
static int db_idx_next( int iterator, uint64_t* primary ) { return db_idx64_next( iterator, primary ); }
static int db_idx_prev( int iterator, uint64_t* primary ) { return db_idx64_previous( iterator, primary ); }
static void db_idx_remove( int iterator ) { db_idx64_remove( iterator ); }
static int db_idx_find_primary( uint64_t code, uint64_t scope, uint64_t table, uint64_t primary, uint64_t& secondary ) {
return db_idx64_find_primary( code, scope, table, &secondary, primary );
}
};
template<>
......@@ -36,8 +39,19 @@ struct secondary_iterator<uint128_t> {
static int db_idx_next( int iterator, uint64_t* primary ) { return db_idx128_next( iterator, primary ); }
static int db_idx_prev( int iterator, uint64_t* primary ) { return db_idx128_previous( iterator, primary ); }
static void db_idx_remove( int iterator ) { db_idx128_remove( iterator ); }
static int db_idx_find_primary( uint64_t code, uint64_t scope, uint64_t table,
uint64_t primary, uint128_t& secondary ) {
return db_idx128_find_primary( code, scope, table, &secondary, primary );
}
};
int db_idx_store( uint64_t scope, uint64_t table, uint64_t payer, uint64_t primary, const uint64_t& secondary ) {
return db_idx64_store( scope, table, payer, primary, &secondary );
}
int db_idx_store( uint64_t scope, uint64_t table, uint64_t payer, uint64_t primary, const uint128_t& secondary ) {
return db_idx128_store( scope, table, payer, primary, &secondary );
}
void db_idx_update( int iterator, uint64_t payer, const uint64_t& secondary ) {
db_idx64_update( iterator, payer, &secondary );
}
......@@ -77,67 +91,73 @@ int db_idx_upperbound( uint64_t code, uint64_t scope, uint64_t table, uint128_t&
template<uint64_t TableName, typename T, typename... Indicies>
class multi_index;
template<uint64_t IndexName, typename Extractor>
struct indexed_by {
enum constants { index_name = IndexName };
typedef Extractor secondary_extractor_type;
typedef decltype( Extractor()(nullptr) ) secondary_type;
};
template<int IndexNumber, uint64_t IndexName, typename T, typename Extractor>
template<uint64_t IndexName, typename T, typename Extractor, int N = 0>
struct index_by {
//typedef typename std::decay<decltype( (Extractor())( *((const T*)(nullptr)) ) )>::type value_type;
typedef Extractor extractor_secondary_type;
typedef decltype( Extractor()(nullptr) ) secondary_type;
typedef Extractor extractor_secondary_type;
typedef typename std::decay<decltype( Extractor()(nullptr) )>::type secondary_type;
index_by(){}
static const int index_number = IndexNumber;
static const uint64_t index_name = IndexName;
enum constants {
index_name = IndexName,
index_number = N
};
constexpr static int number() { return N; }
constexpr static uint64_t name() { return IndexName; }
private:
template<uint64_t, typename, typename... >
friend class multi_index;
Extractor extract_secondary_key;
static auto extract_secondary_key(const T& obj) { return extractor_secondary_type()(obj); }
int store( uint64_t scope, uint64_t payer, const T& obj ) {
// fetch primary key and secondary key here..
return -1;
static int store( uint64_t scope, uint64_t payer, const T& obj ) {
return db_idx_store( scope, IndexName, payer, obj.primary_key(), extract_secondary_key(obj) );
}
void update( int iterator, uint64_t payer, const secondary_type& secondary ) {
static void update( int iterator, uint64_t payer, const secondary_type& secondary ) {
db_idx_update( iterator, payer, secondary );
}
int find_primary( uint64_t code, uint64_t scope, uint64_t primary, secondary_type& secondary )const {
static int find_primary( uint64_t code, uint64_t scope, uint64_t primary, secondary_type& secondary ) {
return db_idx_find_primary( code, scope, IndexName, secondary, primary );
}
void remove( int itr ) {
static void remove( int itr ) {
secondary_iterator<secondary_type>::db_idx_remove( itr );
}
int find_secondary( uint64_t code, uint64_t scope, secondary_type& secondary, uint64_t& primary )const {
static int find_secondary( uint64_t code, uint64_t scope, secondary_type& secondary, uint64_t& primary ) {
return db_idx_find_secondary( code, scope, IndexName, secondary, primary );
}
int lower_bound( uint64_t code, uint64_t scope, secondary_type& secondary, uint64_t& primary )const {
static int lower_bound( uint64_t code, uint64_t scope, secondary_type& secondary, uint64_t& primary ) {
return db_idx_lowerbound( code, scope, IndexName, secondary, primary );
}
int upper_bound( uint64_t code, uint64_t scope, secondary_type& secondary, uint64_t& primary )const {
static int upper_bound( uint64_t code, uint64_t scope, secondary_type& secondary, uint64_t& primary ) {
return db_idx_upperbound( code, scope, IndexName, secondary, primary );
}
};
/*
template<int IndexNumber, uint64_t IndexName, typename T, typename Extractor>
auto make_index_by( Extractor&& l ) {
return index_by<IndexNumber, IndexName, T, Extractor>{ std::forward<Extractor>(l) };
}
*/
namespace hana = boost::hana;
template<uint64_t TableName, typename T, typename... Indicies>
class multi_index
{
private:
struct item : public T
{
template<typename Constructor>
......@@ -154,8 +174,28 @@ class multi_index
uint64_t _code;
uint64_t _scope;
boost::hana::tuple<Indicies...> _indicies;
template<uint64_t I>
struct intc { enum e{ value = I }; operator uint64_t()const{ return I; } };
static constexpr auto transform_indicies( ) {
typedef decltype( hana::zip_shortest(
hana::make_tuple( intc<0>(), intc<1>(), intc<2>(), intc<3>(), intc<4>(), intc<5>() ),
hana::tuple<Indicies...>() ) ) indicies_input_type;
return hana::transform( indicies_input_type(), [&]( auto&& idx ){
typedef typename std::decay<decltype(hana::at_c<0>(idx))>::type num_type;
typedef typename std::decay<decltype(hana::at_c<1>(idx))>::type idx_type;
return index_by<idx_type::index_name,
T,
typename idx_type::secondary_extractor_type,
num_type::e::value>();
});
}
typedef decltype( multi_index::transform_indicies() ) indicies_type;
indicies_type _indicies;
struct by_primary_key;
struct by_primary_itr;
......@@ -167,9 +207,6 @@ class multi_index
>
> _items_index;
// mutable std::map<uint64_t, item*> _items; /// by_primary_key
// mutable std::map<int, item*> _items_by_itr;
const item& load_object_by_primary_iterator( int itr )const {
const auto& by_pitr = _items_index.template get<by_primary_itr>();
auto cacheitr = by_pitr.find( itr );
......@@ -188,7 +225,7 @@ class multi_index
i.__primary_itr = itr;
boost::hana::for_each( _indicies, [&]( auto& idx ) {
i.__iters[idx.index_number] = -1;
i.__iters[ idx.number() ] = -1;
});
});
......@@ -202,18 +239,21 @@ class multi_index
public:
multi_index( uint64_t code, uint64_t scope ):_code(code),_scope(scope){}
~multi_index() {
}
~multi_index() { }
template<typename MultiIndexType, typename IndexType>
uint64_t get_code()const { return _code; }
uint64_t get_scope()const { return _scope; }
template<typename MultiIndexType, typename IndexType, uint64_t Number>
struct index {
private:
typedef typename MultiIndexType::item item_type;
typedef typename IndexType::secondary_type secondary_key_type;
public:
static constexpr uint64_t name() { return IndexType::name(); }
struct const_iterator {
private:
public:
friend bool operator == ( const const_iterator& a, const const_iterator& b ) {
return a._item == b._item;
......@@ -231,8 +271,20 @@ class multi_index
}
const_iterator& operator++() {
if( !_item ) return *this;
if( _item->__iters[Number] == -1 ) {
/// TODO: lookup iter for this item in this index
secondary_key_type temp_secondary_key;
auto idxitr = secondary_iterator<secondary_key_type>::db_idx_find_primary(
_idx.get_code(),
_idx.get_scope(),
_idx.name(),
_item->primary_key(), temp_secondary_key);
}
uint64_t next_pk = 0;
auto next_itr = secondary_iterator<secondary_key_type>::db_idx_next( _item->__iters[IndexType::index_number], &next_pk );
auto next_itr = secondary_iterator<secondary_key_type>::db_idx_next( _item->__iters[Number], &next_pk );
if( next_itr == -1 ) {
_item = nullptr;
return *this;
......@@ -240,15 +292,18 @@ class multi_index
const T& obj = *_idx._multidx.find( next_pk );
auto& mi = const_cast<item_type&>( static_cast<const item_type&>(obj) );
mi.__iters[IndexType::index_number] = next_itr;
mi.__iters[Number] = next_itr;
_item = &mi;
return *this;
}
const_iterator& operator--() {
if( !_item ) {
}
uint64_t prev_pk = 0;
auto prev_itr = secondary_iterator<secondary_key_type>::db_idx_prev( _item->__iters[IndexType::index_number], &prev_pk );
auto prev_itr = secondary_iterator<secondary_key_type>::db_idx_prev( _item->__iters[Number], &prev_pk );
if( prev_itr == -1 ) {
_item = nullptr;
return *this;
......@@ -256,7 +311,7 @@ class multi_index
const T& obj = *_idx._multidx.find( prev_pk );
auto& mi = const_cast<item_type&>( static_cast<const item_type&>(obj) );
mi.__iters[IndexType::index_number] = prev_itr;
mi.__iters[Number] = prev_itr;
_item = &mi;
return *this;
......@@ -281,24 +336,25 @@ class multi_index
}
const_iterator lower_bound( typename IndexType::secondary_type& secondary ) {
uint64_t primary = 0;
auto itr = _idx.lower_bound( _multidx._code, _multidx._scope, secondary, primary );
auto itr = IndexType::lower_bound( _multidx._code, _multidx._scope, secondary, primary );
if( itr == -1 ) return end();
const T& obj = *_multidx.find( primary );
auto& mi = const_cast<item_type&>( static_cast<const item_type&>(obj) );
mi.__iters[IndexType::index_number] = itr;
mi.__iters[Number] = itr;
return const_iterator( *this, &mi );
}
uint64_t get_code()const { return _multidx.get_code(); }
uint64_t get_scope()const { return _multidx.get_scope(); }
private:
friend class multi_index;
index( const MultiIndexType& midx, const IndexType& idx )
:_multidx(midx),_idx(idx){}
index( const MultiIndexType& midx ) //, const IndexType& idx )
:_multidx(midx){}
const MultiIndexType _multidx;
const IndexType& _idx;
};
......@@ -323,14 +379,20 @@ class multi_index
//eosio_assert( _item, "null ptr" );
uint64_t pk;
auto next_itr = db_next_i64( _item->__primary_itr, &pk );
_item = &_multidx.load_object_by_primary_iterator( next_itr );
if( next_itr == -1 )
_item = nullptr;
else
_item = &_multidx.load_object_by_primary_iterator( next_itr );
return *this;
}
const_iterator& operator--() {
//eosio_assert( _item, "null ptr" );
uint64_t pk;
auto next_itr = db_previous_i64( _item->__primary_itr, &pk );
_item = &_multidx.load_object_by_primary_iterator( next_itr );
if( next_itr == -1 )
_item = nullptr;
else
_item = &_multidx.load_object_by_primary_iterator( next_itr );
return *this;
}
......@@ -372,9 +434,21 @@ class multi_index
}
template<uint64_t IndexName>
auto get_index()const {
const auto& idx = boost::hana::find_if( _indicies, []( auto x ){
return std::integral_constant<bool,(decltype(x)::index_name == IndexName)>(); } ).value();
return index<multi_index, typename std::decay<decltype(idx)>::type>( *this, idx );
auto idx = boost::hana::find_if( _indicies, []( auto&& in ){
/*
auto& x = hana::at_c<1>(idxp);
return std::integral_constant<bool,(std::decay<decltype(x)>::type::index_name == IndexName)>();
*/
return std::integral_constant<bool, std::decay<decltype(in)>::type::index_name == IndexName>();
} ).value();
return index<multi_index, decltype(idx), idx.number()>( *this );
/*
typedef typename std::decay<decltype(hana::at_c<0>(idx))>::type num_type;
return index<multi_index, typename std::decay<decltype(hana::at_c<1>(idx))>::type, num_type::value >( *this, hana::at_c<1>(idx) );
*/
}
template<typename Lambda>
......@@ -391,7 +465,7 @@ class multi_index
i.__primary_itr = db_store_i64( _scope, TableName, payer, pk, tmp, sizeof(tmp) );
boost::hana::for_each( _indicies, [&]( auto& idx ) {
i.__iters[idx.index_number] = idx.store( _scope, payer, obj );
i.__iters[idx.number()] = idx.store( _scope, payer, obj );
});
});
......@@ -406,7 +480,7 @@ class multi_index
// eosio_assert( &objitem.__idx == this, "invalid object" );
auto secondary_keys = boost::hana::transform( _indicies, [&]( auto& idx ) {
auto secondary_keys = boost::hana::transform( _indicies, [&]( auto&& idx ) {
return idx.extract_secondary_key( obj );
});
......@@ -421,18 +495,25 @@ class multi_index
db_update_i64( objitem.__primary_itr, payer, tmp, sizeof(tmp) );
boost::hana::for_each( _indicies, [&]( auto& idx ) {
typedef typename std::decay<decltype(idx)>::type index_type;
auto secondary = idx.extract_secondary_key( mutableobj );
if( boost::hana::at_c<std::decay<decltype(idx)>::type::index_number>(secondary_keys) != secondary ) {
auto indexitr = mutableitem.__iters[idx.index_number];
if( hana::at_c<index_type::index_number>(secondary_keys) != secondary ) {
auto indexitr = mutableitem.__iters[idx.number()];
if( indexitr == -1 )
indexitr = mutableitem.__iters[idx.index_number] = idx.find_primary( _code, _scope, pk, secondary );
indexitr = mutableitem.__iters[idx.number()] = idx.find_primary( _code, _scope, pk, secondary );
idx.update( indexitr, payer, secondary );
}
});
}
const T& get( uint64_t primary )const {
auto result = find( primary );
eosio_assert( result != nullptr, "unable to find key" );
return *result;
}
const T* find( uint64_t primary )const {
auto cacheitr = _items_index.find(primary);
if( cacheitr != _items_index.end() )
......@@ -453,7 +534,7 @@ class multi_index
db_remove_i64( objitem.__primary_itr );
boost::hana::for_each( _indicies, [&]( auto& idx ) {
auto i = objitem.__iters[idx.index_number];
auto i = objitem.__iters[idx.number()];
if( i == -1 ) {
typename std::decay<decltype(idx)>::type::secondary_type second;
i = idx.find_primary( _code, _scope, objitem.primary_key(), second );
......
extern "C" {
/**
*
* @return an ID that serves as an iterator to the object stored, -1 for error
*/
int db_store_i64( uint64_t scope, uint64_t table, uint64_t payer, uint64_t id, char* buffer, size_t buffer_size );
int db_update_i64( int iterator, char* buffer, size_t buffer_size );
/**
* max_buffer_size should start out with the space reserved for buffer, but is set to the actual size of buffer
*
* if max_buffer_size is greater than actual buffer size, then buffer will be filled with contents, otherwise not
*
* @return an ID that serves as an iterator to the object stored, -1 for error/not found
*/
int db_find_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id, char* buffer, size_t* max_buffer_size );
int db_lower_bound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id, char* buffer, size_t* max_buffer_size );
int db_upper_bound_i64( uint64_t code, uint64_t scope, uint64_t table, uint64_t id, char* buffer, size_t* max_buffer_size );
/** return an iterator to the next item after @param iterator and optionally fetch data if buffer is not null */
int db_next( int iterator, uint64_t* id, char* buffer, size_t* max_buffer_size );
/** return an iterator to the prev item after @param iterator and optionally fetch data if buffer is not null */
int db_prev( int iterator, uint64_t* id, char* buffer, size_t* max_buffer_size );
/**
* @return the number of elements in the table stored at code/scope/table
*/
int db_count_i64( uint64_t code, uint64_t scope, uint64_t table );
void db_remove_i64( int iterator );
int db_find_primary_index64( uint64_t scope, uint64_t table, uint64_t primary, uint64_t* secondary );
int db_find_secondary_index64( uint64_t scope, uint64_t table, uint64_t* primary, uint64_t secondary );
int db_upper_bound_primary_index64( uint64_t scope, uint64_t table, uint64_t primary, uint64_t* secondary );
int db_upper_bound_secondary_index64( uint64_t scope, uint64_t table, uint64_t* primary, uint64_t secondary );
int db_lower_bound_primary_index64( uint64_t scope, uint64_t table, uint64_t primary, uint64_t* secondary );
int db_lower_bound_secondary_index64( uint64_t scope, uint64_t table, uint64_t* primary, uint64_t secondary );
int db_update_index64( int iterator, uint64_t payer, uint64_t id, uint64_t indexvalue );
int db_remove_index64( int iterator );
int db_next_index64( int iterator, uint64_t* value );
int db_prev_index64( int iterator, uint64_t* value );
int db_find_primary_index128( uint64_t scope, uint64_t table, uint64_t primary, uint128_t* secondary );
int db_find_secondary_index128( uint64_t scope, uint64_t table, uint64_t* primary, const uint128_t* secondary );
int db_upper_bound_primary_index128( uint64_t scope, uint64_t table, uint64_t primary, uint128_t* secondary );
int db_upper_bound_secondary_index128( uint64_t scope, uint64_t table, uint64_t* primary, const uint128_t* secondary );
int db_lower_bound_primary_index128( uint64_t scope, uint64_t table, uint64_t primary, uint128_t* secondary );
int db_lower_bound_secondary_index128( uint64_t scope, uint64_t table, uint64_t* primary, const uint128_t* secondary );
int db_update_index128( int iterator, uint64_t payer, uint64_t id, const uint128_t* secondary );
int db_remove_index128( int iterator );
int db_next_index128( int iterator, uint128_t* value );
int db_prev_index128( int iterator, uint128_t* value );
} /// extern "C"
namespace eosio {
namespace detail {
template<typename T>
struct id_for{};
}
struct limit_order {
uint64_t id;
uint128_t price;
uint64_t expiration;
account_name owner;
uint128_t by_owner_id()const {
uint128_t result(owner);
result << 64;
result |= id;
return result;
}
uint128_t by_price()const { return price; }
uint64_t by_expiration()const { return expiration; }
EOSLIB_SERIALIZE( (id)(price)(expiration)(owner) )
};
template<uint64_t Number, uint64_t IndexName, typename Extractor>
class index_by {
typedef std::decay<decltype( ex( *((const T*)(nullptr)) ) )>::type value_type
static const uint64_t index_name = IndexName;
static const uint64_t number = Number;
Extractor ex;
};
template<typename T, typename IndexTuple>
class multi_index {
auto indicies = make_tuple( make_index(N(byorderid), []( const T& obj ){ return obj.id; }),
make_index(N(byexpiration), []( const T& obj) { return obj.by_expiration(); } )
make_index(N(byprice), []( const T& obj) { return obj.by_price(); } ) );
mutable map<uint64_t, item*> _items;
public:
template<uint64_t IndexName, typename Key>
const T* find( const Key& k )const {
}
template<typename Existing>
auto get_keys( const T& obj ) {
return get_keys<1>( std::make_tuple(
}
template<int I, typename Existing, std::enable_if< I == std::tuple_size(IndexTuple) - 1> = 0 >
auto get_keys( Existing&& e, const T& obj ) {
return std::tuple_cat( std::forward<Existing>(e), std::get<I>( indicies ).extract( obj ) );
}
template<int I, typename Existing, std::enable_if< (I < std::tuple_size(IndexTuple) - 1) > = 0 >
auto get_keys( Existing&& e, const T& obj ) {
return get_keys<I+1>( std::tuple_cat( std::forward<Existing>(e), std::get<I>( indicies ).extract( obj ) ), obj );
}
template<typename Lambda>
const T& create( Lambda&& constructor, uint64_t payer ) {
auto i = new item( *this );
constructor( static_cast<T&>(*i) );
const T& obj = static_cast<const T&>(*i);
char tmp[ pack_size( obj ) ];
datastream<char*> ds( tmp, sizeof(tmp) );
pack( ds, obj );
auto pk = obj.primary_key();
i->__itrs[0] = db_store_i64( _code, _scope, _tables[0], payer, pk, tmp, sizeof(tmp) );
for_each( indicies, [&]( auto& idx ) {
i->__itrs[idx.number] = idx.store( _code, _scope, idx.index_name, payer, pk, idx.extract( obj ) );
});
items[pk] = i;
return obj;
}
template<typename Lambda>
void update( const T& obj, uint64_t payer, Lambda&& updater ) {
T& mobj = const_cast<T&>(obj);
item& i = static_cast<item&>(mobj);
auto pk = mobj.primary_key();
eosio_assert( &i.__mutli_idx == this );
auto old_idx = std::make_tuple(
obj.primary_key(), obj.expiration(), obj.by_owner_id(), obj.by_price() );
updater(mobj);
char tmp[ pack_size( mobj ) ];
datastream<char*> ds( tmp, sizeof(tmp) );
pack( ds, mobj );
db_update_i64( i.__itrs[0], payer, tmp, sizeof(tmp) );
auto new_idx = std::make_tuple(
obj.primary_key(), obj.expiration(), obj.by_owner_id(), obj.by_price() );
if( std::get<1>(old_idx) != std::get<1>(new_idx) ) {
if( i.__itrs[1] == -2 ) i.__itrs[1] = db_idx64_find_primary( pk );
db_idx64_update( i.__itrs[1], payer, std::get<1>(new_idx) );
}
if( std::get<2>(old_idx) != std::get<2>(new_idx) ) {
if( i.__itrs[2] == -2 ) i.__itrs[2] = db_idx64_find_primary( pk );
db_idx64_update( i.__itrs[2], payer, std::get<2>(new_idx) );
}
if( std::get<3>(old_idx) != std::get<3>(new_idx) ) {
if( i.__itrs[3] == -2 ) i.__itrs[3] = db_idx64_find_primary( pk );
db_idx64_update( i.__itrs[3], payer, std::get<3>(new_idx) );
}
}
private:
struct item : public T
{
item( multi_index& o ):__mutli_idx(o) {
}
multi_index& __multi_idx;
int __itrs[3];
};
};
/*
multi_index< N(limitorders), limit_order,
index< N(ownerid), &limit_order::by_owner_id >,
index< N(byprice), &limit_order::by_price >,
index< N(byexpire), &limit_order::by_expiration>
> orderbook;
*/
/*
template<uint64_t Code, uint64_t TableName, typename ObjectType>
class multi_index {
public:
struct cache_object : public ObjectType {
int primary_itr;
};
const cache_object* find( uint64_t primary ) {
auto itr = _cache.find( primary );
if( itr != cache.end() )
return itr->second;
db_find_i64( Code, _scope, TableName, primary );
}
private:
std::map<uint64_t, cache_object*> _cache;
};
auto order = orderbook.begin();
auto end = orderbook.end();
while( order != end ) {
auto cur = order;
++order;
orderbook.remove( cur );
}
const limit_order& order = *orderbook.begin();
/// Options:
// 1. maintain a wasm-side cache of all dereferenced objects
// a. keeps pointers valid
// b. minimizes temporary copies
// c. eliminates redundant deserialization losses
// d. will utilize more heap memory than necessary, potentially hitting sbrk
//
// 2. keep API light and return a copy
template<typename ObjectType, typename SecondaryKeyType>
class index
{
public:
index( uint64_t code, uint64_t scope, uint64_t table );
struct iterator {
iterator( index& idx, int itr )
:_idx(idx), _itr(itr){
if( itr >= 0 )
db_index<SecondaryKeyType>::get( itr, _primary, _secondary );
}
uint64_t primary()const { return _primary; }
const SecondaryKeyType& secondary()const { return _secondary; }
private:
uint64_t _primary;
SecondaryKeyType _secondary;
index& _idx;
int _itr;
};
iterator lower_bound( const SecondaryKeyType& lb ) {
return iterator( db_index<SecondaryKeyType>::secondary_lower_bound( _code, _scope, _table, lb ) );
}
private:
uint64_t _code;
uint64_t _scope;
uint64_t _table;
};
template<typename T, typename Indices>
class multi_index {
public:
struct iterator {
private:
int primary_itr;
};
multi_index( code_name code, scope_name scope, table_name table )
:_code(code),_scope(scope),_table(table){}
void insert( const T& obj, account_name payer ) {
uint64_t id = id_for<T>::get(obj);
auto buf = pack(obj);
store_i64( _scope, _table, payer, id, buf.data(), buf.size() );
Indices::insert( _code, _scope, _table, payer, obj );
}
template<typename Constructor>
void emplace( Constructor&& c ) {
T tmp;
id_for<T>::get(tmp) = allocate_id();
c(tmp);
insert( tmp );
}
template<typename Lambda>
void modify( const T& obj, account_name payer, Lambda&& update ) {
update(tmp);
uint64_t id = id_for<T>::get(value);
auto buf = pack(tmp);
store_i64( _code, _scope, _table, payer, id, buf.data(), buf.size() );
Indices::update( _code, _scope, _table, payer, obj );
}
private:
};
} // namespace eosio
......@@ -20,8 +20,8 @@ extern "C" {
/// The apply method implements the dispatch of events to this contract
void apply( uint64_t code, uint64_t action ) {
eosio::multi_index<N(orders), limit_order,
index_by<0, N(byexp), limit_order, const_mem_fun<limit_order, uint64_t, &limit_order::get_expiration> >,
index_by<1, N(byprice), limit_order, const_mem_fun<limit_order, uint128_t, &limit_order::get_price> >
indexed_by<N(byexp), const_mem_fun<limit_order, uint64_t, &limit_order::get_expiration> >,
indexed_by<N(byprice), const_mem_fun<limit_order, uint128_t, &limit_order::get_price> >
> orders( N(exchange), N(exchange) );
auto payer = code;
......
......@@ -91,8 +91,8 @@
fi
if [ $ARCH == "Darwin" ]; then
OPENSSL_ROOT_DIR=/usr/local/opt/openssl@1.1
OPENSSL_LIBRARIES=/usr/local/opt/openssl@1.1/lib
OPENSSL_ROOT_DIR=/usr/local/opt/openssl
OPENSSL_LIBRARIES=/usr/local/opt/openssl/lib
BINARYEN_BIN=/usr/local/binaryen/bin/
WASM_LLVM_CONFIG=/usr/local/wasm/bin/llvm-config
CXX_COMPILER=clang++
......
Subproject commit ef2b0c8d64f770d80ce537ec04d0de4bdc4d3585
Subproject commit a0cf75ad7c39137ebf03b0f3b0b4e5b7f731296b
......@@ -116,6 +116,7 @@ bool apply_context::is_account( const account_name& account )const {
void apply_context::require_authorization( const account_name& account )const {
for( const auto& auth : act.authorization )
if( auth.actor == account ) return;
wdump((act));
EOS_ASSERT( false, tx_missing_auth, "missing authority of ${account}", ("account",account));
}
void apply_context::require_authorization(const account_name& account,
......
......@@ -36,6 +36,13 @@ namespace eosio { namespace chain {
fc::datastream<char*> ds( abi.data(), abi.size() );
fc::raw::pack( ds, a );
}
eosio::chain::contracts::abi_def get_abi()const {
eosio::chain::contracts::abi_def a;
fc::datastream<const char*> ds( abi.data(), abi.size() );
fc::raw::unpack( ds, a );
return a;
}
};
using account_id_type = account_object::id_type;
......
Subproject commit 664fdd9e79263a894794f96959612ec2d1d013d0
Subproject commit d48ebabf56b4115753fcabb7648a0ffcf3b0f5e9
......@@ -27,7 +27,11 @@ namespace eosio { namespace testing {
transaction_trace push_transaction( packed_transaction& trx );
transaction_trace push_transaction( signed_transaction& trx );
action_result push_action(action&& cert_act, uint64_t authorizer);
action_result push_action(action&& cert_act, uint64_t authorizer);
transaction_trace push_action( const account_name& code, const action_name& act, const account_name& signer, const variant_object &data );
void set_tapos( signed_transaction& trx ) const;
void create_accounts( vector<account_name> names, bool multisig = false ) {
......
......@@ -133,6 +133,32 @@ namespace eosio { namespace testing {
return success();
}
transaction_trace base_tester::push_action( const account_name& code,
const action_name& acttype,
const account_name& actor,
const variant_object& data
)
{ try {
chain::contracts::abi_serializer abis( control->get_database().get<account_object,by_name>(code).get_abi() );
string action_type_name = abis.get_action_type(acttype);
action act;
act.account = code;
act.name = acttype;
act.authorization = vector<permission_level>{{actor, config::active_name}};
act.data = abis.variant_to_binary(action_type_name, data);
wdump((act));
signed_transaction trx;
trx.actions.emplace_back(std::move(act));
set_tapos(trx);
trx.sign(get_private_key(actor, "active"), chain_id_type());
wdump((get_public_key( actor, "active" )));;
return push_transaction(trx);
} FC_CAPTURE_AND_RETHROW( (code)(acttype)(actor) ) }
transaction_trace base_tester::push_reqauth( account_name from, const vector<permission_level>& auths, const vector<private_key_type>& keys ) {
variant pretty_trx = fc::mutable_variant_object()
("actions", fc::variants({
......@@ -367,6 +393,4 @@ namespace eosio { namespace testing {
set_abi(config::system_account_name, test_system_abi);
}
} } /// eosio::test
......@@ -20,20 +20,20 @@
printf "\tDisk space available: ${DISK_AVAIL}G\n\n"
if [ $MEM_GIG -lt 8 ]; then
printf "\tYour system must have 8 or more Gigabytes of physical memory installed.\n"
printf "\tExiting now.\n"
echo "Your system must have 8 or more Gigabytes of physical memory installed."
echo "Exiting now."
exit 1
fi
if [ $OS_MIN -lt 12 ]; then
printf "\tYou must be running Mac OS 10.12.x or higher to install EOSIO.\n"
printf "\tExiting now.\n"
echo "You must be running Mac OS 10.12.x or higher to install EOSIO."
echo "Exiting now."
exit 1
fi
if [ $DISK_AVAIL -lt 100 ]; then
printf "\tYou must have at least 100GB of available storage to install EOSIO.\n"
printf "\tExiting now.\n"
echo "You must have at least 100GB of available storage to install EOSIO."
echo "Exiting now."
exit 1
fi
......
#include <boost/test/unit_test.hpp>
#include <eosio/testing/tester.hpp>
#include <eosio/chain/contracts/abi_serializer.hpp>
#include <eosio/chain_plugin/chain_plugin.hpp>
#include <eosio.system/eosio.system.wast.hpp>
#include <eosio.system/eosio.system.abi.hpp>
#include <Runtime/Runtime.h>
#include <fc/variant_object.hpp>
using namespace eosio::testing;
using mvo = fc::mutable_variant_object;
BOOST_AUTO_TEST_SUITE(eosio_system_tests)
BOOST_FIXTURE_TEST_CASE( eosio_system_load, tester ) try {
produce_block();
edump((name(config::system_account_name)));
set_code(config::system_account_name, eosio_system_wast);
set_abi(config::system_account_name, eosio_system_abi);
produce_block();
create_accounts( {N(dan),N(brendan)} );
push_action(N(eosio), N(issue), N(eosio), mvo()
("to", "eosio")
("quantity", "1000000.0000 EOS")
);
push_action(N(eosio), N(transfer), N(eosio), mvo()
("from", "eosio")
("to", "dan")
("quantity", "100.0000 EOS")
("memo", "hi" )
);
push_action(N(eosio), N(transfer), N(dan), mvo()
("from", "dan")
("to", "brendan")
("quantity", "50.0000 EOS")
("memo", "hi" )
);
wlog( "reg producer" );
auto regtrace = push_action(N(eosio), N(regproducer), N(dan), mvo()
("producer", "dan")
("producer_key", "")
);
wdump((regtrace));
produce_block();
wlog( "transfer" );
push_action(N(eosio), N(transfer), N(dan), mvo()
("from", "dan")
("to", "brendan")
("quantity", "5.0000 EOS")
("memo", "hi" )
);
/*
permission_level_weight plw{ permission_level{N(eosio),N(active)}, 1};;
set_authority( N(dan), N(active),
authority( 1,
vector<key_weight>({ {get_public_key(N(dan),"active"),1 } }),
vector<permission_level_weight>({plw}) ) );
*/
wlog( "stake vote" );
auto trace = push_action(N(eosio), N(stakevote), N(dan), mvo()
("voter", "dan")
("amount", "5.0000 EOS")
);
wdump((trace));
/*
produce_blocks(2);
create_accounts( {N(multitest)} );
produce_blocks(2);
set_code( N(multitest), eosio_system_test_wast );
set_abi( N(multitest), eosio_system_test_abi );
produce_blocks(1);
*/
} FC_LOG_AND_RETHROW()
BOOST_AUTO_TEST_SUITE_END()
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册