Compare commits

...

4 commits

Author SHA1 Message Date
sierra19XX
56a5e676f0 Account Roles Permission 4 - Add chain params and limits 2020-09-04 23:56:05 +10:00
sierra19XX
0d1cd54081 Account Roles Permission 3 - Add Op check 2020-09-02 22:10:28 +10:00
sierra19XX
e82df9ce49 Account Roles Permission 2 - Add marketplace offer/bid tests 2020-09-02 21:33:09 +10:00
sierra19XX
92a900a50b Account Roles Permission 1 - Working code with tests 2020-09-02 13:16:07 +10:00
31 changed files with 1138 additions and 47 deletions

View file

@ -217,6 +217,9 @@ class database_api_impl : public std::enable_shared_from_this<database_api_impl>
vector<offer_history_object> get_offer_history_by_item(const offer_history_id_type lower_id, const nft_id_type item, uint32_t limit) const;
vector<offer_history_object> get_offer_history_by_bidder(const offer_history_id_type lower_id, const account_id_type bidder_account_id, uint32_t limit) const;
// Account Role
vector<account_role_object> get_account_roles_by_owner(account_id_type owner) const;
//private:
const account_object* get_account_from_string( const std::string& name_or_id,
bool throw_if_not_found = true ) const;
@ -2888,6 +2891,23 @@ vector<offer_history_object> database_api_impl::get_offer_history_by_bidder(cons
return result;
}
vector<account_role_object> database_api::get_account_roles_by_owner(account_id_type owner) const
{
return my->get_account_roles_by_owner(owner);
}
vector<account_role_object> database_api_impl::get_account_roles_by_owner(account_id_type owner) const
{
const auto &idx_aro = _db.get_index_type<account_role_index>().indices().get<by_owner>();
auto idx_aro_range = idx_aro.equal_range(owner);
vector<account_role_object> result;
for (auto itr = idx_aro_range.first; itr != idx_aro_range.second; ++itr)
{
result.push_back(*itr);
}
return result;
}
//////////////////////////////////////////////////////////////////////
// //
// Private methods //

View file

@ -52,6 +52,7 @@
#include <graphene/chain/custom_account_authority_object.hpp>
#include <graphene/chain/nft_object.hpp>
#include <graphene/chain/offer_object.hpp>
#include <graphene/chain/account_role_object.hpp>
#include <graphene/market_history/market_history_plugin.hpp>
@ -829,6 +830,11 @@ class database_api
vector<offer_history_object> get_offer_history_by_issuer(const offer_history_id_type lower_id, const account_id_type issuer_account_id, uint32_t limit) const;
vector<offer_history_object> get_offer_history_by_item(const offer_history_id_type lower_id, const nft_id_type item, uint32_t limit) const;
vector<offer_history_object> get_offer_history_by_bidder(const offer_history_id_type lower_id, const account_id_type bidder_account_id, uint32_t limit) const;
//////////////////
// ACCOUNT ROLE //
//////////////////
vector<account_role_object> get_account_roles_by_owner(account_id_type owner) const;
private:
std::shared_ptr< database_api_impl > my;
};
@ -999,4 +1005,7 @@ FC_API(graphene::app::database_api,
(get_offer_history_by_issuer)
(get_offer_history_by_item)
(get_offer_history_by_bidder)
// Account Roles
(get_account_roles_by_owner)
)

View file

