diff --git a/golos.publication/golos.publication.abi b/golos.publication/golos.publication.abi index 1da9fef7..5e8b712b 100644 --- a/golos.publication/golos.publication.abi +++ b/golos.publication/golos.publication.abi @@ -529,16 +529,6 @@ } ] }, - { - "name": "createacc", - "base": "", - "fields": [ - { - "name": "name", - "type": "name" - } - ] - }, { "name": "setlimit", "base": "", @@ -872,6 +862,34 @@ } ] }, + { + "name": "addpermlink", + "base": "", + "fields": [ + { "type": "mssgid", "name": "msg" }, + { "type": "mssgid", "name": "parent" }, + { "type": "uint16", "name": "level" }, + { "type": "uint32", "name": "childcount" } + ] + },{ + "name": "delpermlink", + "base": "", + "fields": [ + { "type": "mssgid", "name": "msg" } + ] + },{ + "name": "addpermlinks", + "base": "", + "fields": [ + { "type": "addpermlink[]", "name": "permlinks" } + ] + },{ + "name": "delpermlinks", + "base": "", + "fields": [ + { "type": "mssgid[]", "name": "permlinks" } + ] + }, { "name": "post_event", "base": "", @@ -964,10 +982,6 @@ "name": "closemssgs", "type": "closemssgs" }, - { - "name": "createacc", - "type": "createacc" - }, { "name": "setrules", "type": "setrules" @@ -1007,7 +1021,12 @@ { "name": "setmaxpayout", "type": "setmaxpayout" - } + }, + + { "name": "addpermlink", "type": "addpermlink" }, + { "name": "delpermlink", "type": "delpermlink" }, + { "name": "addpermlinks", "type": "addpermlinks" }, + { "name": "delpermlinks", "type": "delpermlinks" } ], "tables": [ { diff --git a/golos.publication/golos.publication.cpp b/golos.publication/golos.publication.cpp index 9d9850e6..b205c52c 100644 --- a/golos.publication/golos.publication.cpp +++ b/golos.publication/golos.publication.cpp @@ -62,6 +62,15 @@ extern "C" { execute_action(&publication::set_max_payout); if (NN(deletevotes) == action) execute_action(&publication::deletevotes); + + if (NN(addpermlink) == action) + execute_action(&publication::addpermlink); + if (NN(delpermlink) == action) + execute_action(&publication::delpermlink); + if (NN(addpermlinks) == action) + execute_action(&publication::addpermlinks); + if (NN(delpermlinks) == action) + execute_action(&publication::delpermlinks); } #undef NN } @@ -271,7 +280,7 @@ void publication::create_message( item.cashout_time = cur_time + seconds(cashout_window_param.window).count(); }); - permlink_table.emplace(message_id.author, [&]( auto &item) { + permlink_table.emplace(message_id.author, [&](auto& item) { item.id = message_pk; item.parentacc = parent_id.author; item.parent_id = parent_pk; @@ -281,6 +290,66 @@ void publication::create_message( }); } +void publication::addpermlink(structures::mssgid msg, structures::mssgid parent, uint16_t level, uint32_t childcount) { + require_auth(_self); + uint64_t parent_pk = 0; + if (parent.author) { + eosio::check(parent.permlink.size() > 0, "Parent permlink must not be empty"); + tables::permlink_table tbl(_self, parent.author.value); + auto idx = tbl.get_index<"byvalue"_n>(); + auto itr = idx.find(parent.permlink); + eosio::check(itr != idx.end(), "Parent permlink doesn't exist"); + eosio::check(itr->level + 1 == level, "Parent permlink level mismatch"); + eosio::check(itr->childcount > 0, "Parent permlink should have children"); + // Note: can try to also check (itr.childcount <= actual children), but it's hard due scope + parent_pk = itr->id; + } else { + eosio::check(msg.permlink.size() > 0, "Permlink must not be empty"); + eosio::check(msg.permlink.size() < config::max_length, "Permlink must be less than 256 symbols"); + eosio::check(validate_permlink(msg.permlink), "Permlink must only contain 0-9, a-z and _ symbols"); + eosio::check(0 == level, "Root permlink must have 0 level"); + eosio::check(parent.permlink.size() == 0, "Root permlink must have empty parent"); + } + eosio::check(is_account(msg.author), "Author account must exist"); + + tables::permlink_table tbl(_self, msg.author.value); + auto idx = tbl.get_index<"byvalue"_n>(); + auto itr = idx.find(msg.permlink); + eosio::check(itr == idx.end(), "Permlink already exists"); + + tbl.emplace(_self, [&](auto& pl) { + pl.id = tbl.available_primary_key(); + pl.parentacc = parent.author; + pl.parent_id = parent_pk; + pl.value = msg.permlink; + pl.level = level; + pl.childcount = childcount; + }); +} + +void publication::delpermlink(structures::mssgid msg) { + require_auth(_self); + tables::permlink_table tbl(_self, msg.author.value); + auto idx = tbl.get_index<"byvalue"_n>(); + auto itr = idx.find(msg.permlink); + eosio::check(itr != idx.end(), "Permlink doesn't exist"); + idx.erase(itr); +} + +void publication::addpermlinks(std::vector permlinks) { + eosio::check(permlinks.size() > 0, "`permlinks` must not be empty"); + for (const auto& p: permlinks) { + addpermlink(p.msg, p.parent, p.level, p.childcount); + } +} + +void publication::delpermlinks(std::vector permlinks) { + eosio::check(permlinks.size() > 0, "`permlinks` must not be empty"); + for (const auto& p: permlinks) { + delpermlink(p); + } +} + void publication::update_message(structures::mssgid message_id, std::string headermssg, std::string bodymssg, std::string languagemssg, std::vector tags, diff --git a/golos.publication/golos.publication.hpp b/golos.publication/golos.publication.hpp index 620767f6..c2a1ac3f 100644 --- a/golos.publication/golos.publication.hpp +++ b/golos.publication/golos.publication.hpp @@ -34,6 +34,13 @@ class publication : public contract { void calcrwrdwt(name account, int64_t mssg_id, int64_t post_charge); void paymssgrwrd(structures::mssgid message_id); void deletevotes(int64_t message_id, name author); + + [[eosio::action]] + void addpermlink(structures::mssgid msg, structures::mssgid parent, uint16_t level, uint32_t childcount); + [[eosio::action]] void delpermlink(structures::mssgid msg); + [[eosio::action]] void addpermlinks(std::vector permlinks); + [[eosio::action]] void delpermlinks(std::vector permlinks); + private: const posting_state& params(); void set_vote(name voter, const structures::mssgid &message_id, int16_t weight); diff --git a/golos.publication/objects.hpp b/golos.publication/objects.hpp index de2ce766..ae9f78bc 100644 --- a/golos.publication/objects.hpp +++ b/golos.publication/objects.hpp @@ -87,6 +87,13 @@ struct permlink { } }; +struct permlink_info { + mssgid msg; + mssgid parent; + uint16_t level; + uint32_t childcount; +}; + struct delegate_voter { delegate_voter() = default; diff --git a/tests/golos.posting_test_api.hpp b/tests/golos.posting_test_api.hpp index d460b9f4..2690bb12 100644 --- a/tests/golos.posting_test_api.hpp +++ b/tests/golos.posting_test_api.hpp @@ -181,6 +181,24 @@ struct golos_posting_api: base_contract_api { return closemssgs(_code); } + action_result add_permlink(mssgid msg, mssgid parent, uint16_t level = 0, uint32_t childcount = 0) { + return push(N(addpermlink), _code, args() + ("msg", msg) + ("parent", parent) + ("level", level) + ("childcount", childcount) + ); + } + action_result del_permlink(mssgid msg) { + return push(N(delpermlink), _code, args()("msg", msg)); + } + action_result add_permlinks(std::vector permlinks) { + return push(N(addpermlinks), _code, args()("permlinks", permlinks)); + } + action_result del_permlinks(std::vector permlinks) { + return push(N(delpermlinks), _code, args()("permlinks", permlinks)); + } + action_result init_default_params() { auto vote_changes = get_str_vote_changes(max_vote_changes); diff --git a/tests/golos.posting_unit_tests.cpp b/tests/golos.posting_unit_tests.cpp index 71dfce19..52958df4 100644 --- a/tests/golos.posting_unit_tests.cpp +++ b/tests/golos.posting_unit_tests.cpp @@ -42,8 +42,23 @@ class posting_tester : public golos_tester { vector _users; struct errors: contract_error_messages { - const string too_low_power = amsg("too low voting power"); + const string too_low_power = amsg("too low voting power"); const string too_low_weight = amsg("too low vote weight"); + + const string plnk_empty_parent = amsg("Parent permlink must not be empty"); + const string plnk_no_parent = amsg("Parent permlink doesn't exist"); + const string plnk_bad_level = amsg("Parent permlink level mismatch"); + const string plnk_bad_children = amsg("Parent permlink should have children"); + const string plnk_empty = amsg("Permlink must not be empty"); + const string plnk_too_long = amsg("Permlink must be less than 256 symbols"); + const string plnk_invalid = amsg("Permlink must only contain 0-9, a-z and _ symbols"); + const string plnk_bad_root_lvl = amsg("Root permlink must have 0 level"); + const string plnk_root_parent = amsg("Root permlink must have empty parent"); + const string plnk_no_author = amsg("Author account must exist"); + const string plnk_already_there = amsg("Permlink already exists"); + const string plnk_not_found = amsg("Permlink doesn't exist"); + const string plnk_empty_vector = amsg("`permlinks` must not be empty"); + } err; public: @@ -314,4 +329,96 @@ BOOST_FIXTURE_TEST_CASE(overcharge_test, posting_tester) try { } FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE(permlinks_internal_test, posting_tester) try { + BOOST_TEST_MESSAGE("Test internal permlink actions"); + BOOST_TEST_MESSAGE("--- create test accounts and init"); + _users.resize(2); + init(100500); + produce_block(); + + auto parent = _users[0]; + auto author = _users[1]; + auto unk = "non.existing"_n; + + BOOST_TEST_MESSAGE("--- fails on bad parameters"); + mssgid msg{parent, "test"}; + mssgid empty{}; + BOOST_CHECK_EQUAL(err.plnk_empty_parent, post.add_permlink(msg, {unk, ""})); + BOOST_CHECK_EQUAL(err.plnk_no_parent, post.add_permlink(msg, {unk, "not-created"})); + BOOST_CHECK_EQUAL(err.plnk_empty, post.add_permlink({unk, ""}, empty)); + BOOST_CHECK_EQUAL(err.plnk_too_long, post.add_permlink({unk, std::string(256, 'a')}, empty)); + BOOST_CHECK_EQUAL(err.plnk_invalid, post.add_permlink({unk, "bad+symbol"}, empty)); + BOOST_CHECK_EQUAL(err.plnk_bad_root_lvl, post.add_permlink(msg, empty, 1)); + BOOST_CHECK_EQUAL(err.plnk_bad_root_lvl, post.add_permlink(msg, empty, 10)); + BOOST_CHECK_EQUAL(err.plnk_root_parent, post.add_permlink(msg, {{}, "non-empty"})); + BOOST_CHECK_EQUAL(err.plnk_no_author, post.add_permlink({unk, "not-registered"}, empty)); + + BOOST_CHECK_EQUAL(err.plnk_empty_vector, post.add_permlinks({})); + BOOST_CHECK_EQUAL(err.plnk_empty_vector, post.del_permlinks({})); + + BOOST_TEST_MESSAGE("--- add permlink to test parent asserts"); + mssgid lastchild{author, "no-children"}; + BOOST_CHECK_EQUAL(success(), post.add_permlink(msg, empty, 0, 1)); + BOOST_CHECK_EQUAL(success(), post.add_permlink(lastchild, msg, 1)); + mssgid longpl{parent, std::string(255, 'a')}; + BOOST_CHECK_EQUAL(success(), post.add_permlink(longpl, empty)); + CHECK_MATCHING_OBJECT(post.get_permlink(msg), mvo()("value", msg.permlink)); + CHECK_MATCHING_OBJECT(post.get_permlink(lastchild), mvo()("value", lastchild.permlink)); + CHECK_MATCHING_OBJECT(post.get_permlink(longpl), mvo()("value", longpl.permlink)); + + BOOST_TEST_MESSAGE("--- fails on mismatch with parent"); + mssgid poem{author, "poem"}; + BOOST_CHECK_EQUAL(err.plnk_bad_level, post.add_permlink(poem, msg, 0)); + BOOST_CHECK_EQUAL(err.plnk_bad_level, post.add_permlink(poem, msg, 2)); + BOOST_CHECK_EQUAL(err.plnk_bad_level, post.add_permlink(poem, msg, 3)); + BOOST_CHECK_EQUAL(err.plnk_bad_level, post.add_permlink(poem, lastchild, 0)); + BOOST_CHECK_EQUAL(err.plnk_bad_level, post.add_permlink(poem, lastchild, 1)); + BOOST_CHECK_EQUAL(err.plnk_bad_level, post.add_permlink(poem, lastchild, 3)); + BOOST_CHECK_EQUAL(err.plnk_bad_level, post.add_permlink(poem, lastchild, 4)); + BOOST_CHECK_EQUAL(err.plnk_bad_children, post.add_permlink(poem, lastchild, 2)); + + BOOST_TEST_MESSAGE("--- fails if already exists or not exists"); + BOOST_CHECK_EQUAL(err.plnk_already_there, post.add_permlink(msg, empty)); + BOOST_CHECK_EQUAL(err.plnk_not_found, post.del_permlink(poem)); + + BOOST_TEST_MESSAGE("--- delete works even with parent having children"); + BOOST_CHECK_EQUAL(success(), post.del_permlink(msg)); + BOOST_CHECK(post.get_permlink(msg).is_null()); + produce_block(); // avoid "duplicate transaction" + BOOST_CHECK_EQUAL(err.plnk_not_found, post.del_permlink(msg)); + + BOOST_TEST_MESSAGE("--- test batch add/delete"); + BOOST_CHECK_EQUAL(success(), post.add_permlinks({ + {{parent, "1st"}, empty, 0, 1}, + {{author, "2nd"}, {parent, "1st"}, 1, 1}, + {{parent, "2nd-a"}, {parent, "1st"}, 1, 0}, + {{author, "3rd"}, {author, "2nd"}, 2, 1}, + {{author, "4th"}, {author, "3rd"}, 3, 0}, + })); + BOOST_CHECK_EQUAL(success(), post.add_permlinks({{{parent, "one-more"}, empty, 0, 0}})); + BOOST_CHECK_EQUAL(success(), post.del_permlinks({{author, "3rd"}})); + BOOST_CHECK_EQUAL(success(), post.del_permlinks({ + {parent, "1st"}, + {author, "2nd"}, + {parent, "2nd-a"}, + {author, "4th"}, + {parent, "one-more"} + })); + produce_block(); + BOOST_CHECK_EQUAL(err.plnk_not_found, post.del_permlinks({{parent, "1st"}})); + BOOST_CHECK_EQUAL(err.plnk_not_found, post.del_permlinks({{author, "2nd"}})); + BOOST_CHECK_EQUAL(err.plnk_not_found, post.del_permlinks({{parent, "2nd-a"}})); + BOOST_CHECK_EQUAL(err.plnk_not_found, post.del_permlinks({{author, "3rd"}})); + BOOST_CHECK_EQUAL(err.plnk_not_found, post.del_permlinks({{author, "4th"}})); + BOOST_CHECK_EQUAL(err.plnk_not_found, post.del_permlinks({{parent, "one-more"}})); + + BOOST_CHECK(post.get_permlink({parent, "1st"}).is_null()); + BOOST_CHECK(post.get_permlink({author, "2nd"}).is_null()); + BOOST_CHECK(post.get_permlink({parent, "2nd-a"}).is_null()); + BOOST_CHECK(post.get_permlink({author, "3rd"}).is_null()); + BOOST_CHECK(post.get_permlink({author, "4th"}).is_null()); + BOOST_CHECK(post.get_permlink({parent, "one-more"}).is_null()); + +} FC_LOG_AND_RETHROW() + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/golos.publication_rewards_types.hpp b/tests/golos.publication_rewards_types.hpp index f625a856..cf0b72c4 100644 --- a/tests/golos.publication_rewards_types.hpp +++ b/tests/golos.publication_rewards_types.hpp @@ -65,6 +65,13 @@ struct mssgid { } }; +struct permlink_info { + mssgid msg; + mssgid parent; + uint16_t level; + uint32_t childcount; +}; + struct aprox_val_t { double val; @@ -418,3 +425,4 @@ struct state { FC_REFLECT(eosio::testing::beneficiary, (account)(weight)) FC_REFLECT(eosio::testing::mssgid, (author)(permlink)) +FC_REFLECT(eosio::testing::permlink_info, (msg)(parent)(level)(childcount))