Add stricter checks to NFTs

This commit is contained in:
sierra19XX 2020-07-29 05:50:49 +00:00
parent 10ecad7566
commit 82e9d92089
9 changed files with 141 additions and 4 deletions

View file

@ -123,6 +123,7 @@ add_library( graphene_chain
offer_object.cpp
offer_evaluator.cpp
nft_evaluator.cpp
nft_object.cpp
${HEADERS}
${PROTOCOL_HEADERS}

View file

@ -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))

View file

@ -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))

View file

@ -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

View file

@ -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,

View file

@ -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

View file

@ -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_);

View 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

View file

@ -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);