@ -124,6 +124,8 @@ add_library( graphene_chain
offer_evaluator.cpp
nft_evaluator.cpp
protocol/nft.cpp
protocol/account_role.cpp
account_role_evaluator.cpp
${HEADERS}
${PROTOCOL_HEADERS}

View file

@ -0,0 +1,162 @@
#include <graphene/chain/account_role_evaluator.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/account_role_object.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/rbac_hardfork_visitor.hpp>
namespace graphene
{
namespace chain
{
void_result account_role_create_evaluator::do_evaluate(const account_role_create_operation &op)
{
try
{
const database &d = db();
auto now = d.head_block_time();
FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF");
op.owner(d);
rbac_operation_hardfork_visitor arvtor(now);
for (const auto &op_type : op.allowed_operations)
{
arvtor(op_type);
}
for (const auto &acc : op.whitelisted_accounts)
{
acc(d);
}
FC_ASSERT(op.valid_to > now, "valid_to expiry should be in future");
FC_ASSERT((op.valid_to - now) <= fc::seconds(d.get_global_properties().parameters.account_roles_max_lifetime()), "Validity of the account role beyond max expiry");
const auto &ar_idx = d.get_index_type<account_role_index>().indices().get<by_owner>();
auto aro_range = ar_idx.equal_range(op.owner);
FC_ASSERT(std::distance(aro_range.first, aro_range.second) < d.get_global_properties().parameters.account_roles_max_per_account(), "Max account roles that can be created by one owner is reached");
return void_result();
}
FC_CAPTURE_AND_RETHROW((op))
}
object_id_type account_role_create_evaluator::do_apply(const account_role_create_operation &op)
{
try
{
database &d = db();
return d.create<account_role_object>([&op](account_role_object &obj) mutable {
obj.owner = op.owner;
obj.name = op.name;
obj.metadata = op.metadata;
obj.allowed_operations = op.allowed_operations;
obj.whitelisted_accounts = op.whitelisted_accounts;
obj.valid_to = op.valid_to;
})
.id;
}
FC_CAPTURE_AND_RETHROW((op))
}
void_result account_role_update_evaluator::do_evaluate(const account_role_update_operation &op)
{
try
{
const database &d = db();
auto now = d.head_block_time();
FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF");
op.owner(d);
const account_role_object &aobj = op.account_role_id(d);
FC_ASSERT(aobj.owner == op.owner, "Only owner account can update account role object");
for (const auto &op_type : op.allowed_operations_to_remove)
{
FC_ASSERT(aobj.allowed_operations.find(op_type) != aobj.allowed_operations.end(),
"Cannot remove non existent operation");
}
for (const auto &acc : op.accounts_to_remove)
{
FC_ASSERT(aobj.whitelisted_accounts.find(acc) != aobj.whitelisted_accounts.end(),
"Cannot remove non existent account");
}
rbac_operation_hardfork_visitor arvtor(now);
for (const auto &op_type : op.allowed_operations_to_add)
{
arvtor(op_type);
}
FC_ASSERT((aobj.allowed_operations.size() + op.allowed_operations_to_add.size() - op.allowed_operations_to_remove.size()) > 0, "Allowed operations should be positive");
for (const auto &acc : op.accounts_to_add)
{
acc(d);
}
FC_ASSERT((aobj.whitelisted_accounts.size() + op.accounts_to_add.size() - op.accounts_to_remove.size()) > 0, "Accounts should be positive");
if (op.valid_to)
{
FC_ASSERT(*op.valid_to > now, "valid_to expiry should be in future");
FC_ASSERT((*op.valid_to - now) <= fc::seconds(d.get_global_properties().parameters.account_roles_max_lifetime()), "Validity of the account role beyond max expiry");
}
return void_result();
}
FC_CAPTURE_AND_RETHROW((op))
}
void_result account_role_update_evaluator::do_apply(const account_role_update_operation &op)
{
try
{
database &d = db();
const account_role_object &aobj = op.account_role_id(d);
d.modify(aobj, [&op](account_role_object &obj) {
if (op.name)
obj.name = *op.name;
if (op.metadata)
obj.metadata = *op.metadata;
obj.allowed_operations.insert(op.allowed_operations_to_add.begin(), op.allowed_operations_to_add.end());
obj.whitelisted_accounts.insert(op.accounts_to_add.begin(), op.accounts_to_add.end());
for (const auto &op_type : op.allowed_operations_to_remove)
obj.allowed_operations.erase(op_type);
for (const auto &acc : op.accounts_to_remove)
obj.whitelisted_accounts.erase(acc);
if (op.valid_to)
obj.valid_to = *op.valid_to;
});
return void_result();
}
FC_CAPTURE_AND_RETHROW((op))
}
void_result account_role_delete_evaluator::do_evaluate(const account_role_delete_operation &op)
{
try
{
const database &d = db();
auto now = d.head_block_time();
FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF");
op.owner(d);
const account_role_object &aobj = op.account_role_id(d);
FC_ASSERT(aobj.owner == op.owner, "Only owner account can delete account role object");
return void_result();
}
FC_CAPTURE_AND_RETHROW((op))
}
void_result account_role_delete_evaluator::do_apply(const account_role_delete_operation &op)
{
try
{
database &d = db();
const account_role_object &aobj = op.account_role_id(d);
d.remove(aobj);
return void_result();
}
FC_CAPTURE_AND_RETHROW((op))
}
} // namespace chain
} // namespace graphene

View file

@ -4,37 +4,13 @@
#include <graphene/chain/custom_account_authority_object.hpp>
#include <graphene/chain/custom_permission_object.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/rbac_hardfork_visitor.hpp>
namespace graphene
{
namespace chain
{
struct rbac_operation_hardfork_visitor
{
typedef void result_type;
const fc::time_point_sec block_time;
rbac_operation_hardfork_visitor(const fc::time_point_sec bt) : block_time(bt) {}
void operator()(int op_type) const
{
int first_allowed_op = operation::tag<custom_permission_create_operation>::value;
switch (op_type)
{
case operation::tag<custom_permission_create_operation>::value:
case operation::tag<custom_permission_update_operation>::value:
case operation::tag<custom_permission_delete_operation>::value:
case operation::tag<custom_account_authority_create_operation>::value:
case operation::tag<custom_account_authority_update_operation>::value:
case operation::tag<custom_account_authority_delete_operation>::value:
FC_ASSERT(block_time >= HARDFORK_NFT_TIME, "Custom permission not allowed on this operation yet!");
break;
default:
FC_ASSERT(op_type < first_allowed_op, "Custom permission not allowed on this operation!");
}
}
};
void_result create_custom_account_authority_evaluator::do_evaluate(const custom_account_authority_create_operation &op)
{
try

View file

@ -30,6 +30,7 @@
#include <graphene/chain/custom_permission_object.hpp>
#include <graphene/chain/custom_account_authority_object.hpp>
#include <graphene/chain/offer_object.hpp>
#include <graphene/chain/account_role_object.hpp>
#include <fc/smart_ref_impl.hpp>
@ -192,4 +193,11 @@ bool database::item_locked(const nft_id_type &item) const
auto items_itr = market_items._locked_items.find(item);
return (items_itr != market_items._locked_items.end());
}
bool database::account_role_valid(const account_role_object &aro, account_id_type account, optional<int> op_type) const
{
return (aro.valid_to > head_block_time()) &&
(aro.whitelisted_accounts.find(account) != aro.whitelisted_accounts.end()) &&
(!op_type || (aro.allowed_operations.find(*op_type) != aro.allowed_operations.end()));
}
} }

View file

@ -52,6 +52,7 @@
#include <graphene/chain/custom_permission_object.hpp>
#include <graphene/chain/custom_account_authority_object.hpp>
#include <graphene/chain/offer_object.hpp>
#include <graphene/chain/account_role_object.hpp>
#include <graphene/chain/nft_object.hpp>
@ -85,6 +86,7 @@
#include <graphene/chain/custom_account_authority_evaluator.hpp>
#include <graphene/chain/offer_evaluator.hpp>
#include <graphene/chain/nft_evaluator.hpp>
#include <graphene/chain/account_role_evaluator.hpp>
#include <graphene/chain/protocol/fee_schedule.hpp>
@ -186,6 +188,9 @@ const uint8_t offer_object::type_id;
const uint8_t offer_history_object::space_id;
const uint8_t offer_history_object::type_id;
const uint8_t account_role_object::space_id;
const uint8_t account_role_object::type_id;
void database::initialize_evaluators()
{
_operation_evaluators.resize(255);
@ -275,6 +280,9 @@ void database::initialize_evaluators()
register_evaluator<nft_safe_transfer_from_evaluator>();
register_evaluator<nft_approve_evaluator>();
register_evaluator<nft_set_approval_for_all_evaluator>();
register_evaluator<account_role_create_evaluator>();
register_evaluator<account_role_update_evaluator>();
register_evaluator<account_role_delete_evaluator>();
}
void database::initialize_indexes()
@ -323,6 +331,7 @@ void database::initialize_indexes()
add_index< primary_index<nft_metadata_index > >();
add_index< primary_index<nft_index > >();
add_index< primary_index<account_role_index> >();
//Implementation object indexes
add_index< primary_index<transaction_index > >();

View file

@ -941,6 +941,15 @@ void clear_expired_custom_account_authorities(database& db)
}
}
void clear_expired_account_roles(database& db)
{
const auto& arindex = db.get_index_type<account_role_index>().indices().get<by_expiration>();
while(!arindex.empty() && arindex.begin()->valid_to < db.head_block_time())
{
db.remove(*arindex.begin());
}
}
// Schedules payouts from a dividend distribution account to the current holders of the
// dividend-paying asset. This takes any deposits made to the dividend distribution account
// since the last time it was called, and distributes them to the current owners of the
@ -1668,6 +1677,16 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
p.pending_parameters->extensions.value.gpos_subperiod = p.parameters.extensions.value.gpos_subperiod;
if( !p.pending_parameters->extensions.value.gpos_vesting_lockin_period.valid() )
p.pending_parameters->extensions.value.gpos_vesting_lockin_period = p.parameters.extensions.value.gpos_vesting_lockin_period;
if( !p.pending_parameters->extensions.value.rbac_max_permissions_per_account.valid() )
p.pending_parameters->extensions.value.rbac_max_permissions_per_account = p.parameters.extensions.value.rbac_max_permissions_per_account;
if( !p.pending_parameters->extensions.value.rbac_max_account_authority_lifetime.valid() )
p.pending_parameters->extensions.value.rbac_max_account_authority_lifetime = p.parameters.extensions.value.rbac_max_account_authority_lifetime;
if( !p.pending_parameters->extensions.value.rbac_max_authorities_per_permission.valid() )
p.pending_parameters->extensions.value.rbac_max_authorities_per_permission = p.parameters.extensions.value.rbac_max_authorities_per_permission;
if( !p.pending_parameters->extensions.value.account_roles_max_per_account.valid() )
p.pending_parameters->extensions.value.account_roles_max_per_account = p.parameters.extensions.value.account_roles_max_per_account;
if( !p.pending_parameters->extensions.value.account_roles_max_lifetime.valid() )
p.pending_parameters->extensions.value.account_roles_max_lifetime = p.parameters.extensions.value.account_roles_max_lifetime;
p.parameters = std::move(*p.pending_parameters);
p.pending_parameters.reset();
}
@ -1717,8 +1736,9 @@ void database::perform_chain_maintenance(const signed_block& next_block, const g
modify( d, [](asset_bitasset_data_object& o) { o.force_settled_volume = 0; });
// Ideally we have to do this after every block but that leads to longer block applicaiton/replay times.
// So keep it here as it is not critical. valid_to check ensures
// these custom account auths are not usable.
// these custom account auths and account roles are not usable.
clear_expired_custom_account_authorities(*this);
clear_expired_account_roles(*this);
// process_budget needs to run at the bottom because
// it needs to know the next_maintenance_time
process_budget();

View file

@ -344,6 +344,15 @@ struct get_impacted_account_visitor
void operator()( const finalize_offer_operation& op ) {
_impacted.insert( op.fee_paying_account );
}
void operator()( const account_role_create_operation& op ){
_impacted.insert( op.owner );
}
void operator()( const account_role_update_operation& op ){
_impacted.insert( op.owner );
}
void operator()( const account_role_delete_operation& op ){
_impacted.insert( op.owner );
}
};
void graphene::chain::operation_get_impacted_accounts( const operation& op, flat_set<account_id_type>& result )
@ -432,6 +441,12 @@ void get_relevant_accounts( const object* obj, flat_set<account_id_type>& accoun
} case balance_object_type:{
/** these are free from any accounts */
break;
} case account_role_type:{
const auto& aobj = dynamic_cast<const account_role_object*>(obj);
assert( aobj != nullptr );
accounts.insert( aobj->owner );
accounts.insert( aobj->whitelisted_accounts.begin(), aobj->whitelisted_accounts.end() );
break;
}
}
}

View file

@ -0,0 +1,35 @@
#pragma once
#include <graphene/chain/database.hpp>
#include <graphene/chain/evaluator.hpp>
#include <graphene/chain/protocol/operations.hpp>
#include <graphene/chain/protocol/types.hpp>
namespace graphene { namespace chain {
class account_role_create_evaluator : public evaluator<account_role_create_evaluator>
{
public:
typedef account_role_create_operation operation_type;
void_result do_evaluate( const account_role_create_operation& o );
object_id_type do_apply( const account_role_create_operation& o );
};
class account_role_update_evaluator : public evaluator<account_role_update_evaluator>
{
public:
typedef account_role_update_operation operation_type;
void_result do_evaluate( const account_role_update_operation& o );
void_result do_apply( const account_role_update_operation& o );
};
class account_role_delete_evaluator : public evaluator<account_role_delete_evaluator>
{
public:
typedef account_role_delete_operation operation_type;
void_result do_evaluate( const account_role_delete_operation& o );
void_result do_apply( const account_role_delete_operation& o );
};
} } // graphene::chain

