Add stricter checks to NFTs
This commit is contained in:
parent
10ecad7566
commit
82e9d92089
9 changed files with 141 additions and 4 deletions
|
|
@ -123,6 +123,7 @@ add_library( graphene_chain
|
|||
offer_object.cpp
|
||||
offer_evaluator.cpp
|
||||
nft_evaluator.cpp
|
||||
nft_object.cpp
|
||||
|
||||
${HEADERS}
|
||||
${PROTOCOL_HEADERS}
|
||||
|
|
|
|||
|
|
@ -46,8 +46,12 @@ void_result create_custom_account_authority_evaluator::do_evaluate(const custom_
|
|||
const custom_permission_object &pobj = op.permission_id(d);
|
||||
FC_ASSERT(pobj.account == op.owner_account, "Only owner account can update account authority object");
|
||||
FC_ASSERT(op.valid_to > now, "valid_to expiry should be in future");
|
||||
FC_ASSERT((op.valid_to - op.valid_from) <= fc::seconds(d.get_global_properties().parameters.rbac_max_account_authority_lifetime()), "Validity of the auth beyond max expiry");
|
||||
rbac_operation_hardfork_visitor rvtor(now);
|
||||
rvtor(op.operation_type);
|
||||
const auto& cindex = d.get_index_type<custom_account_authority_index>().indices().get<by_permission_and_op>();
|
||||
auto count = cindex.count(boost::make_tuple(op.permission_id));
|
||||
FC_ASSERT(count < d.get_global_properties().parameters.rbac_max_authorities_per_permission(), "Max operations that can be linked to a permission reached");
|
||||
return void_result();
|
||||
}
|
||||
FC_CAPTURE_AND_RETHROW((op))
|
||||
|
|
@ -93,6 +97,7 @@ void_result update_custom_account_authority_evaluator::do_evaluate(const custom_
|
|||
valid_to = *op.new_valid_to;
|
||||
}
|
||||
FC_ASSERT(valid_from < valid_to, "valid_from should be before valid_to");
|
||||
FC_ASSERT((valid_to - valid_from) <= fc::seconds(d.get_global_properties().parameters.rbac_max_account_authority_lifetime()), "Validity of the auth beyond max expiry");
|
||||
return void_result();
|
||||
}
|
||||
FC_CAPTURE_AND_RETHROW((op))
|
||||
|
|
|
|||
|
|
@ -26,6 +26,8 @@ void_result create_custom_permission_evaluator::do_evaluate(const custom_permiss
|
|||
const auto &pindex = d.get_index_type<custom_permission_index>().indices().get<by_account_and_permission>();
|
||||
auto pitr = pindex.find(boost::make_tuple(op.owner_account, op.permission_name));
|
||||
FC_ASSERT(pitr == pindex.end(), "Permission name already exists for the given account");
|
||||
auto count = pindex.count(boost::make_tuple(op.owner_account));
|
||||
FC_ASSERT(count < d.get_global_properties().parameters.rbac_max_permissions_per_account(), "Max permissions per account reached");
|
||||
return void_result();
|
||||
}
|
||||
FC_CAPTURE_AND_RETHROW((op))
|
||||
|
|
|
|||
|
|
@ -236,4 +236,11 @@
|
|||
#define GPOS_VESTING_LOCKIN_PERIOD (60*60*24*30) // 1 month
|
||||
|
||||
#define RBAC_MIN_PERMISSION_NAME_LENGTH 3
|
||||
#define RBAC_MAX_PERMISSION_NAME_LENGTH 10
|
||||
#define RBAC_MAX_PERMISSION_NAME_LENGTH 10
|
||||
#define RBAC_MAX_PERMISSIONS_PER_ACCOUNT 5 // 5 per account
|
||||
#define RBAC_MAX_ACCOUNT_AUTHORITY_LIFETIME 180*24*60*60 // 6 months
|
||||
#define RBAC_MAX_AUTHS_PER_PERMISSION 15 // 15 ops linked per permission
|
||||
|
||||
#define NFT_TOKEN_MIN_LENGTH 3
|
||||
#define NFT_TOKEN_MAX_LENGTH 15
|
||||
#define NFT_URI_MAX_LENGTH GRAPHENE_MAX_URL_LENGTH
|
||||
|
|
@ -48,6 +48,10 @@ namespace graphene { namespace chain {
|
|||
optional < uint32_t > gpos_subperiod = GPOS_SUBPERIOD;
|
||||
optional < uint32_t > gpos_period_start = HARDFORK_GPOS_TIME.sec_since_epoch();
|
||||
optional < uint32_t > gpos_vesting_lockin_period = GPOS_VESTING_LOCKIN_PERIOD;
|
||||
/* rbac parameters */
|
||||
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;
|
||||
};
|
||||
|
||||
struct chain_parameters
|
||||
|
|
@ -138,7 +142,16 @@ namespace graphene { namespace chain {
|
|||
}
|
||||
inline uint32_t gpos_vesting_lockin_period()const {
|
||||
return extensions.value.gpos_vesting_lockin_period.valid() ? *extensions.value.gpos_vesting_lockin_period : GPOS_VESTING_LOCKIN_PERIOD; /// GPOS vesting lockin period
|
||||
}
|
||||
}
|
||||
inline uint16_t rbac_max_permissions_per_account()const {
|
||||
return extensions.value.rbac_max_permissions_per_account.valid() ? *extensions.value.rbac_max_permissions_per_account : RBAC_MAX_PERMISSIONS_PER_ACCOUNT;
|
||||
}
|
||||
inline uint32_t rbac_max_account_authority_lifetime()const {
|
||||
return extensions.value.rbac_max_account_authority_lifetime.valid() ? *extensions.value.rbac_max_account_authority_lifetime : RBAC_MAX_ACCOUNT_AUTHORITY_LIFETIME;
|
||||
}
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
} } // graphene::chain
|
||||
|
|
@ -156,6 +169,9 @@ FC_REFLECT( graphene::chain::parameter_extension,
|
|||
(gpos_subperiod)
|
||||
(gpos_period_start)
|
||||
(gpos_vesting_lockin_period)
|
||||
(rbac_max_permissions_per_account)
|
||||
(rbac_max_account_authority_lifetime)
|
||||
(rbac_max_authorities_per_permission)
|
||||
)
|
||||
|
||||
FC_REFLECT( graphene::chain::chain_parameters,
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ namespace graphene { namespace chain {
|
|||
optional<double> revenue_split;
|
||||
|
||||
account_id_type fee_payer()const { return owner; }
|
||||
void validate() const;
|
||||
};
|
||||
|
||||
struct nft_metadata_update_operation : public base_operation
|
||||
|
|
@ -33,6 +34,7 @@ namespace graphene { namespace chain {
|
|||
optional<double> revenue_split;
|
||||
|
||||
account_id_type fee_payer()const { return owner; }
|
||||
void validate() const;
|
||||
};
|
||||
|
||||
struct nft_mint_operation : public base_operation
|
||||
|
|
@ -49,6 +51,7 @@ namespace graphene { namespace chain {
|
|||
std::string token_uri;
|
||||
|
||||
account_id_type fee_payer()const { return payer; }
|
||||
void validate() const;
|
||||
};
|
||||
|
||||
struct nft_safe_transfer_from_operation : public base_operation
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ namespace graphene { namespace chain {
|
|||
|
||||
void_result nft_metadata_create_evaluator::do_evaluate( const nft_metadata_create_operation& op )
|
||||
{ try {
|
||||
op.owner(db());
|
||||
const auto& idx_nft_md_by_name = db().get_index_type<nft_metadata_index>().indices().get<by_name>();
|
||||
FC_ASSERT( idx_nft_md_by_name.find(op.name) == idx_nft_md_by_name.end(), "NFT name already in use" );
|
||||
const auto& idx_nft_md_by_symbol = db().get_index_type<nft_metadata_index>().indices().get<by_symbol>();
|
||||
|
|
@ -33,6 +34,7 @@ object_id_type nft_metadata_create_evaluator::do_apply( const nft_metadata_creat
|
|||
|
||||
void_result nft_metadata_update_evaluator::do_evaluate( const nft_metadata_update_operation& op )
|
||||
{ try {
|
||||
op.owner(db());
|
||||
const auto& idx_nft_md = db().get_index_type<nft_metadata_index>().indices().get<by_id>();
|
||||
auto itr_nft_md = idx_nft_md.find(op.nft_metadata_id);
|
||||
FC_ASSERT( itr_nft_md != idx_nft_md.end(), "NFT metadata not found" );
|
||||
|
|
@ -65,6 +67,12 @@ void_result nft_metadata_update_evaluator::do_apply( const nft_metadata_update_o
|
|||
|
||||
void_result nft_mint_evaluator::do_evaluate( const nft_mint_operation& op )
|
||||
{ try {
|
||||
op.payer(db());
|
||||
op.owner(db());
|
||||
op.approved(db());
|
||||
for(const auto& op_iter: op.approved_operators) {
|
||||
op_iter(db());
|
||||
}
|
||||
const auto& idx_nft_md = db().get_index_type<nft_metadata_index>().indices().get<by_id>();
|
||||
auto itr_nft_md = idx_nft_md.find(op.nft_metadata_id);
|
||||
FC_ASSERT( itr_nft_md != idx_nft_md.end(), "NFT metadata not found" );
|
||||
|
|
@ -171,6 +179,7 @@ object_id_type nft_approve_evaluator::do_apply( const nft_approve_operation& op
|
|||
|
||||
void_result nft_set_approval_for_all_evaluator::do_evaluate( const nft_set_approval_for_all_operation& op )
|
||||
{ try {
|
||||
op.owner(db());
|
||||
const auto& idx_acc = db().get_index_type<account_index>().indices().get<by_id>();
|
||||
|
||||
auto itr_operator = idx_acc.find(op.operator_);
|
||||
|
|
|
|||
69
libraries/chain/nft_object.cpp
Normal file
69
libraries/chain/nft_object.cpp
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
#include <graphene/chain/protocol/nft_ops.hpp>
|
||||
#include <graphene/chain/protocol/operations.hpp>
|
||||
|
||||
namespace graphene
|
||||
{
|
||||
namespace chain
|
||||
{
|
||||
|
||||
bool is_valid_nft_token_name(const string &name)
|
||||
{
|
||||
try
|
||||
{
|
||||
const size_t len = name.size();
|
||||
// NFT_TOKEN_MIN_LENGTH <= len minimum length check
|
||||
if (len < NFT_TOKEN_MIN_LENGTH)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// len <= NFT_TOKEN_MAX_LENGTH max length check
|
||||
if (len > NFT_TOKEN_MAX_LENGTH)
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// First character should be a letter between a-z/A-Z
|
||||
if (!((name[0] >= 'a' && name[0] <= 'z') || (name[0] >= 'A' && name[0] <= 'Z')))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
// Any character should either be a small case letter a-z or a digit 0-9
|
||||
for (const auto &ch : name)
|
||||
{
|
||||
if (!((ch >= 'a' && ch <= 'z') || (ch >= 'A' && ch <= 'Z') || (ch >= '0' && ch <= '9') || (ch == ' ')))
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
FC_CAPTURE_AND_RETHROW((name))
|
||||
}
|
||||
|
||||
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
|
||||
{
|
||||
FC_ASSERT(fee.amount >= 0, "Fee must not be negative");
|
||||
if(name)
|
||||
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");
|
||||
}
|
||||
|
||||
} // namespace chain
|
||||
} // namespace graphene
|
||||
|
|
@ -27,10 +27,35 @@ BOOST_AUTO_TEST_CASE( nft_metadata_create_test ) {
|
|||
|
||||
nft_metadata_create_operation op;
|
||||
op.owner = mdowner_id;
|
||||
op.name = "NFT Test";
|
||||
op.symbol = "NFT";
|
||||
op.base_uri = "http://nft.example.com";
|
||||
|
||||
op.name = "123";
|
||||
BOOST_CHECK_THROW(op.validate(), fc::exception);
|
||||
op.name = "";
|
||||
BOOST_CHECK_THROW(op.validate(), fc::exception);
|
||||
op.name = "1ab";
|
||||
BOOST_CHECK_THROW(op.validate(), fc::exception);
|
||||
op.name = ".abc";
|
||||
BOOST_CHECK_THROW(op.validate(), fc::exception);
|
||||
op.name = "abc.";
|
||||
BOOST_CHECK_THROW(op.validate(), fc::exception);
|
||||
op.name = "ABC";
|
||||
BOOST_CHECK_NO_THROW(op.validate());
|
||||
op.name = "abcdefghijklmnopq";
|
||||
BOOST_CHECK_THROW(op.validate(), fc::exception);
|
||||
op.name = "ab";
|
||||
BOOST_CHECK_THROW(op.validate(), fc::exception);
|
||||
op.name = "***";
|
||||
BOOST_CHECK_THROW(op.validate(), fc::exception);
|
||||
op.name = "a12";
|
||||
BOOST_CHECK_NO_THROW(op.validate());
|
||||
op.name = "a1b";
|
||||
BOOST_CHECK_NO_THROW(op.validate());
|
||||
op.name = "abc";
|
||||
BOOST_CHECK_NO_THROW(op.validate());
|
||||
op.name = "abc123defg12345";
|
||||
BOOST_CHECK_NO_THROW(op.validate());
|
||||
op.name = "NFT Test";
|
||||
trx.operations.push_back(op);
|
||||
sign(trx, mdowner_private_key);
|
||||
PUSH_TX(db, trx, ~0);
|
||||
|
|
|
|||
Loading…
Reference in a new issue