ppy marketplace 5 - Add revenue split

This commit is contained in:
sierra19XX 2020-07-07 18:40:58 +00:00
parent db9a35fa17
commit c0da3a2e84
7 changed files with 129 additions and 17 deletions

View file

@ -16,6 +16,8 @@ namespace graphene { namespace chain {
std::string name;
std::string symbol;
std::string base_uri;
optional<account_id_type> revenue_partner;
optional<double> revenue_split;
};
class nft_object : public abstract_object<nft_object>
@ -87,7 +89,9 @@ FC_REFLECT_DERIVED( graphene::chain::nft_metadata_object, (graphene::db::object)
(owner)
(name)
(symbol)
(base_uri) )
(base_uri)
(revenue_partner)
(revenue_split) )
FC_REFLECT_DERIVED( graphene::chain::nft_object, (graphene::db::object),
(nft_metadata_id)

View file

@ -13,6 +13,8 @@ namespace graphene { namespace chain {
std::string name;
std::string symbol;
std::string base_uri;
optional<account_id_type> revenue_partner;
optional<double> revenue_split;
account_id_type fee_payer()const { return owner; }
};
@ -27,6 +29,8 @@ namespace graphene { namespace chain {
optional<std::string> name;
optional<std::string> symbol;
optional<std::string> base_uri;
optional<account_id_type> revenue_partner;
optional<double> revenue_split;
account_id_type fee_payer()const { return owner; }
};
@ -97,8 +101,8 @@ 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) )
FC_REFLECT( graphene::chain::nft_metadata_update_operation, (fee) (owner) (nft_metadata_id) (name) (symbol) (base_uri) )
FC_REFLECT( graphene::chain::nft_metadata_create_operation, (fee) (owner) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) )
FC_REFLECT( graphene::chain::nft_metadata_update_operation, (fee) (owner) (nft_metadata_id) (name) (symbol) (base_uri) (revenue_partner) (revenue_split) )
FC_REFLECT( graphene::chain::nft_mint_operation, (fee) (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) )

View file

@ -9,6 +9,11 @@ void_result nft_metadata_create_evaluator::do_evaluate( const nft_metadata_creat
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>();
FC_ASSERT( idx_nft_md_by_symbol.find(op.symbol) == idx_nft_md_by_symbol.end(), "NFT symbol already in use" );
FC_ASSERT( (op.revenue_partner && op.revenue_split) || (!op.revenue_partner && !op.revenue_split), "NFT revenue partner info invalid" );
if(op.revenue_partner) {
(*op.revenue_partner)(db());
FC_ASSERT( *op.revenue_split >= 0.0 && *op.revenue_split <= 1.0, "Revenue split percent invalid" );
}
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
@ -19,6 +24,8 @@ object_id_type nft_metadata_create_evaluator::do_apply( const nft_metadata_creat
obj.name = op.name;
obj.symbol = op.symbol;
obj.base_uri = op.base_uri;
obj.revenue_partner = op.revenue_partner;
obj.revenue_split = op.revenue_split;
});
return new_nft_metadata_object.id;
} FC_CAPTURE_AND_RETHROW( (op) ) }
@ -30,7 +37,11 @@ void_result nft_metadata_update_evaluator::do_evaluate( const nft_metadata_updat
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" );
FC_ASSERT( itr_nft_md->owner == op.owner, "Only owner can modify NFT metadata" );
FC_ASSERT( (op.revenue_partner && op.revenue_split) || (!op.revenue_partner && !op.revenue_split), "NFT revenue partner info invalid" );
if(op.revenue_partner) {
(*op.revenue_partner)(db());
FC_ASSERT( *op.revenue_split >= 0.0 && *op.revenue_split <= 1.0, "Revenue split percent invalid" );
}
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }
@ -43,6 +54,10 @@ void_result nft_metadata_update_evaluator::do_apply( const nft_metadata_update_o
obj.symbol = *op.symbol;
if( op.base_uri.valid() )
obj.base_uri = *op.base_uri;
if( op.revenue_partner.valid() )
obj.revenue_partner = op.revenue_partner;
if( op.revenue_split.valid() )
obj.revenue_split = op.revenue_split;
});
return void_result();
} FC_CAPTURE_AND_RETHROW( (op) ) }

View file

