* private-key option update * ppy marketplace 1 - add evaluators and objects * NFT object and basic operations * ci: update .gitlab-ci.yml * ci: update .gitlab-ci.yml * NFT evaluators and basic tests, no evaluator checks * Evaluator checks in place * ppy marketplace 2 - batch sale, offer_object escrow * Database API * Wallet API * NFT metadata implemented * Fix NFT tests * Database API for NFT metadata and enumerables * ppy marketplace 4 - Add tests NFT+Marketplace * ppy marketplace 5 - Add revenue split * ppy marketplace 6 - Remove unnecessary files * ppy marketplace 7 - Add db, wallet changes and some NFT fixes * ppy marketplace 8 - Add pagination for list APIs * ci: update .gitlab-ci.yml * New DB API, list all NFTs, list NFTs by owner * Marketplace + NFT + RBAC (#368) * rbac1 - evaluators and op validators added * rbac2 - op_type hf checks * rbac3 - tx auth verify changes * Update .gitlab-ci.yml * rbac4 - basic op tests * rbac5 - clear expired and deleted permission linked auths * rbac6 - more tests * rbac7 - more tests * rbac8 - more tests * rbac9 - wallet and db api changes * rbac10 - db api changes for required signature fetch * rbac11 - add db_api tests * rbac12 - add missing code for key auths Co-authored-by: Roshan Syed <roshan.syed.rs@gmail.com> Co-authored-by: sierra19XX <15652887+sierra19XX@users.noreply.github.com> * Fix nft_get_token_uri returning empty string * Fix nft_mint_evaluator to save token_uri * Fix cli_wallet to properly pass metadata id for nft_create * ppy marketplace 9 - FC_REFLECT offer create op * Add stricter checks to NFTs * GPOS2 HF - Handle rolling period on missing blocks (#369) * Mainnet chain halt 5050 Issue (#370) * Unlisting offers, add result in offer history object * Reverting genesis.json wrong commit * Add non-transferable non-sellable properties to NFTs * Review comments - change variable names, use scoped enums * nft_metadata_update changes * NFT HF checks and op fee addition changes * NFT make revenue_split integer from double * revenue_split condition check allow zero or above * Peerplays Marketplace + NFT (#367) * ppy marketplace 1 - add evaluators and objects * NFT object and basic operations * ci: update .gitlab-ci.yml * ci: update .gitlab-ci.yml * NFT evaluators and basic tests, no evaluator checks * Evaluator checks in place * ppy marketplace 2 - batch sale, offer_object escrow * Database API * Wallet API * NFT metadata implemented * Fix NFT tests * Database API for NFT metadata and enumerables * ppy marketplace 4 - Add tests NFT+Marketplace * ppy marketplace 5 - Add revenue split * ppy marketplace 6 - Remove unnecessary files * ppy marketplace 7 - Add db, wallet changes and some NFT fixes * ppy marketplace 8 - Add pagination for list APIs * New DB API, list all NFTs, list NFTs by owner * Marketplace + NFT + RBAC (#368) * rbac1 - evaluators and op validators added * rbac2 - op_type hf checks * rbac3 - tx auth verify changes * Update .gitlab-ci.yml * rbac4 - basic op tests * rbac5 - clear expired and deleted permission linked auths * rbac6 - more tests * rbac7 - more tests * rbac8 - more tests * rbac9 - wallet and db api changes * rbac10 - db api changes for required signature fetch * rbac11 - add db_api tests * rbac12 - add missing code for key auths Co-authored-by: Roshan Syed <roshan.syed.rs@gmail.com> Co-authored-by: sierra19XX <15652887+sierra19XX@users.noreply.github.com> * Fix nft_get_token_uri returning empty string * Fix nft_mint_evaluator to save token_uri * Fix cli_wallet to properly pass metadata id for nft_create * ppy marketplace 9 - FC_REFLECT offer create op * Add stricter checks to NFTs * Unlisting offers, add result in offer history object * Reverting genesis.json wrong commit * Add non-transferable non-sellable properties to NFTs * Review comments - change variable names, use scoped enums * nft_metadata_update changes * NFT HF checks and op fee addition changes * NFT make revenue_split integer from double * revenue_split condition check allow zero or above Co-authored-by: Srdjan Obucina <obucinac@gmail.com> Co-authored-by: Roshan Syed <roshan.syed.rs@gmail.com> Co-authored-by: obucina <11353193+obucina@users.noreply.github.com> * Beatrice NFT HF Co-authored-by: pbattu123 <43043205+pbattu123@users.noreply.github.com> Co-authored-by: pbattu123 <p.battu@pbsa.info> Co-authored-by: Srdjan Obucina <obucinac@gmail.com> Co-authored-by: Roshan Syed <roshan.syed.rs@gmail.com> Co-authored-by: obucina <11353193+obucina@users.noreply.github.com>
338 lines
No EOL
14 KiB
C++
338 lines
No EOL
14 KiB
C++
#include <graphene/chain/offer_evaluator.hpp>
|
|
#include <graphene/chain/account_object.hpp>
|
|
#include <graphene/chain/offer_object.hpp>
|
|
#include <graphene/chain/nft_object.hpp>
|
|
#include <graphene/chain/exceptions.hpp>
|
|
#include <graphene/chain/hardfork.hpp>
|
|
#include <graphene/chain/is_authorized_asset.hpp>
|
|
#include <iostream>
|
|
|
|
namespace graphene
|
|
{
|
|
namespace chain
|
|
{
|
|
void_result offer_evaluator::do_evaluate(const offer_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.issuer(d);
|
|
for (const auto &item : op.item_ids)
|
|
{
|
|
const auto &nft_obj = item(d);
|
|
FC_ASSERT(!d.item_locked(item), "Item(s) is already on sale");
|
|
bool is_owner = (nft_obj.owner == op.issuer);
|
|
bool is_approved = (nft_obj.approved == op.issuer);
|
|
bool is_approved_operator = (std::find(nft_obj.approved_operators.begin(), nft_obj.approved_operators.end(), op.issuer) != nft_obj.approved_operators.end());
|
|
if (op.buying_item)
|
|
{
|
|
FC_ASSERT(!is_owner, "Buyer cannot already be an onwer of the item");
|
|
FC_ASSERT(!is_approved, "Buyer cannot already be approved account of the item");
|
|
FC_ASSERT(!is_approved_operator, "Buyer cannot already be an approved operator of the item");
|
|
}
|
|
else
|
|
{
|
|
FC_ASSERT(is_owner, "Issuer has no authority to sell 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");
|
|
}
|
|
FC_ASSERT(op.offer_expiration_date > d.head_block_time(), "Expiration should be in future");
|
|
FC_ASSERT(op.fee.amount >= 0, "Invalid fee");
|
|
FC_ASSERT(op.minimum_price.amount >= 0 && op.maximum_price.amount > 0, "Invalid amount");
|
|
FC_ASSERT(op.minimum_price.asset_id == op.maximum_price.asset_id, "Asset ID mismatch");
|
|
FC_ASSERT(op.maximum_price >= op.minimum_price, "Invalid max min prices");
|
|
return void_result();
|
|
}
|
|
FC_CAPTURE_AND_RETHROW((op))
|
|
}
|
|
|
|
object_id_type offer_evaluator::do_apply(const offer_operation &op)
|
|
{
|
|
try
|
|
{
|
|
database &d = db();
|
|
if (op.buying_item)
|
|
{
|
|
d.adjust_balance(op.issuer, -op.maximum_price);
|
|
}
|
|
|
|
const auto &offer_obj = db().create<offer_object>([&](offer_object &obj) {
|
|
obj.issuer = op.issuer;
|
|
|
|
obj.item_ids = op.item_ids;
|
|
|
|
obj.minimum_price = op.minimum_price;
|
|
obj.maximum_price = op.maximum_price;
|
|
|
|
obj.buying_item = op.buying_item;
|
|
obj.offer_expiration_date = op.offer_expiration_date;
|
|
});
|
|
return offer_obj.id;
|
|
}
|
|
FC_CAPTURE_AND_RETHROW((op))
|
|
}
|
|
|
|
void_result bid_evaluator::do_evaluate(const bid_operation &op)
|
|
{
|
|
try
|
|
{
|
|
const database &d = db();
|
|
auto now = d.head_block_time();
|
|
FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF");
|
|
const auto &offer = op.offer_id(d);
|
|
op.bidder(d);
|
|
for (const auto &item : offer.item_ids)
|
|
{
|
|
const auto &nft_obj = item(d);
|
|
bool is_owner = (nft_obj.owner == op.bidder);
|
|
bool is_approved = (nft_obj.approved == op.bidder);
|
|
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, "Bidder has no authority to sell the item");
|
|
}
|
|
else
|
|
{
|
|
FC_ASSERT(!is_owner, "Bidder cannot already be an onwer of the item");
|
|
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");
|
|
}
|
|
}
|
|
|
|
FC_ASSERT(op.bid_price.asset_id == offer.minimum_price.asset_id, "Asset type mismatch");
|
|
FC_ASSERT(offer.minimum_price.amount == 0 || op.bid_price >= offer.minimum_price);
|
|
FC_ASSERT(offer.maximum_price.amount == 0 || op.bid_price <= offer.maximum_price);
|
|
if (offer.bidder)
|
|
{
|
|
FC_ASSERT((offer.buying_item && op.bid_price < *offer.bid_price) || (!offer.buying_item && op.bid_price > *offer.bid_price), "There is already a better bid than this");
|
|
}
|
|
return void_result();
|
|
}
|
|
FC_CAPTURE_AND_RETHROW((op))
|
|
}
|
|
|
|
void_result bid_evaluator::do_apply(const bid_operation &op)
|
|
{
|
|
try
|
|
{
|
|
database &d = db();
|
|
|
|
const auto &offer = op.offer_id(d);
|
|
|
|
if (!offer.buying_item)
|
|
{
|
|
if (offer.bidder)
|
|
{
|
|
d.adjust_balance(*offer.bidder, *offer.bid_price);
|
|
}
|
|
d.adjust_balance(op.bidder, -op.bid_price);
|
|
}
|
|
d.modify(op.offer_id(d), [&](offer_object &o) {
|
|
if (op.bid_price == (offer.buying_item ? offer.minimum_price : offer.maximum_price))
|
|
{
|
|
o.offer_expiration_date = d.head_block_time();
|
|
}
|
|
o.bidder = op.bidder;
|
|
o.bid_price = op.bid_price;
|
|
});
|
|
return void_result();
|
|
}
|
|
FC_CAPTURE_AND_RETHROW((op))
|
|
}
|
|
|
|
void_result cancel_offer_evaluator::do_evaluate(const cancel_offer_operation &op)
|
|
{
|
|
try
|
|
{
|
|
const database &d = db();
|
|
auto now = d.head_block_time();
|
|
FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF");
|
|
const auto &offer = op.offer_id(d);
|
|
op.issuer(d);
|
|
FC_ASSERT(op.issuer == offer.issuer, "Only offer issuer can cancel the offer");
|
|
FC_ASSERT(offer.offer_expiration_date > d.head_block_time(), "Expiration should be in future when cancelling the offer");
|
|
return void_result();
|
|
}
|
|
FC_CAPTURE_AND_RETHROW((op))
|
|
}
|
|
|
|
void_result cancel_offer_evaluator::do_apply(const cancel_offer_operation &op)
|
|
{
|
|
try
|
|
{
|
|
database &d = db();
|
|
|
|
const auto &offer = op.offer_id(d);
|
|
if (offer.buying_item)
|
|
{
|
|
// Refund the max price to issuer
|
|
d.adjust_balance(offer.issuer, offer.maximum_price);
|
|
}
|
|
else
|
|
{
|
|
if (offer.bidder)
|
|
{
|
|
// Refund the bid price to the best bidder till now
|
|
d.adjust_balance(*offer.bidder, *offer.bid_price);
|
|
}
|
|
}
|
|
|
|
d.create<offer_history_object>([&](offer_history_object &obj) {
|
|
obj.issuer = offer.issuer;
|
|
obj.item_ids = offer.item_ids;
|
|
obj.bidder = offer.bidder;
|
|
obj.bid_price = offer.bid_price;
|
|
obj.minimum_price = offer.minimum_price;
|
|
obj.maximum_price = offer.maximum_price;
|
|
obj.buying_item = offer.buying_item;
|
|
obj.offer_expiration_date = offer.offer_expiration_date;
|
|
obj.result = result_type::Cancelled;
|
|
});
|
|
// This should unlock the item
|
|
d.remove(op.offer_id(d));
|
|
|
|
return void_result();
|
|
}
|
|
FC_CAPTURE_AND_RETHROW((op))
|
|
}
|
|
|
|
void_result finalize_offer_evaluator::do_evaluate(const finalize_offer_operation &op)
|
|
{
|
|
try
|
|
{
|
|
const database &d = db();
|
|
auto now = d.head_block_time();
|
|
FC_ASSERT(now >= HARDFORK_NFT_TIME, "Not allowed until NFT HF");
|
|
const auto &offer = op.offer_id(d);
|
|
|
|
if (op.result != result_type::ExpiredNoBid)
|
|
{
|
|
FC_ASSERT(offer.bidder, "No valid bidder");
|
|
FC_ASSERT((*offer.bid_price).amount >= 0, "Invalid bid price");
|
|
}
|
|
else
|
|
{
|
|
FC_ASSERT(!offer.bidder, "There should not be a valid bidder");
|
|
}
|
|
|
|
switch (op.result)
|
|
{
|
|
case result_type::Expired:
|
|
case result_type::ExpiredNoBid:
|
|
FC_ASSERT(offer.offer_expiration_date <= d.head_block_time(), "Offer finalized beyong expiration time");
|
|
break;
|
|
default:
|
|
FC_THROW_EXCEPTION(fc::assert_exception, "finalize_offer_operation: unknown result type.");
|
|
break;
|
|
}
|
|
return void_result();
|
|
}
|
|
FC_CAPTURE_AND_RETHROW((op))
|
|
}
|
|
|
|
void_result finalize_offer_evaluator::do_apply(const finalize_offer_operation &op)
|
|
{
|
|
try
|
|
{
|
|
database &d = db();
|
|
offer_object offer = op.offer_id(d);
|
|
// 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)
|
|
{
|
|
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 / GRAPHENE_100_PERCENT) / 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)
|
|
{
|
|
// 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
|
|
{
|
|
// Sell Offer, send the seller his amount
|
|
d.adjust_balance(offer.issuer, seller_amount);
|
|
}
|
|
// Tranfer the NFTs
|
|
for (auto item : offer.item_ids)
|
|
{
|
|
auto &nft_obj = item(d);
|
|
d.modify(nft_obj, [&offer](nft_object &obj) {
|
|
if (offer.buying_item)
|
|
{
|
|
obj.owner = offer.issuer;
|
|
}
|
|
else
|
|
{
|
|
obj.owner = *offer.bidder;
|
|
}
|
|
obj.approved = {};
|
|
obj.approved_operators.clear();
|
|
});
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (offer.buying_item)
|
|
{
|
|
d.adjust_balance(offer.issuer, offer.maximum_price);
|
|
}
|
|
}
|
|
d.create<offer_history_object>([&](offer_history_object &obj) {
|
|
obj.issuer = offer.issuer;
|
|
obj.item_ids = offer.item_ids;
|
|
obj.bidder = offer.bidder;
|
|
obj.bid_price = offer.bid_price;
|
|
obj.minimum_price = offer.minimum_price;
|
|
obj.maximum_price = offer.maximum_price;
|
|
obj.buying_item = offer.buying_item;
|
|
obj.offer_expiration_date = offer.offer_expiration_date;
|
|
obj.result = op.result;
|
|
});
|
|
// This should unlock the item
|
|
d.remove(op.offer_id(d));
|
|
return void_result();
|
|
}
|
|
FC_CAPTURE_AND_RETHROW((op))
|
|
}
|
|
} // namespace chain
|
|
} // namespace graphene
|