View file

@ -0,0 +1,49 @@
#pragma once
#include <graphene/chain/protocol/types.hpp>
#include <graphene/db/object.hpp>
#include <graphene/db/generic_index.hpp>
namespace graphene
{
namespace chain
{
using namespace graphene::db;
class account_role_object : public abstract_object<account_role_object>
{
public:
static const uint8_t space_id = protocol_ids;
static const uint8_t type_id = account_role_type;
account_id_type owner;
std::string name;
std::string metadata;
flat_set<int> allowed_operations;
flat_set<account_id_type> whitelisted_accounts;
time_point_sec valid_to;
};
struct by_owner;
struct by_expiration;
using account_role_multi_index_type = multi_index_container<
account_role_object,
indexed_by<
ordered_unique< tag<by_id>,
member<object, object_id_type, &object::id>
>,
ordered_non_unique< tag<by_owner>,
member<account_role_object, account_id_type, &account_role_object::owner>
>,
ordered_unique< tag<by_expiration>,
composite_key<account_role_object,
member<account_role_object, time_point_sec, &account_role_object::valid_to>,
member<object, object_id_type, &object::id>>
>
>
>;
using account_role_index = generic_index<account_role_object, account_role_multi_index_type>;
} // namespace chain
} // namespace graphene
FC_REFLECT_DERIVED(graphene::chain::account_role_object, (graphene::db::object),
(owner)(name)(metadata)(allowed_operations)(whitelisted_accounts)(valid_to))

View file

@ -243,4 +243,7 @@
#define NFT_TOKEN_MIN_LENGTH 3
#define NFT_TOKEN_MAX_LENGTH 15
#define NFT_URI_MAX_LENGTH GRAPHENE_MAX_URL_LENGTH
#define NFT_URI_MAX_LENGTH GRAPHENE_MAX_URL_LENGTH
#define ACCOUNT_ROLES_MAX_PER_ACCOUNT 20 // Max 20 roles can be created by a resource owner
#define ACCOUNT_ROLES_MAX_LIFETIME 365*24*60*60 // 1 Year

View file