@ -11,7 +11,6 @@ namespace graphene
{
namespace chain
{
void_result offer_evaluator::do_evaluate(const offer_operation &op)
{
try
@ -33,7 +32,7 @@ namespace graphene
}
else
{
FC_ASSERT(is_owner || is_approved || is_approved_operator, "Issuer has no authority to sell the item");
FC_ASSERT(is_owner, "Issuer has no authority to sell the item");
}
}
FC_ASSERT(op.offer_expiration_date > d.head_block_time(), "Expiration should be in future");
@ -87,7 +86,7 @@ namespace graphene
bool is_approved_operator = (std::find(nft_obj.approved_operators.begin(), nft_obj.approved_operators.end(), op.bidder) != nft_obj.approved_operators.end());
if (offer.buying_item)
{
FC_ASSERT(is_owner || is_approved || is_approved_operator, "Bidder has no authority to sell the item");
FC_ASSERT(is_owner, "Bidder has no authority to sell the item");
}
else
{
@ -165,7 +164,6 @@ namespace graphene
FC_THROW_EXCEPTION(fc::assert_exception, "finalize_offer_operation: unknown result type.");
break;
}
return void_result();
}
FC_CAPTURE_AND_RETHROW((op))
@ -176,25 +174,61 @@ namespace graphene
try
{
database &d = db();
offer_object offer = op.offer_id(d);
vector<nft_safe_transfer_from_operation> xfer_ops;
// Calculate the fees for all revenue partners of the items
auto calc_fee = [&](int64_t &tot_fees) {
map<account_id_type, asset> fee_map;
for (const auto &item : offer.item_ids)
{
const auto &nft_obj = item(d);
const auto &nft_meta_obj = nft_obj.nft_metadata_id(d);
if (nft_meta_obj.revenue_partner && *nft_meta_obj.revenue_split > 0.0)
{
const auto &rev_partner = *nft_meta_obj.revenue_partner;
const auto &rev_split = *nft_meta_obj.revenue_split;
int64_t item_fee = static_cast<int64_t>((rev_split * (*offer.bid_price).amount.value) / offer.item_ids.size());
const auto& fee_asset = asset(item_fee, (*offer.bid_price).asset_id);
auto ret_val = fee_map.insert({rev_partner, fee_asset});
if ( ret_val.second == false ) {
fee_map[rev_partner] += fee_asset;
}
tot_fees += item_fee;
}
}
return fee_map;
};
if (op.result != result_type::ExpiredNoBid)
{
int64_t tot_fees = 0;
auto &&fee_map = calc_fee(tot_fees);
// Transfer all the fee
for (const auto &fee_itr : fee_map)
{
auto &acc = fee_itr.first;
auto &acc_fee = fee_itr.second;
d.adjust_balance(acc, acc_fee);
}
// Calculate the remaining seller amount after the fee is deducted
auto &&seller_amount = *offer.bid_price - asset(tot_fees, (*offer.bid_price).asset_id);
// Buy Offer
if (offer.buying_item)
{
d.adjust_balance(*offer.bidder, *offer.bid_price);
// Send the seller his amount
d.adjust_balance(*offer.bidder, seller_amount);
if (offer.bid_price < offer.maximum_price)
{
// Send the buyer the delta
d.adjust_balance(offer.issuer, offer.maximum_price - *offer.bid_price);
}
}
else
{
d.adjust_balance(offer.issuer, *offer.bid_price);
// Sell Offer, send the seller his amount
d.adjust_balance(offer.issuer, seller_amount);
}
// Safely tranfer the NFTs with the ops
for (const auto &item : offer.item_ids)
{
const auto &nft_obj = item(d);
@ -244,7 +278,6 @@ namespace graphene
d.apply_operation(xfer_context, xfer_op);
}
}
return void_result();
}
FC_CAPTURE_AND_RETHROW((op))

View file