@ -283,6 +283,7 @@ namespace graphene { namespace chain {
uint64_t get_random_bits( uint64_t bound );
const witness_schedule_object& get_witness_schedule_object()const;
bool item_locked(const nft_id_type& item)const;
bool account_role_valid(const account_role_object& aro, account_id_type account, optional<int> op_type = optional<int>()) const;
time_point_sec head_block_time()const;
uint32_t head_block_num()const;

View file

@ -20,6 +20,7 @@ namespace graphene { namespace chain {
optional<uint16_t> revenue_split;
bool is_transferable = false;
bool is_sellable = true;
optional<account_role_id_type> account_role;
};
class nft_object : public abstract_object<nft_object>
@ -95,7 +96,8 @@ FC_REFLECT_DERIVED( graphene::chain::nft_metadata_object, (graphene::db::object)
(revenue_partner)
(revenue_split)
(is_transferable)
(is_sellable) )
(is_sellable)
(account_role) )
FC_REFLECT_DERIVED( graphene::chain::nft_object, (graphene::db::object),
(nft_metadata_id)

View file

@ -0,0 +1,82 @@
#pragma once
#include <graphene/chain/protocol/base.hpp>
#include <graphene/chain/protocol/types.hpp>
namespace graphene
{
namespace chain
{
struct account_role_create_operation : public base_operation
{
struct fee_parameters_type
{
uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION;
uint32_t price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION;
};
asset fee;
account_id_type owner;
std::string name;
std::string metadata;
flat_set<int> allowed_operations;
flat_set<account_id_type> whitelisted_accounts;
time_point_sec valid_to;
extensions_type extensions;
account_id_type fee_payer() const { return owner; }
void validate() const;
share_type calculate_fee(const fee_parameters_type &k) const;
};
struct account_role_update_operation : public base_operation
{
struct fee_parameters_type
{
uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION;
uint32_t price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION;
};
asset fee;
account_id_type owner;
account_role_id_type account_role_id;
optional<std::string> name;
optional<std::string> metadata;
flat_set<int> allowed_operations_to_add;
flat_set<int> allowed_operations_to_remove;
flat_set<account_id_type> accounts_to_add;
flat_set<account_id_type> accounts_to_remove;
optional<time_point_sec> valid_to;
extensions_type extensions;
account_id_type fee_payer() const { return owner; }
void validate() const;
share_type calculate_fee(const fee_parameters_type &k) const;
};
struct account_role_delete_operation : public base_operation
{
struct fee_parameters_type
{
uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION;
};
asset fee;
account_id_type owner;
account_role_id_type account_role_id;
extensions_type extensions;
account_id_type fee_payer() const { return owner; }
void validate() const;
share_type calculate_fee(const fee_parameters_type &k) const { return k.fee; }
};
} // namespace chain
} // namespace graphene
FC_REFLECT(graphene::chain::account_role_create_operation::fee_parameters_type, (fee)(price_per_kbyte))
FC_REFLECT(graphene::chain::account_role_update_operation::fee_parameters_type, (fee)(price_per_kbyte))
FC_REFLECT(graphene::chain::account_role_delete_operation::fee_parameters_type, (fee))
FC_REFLECT(graphene::chain::account_role_create_operation, (fee)(owner)(name)(metadata)(allowed_operations)(whitelisted_accounts)(valid_to)(extensions))
FC_REFLECT(graphene::chain::account_role_update_operation, (fee)(owner)(account_role_id)(name)(metadata)(allowed_operations_to_add)(allowed_operations_to_remove)(accounts_to_add)(accounts_to_remove)(valid_to)(extensions))
FC_REFLECT(graphene::chain::account_role_delete_operation, (fee)(owner)(account_role_id)(owner)(extensions))

View file

@ -52,6 +52,9 @@ namespace graphene { namespace chain {
optional < uint16_t > rbac_max_permissions_per_account = RBAC_MAX_PERMISSIONS_PER_ACCOUNT;
optional < uint32_t > rbac_max_account_authority_lifetime = RBAC_MAX_ACCOUNT_AUTHORITY_LIFETIME;
optional < uint16_t > rbac_max_authorities_per_permission = RBAC_MAX_AUTHS_PER_PERMISSION;
/* Account Roles - Permissions Parameters */
optional < uint16_t > account_roles_max_per_account = ACCOUNT_ROLES_MAX_PER_ACCOUNT;
optional < uint32_t > account_roles_max_lifetime = ACCOUNT_ROLES_MAX_LIFETIME;
};
struct chain_parameters
@ -152,6 +155,12 @@ namespace graphene { namespace chain {
inline uint16_t rbac_max_authorities_per_permission()const {
return extensions.value.rbac_max_authorities_per_permission.valid() ? *extensions.value.rbac_max_authorities_per_permission : RBAC_MAX_AUTHS_PER_PERMISSION;
}
inline uint16_t account_roles_max_per_account()const {
return extensions.value.account_roles_max_per_account.valid() ? *extensions.value.account_roles_max_per_account : ACCOUNT_ROLES_MAX_PER_ACCOUNT;
}
inline uint32_t account_roles_max_lifetime()const {
return extensions.value.account_roles_max_lifetime.valid() ? *extensions.value.account_roles_max_lifetime : ACCOUNT_ROLES_MAX_LIFETIME;
}
};
} } // graphene::chain
@ -172,6 +181,8 @@ FC_REFLECT( graphene::chain::parameter_extension,
(rbac_max_permissions_per_account)
(rbac_max_account_authority_lifetime)
(rbac_max_authorities_per_permission)
(account_roles_max_per_account)
(account_roles_max_lifetime)
)
FC_REFLECT( graphene::chain::chain_parameters,

View file

@ -20,6 +20,7 @@ struct custom_account_authority_create_operation : public base_operation
time_point_sec valid_from;
time_point_sec valid_to;
account_id_type owner_account;
extensions_type extensions;
account_id_type fee_payer() const { return owner_account; }
void validate() const;
@ -38,6 +39,7 @@ struct custom_account_authority_update_operation : public base_operation
optional<time_point_sec> new_valid_from;
optional<time_point_sec> new_valid_to;
account_id_type owner_account;
extensions_type extensions;
account_id_type fee_payer() const { return owner_account; }
void validate() const;
@ -54,6 +56,7 @@ struct custom_account_authority_delete_operation : public base_operation
asset fee;
custom_account_authority_id_type auth_id;
account_id_type owner_account;
extensions_type extensions;
account_id_type fee_payer() const { return owner_account; }
void validate() const;
@ -64,10 +67,10 @@ struct custom_account_authority_delete_operation : public base_operation
} // namespace graphene
FC_REFLECT(graphene::chain::custom_account_authority_create_operation::fee_parameters_type, (fee)(price_per_kbyte))
FC_REFLECT(graphene::chain::custom_account_authority_create_operation, (fee)(permission_id)(operation_type)(valid_from)(valid_to)(owner_account))
FC_REFLECT(graphene::chain::custom_account_authority_create_operation, (fee)(permission_id)(operation_type)(valid_from)(valid_to)(owner_account)(extensions))
FC_REFLECT(graphene::chain::custom_account_authority_update_operation::fee_parameters_type, (fee))
FC_REFLECT(graphene::chain::custom_account_authority_update_operation, (fee)(auth_id)(new_valid_from)(new_valid_to)(owner_account))
FC_REFLECT(graphene::chain::custom_account_authority_update_operation, (fee)(auth_id)(new_valid_from)(new_valid_to)(owner_account)(extensions))
FC_REFLECT(graphene::chain::custom_account_authority_delete_operation::fee_parameters_type, (fee))
FC_REFLECT(graphene::chain::custom_account_authority_delete_operation, (fee)(auth_id)(owner_account))
FC_REFLECT(graphene::chain::custom_account_authority_delete_operation, (fee)(auth_id)(owner_account)(extensions))

View file

@ -18,6 +18,7 @@ struct custom_permission_create_operation : public base_operation
account_id_type owner_account;
string permission_name;
authority auth;
extensions_type extensions;
account_id_type fee_payer() const { return owner_account; }
void validate() const;
@ -35,6 +36,7 @@ struct custom_permission_update_operation : public base_operation
custom_permission_id_type permission_id;
optional<authority> new_auth;
account_id_type owner_account;
extensions_type extensions;
account_id_type fee_payer() const { return owner_account; }
void validate() const;
@ -51,6 +53,7 @@ struct custom_permission_delete_operation : public base_operation
asset fee;
custom_permission_id_type permission_id;
account_id_type owner_account;
extensions_type extensions;
account_id_type fee_payer() const { return owner_account; }
void validate() const;
@ -61,10 +64,10 @@ struct custom_permission_delete_operation : public base_operation
} // namespace graphene
FC_REFLECT(graphene::chain::custom_permission_create_operation::fee_parameters_type, (fee)(price_per_kbyte))
FC_REFLECT(graphene::chain::custom_permission_create_operation, (fee)(owner_account)(permission_name)(auth))
FC_REFLECT(graphene::chain::custom_permission_create_operation, (fee)(owner_account)(permission_name)(auth)(extensions))
FC_REFLECT(graphene::chain::custom_permission_update_operation::fee_parameters_type, (fee))
FC_REFLECT(graphene::chain::custom_permission_update_operation, (fee)(permission_id)(new_auth)(owner_account))
FC_REFLECT(graphene::chain::custom_permission_update_operation, (fee)(permission_id)(new_auth)(owner_account)(extensions))
FC_REFLECT(graphene::chain::custom_permission_delete_operation::fee_parameters_type, (fee))
FC_REFLECT(graphene::chain::custom_permission_delete_operation, (fee)(permission_id)(owner_account))
FC_REFLECT(graphene::chain::custom_permission_delete_operation, (fee)(permission_id)(owner_account)(extensions))

View file

@ -21,6 +21,9 @@ namespace graphene { namespace chain {
optional<uint16_t> revenue_split;
bool is_transferable = false;
bool is_sellable = true;
// Accounts Role
optional<account_role_id_type> account_role;
extensions_type extensions;
account_id_type fee_payer()const { return owner; }
void validate() const;
@ -29,7 +32,11 @@ namespace graphene { namespace chain {
struct nft_metadata_update_operation : public base_operation
{
struct fee_parameters_type { uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION; };
struct fee_parameters_type
{
uint64_t fee = GRAPHENE_BLOCKCHAIN_PRECISION;
uint32_t price_per_kbyte = GRAPHENE_BLOCKCHAIN_PRECISION;
};
asset fee;
account_id_type owner;
@ -41,6 +48,9 @@ namespace graphene { namespace chain {
optional<uint16_t> revenue_split;
optional<bool> is_transferable;
optional<bool> is_sellable;
// Accounts Role
optional<account_role_id_type> account_role;
extensions_type extensions;
account_id_type fee_payer()const { return owner; }
void validate() const;
@ -63,6 +73,7 @@ namespace graphene { namespace chain {
account_id_type approved;
vector<account_id_type> approved_operators;
std::string token_uri;
extensions_type extensions;
account_id_type fee_payer()const { return payer; }
void validate() const;
@ -84,6 +95,7 @@ namespace graphene { namespace chain {
account_id_type to;
nft_id_type token_id;
string data;
extensions_type extensions;
account_id_type fee_payer()const { return operator_; }
share_type calculate_fee(const fee_parameters_type &k) const;
@ -98,6 +110,7 @@ namespace graphene { namespace chain {
account_id_type approved;
nft_id_type token_id;
extensions_type extensions;
account_id_type fee_payer()const { return operator_; }
share_type calculate_fee(const fee_parameters_type &k) const;
@ -112,6 +125,7 @@ namespace graphene { namespace chain {
account_id_type operator_;
bool approved;
extensions_type extensions;
account_id_type fee_payer()const { return owner; }
share_type calculate_fee(const fee_parameters_type &k) const;
@ -126,10 +140,10 @@ FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation::fee_parameters_ty
FC_REFLECT( graphene::chain::nft_approve_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::nft_set_approval_for_all_operation::fee_parameters_type, (fee) )
FC_REFLECT( graphene::chain::nft_metadata_create_operation, (fee) (owner) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) (is_transferable) (is_sellable) )
FC_REFLECT( graphene::chain::nft_metadata_update_operation, (fee) (owner) (nft_metadata_id) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) (is_transferable) (is_sellable) )
FC_REFLECT( graphene::chain::nft_mint_operation, (fee) (payer) (nft_metadata_id) (owner) (approved) (approved_operators) (token_uri) )
FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation, (fee) (operator_) (from) (to) (token_id) (data) )
FC_REFLECT( graphene::chain::nft_approve_operation, (fee) (operator_) (approved) (token_id) )
FC_REFLECT( graphene::chain::nft_set_approval_for_all_operation, (fee) (owner) (operator_) (approved) )
FC_REFLECT( graphene::chain::nft_metadata_create_operation, (fee) (owner) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) (is_transferable) (is_sellable) (account_role) (extensions) )
FC_REFLECT( graphene::chain::nft_metadata_update_operation, (fee) (owner) (nft_metadata_id) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) (is_transferable) (is_sellable) (account_role) (extensions) )
FC_REFLECT( graphene::chain::nft_mint_operation, (fee) (payer) (nft_metadata_id) (owner) (approved) (approved_operators) (token_uri) (extensions) )
FC_REFLECT( graphene::chain::nft_safe_transfer_from_operation, (fee) (operator_) (from) (to) (token_id) (data) (extensions) )
FC_REFLECT( graphene::chain::nft_approve_operation, (fee) (operator_) (approved) (token_id) (extensions) )
FC_REFLECT( graphene::chain::nft_set_approval_for_all_operation, (fee) (owner) (operator_) (approved) (extensions) )

View file

@ -49,6 +49,7 @@
#include <graphene/chain/protocol/custom_account_authority.hpp>
#include <graphene/chain/protocol/offer.hpp>
#include <graphene/chain/protocol/nft_ops.hpp>
#include <graphene/chain/protocol/account_role.hpp>
namespace graphene { namespace chain {
@ -155,7 +156,10 @@ namespace graphene { namespace chain {
nft_mint_operation,
nft_safe_transfer_from_operation,
nft_approve_operation,
nft_set_approval_for_all_operation
nft_set_approval_for_all_operation,
account_role_create_operation,
account_role_update_operation,
account_role_delete_operation
> operation;
/// @} // operations group

View file

@ -176,6 +176,7 @@ namespace graphene { namespace chain {
offer_object_type,
nft_metadata_type,
nft_object_type,
account_role_type,
OBJECT_TYPE_COUNT ///< Sentry value which contains the number of different object types
};
@ -241,6 +242,7 @@ namespace graphene { namespace chain {
class offer_object;
class nft_metadata_object;
class nft_object;
class account_role_object;
typedef object_id< protocol_ids, account_object_type, account_object> account_id_type;
typedef object_id< protocol_ids, asset_object_type, asset_object> asset_id_type;
@ -272,6 +274,7 @@ namespace graphene { namespace chain {
typedef object_id< protocol_ids, offer_object_type, offer_object> offer_id_type;
typedef object_id< protocol_ids, nft_metadata_type, nft_metadata_object> nft_metadata_id_type;
typedef object_id< protocol_ids, nft_object_type, nft_object> nft_id_type;
typedef object_id< protocol_ids, account_role_type, account_role_object> account_role_id_type;
// implementation types
class global_property_object;
@ -459,6 +462,7 @@ FC_REFLECT_ENUM( graphene::chain::object_type,
(offer_object_type)
(nft_metadata_type)
(nft_object_type)
(account_role_type)
(OBJECT_TYPE_COUNT)
)
FC_REFLECT_ENUM( graphene::chain::impl_object_type,
@ -535,6 +539,7 @@ FC_REFLECT_TYPENAME( graphene::chain::custom_account_authority_id_type )
FC_REFLECT_TYPENAME( graphene::chain::offer_history_id_type )
FC_REFLECT_TYPENAME( graphene::chain::nft_metadata_id_type )
FC_REFLECT_TYPENAME( graphene::chain::nft_id_type )
FC_REFLECT_TYPENAME( graphene::chain::account_role_id_type )
FC_REFLECT( graphene::chain::void_t, )

View file

@ -0,0 +1,48 @@
#pragma once
#include <graphene/chain/protocol/operations.hpp>
#include <graphene/chain/hardfork.hpp>
namespace graphene
{
namespace chain
{
struct rbac_operation_hardfork_visitor
{
typedef void result_type;
const fc::time_point_sec block_time;
rbac_operation_hardfork_visitor(const fc::time_point_sec bt) : block_time(bt) {}
void operator()(int op_type) const
{
int first_allowed_op = operation::tag<custom_permission_create_operation>::value;
switch (op_type)
{
case operation::tag<custom_permission_create_operation>::value:
case operation::tag<custom_permission_update_operation>::value:
case operation::tag<custom_permission_delete_operation>::value:
case operation::tag<custom_account_authority_create_operation>::value:
case operation::tag<custom_account_authority_update_operation>::value:
case operation::tag<custom_account_authority_delete_operation>::value:
case operation::tag<offer_operation>::value:
case operation::tag<bid_operation>::value:
case operation::tag<cancel_offer_operation>::value:
case operation::tag<finalize_offer_operation>::value:
case operation::tag<nft_metadata_create_operation>::value:
case operation::tag<nft_metadata_update_operation>::value:
case operation::tag<nft_mint_operation>::value:
case operation::tag<nft_safe_transfer_from_operation>::value:
case operation::tag<nft_approve_operation>::value:
case operation::tag<nft_set_approval_for_all_operation>::value:
case operation::tag<account_role_create_operation>::value:
case operation::tag<account_role_update_operation>::value:
case operation::tag<account_role_delete_operation>::value:
FC_ASSERT(block_time >= HARDFORK_NFT_TIME, "Custom permissions and roles not allowed on this operation yet!");
break;
default:
FC_ASSERT(op_type >= operation::tag<transfer_operation>::value && op_type < first_allowed_op, "Custom permissions and roles not allowed on this operation!");
}
}
};
} // namespace chain
} // namespace graphene

View file

@ -1,5 +1,7 @@
#include <graphene/chain/nft_evaluator.hpp>
#include <graphene/chain/nft_object.hpp>
#include <graphene/chain/protocol/operations.hpp>
#include <graphene/chain/account_role_object.hpp>
#include <graphene/chain/hardfork.hpp>
namespace graphene { namespace chain {
@ -18,6 +20,10 @@ void_result nft_metadata_create_evaluator::do_evaluate( const nft_metadata_creat
(*op.revenue_partner)(db());
FC_ASSERT(*op.revenue_split >= 0 && *op.revenue_split <= GRAPHENE_100_PERCENT, "Revenue split percent invalid");
}
if(op.account_role) {
const auto& ar_obj = (*op.account_role)(db());
FC_ASSERT(ar_obj.owner == op.owner, "Only the Account Role created by the owner can be attached");
}
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
@ -32,6 +38,7 @@ object_id_type nft_metadata_create_evaluator::do_apply( const nft_metadata_creat
obj.revenue_split = op.revenue_split;
obj.is_transferable = op.is_transferable;
obj.is_sellable = op.is_sellable;
obj.account_role = op.account_role;
});
return new_nft_metadata_object.id;
} FC_CAPTURE_AND_RETHROW( (op) ) }
@ -57,6 +64,10 @@ void_result nft_metadata_update_evaluator::do_evaluate( const nft_metadata_updat
(*op.revenue_partner)(db());
FC_ASSERT(*op.revenue_split >= 0 && *op.revenue_split <= GRAPHENE_100_PERCENT, "Revenue split percent invalid");
}
if(op.account_role) {
const auto& ar_obj = (*op.account_role)(db());
FC_ASSERT(ar_obj.owner == op.owner, "Only the Account Role created by the owner can be attached");
}
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
@ -77,6 +88,8 @@ void_result nft_metadata_update_evaluator::do_apply( const nft_metadata_update_o
obj.is_transferable = *op.is_transferable;
if( op.is_sellable.valid() )
obj.is_sellable = *op.is_sellable;
if( op.account_role.valid() )
obj.account_role = op.account_role;
});
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
@ -144,6 +157,17 @@ void_result nft_safe_transfer_from_evaluator::do_evaluate( const nft_safe_transf
const auto& nft_meta_obj = itr_nft->nft_metadata_id(db());
FC_ASSERT( nft_meta_obj.is_transferable == true, "NFT is not transferable");
if (nft_meta_obj.account_role)
{
const auto &ar_idx = db().get_index_type<account_role_index>().indices().get<by_id>();
auto ar_itr = ar_idx.find(*nft_meta_obj.account_role);
if(ar_itr != ar_idx.end())
{
FC_ASSERT(db().account_role_valid(*ar_itr, op.operator_, get_type()), "Account role not valid");
FC_ASSERT(db().account_role_valid(*ar_itr, op.to), "Account role not valid");
}
}
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }

View file

@ -2,6 +2,8 @@
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/offer_object.hpp>
#include <graphene/chain/nft_object.hpp>
#include <graphene/chain/protocol/operations.hpp>
#include <graphene/chain/account_role_object.hpp>
#include <graphene/chain/exceptions.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/is_authorized_asset.hpp>
@ -38,6 +40,15 @@ namespace graphene
}
const auto &nft_meta_obj = nft_obj.nft_metadata_id(d);
FC_ASSERT(nft_meta_obj.is_sellable == true, "NFT is not sellable");
if (nft_meta_obj.account_role)
{
const auto &ar_idx = db().get_index_type<account_role_index>().indices().get<by_id>();
auto ar_itr = ar_idx.find(*nft_meta_obj.account_role);
if(ar_itr != ar_idx.end())
{
FC_ASSERT(db().account_role_valid(*ar_itr, op.issuer, get_type()), "Account role not valid");
}
}
}
FC_ASSERT(op.offer_expiration_date > d.head_block_time(), "Expiration should be in future");
FC_ASSERT(op.fee.amount >= 0, "Invalid fee");
@ -100,6 +111,18 @@ namespace graphene
FC_ASSERT(!is_approved, "Bidder cannot already be an approved account of the item");
FC_ASSERT(!is_approved_operator, "Bidder cannot already be an approved operator of the item");
}
const auto &nft_meta_obj = nft_obj.nft_metadata_id(d);
FC_ASSERT(nft_meta_obj.is_sellable == true, "NFT is not sellable");
if (nft_meta_obj.account_role)
{
const auto &ar_idx = db().get_index_type<account_role_index>().indices().get<by_id>();
auto ar_itr = ar_idx.find(*nft_meta_obj.account_role);
if(ar_itr != ar_idx.end())
{
FC_ASSERT(db().account_role_valid(*ar_itr, op.bidder, get_type()), "Account role not valid");
}
}
}
FC_ASSERT(op.bid_price.asset_id == offer.minimum_price.asset_id, "Asset type mismatch");

View file

@ -196,6 +196,18 @@ struct proposal_operation_hardfork_visitor
FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "nft_set_approval_for_all_operation not allowed yet!" );
}
void operator()(const account_role_create_operation &v) const {
FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "account_role_create_operation not allowed yet!" );
}
void operator()(const account_role_update_operation &v) const {
FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "account_role_update_operation not allowed yet!" );
}
void operator()(const account_role_delete_operation &v) const {
FC_ASSERT( block_time >= HARDFORK_NFT_TIME, "account_role_delete_operation not allowed yet!" );
}
// loop and self visit in proposals
void operator()(const proposal_create_operation &v) const {

View file

@ -0,0 +1,61 @@
#include <graphene/chain/protocol/account_role.hpp>
#include <graphene/chain/protocol/operations.hpp>
namespace graphene
{
namespace chain
{
void account_role_create_operation::validate() const
{
FC_ASSERT(fee.amount >= 0, "Fee must not be negative");
FC_ASSERT(allowed_operations.size() > 0, "Allowed operations should be positive");
FC_ASSERT(whitelisted_accounts.size() > 0, "Whitelisted accounts should be positive");
}
void account_role_update_operation::validate() const
{
FC_ASSERT(fee.amount >= 0, "Fee must not be negative");
for (auto aop : allowed_operations_to_add)
{
FC_ASSERT(aop >= 0 && aop < operation::count(), "operation_type is not valid");
FC_ASSERT(allowed_operations_to_remove.find(aop) == allowed_operations_to_remove.end(),
"Cannot add and remove allowed operation at the same time.");
}
for (auto aop : allowed_operations_to_remove)
{
FC_ASSERT(aop >= 0 && aop < operation::count(), "operation_type is not valid");
FC_ASSERT(allowed_operations_to_add.find(aop) == allowed_operations_to_add.end(),
"Cannot add and remove allowed operation at the same time.");
}
for (auto acc : accounts_to_add)
{
FC_ASSERT(accounts_to_remove.find(acc) == accounts_to_remove.end(),
"Cannot add and remove accounts at the same time.");
}
for (auto acc : accounts_to_remove)
{
FC_ASSERT(accounts_to_add.find(acc) == accounts_to_add.end(),
"Cannot add and remove accounts at the same time.");
}
}
void account_role_delete_operation::validate() const
{
FC_ASSERT(fee.amount >= 0, "Fee must not be negative");
}
share_type account_role_create_operation::calculate_fee(const fee_parameters_type &k) const
{
return k.fee + calculate_data_fee(fc::raw::pack_size(*this), k.price_per_kbyte);
}
share_type account_role_update_operation::calculate_fee(const fee_parameters_type &k) const
{
return k.fee + calculate_data_fee(fc::raw::pack_size(*this), k.price_per_kbyte);
}
} // namespace chain
} // namespace graphene

View file

@ -45,7 +45,6 @@ void nft_metadata_create_operation::validate() const
FC_ASSERT(fee.amount >= 0, "Fee must not be negative");
FC_ASSERT(is_valid_nft_token_name(name), "Invalid NFT name provided");
FC_ASSERT(is_valid_nft_token_name(symbol), "Invalid NFT symbol provided");
FC_ASSERT(base_uri.length() <= NFT_URI_MAX_LENGTH, "Invalid NFT Base URI");
}
void nft_metadata_update_operation::validate() const
@ -55,14 +54,11 @@ void nft_metadata_update_operation::validate() const
FC_ASSERT(is_valid_nft_token_name(*name), "Invalid NFT name provided");
if(symbol)
FC_ASSERT(is_valid_nft_token_name(*symbol), "Invalid NFT symbol provided");
if(base_uri)
FC_ASSERT((*base_uri).length() <= NFT_URI_MAX_LENGTH, "Invalid NFT Base URI");
}
void nft_mint_operation::validate() const
{
FC_ASSERT(fee.amount >= 0, "Fee must not be negative");
FC_ASSERT(token_uri.length() <= NFT_URI_MAX_LENGTH, "Invalid NFT Token URI");
}
share_type nft_metadata_create_operation::calculate_fee(const fee_parameters_type &k) const
@ -72,7 +68,7 @@ share_type nft_metadata_create_operation::calculate_fee(const fee_parameters_typ
share_type nft_metadata_update_operation::calculate_fee(const fee_parameters_type &k) const
{
return k.fee;
return k.fee + calculate_data_fee( fc::raw::pack_size(*this), k.price_per_kbyte );
}
share_type nft_mint_operation::calculate_fee(const fee_parameters_type &k) const

View file

@ -1950,6 +1950,7 @@ class wallet_api
optional<uint16_t> revenue_split,
bool is_transferable,
bool is_sellable,
optional<account_role_id_type> role_id,
bool broadcast);
/**
@ -1975,6 +1976,7 @@ class wallet_api
optional<uint16_t> revenue_split,
optional<bool> is_transferable,
optional<bool> is_sellable,
optional<account_role_id_type> role_id,
bool broadcast);
/**
@ -2113,6 +2115,28 @@ class wallet_api
vector<offer_history_object> get_offer_history_by_item(const nft_id_type item, uint32_t limit, optional<offer_history_id_type> lower_id) const;
vector<offer_history_object> get_offer_history_by_bidder(string bidder_account_id_or_name, uint32_t limit, optional<offer_history_id_type> lower_id) const;
signed_transaction create_account_role(string owner_account_id_or_name,
string name,
string metadata,
flat_set<int> allowed_operations,
flat_set<account_id_type> whitelisted_accounts,
time_point_sec valid_to,
bool broadcast);
signed_transaction update_account_role(string owner_account_id_or_name,
account_role_id_type role_id,
optional<string> name,
optional<string> metadata,
flat_set<int> operations_to_add,
flat_set<int> operations_to_remove,
flat_set<account_id_type> accounts_to_add,
flat_set<account_id_type> accounts_to_remove,
optional<time_point_sec> valid_to,
bool broadcast);
signed_transaction delete_account_role(string owner_account_id_or_name,
account_role_id_type role_id,
bool broadcast);
vector<account_role_object> get_account_roles_by_owner(string owner_account_id_or_name) const;
void dbg_make_uia(string creator, string symbol);
void dbg_make_mia(string creator, string symbol);
void dbg_push_blocks( std::string src_filename, uint32_t count );
@ -2387,6 +2411,10 @@ FC_API( graphene::wallet::wallet_api,
(get_offer_history_by_issuer)
(get_offer_history_by_item)
(get_offer_history_by_bidder)
(create_account_role)
(update_account_role)
(delete_account_role)
(get_account_roles_by_owner)
(get_upcoming_tournaments)
(get_tournaments)
(get_tournaments_by_state)

View file

@ -6375,6 +6375,7 @@ signed_transaction wallet_api::nft_metadata_create(string owner_account_id_or_na
optional<uint16_t> revenue_split,
bool is_transferable,
bool is_sellable,
optional<account_role_id_type> role_id,
bool broadcast)
{
account_object owner_account = my->get_account(owner_account_id_or_name);
@ -6397,6 +6398,7 @@ signed_transaction wallet_api::nft_metadata_create(string owner_account_id_or_na
}
op.is_transferable = is_transferable;
op.is_sellable = is_sellable;
op.account_role = role_id;
signed_transaction trx;
trx.operations.push_back(op);
@ -6415,6 +6417,7 @@ signed_transaction wallet_api::nft_metadata_update(string owner_account_id_or_na
optional<uint16_t> revenue_split,
optional<bool> is_transferable,
optional<bool> is_sellable,
optional<account_role_id_type> role_id,
bool broadcast)
{
account_object owner_account = my->get_account(owner_account_id_or_name);
@ -6438,6 +6441,7 @@ signed_transaction wallet_api::nft_metadata_update(string owner_account_id_or_na
}
op.is_transferable = is_transferable;
op.is_sellable = is_sellable;
op.account_role = role_id;
signed_transaction trx;
trx.operations.push_back(op);
@ -6719,6 +6723,88 @@ vector<offer_history_object> wallet_api::get_offer_history_by_bidder(string bidd
account_object bidder_account = my->get_account(bidder_account_id_or_name);
return my->_remote_db->get_offer_history_by_bidder(lb_id, bidder_account.id, limit);
}
signed_transaction wallet_api::create_account_role(string owner_account_id_or_name,
string name,
string metadata,
flat_set<int> allowed_operations,
flat_set<account_id_type> whitelisted_accounts,
time_point_sec valid_to,
bool broadcast)
{
account_object owner_account = my->get_account(owner_account_id_or_name);
account_role_create_operation op;
op.owner = owner_account.id;
op.name = name;
op.metadata = metadata;
op.allowed_operations = allowed_operations;
op.whitelisted_accounts = whitelisted_accounts;
op.valid_to = valid_to;
signed_transaction trx;
trx.operations.push_back(op);
my->set_operation_fees( trx, my->_remote_db->get_global_properties().parameters.current_fees );
trx.validate();
return my->sign_transaction( trx, broadcast );
}
signed_transaction wallet_api::update_account_role(string owner_account_id_or_name,
account_role_id_type role_id,
optional<string> name,
optional<string> metadata,
flat_set<int> operations_to_add,
flat_set<int> operations_to_remove,
flat_set<account_id_type> accounts_to_add,
flat_set<account_id_type> accounts_to_remove,
optional<time_point_sec> valid_to,
bool broadcast)
{
account_object owner_account = my->get_account(owner_account_id_or_name);
account_role_update_operation op;
op.owner = owner_account.id;
op.account_role_id = role_id;
op.name = name;
op.metadata = metadata;
op.allowed_operations_to_add = operations_to_add;
op.allowed_operations_to_remove = operations_to_remove;
op.accounts_to_add = accounts_to_add;
op.accounts_to_remove = accounts_to_remove;
op.valid_to = valid_to;
signed_transaction trx;
trx.operations.push_back(op);
my->set_operation_fees( trx, my->_remote_db->get_global_properties().parameters.current_fees );
trx.validate();
return my->sign_transaction( trx, broadcast );
}
signed_transaction wallet_api::delete_account_role(string owner_account_id_or_name,
account_role_id_type role_id,
bool broadcast)
{
account_object owner_account = my->get_account(owner_account_id_or_name);
account_role_delete_operation op;
op.owner = owner_account.id;
op.account_role_id = role_id;
signed_transaction trx;
trx.operations.push_back(op);
my->set_operation_fees( trx, my->_remote_db->get_global_properties().parameters.current_fees );
trx.validate();
return my->sign_transaction( trx, broadcast );
}
vector<account_role_object> wallet_api::get_account_roles_by_owner(string owner_account_id_or_name) const
{
account_object owner_account = my->get_account(owner_account_id_or_name);
return my->_remote_db->get_account_roles_by_owner(owner_account.id);
}
// default ctor necessary for FC_REFLECT
signed_block_with_info::signed_block_with_info()
{

View file

@ -47,6 +47,7 @@
#include <graphene/chain/custom_account_authority_object.hpp>
#include <graphene/chain/offer_object.hpp>
#include <graphene/chain/nft_object.hpp>
#include <graphene/chain/account_role_object.hpp>
#include <fc/smart_ref_impl.hpp>
#include <iostream>

View file

@ -0,0 +1,379 @@
#include <boost/test/unit_test.hpp>
#include <fc/reflect/variant.hpp>
#include <graphene/chain/protocol/operations.hpp>
#include <graphene/chain/hardfork.hpp>
#include <graphene/chain/database.hpp>
#include <graphene/chain/protocol/protocol.hpp>
#include <graphene/chain/exceptions.hpp>
#include <graphene/chain/account_object.hpp>
#include <graphene/chain/asset_object.hpp>
#include <graphene/chain/committee_member_object.hpp>
#include <graphene/chain/proposal_object.hpp>
#include <graphene/chain/custom_permission_object.hpp>
#include <graphene/chain/custom_account_authority_object.hpp>
#include <graphene/chain/nft_object.hpp>
#include <graphene/chain/account_role_object.hpp>
#include <graphene/chain/offer_object.hpp>
#include <graphene/db/simple_index.hpp>
#include <fc/crypto/digest.hpp>
#include "../common/database_fixture.hpp"
using namespace graphene::chain;
using namespace graphene::chain::test;
BOOST_FIXTURE_TEST_SUITE(account_role_tests, database_fixture)
BOOST_AUTO_TEST_CASE(account_role_create_test)
{
try
{
BOOST_TEST_MESSAGE("account_role_create_test");
generate_blocks(HARDFORK_NFT_TIME);
generate_block();
generate_block();
set_expiration(db, trx);
ACTORS((resourceowner)(alice)(bob)(charlie));
upgrade_to_lifetime_member(resourceowner);
upgrade_to_lifetime_member(alice);
upgrade_to_lifetime_member(bob);
upgrade_to_lifetime_member(charlie);
transfer(committee_account, resourceowner_id, asset(100000 * GRAPHENE_BLOCKCHAIN_PRECISION));
transfer(committee_account, alice_id, asset(100000 * GRAPHENE_BLOCKCHAIN_PRECISION));
transfer(committee_account, bob_id, asset(100000 * GRAPHENE_BLOCKCHAIN_PRECISION));
transfer(committee_account, charlie_id, asset(100000 * GRAPHENE_BLOCKCHAIN_PRECISION));
{
BOOST_TEST_MESSAGE("Send account_role_create_operation");
account_role_create_operation op;
op.owner = resourceowner_id;
op.name = "Test Account Role";
op.metadata = "{\"country\": \"earth\", \"race\": \"human\" }";
int ops[] = {operation::tag<nft_safe_transfer_from_operation>::value,
operation::tag<nft_approve_operation>::value,
operation::tag<nft_set_approval_for_all_operation>::value,
operation::tag<offer_operation>::value,
operation::tag<bid_operation>::value,
operation::tag<cancel_offer_operation>::value};
op.allowed_operations.insert(ops, ops + 6);
op.whitelisted_accounts.emplace(alice_id);
op.whitelisted_accounts.emplace(bob_id);
op.valid_to = db.head_block_time() + 1000;
trx.operations.push_back(op);
sign(trx, resourceowner_private_key);
PUSH_TX(db, trx);
trx.clear();
}
const auto &idx = db.get_index_type<account_role_index>().indices().get<by_id>();
BOOST_REQUIRE(idx.size() == 1);
auto obj = idx.begin();
BOOST_REQUIRE(obj != idx.end());
BOOST_CHECK(obj->owner == resourceowner_id);
BOOST_CHECK(obj->name == "Test Account Role");
BOOST_CHECK(obj->metadata == "{\"country\": \"earth\", \"race\": \"human\" }");
flat_set<int> expected_allowed_operations = {operation::tag<nft_safe_transfer_from_operation>::value,
operation::tag<nft_approve_operation>::value,
operation::tag<nft_set_approval_for_all_operation>::value,
operation::tag<offer_operation>::value,
operation::tag<bid_operation>::value,
operation::tag<cancel_offer_operation>::value};
BOOST_CHECK(obj->allowed_operations == expected_allowed_operations);
flat_set<account_id_type> expected_whitelisted_accounts = {alice_id, bob_id};
BOOST_CHECK(obj->whitelisted_accounts == expected_whitelisted_accounts);
BOOST_CHECK(obj->valid_to == db.head_block_time() + 1000);
}
FC_LOG_AND_RETHROW()
}
BOOST_AUTO_TEST_CASE(account_role_update_test)
{
try
{
BOOST_TEST_MESSAGE("account_role_create_test");
INVOKE(account_role_create_test);
GET_ACTOR(alice);
GET_ACTOR(bob);
GET_ACTOR(charlie);
GET_ACTOR(resourceowner);
set_expiration(db, trx);
{
BOOST_TEST_MESSAGE("Send account_role_update_operation");
account_role_update_operation op;
op.owner = resourceowner_id;
op.account_role_id = account_role_id_type(0);
op.name = "Test Account Role Update";
op.metadata = "{\"country\": \"earth\", \"race\": \"human\", \"op\":\"update\" }";
int ops_add[] = {operation::tag<finalize_offer_operation>::value};
int ops_delete[] = {operation::tag<nft_safe_transfer_from_operation>::value, operation::tag<nft_approve_operation>::value};
op.allowed_operations_to_add.insert(ops_add, ops_add + 1);
op.allowed_operations_to_remove.insert(ops_delete, ops_delete + 2);
op.valid_to = db.head_block_time() + 10000;
trx.operations.push_back(op);
sign(trx, resourceowner_private_key);
PUSH_TX(db, trx);
trx.clear();
}
const auto &idx = db.get_index_type<account_role_index>().indices().get<by_id>();
BOOST_REQUIRE(idx.size() == 1);
auto obj = idx.begin();
BOOST_REQUIRE(obj != idx.end());
BOOST_CHECK(obj->owner == resourceowner_id);
BOOST_CHECK(obj->name == "Test Account Role Update");
BOOST_CHECK(obj->metadata == "{\"country\": \"earth\", \"race\": \"human\", \"op\":\"update\" }");
flat_set<int> expected_allowed_operations = {operation::tag<finalize_offer_operation>::value,
operation::tag<nft_set_approval_for_all_operation>::value,
operation::tag<offer_operation>::value,
operation::tag<bid_operation>::value,
operation::tag<cancel_offer_operation>::value};
BOOST_CHECK(obj->allowed_operations == expected_allowed_operations);
flat_set<account_id_type> expected_whitelisted_accounts = {alice_id, bob_id};
BOOST_CHECK(obj->whitelisted_accounts == expected_whitelisted_accounts);
BOOST_CHECK(obj->valid_to == db.head_block_time() + 10000);
}
FC_LOG_AND_RETHROW()
}
BOOST_AUTO_TEST_CASE(account_role_delete_test)
{
try
{
BOOST_TEST_MESSAGE("account_role_delete_test");
INVOKE(account_role_create_test);
GET_ACTOR(resourceowner);
set_expiration(db, trx);
{
BOOST_TEST_MESSAGE("Send account_role_delete_operation");
account_role_delete_operation op;
op.owner = resourceowner_id;
op.account_role_id = account_role_id_type(0);
trx.operations.push_back(op);
sign(trx, resourceowner_private_key);
PUSH_TX(db, trx);
trx.clear();
}
const auto &idx = db.get_index_type<account_role_index>().indices().get<by_id>();
BOOST_REQUIRE(idx.size() == 0);
}
FC_LOG_AND_RETHROW()
}
BOOST_AUTO_TEST_CASE(nft_metadata_create_mint_with_account_role_test)
{
try
{
BOOST_TEST_MESSAGE("nft_metadata_create_mint_with_account_role_test");
INVOKE(account_role_create_test);
GET_ACTOR(alice);
GET_ACTOR(bob);
GET_ACTOR(resourceowner);
{
BOOST_TEST_MESSAGE("Send nft_metadata_create_operation");
nft_metadata_create_operation op;
op.owner = resourceowner_id;
op.name = "NFT Test";
op.symbol = "NFT";
op.base_uri = "http://nft.example.com";
op.revenue_partner = resourceowner_id;
op.revenue_split = 1000;
op.is_transferable = true;
op.is_sellable = true;
op.account_role = account_role_id_type(0);
trx.operations.push_back(op);
sign(trx, resourceowner_private_key);
PUSH_TX(db, trx);
trx.clear();
}
generate_block();
BOOST_TEST_MESSAGE("Check nft_metadata_create_operation results");
const auto &nftmd_idx = db.get_index_type<nft_metadata_index>().indices().get<by_id>();
BOOST_REQUIRE(nftmd_idx.size() == 1);
auto nftmd_obj = nftmd_idx.begin();
BOOST_REQUIRE(nftmd_obj != nftmd_idx.end());
BOOST_CHECK(nftmd_obj->owner == resourceowner_id);
BOOST_CHECK(nftmd_obj->name == "NFT Test");
BOOST_CHECK(nftmd_obj->symbol == "NFT");
BOOST_CHECK(nftmd_obj->base_uri == "http://nft.example.com");
BOOST_CHECK(nftmd_obj->account_role == account_role_id_type(0));
{
BOOST_TEST_MESSAGE("Send nft_mint_operation");
nft_mint_operation op;
op.payer = resourceowner_id;
op.nft_metadata_id = nftmd_obj->id;
op.owner = alice_id;
op.approved = alice_id;
trx.operations.push_back(op);
sign(trx, resourceowner_private_key);
PUSH_TX(db, trx);
trx.clear();
}
BOOST_TEST_MESSAGE("Check nft_mint_operation results");
const auto &nft_idx = db.get_index_type<nft_index>().indices().get<by_id>();
BOOST_REQUIRE(nft_idx.size() == 1);
auto nft_obj = nft_idx.begin();
BOOST_REQUIRE(nft_obj != nft_idx.end());
BOOST_CHECK(nft_obj->owner == alice_id);
BOOST_CHECK(nft_obj->approved == alice_id);
}
FC_LOG_AND_RETHROW()
}
BOOST_AUTO_TEST_CASE(nft_safe_transfer_account_role_test)
{
try
{
BOOST_TEST_MESSAGE("nft_safe_transfer_account_role_test");
INVOKE(nft_metadata_create_mint_with_account_role_test);
GET_ACTOR(alice);
GET_ACTOR(bob);
GET_ACTOR(charlie);
{
BOOST_TEST_MESSAGE("Send nft_safe_transfer_from_operation");
nft_safe_transfer_from_operation op;
op.operator_ = alice_id;
op.from = alice_id;
op.to = bob_id;
op.token_id = nft_id_type(0);
op.data = "data";
trx.operations.push_back(op);
sign(trx, alice_private_key);
PUSH_TX(db, trx);
trx.clear();
}
generate_block();
BOOST_TEST_MESSAGE("Check nft_safe_transfer_from_operation results");
const auto &nft_idx = db.get_index_type<nft_index>().indices().get<by_id>();
BOOST_REQUIRE(nft_idx.size() == 1);
auto nft_obj = nft_idx.begin();
BOOST_REQUIRE(nft_obj != nft_idx.end());
BOOST_CHECK(nft_obj->owner == bob_id);
{
BOOST_TEST_MESSAGE("Send nft_safe_transfer_from_operation");
nft_safe_transfer_from_operation op;
op.operator_ = bob_id;
op.from = bob_id;
op.to = charlie_id;
op.token_id = nft_id_type(0);
op.data = "data";
trx.operations.push_back(op);
sign(trx, bob_private_key);
// Charlie is not whitelisted by resource creator
BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception);
trx.clear();
}
}
FC_LOG_AND_RETHROW()
}
BOOST_AUTO_TEST_CASE(nft_offer_bid_account_role_test)
{
try
{
BOOST_TEST_MESSAGE("nft_offer_bid_account_role_test");
INVOKE(nft_metadata_create_mint_with_account_role_test);
GET_ACTOR(alice);
GET_ACTOR(bob);
GET_ACTOR(charlie);
{
BOOST_TEST_MESSAGE("Send create_offer");
offer_operation offer_op;
offer_op.item_ids.emplace(nft_id_type(0));
offer_op.issuer = alice_id;
offer_op.buying_item = false;
offer_op.maximum_price = asset(10000);
offer_op.minimum_price = asset(10);
offer_op.offer_expiration_date = db.head_block_time() + fc::seconds(15);
trx.operations.push_back(offer_op);
sign(trx, alice_private_key);
PUSH_TX(db, trx);
trx.clear();
}
// Charlie tries to bid but fails.
{
BOOST_TEST_MESSAGE("Send create_bid by charlie");
bid_operation bid_op;
bid_op.offer_id = offer_id_type(0);
// Buy it now price
bid_op.bid_price = asset(10000);
bid_op.bidder = charlie_id;
trx.operations.push_back(bid_op);
sign(trx, charlie_private_key);
// Charlie is not whitelisting to perform bid_operation by resource/metadata owner
BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception);
trx.clear();
}
// Bob succeeds in bidding.
{
BOOST_TEST_MESSAGE("Send create_bid by bob");
bid_operation bid_op;
bid_op.offer_id = offer_id_type(0);
// Buy it now price
bid_op.bid_price = asset(10000);
bid_op.bidder = bob_id;
trx.operations.push_back(bid_op);
sign(trx, bob_private_key);
// Bob is whitelisted in account role created by resource/metadata owner
PUSH_TX(db, trx);
trx.clear();
}
generate_block();
BOOST_TEST_MESSAGE("Check offer results");
const auto &nft_idx = db.get_index_type<nft_index>().indices().get<by_id>();
BOOST_REQUIRE(nft_idx.size() == 1);
auto nft_obj = nft_idx.begin();
BOOST_REQUIRE(nft_obj != nft_idx.end());
BOOST_CHECK(nft_obj->owner == bob_id);
// Charlie tries to bid (buy offer) but fails
{
BOOST_TEST_MESSAGE("Send create_offer");
offer_operation offer_op;
offer_op.item_ids.emplace(nft_id_type(0));
offer_op.issuer = charlie_id;
offer_op.buying_item = true;
offer_op.maximum_price = asset(10000);
offer_op.minimum_price = asset(10);
offer_op.offer_expiration_date = db.head_block_time() + fc::seconds(15);
trx.operations.push_back(offer_op);
sign(trx, charlie_private_key);
// Charlie is not whitelisting to perform offer_operation by resource/metadata owner
BOOST_CHECK_THROW(PUSH_TX(db, trx), fc::exception);
trx.clear();
}
}
FC_LOG_AND_RETHROW()
}
BOOST_AUTO_TEST_SUITE_END()