@ -1904,6 +1904,8 @@ class wallet_api
* @param name Name of the token group
* @param symbol Symbol of the token group
* @param base_uri Base URI for token URI
* @param revenue_partner revenue partner for this type of Token
* @param revenue_split revenue split for the sale
* @param broadcast true to broadcast transaction to the network
* @return Signed transaction transfering the funds
*/
@ -1911,6 +1913,8 @@ class wallet_api
string name,
string symbol,
string base_uri,
optional<string> revenue_partner,
optional<double> revenue_split,
bool broadcast);
/**
@ -1919,6 +1923,8 @@ class wallet_api
* @param name Name of the token group
* @param symbol Symbol of the token group
* @param base_uri Base URI for token URI
* @param revenue_partner revenue partner for this type of Token
* @param revenue_split revenue split for the sale
* @param broadcast true to broadcast transaction to the network
* @return Signed transaction transfering the funds
*/
@ -1926,6 +1932,8 @@ class wallet_api
string name,
string symbol,
string base_uri,
optional<string> revenue_partner,
optional<double> revenue_split,
bool broadcast);
/**

View file

@ -6161,6 +6161,8 @@ signed_transaction wallet_api::nft_metadata_create(string owner_account_id_or_na
string name,
string symbol,
string base_uri,
optional<string> revenue_partner,
optional<double> revenue_split,
bool broadcast)
{
account_object owner_account = my->get_account(owner_account_id_or_name);
@ -6170,6 +6172,17 @@ signed_transaction wallet_api::nft_metadata_create(string owner_account_id_or_na
op.name = name;
op.symbol = symbol;
op.base_uri = base_uri;
if( revenue_partner )
{
account_object partner_account = my->get_account(*revenue_partner);
op.revenue_partner = partner_account.id;
double rev_split = 0.0;
if( revenue_split )
{
rev_split = *revenue_split;
}
op.revenue_split = rev_split;
}
signed_transaction trx;
trx.operations.push_back(op);
@ -6183,6 +6196,8 @@ signed_transaction wallet_api::nft_metadata_update(string owner_account_id_or_na
string name,
string symbol,
string base_uri,
optional<string> revenue_partner,
optional<double> revenue_split,
bool broadcast)
{
account_object owner_account = my->get_account(owner_account_id_or_name);
@ -6192,6 +6207,17 @@ signed_transaction wallet_api::nft_metadata_update(string owner_account_id_or_na
op.name = name;
op.symbol = symbol;
op.base_uri = base_uri;
if( revenue_partner )
{
account_object partner_account = my->get_account(*revenue_partner);
op.revenue_partner = partner_account.id;
double rev_split = 0.0;
if( revenue_split )
{
rev_split = *revenue_split;
}
op.revenue_split = rev_split;
}
signed_transaction trx;
trx.operations.push_back(op);

View file

@ -33,6 +33,8 @@ BOOST_AUTO_TEST_CASE(nft_metadata_create_test)
op.name = "NFT Test";
op.symbol = "NFT";
op.base_uri = "http://nft.example.com";
op.revenue_partner = mdowner_id;
op.revenue_split = 0.1;
trx.operations.push_back(op);
sign(trx, mdowner_private_key);
@ -337,10 +339,12 @@ BOOST_AUTO_TEST_CASE(best_buy_bid_for_sell_offer)
GET_ACTOR(bob);
GET_ACTOR(charlie);
GET_ACTOR(operator1);
GET_ACTOR(mdowner);
int64_t bob_balance = get_balance(bob_id(db), asset_id_type()(db));
int64_t alice_balance = get_balance(alice_id(db), asset_id_type()(db));
int64_t charlie_balance = get_balance(charlie_id(db), asset_id_type()(db));
int64_t mdowner_balance = get_balance(mdowner_id(db), asset_id_type()(db));
const auto &offer_obj = sell_offer(db);
bid_operation bid_op;
@ -375,8 +379,11 @@ BOOST_AUTO_TEST_CASE(best_buy_bid_for_sell_offer)
auto cached_offer_obj = offer_obj;
// Generate a block and offer should be finalized with bid
generate_block();
int64_t partner_fee = 2 * static_cast<int64_t>((0.1 * (*cached_offer_obj.bid_price).amount.value)/2);
BOOST_CHECK_EQUAL(get_balance(alice_id(db), asset_id_type()(db)),
(alice_balance + cached_offer_obj.maximum_price.amount).value);
(alice_balance + cached_offer_obj.maximum_price.amount).value - partner_fee);
BOOST_CHECK_EQUAL(get_balance(mdowner_id(db), asset_id_type()(db)),
mdowner_balance + partner_fee);
const auto &oidx = db.get_index_type<offer_index>().indices().get<by_id>();
const auto &ohidx = db.get_index_type<offer_history_index>().indices().get<by_id>();
BOOST_REQUIRE(oidx.size() == 0);
@ -408,12 +415,17 @@ BOOST_AUTO_TEST_CASE(expire_with_bid_for_sell_offer_test)
INVOKE(second_buy_bid_for_sell_offer_test);
GET_ACTOR(alice);
GET_ACTOR(charlie);
GET_ACTOR(mdowner);
int64_t alice_balance = get_balance(alice_id(db), asset_id_type()(db));
int64_t mdowner_balance = get_balance(mdowner_id(db), asset_id_type()(db));
const auto &offer_obj = sell_offer(db);
auto cached_offer_obj = offer_obj;
generate_blocks(5);
int64_t partner_fee = 2 * static_cast<int64_t>((0.1 * (*cached_offer_obj.bid_price).amount.value)/2);
BOOST_CHECK_EQUAL(get_balance(mdowner_id(db), asset_id_type()(db)),
mdowner_balance + partner_fee);
BOOST_CHECK_EQUAL(get_balance(alice_id(db), asset_id_type()(db)),
(alice_balance + (*cached_offer_obj.bid_price).amount).value);
(alice_balance + (*cached_offer_obj.bid_price).amount).value - partner_fee);
const auto &oidx = db.get_index_type<offer_index>().indices().get<by_id>();
const auto &ohidx = db.get_index_type<offer_history_index>().indices().get<by_id>();
BOOST_REQUIRE(oidx.size() == 0);
@ -624,9 +636,11 @@ BOOST_AUTO_TEST_CASE(best_sell_bid_for_buy_offer)
INVOKE(second_sell_bid_for_buy_offer_test);
GET_ACTOR(alice);
GET_ACTOR(bob);
GET_ACTOR(mdowner);
int64_t bob_balance = get_balance(bob_id(db), asset_id_type()(db));
int64_t alice_balance = get_balance(alice_id(db), asset_id_type()(db));
int64_t mdowner_balance = get_balance(mdowner_id(db), asset_id_type()(db));
const auto &offer_obj = buy_offer(db);
bid_operation bid_op;
@ -658,8 +672,11 @@ BOOST_AUTO_TEST_CASE(best_sell_bid_for_buy_offer)
// Generate a block and offer should be finalized with bid
generate_block();
// Check balances
int64_t partner_fee = 2 * static_cast<int64_t>((0.1 * (*cached_offer_obj.bid_price).amount.value)/2);
BOOST_CHECK_EQUAL(get_balance(mdowner_id(db), asset_id_type()(db)),
mdowner_balance + partner_fee);
BOOST_CHECK_EQUAL(get_balance(bob_id(db), asset_id_type()(db)),
(bob_balance + exp_delta_bidder1.amount).value);
(bob_balance + exp_delta_bidder1.amount).value - partner_fee);
BOOST_CHECK_EQUAL(get_balance(alice_id(db), asset_id_type()(db)),
(alice_balance + exp_delta_bidder2.amount).value);
const auto &oidx = db.get_index_type<offer_index>().indices().get<by_id>();
@ -693,15 +710,20 @@ BOOST_AUTO_TEST_CASE(expire_with_bid_for_buy_offer_test)
INVOKE(second_sell_bid_for_buy_offer_test);
GET_ACTOR(alice);
GET_ACTOR(bob);
GET_ACTOR(mdowner);
int64_t alice_balance = get_balance(alice_id(db), asset_id_type()(db));
int64_t bob_balance = get_balance(bob_id(db), asset_id_type()(db));
int64_t mdowner_balance = get_balance(mdowner_id(db), asset_id_type()(db));
const auto &offer_obj = buy_offer(db);
auto cached_offer_obj = offer_obj;
generate_blocks(5);
int64_t partner_fee = 2 * static_cast<int64_t>((0.1 * (*cached_offer_obj.bid_price).amount.value)/2);
BOOST_CHECK_EQUAL(get_balance(mdowner_id(db), asset_id_type()(db)),
mdowner_balance + partner_fee);
BOOST_CHECK_EQUAL(get_balance(alice_id(db), asset_id_type()(db)),
(alice_balance + cached_offer_obj.maximum_price.amount - (*cached_offer_obj.bid_price).amount).value);
BOOST_CHECK_EQUAL(get_balance(bob_id(db), asset_id_type()(db)),
(bob_balance + (*cached_offer_obj.bid_price).amount).value);
(bob_balance + (*cached_offer_obj.bid_price).amount).value - partner_fee);
const auto &oidx = db.get_index_type<offer_index>().indices().get<by_id>();
const auto &ohidx = db.get_index_type<offer_history_index>().indices().get<by_id>();
BOOST_REQUIRE(oidx.size() == 0);