diff --git a/README.md b/README.md index f459f12f..111c42a3 100644 --- a/README.md +++ b/README.md @@ -36,8 +36,10 @@ After build: * The contracts are built into a _bin/\_ folder in their respective directories. * Finally, simply use __cleos__ to _set contract_ by pointing to the previously mentioned directory. -Misc: -* Run a single test: `make compile && ./build/tests/unit_test --log_level=all --run_test=eosio_system_tests/claim_once_a_day_during_3_years` +Run interactive tests: +1. Start the interactive shell: `make dev-docker-start` +1. Run all the tests: `make test` +1. Run a single test: `make compile && ./build/tests/unit_test --log_level=all --run_test=eosio_wps_tests/committee_reg_edit_rmv` ### License [MIT](https://github.com/worldwide-asset-exchange/wax-eos-contracts/blob/master/LICENSE) diff --git a/contracts/eosio.system/CMakeLists.txt b/contracts/eosio.system/CMakeLists.txt index 7ee1cdb8..2d149632 100755 --- a/contracts/eosio.system/CMakeLists.txt +++ b/contracts/eosio.system/CMakeLists.txt @@ -5,6 +5,7 @@ add_contract(eosio.system eosio.system ${CMAKE_CURRENT_SOURCE_DIR}/src/native.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/producer_pay.cpp ${CMAKE_CURRENT_SOURCE_DIR}/src/voting.cpp + ${CMAKE_CURRENT_SOURCE_DIR}/src/wps.cpp ) target_include_directories(eosio.system diff --git a/contracts/eosio.system/README.md b/contracts/eosio.system/README.md index 9e8c0fa6..8b072dcc 100755 --- a/contracts/eosio.system/README.md +++ b/contracts/eosio.system/README.md @@ -6,9 +6,10 @@ This contract provides multiple functionalities: - Producers register in order to be voted for, and can claim per-block and per-vote rewards. - Users can buy and sell RAM at a market-determined price. - Users can bid on premium names. +- The WAX Worker Proposal System to vote for and fund projects beneficial to the overall ecosystem - A resource exchange system (REX) allows token holders to lend their tokens, and users to rent CPU and Network resources in return for a market-determined fee. -Actions: +# Actions: The naming convention is codeaccount::actionname followed by a list of paramters. ## eosio::regproducer producer producer_key url location @@ -53,133 +54,174 @@ The naming convention is codeaccount::actionname followed by a list of paramters ## eosio::claimrewards producer - **producer** producer account claiming per-block and per-vote rewards - -## eosio::deposit owner amount - - Deposits tokens to user REX fund - - **owner** REX fund owner account - - **amount** amount of tokens to be deposited - - An inline transfer from 'owner' liquid balance is executed. - - All REX-related costs and proceeds are deducted from and added to 'owner' REX fund, with one exception being buying REX using staked tokens. - - Storage change is billed to 'owner'. - -## eosio::withdraw owner amount - - Withdraws tokens from user REX fund - - **owner** REX fund owner account - - **amount** amount of tokens to be withdrawn - - An inline transfer to 'owner' liquid balance is executed. - -## eosio::buyrex from amount - - Buys REX in exchange for tokens taken out of user REX fund - - **from** owner account name - - **amount** amount of tokens to be used for purchase - - 'amount' tokens are taken out of 'from' REX fund. - - User must vote for at least 21 producers or delegate vote to proxy before buying REX. - - Tokens used in purchase are added to user's voting power. - - Bought REX cannot be sold before 4 days counting from end of day of purchase. - - Storage change is billed to 'from' account. - - By buying REX, user is lending tokens in order to be rented as CPU or NET resourses. - -## eosio::unstaketorex owner receiver from\_net from\_cpu - - Buys REX using staked tokens - - **owner** owner of staked tokens - - **receiver** account name that tokens have previously been staked to - - **from_net** amount of tokens to be unstaked from NET bandwidth and used for REX purchase - - **from_cpu** amount of tokens to be unstaked from CPU bandwidth and used for REX purchase - - User must vote for at least 21 producers or delegate vote to proxy before buying REX. - - Tokens used in purchase are added to user's voting power. - - Bought REX cannot be sold before 4 days counting from end of day of purchase. - - Storage change is billed to 'owner' account. - -## eosio::sellrex from rex - - Sells REX in exchange for core tokens - - **from** owner account of REX - - **rex** amount of REX to be sold - - Proceeds are deducted from user's voting power. - - If cannot be processed immediately, sell order is added to a queue and will be processed within 30 days at most. - - In case sell order is queued, storage change is billed to 'from' account. - -## eosio::cnclrexorder owner - - Cancels unfilled REX sell order by owner if one exists. - - **owner** owner account name - -## eosio::mvtosavings owner rex - - Moves REX to owner's REX savings bucket - - REX held in savings bucket does not mature and cannot be sold directly - - REX is moved out from the owner's maturity buckets as necessary starting with the bucket with furthest maturity date - - **owner** owner account of REX - - **rex** amount of REX to be moved to savings bucket - -## eosio::mvfrsavings owner rex - - Moves REX from owner's savings bucket to a bucket with a maturity date that is 4 days after the end of the day - - This action is required if the owner wants to sell REX held in savings bucket - - **owner** owner account of REX - - **rex** amount of REX to be moved from savings bucket - -## eosio::rentcpu from receiver loan\_payment loan\_fund - - Rents CPU resources for 30 days in exchange for market-determined price - - **from** account creating and paying for CPU loan - - **receiver** account receiving rented CPU resources - - **loan_payment** tokens paid for the loan - - **loan_fund** additional tokens (can be zero) added to loan fund and used later for loan renewal - - Rents as many core tokens as determined by market price and stakes them for CPU bandwidth for the benefit of `receiver` account. - - `loan_payment` is used for renting, it has to be greater than zero. Amount of rented resources is calculated from `loan_payment`. - - After 30 days the rented core delegation of CPU will expire or be renewed at new market price depending on available loan fund. - - `loan_fund` can be zero, and is added to loan balance. Loan balance represents a reserve that is used at expiration for automatic loan renewal. - - 'from' account can add tokens to loan balance using action `fundcpuloan` and withdraw from loan balance using `defcpuloan`. - - At expiration, if balance is greater than or equal to `loan_payment`, `loan_payment` is taken out of loan balance and used to renew the loan. Otherwise, the loan is closed and user is refunded any remaining balance. - -## eosio::rentnet from receiver loan\_payment loan\_fund - - Rents Network resources for 30 days in exchange for market-determined price - - **from** account creating and paying for Network loan - - **receiver** account receiving rented Network resources - - **loan_payment** tokens paid for the loan - - **loan_fund** additional tokens (can be zero) added to loan fund and used later for loan renewal - - Rents as many core tokens as determined by market price and stakes them for Network bandwidth for the benefit of `receiver` account. - - `loan_payment` is used for renting, it has to be greater than zero. Amount of rented resources is calculated from `loan_payment`. - - After 30 days the rented core delegation of Network will expire or be renewed at new market price depending on available loan fund. - - `loan_fund` can be zero, and is added to loan balance. Loan balance represents a reserve that is used at expiration for automatic loan renewal. - - 'from' account can add tokens to loan balance using action `fundnetloan` and withdraw from loan balance using `defnetloan`. - - At expiration, if balance is greater than or equal to `loan_payment`, `loan_payment` is taken out of loan balance and used to renew the loan. Otherwise, the loan is closed and user is refunded any remaining balance. - -## eosio::fundcpuloan from loan\_num payment - - Transfers tokens from REX fund to the fund of a specific CPU loan in order to be used for loan renewal at expiry - - **from** loan creator account - - **loan_num** loan id - - **payment** tokens transfered from REX fund to loan fund - -## eosio::fundnetloan from loan\_num payment - - Transfers tokens from REX fund to the fund of a specific Network loan in order to be used for loan renewal at expiry - - **from** loan creator account - - **loan_num** loan id - - **payment** tokens transfered from REX fund to loan fund - -## eosio::defcpuloan from loan\_num amount - - Withdraws tokens from the fund of a specific CPU loan and adds them to REX fund - - **from** loan creator account - - **loan_num** loan id - - **amount** tokens transfered from CPU loan fund to REX fund - -## eosio::defcpuloan from loan\_num amount - - Withdraws tokens from the fund of a specific CPU loan and adds them to REX fund - - **from** loan creator account - - **loan_num** loan id - - **amount** tokens transfered from NET loan fund to REX fund - -## eosio::updaterex owner - - Updates REX owner vote weight to current value of held REX - - **owner** REX owner account - -## eosio::rexexec user max - - Performs REX maintenance by processing a specified number of REX sell orders and expired loans - - **user** any account can execute this action - - **max** number of each of CPU loans, NET loans, and sell orders to be processed - -## eosio::consolidate owner - - Consolidates REX maturity buckets into one bucket that cannot be sold before 4 days - - **owner** REX owner account name - -## eosio::closerex owner - - Deletes unused REX-related database entries and frees occupied RAM - - **owner** user account name - - If owner has a non-zero REX balance, the action fails; otherwise, owner REX balance entry is deleted. - - If owner has no outstanding loans and a zero REX fund balance, REX fund entry is deleted. + +## eosio::setwpsenv + +Required authority: `_self` + +Description: Sets up the global WPS parameters, which includes vote participation required (in percentage of `total_activated_stake`), expiry time for proposals on vote, and maximum duration of a project. The default values proposed when the WPS is ratified will be 5, 30, and 180, respectively. + +## eosio::regproposer + +Required authority: Account owner + +Description: Register an account as a proposer. All fields required. RAM is billed to the registrant's account. Account is added to the proposers table. + +## eosio::editproposer + +Required authority: Account owner + +Description: Edit proposer info. All fields required. + +## eosio::rmvproposer + +Required authority: Account owner + +Description: Remove account from the proposers table. + +## eosio::regproposal + +Required authority: Proposer + + +Description: Register a proposal. Account must be on the proposers table. All fields required. RAM is billed to the proposer's account. Proposal is added to the proposals table. One proposer can register only one proposal at a time. + +## eosio::editproposal + +Required authority: Proposer + + +Description: Edit proposal info. All fields required. + +## eosio::rmvproposal + +Required authority: Proposer + +Description: Delete proposal from the proposals table. + +## eosio::regcommittee + +Required authority: `_self` + +Description: Register a committee responsible for a certain category. The account is added to the committees table. RAM is billed to the contract's account. All fields are required. Oversight power is given to the oversight committee. Committees can only be registered using `eosio` permissions. + +## eosio::edcommittee + +Required authority: `_self` + +Description: Edit committee information. All fields required. + +## eosio::rmvcommittee + +Required authority: `_self` + +Description: Remove committee from the committees table. + +## eosio::regreviewer + +Required authority: Committee + +Description: Register account as a reviewer. All fields required. RAM billed to committee account. Reviewer is added to reviewers table, with the committee that the account is associated with. + +## eosio::editreviewer + +Required authority: Committee + +Description: Edit reviewer information. All fields required. + +## eosio::rmvreviewer + +Required authority: Committee + +Description: Remove reviewer from the reviewers table. + +## eosio::acceptprop + +Required authority: Reviewer + +Description: Accept a proposal with PENDING status. Change its status to ON VOTE. All fields required. + +## eosio::rejectprop + +Required authority: Reviewer + +Description: Reject a proposal with PENDING status. Change its status to REJECTED. Move proposal to the rejected proposals table. All fields required. + +## eosio::approve + +Required authority: Reviewer + +Description: Approve funding for proposals with the CHECKED VOTES status. Proposal status changes to APPROVED. All fields required. + +## eosio::claimfunds + +Required authority: Proposer + +Description: Claim funding for a proposal with the APPROVED status. The proposer can claim a portion of the funds for each iteration of the project's duration. When all iterations have been completed, the proposal status changes to COMPLETED. It is then transferred to the completed proposals table. All fields required. + +## eosio::rmvreject + +Required authority: Reviewer + +Description: Clear a proposal on the rejected proposals table when it is no longer needed there. + +## eosio::rmvcompleted + +Required authority: Reviewer + +Description: Clear a proposal on the completed proposals table when it is no longer needed there. + +## eosio::voteproposal + +Required authority: Account owner + +Description: Vote for a proposal. Each account is limited to one vote. Vote weight is determined by the amount of WAX staked. Voting for another proposal will take away the votes of an earlier proposal. All fields required. + +## eosio::rejectfund + +Required authority: Committee (oversight) + +Description: Reject a proposal with APPROVED status being funded. The proposal is transferred to the rejected proposals table. All fiels required. + +# Tables + +You can find information on the tables directly using `cleos`: +```console +cleos get table eosio eosio +``` + +## proposals + +Description: Table of ongoing proposals. Indexed by proposer account name and proposal id. + +Code: `_self` + +Scope: `_self` + +## proposers + +Description: Table of proposers. Indexed by account name. + +Code: `_self` + +Scope: `_self` + +## reviewers + +Description: Table of reviewers. Indexed by account name. + +Code: `_self` + +Scope: `_self` + +## committees + +Description: Table of committees. Indexed by account name. + +Code: `_self` + +Scope: `_self` + +## wpsglobal + +Description: Table of WPS global environment variables. diff --git a/contracts/eosio.system/include/eosio.system/eosio.system.hpp b/contracts/eosio.system/include/eosio.system/eosio.system.hpp index 733e7f91..79c0fcdd 100755 --- a/contracts/eosio.system/include/eosio.system/eosio.system.hpp +++ b/contracts/eosio.system/include/eosio.system/eosio.system.hpp @@ -30,6 +30,9 @@ namespace eosiosystem { using eosio::time_point; using eosio::time_point_sec; using eosio::unsigned_int; + using std::string; + using std::vector; + using std::set; template static inline auto has_field( F flags, E field ) @@ -68,6 +71,7 @@ namespace eosiosystem { static const time_point gbm_initial_time(eosio::seconds(1561939200)); // July 1st 2019 00:00:00 static const time_point gbm_final_time = gbm_initial_time + eosio::microseconds(useconds_in_gbm_period); // July 1st 2022 00:00:00 + //static const uint32_t seconds_per_day = 60 * 60 * 24; /** * @@ -284,6 +288,133 @@ namespace eosiosystem { (last_claim_time)(last_vote_weight)(proxied_vote_weight)(is_proxy)(flags1)(reserved2)(reserved3) ) }; + struct [[eosio::table, eosio::contract("eosio.system")]] wps_voter { + name owner; + std::vector proposals; /// the proposals approved by this voter if no proxy is set + double last_vote_weight = 0; + + uint64_t primary_key()const { return owner.value; } + + EOSLIB_SERIALIZE( wps_voter, (owner)(proposals)(last_vote_weight)) + }; + + + struct [[eosio::table, eosio::contract("eosio.system")]] proposer { + name account; + string first_name; + string last_name; + string img_url; + string bio; + string country; + string telegram; + string website; + string linkedin; + time_point_sec last_claim_time; + uint64_t primary_key() const { return account.value; } + EOSLIB_SERIALIZE( proposer, (account)(first_name)(last_name)(img_url)(bio)(country)(telegram)(website)(linkedin)(last_claim_time) ) + }; + + struct PROPOSAL_STATUS { + const static uint8_t PENDING = 1; + const static uint8_t REJECTED = 2; + const static uint8_t ON_VOTE = 3; + const static uint8_t FINISHED_VOTING = 4; + const static uint8_t APPROVED = 5; // approve + const static uint8_t COMPLETED = 6; + }; + + struct [[eosio::table, eosio::contract("eosio.system")]] proposal { + name proposer; // proposer + uint64_t id; + name committee; // committee + string category; // category + uint16_t subcategory; // subcategory + string title; // title + string summary; // summary + string project_img_url; // project image or video url + string description; // overview + string roadmap; // roadmap + uint64_t duration; // duration + vector members; // linkedin + asset funding_goal; // amount of EOS + double total_votes; // total votes + uint8_t status; // status + time_point_sec vote_start_time; // time when voting starts (seconds) + time_point_sec fund_start_time; // time when funding starts (seconds) + uint32_t iteration_of_funding; // current number of iterations + uint32_t total_iterations; // total number of iterations + uint64_t primary_key() const { return proposer.value; } + uint64_t by_id() const { return id; } + double by_votes()const { return total_votes; } + EOSLIB_SERIALIZE( proposal, (proposer)(id)(committee)(category)(subcategory)(title)(summary)(project_img_url)(description)(roadmap)(duration)(members)(funding_goal) + (total_votes)(status)(vote_start_time)(fund_start_time)(iteration_of_funding)(total_iterations) ) + }; + + struct [[eosio::table, eosio::contract("eosio.system")]] committee { + name committeeman; + string category; + bool is_oversight; + uint64_t primary_key() const { return committeeman.value; } + EOSLIB_SERIALIZE( committee, (committeeman)(category)(is_oversight) ); + }; + + struct [[eosio::table, eosio::contract("eosio.system")]] reviewer { + name account; + name committee; + string first_name; + string last_name; + uint64_t primary_key() const { return account.value; } + EOSLIB_SERIALIZE( reviewer, (account)(committee)(first_name)(last_name) ) + }; + + struct [[eosio::table("wpsglobal"), eosio::contract("eosio.system")]] wpsenv { + uint32_t total_voting_percent = 5; // 5% + uint32_t duration_of_voting = 30; // voting duration (days) + uint32_t max_duration_of_funding = 500; // funding duration (days) + uint32_t total_iteration_of_funding = 6; // + uint64_t primary_key() const { return 0; } + EOSLIB_SERIALIZE( wpsenv, (total_voting_percent)(duration_of_voting)(max_duration_of_funding)(total_iteration_of_funding) ) + }; + + /** + * Proposers table + * + * @details The proposers table stores all WPS proposers' information + */ + typedef eosio::multi_index<"proposers"_n, proposer> proposer_table; + + + /** + * Proposals table + * + * @details The proposals table stores all WPS proposal items + */ + typedef eosio::multi_index< "proposals"_n, proposal, + indexed_by< "idx"_n, const_mem_fun >, + indexed_by<"prototalvote"_n, const_mem_fun > + > proposal_table; + + /** + * Committees table + * + * @details The committees table stores all WPS committee accounts' information + */ + typedef eosio::multi_index< "committees"_n, committee> committee_table; + + /** + * Reviewers table + * + * @details The reviewers table stores all WPS reviewer accounts' information + */ + typedef eosio::multi_index< "reviewers"_n, reviewer> reviewer_table; + + /** + * WPS environment singleton + * + * @details The WPS environment singleton holds configurable variables for the system + */ + typedef eosio::singleton< "wpsglobal"_n, wpsenv > wps_env_singleton; + /** * Voters table * @@ -291,6 +422,12 @@ namespace eosiosystem { */ typedef eosio::multi_index< "voters"_n, voter_info > voters_table; + /** + * WPS voters table + * + * @details The WPS voters table stores all the `wps_voter`s instances, all WPS voters information. + */ + typedef eosio::multi_index< "wpsvoters"_n, wps_voter > wps_voters_table; /** * Defines producer info table added in version 1.0 @@ -401,6 +538,7 @@ namespace eosiosystem { private: voters_table _voters; + wps_voters_table _wpsvoters; producers_table _producers; producers_table2 _producers2; global_state_singleton _global; @@ -410,6 +548,10 @@ namespace eosiosystem { eosio_global_state2 _gstate2; eosio_global_state3 _gstate3; rammarket _rammarket; + proposer_table _proposers; + proposal_table _proposals; + committee_table _committees; + reviewer_table _reviewers; public: static constexpr eosio::name active_permission{"active"_n}; @@ -875,6 +1017,99 @@ namespace eosiosystem { [[eosio::action]] void bidrefund( const name& bidder, const name& newname ); + [[eosio::action]] + void regproposer(name account, const string& first_name, const string& last_name, + const string& img_url, const string& bio, const string& country, const string& telegram, + const string& website, const string& linkedin); + + [[eosio::action]] + void editproposer(name account, const string& first_name, const string& last_name, + const string& img_url, const string& bio, const string& country, const string& telegram, + const string& website, const string& linkedin); + + [[eosio::action]] + void rmvproposer(name account); + + [[eosio::action]] + void claimfunds(name account); + + [[eosio::action]] + void regproposal( + name proposer, + name committee, + uint16_t subcategory, + const string& title, + const string& summary, + const string& project_img_url, + const string& description, + const string& roadmap, + uint64_t duration, + const vector& members, + const asset& funding_goal, + uint32_t total_iterations + ); + + [[eosio::action]] + void editproposal( + name proposer, + name committee, + uint16_t subcategory, + const string& title, + const string& summary, + const string& project_img_url, + const string& description, + const string& roadmap, + uint64_t duration, + const vector& members, + const asset& funding_goal, + uint32_t total_iterations + ); + + [[eosio::action]] + void rmvproposal(name proposer); + + [[eosio::action]] + void regreviewer(name committee, name reviewer, const string& first_name, const string& last_name); + + [[eosio::action]] + void editreviewer(name committee, name reviewer, const string& first_name, const string& last_name); + + [[eosio::action]] + void rmvreviewer(name committee, name reviewer); + + [[eosio::action]] + void acceptprop(name reviewer, name proposer); + + [[eosio::action]] + void rejectprop(name reviewer, name proposer, const string& reason); + + [[eosio::action]] + void approve(name reviewer, name proposer); + + [[eosio::action]] + void rmvreject(name reviewer, name proposer); + + [[eosio::action]] + void rmvcompleted(name reviewer, name proposer); + + [[eosio::action]] + void setwpsenv(uint32_t total_voting_percent, uint32_t duration_of_voting, uint32_t max_duration_of_funding, uint32_t total_iteration_of_funding); + + [[eosio::action]] + void regcommittee(name committeeman, const string& category, bool is_oversight); + + [[eosio::action]] + void edcommittee(name committeeman, const string& category, bool is_oversight); + + [[eosio::action]] + void rmvcommittee(name committeeman); + + [[eosio::action]] + void rejectfund(name committeeman, name proposer, const string& reason); + + [[eosio::action]] + void voteproposal(const name& voter_name, const std::vector& proposals); + using init_action = eosio::action_wrapper<"init"_n, &system_contract::init>; using setacctram_action = eosio::action_wrapper<"setacctram"_n, &system_contract::setacctram>; using setacctnet_action = eosio::action_wrapper<"setacctnet"_n, &system_contract::setacctnet>; @@ -904,6 +1139,27 @@ namespace eosiosystem { using setpriv_action = eosio::action_wrapper<"setpriv"_n, &system_contract::setpriv>; using setalimits_action = eosio::action_wrapper<"setalimits"_n, &system_contract::setalimits>; using setparams_action = eosio::action_wrapper<"setparams"_n, &system_contract::setparams>; + using regproposer_action = eosio::action_wrapper<"regproposer"_n, &system_contract::regproposer>; + using editproposer_action = eosio::action_wrapper<"editproposer"_n, &system_contract::editproposer>; + using rmvproposer_action = eosio::action_wrapper<"rmvproposer"_n, &system_contract::rmvproposer>; + using regproposal_action = eosio::action_wrapper<"regproposal"_n, &system_contract::regproposal>; + using editproposal_action = eosio::action_wrapper<"editproposal"_n, &system_contract::editproposal>; + using rmvproposal_action = eosio::action_wrapper<"rmvproposal"_n, &system_contract::rmvproposal>; + using claimfunds_action = eosio::action_wrapper<"claimfunds"_n, &system_contract::claimfunds>; + using regreviewer_action = eosio::action_wrapper<"regreviewer"_n, &system_contract::regreviewer>; + using editreviewer_action = eosio::action_wrapper<"editreviewer"_n, &system_contract::editreviewer>; + using rmvreviewer_action = eosio::action_wrapper<"rmvreviewer"_n, &system_contract::rmvreviewer>; + using acceptprop_action = eosio::action_wrapper<"acceptprop"_n, &system_contract::acceptprop>; + using rejectprop_action = eosio::action_wrapper<"rejectprop"_n, &system_contract::rejectprop>; + using approve_action = eosio::action_wrapper<"approve"_n, &system_contract::approve>; + using regcommittee_action = eosio::action_wrapper<"regcommittee"_n, &system_contract::regcommittee>; + using edcommittee_action = eosio::action_wrapper<"edcommittee"_n, &system_contract::edcommittee>; + using rmvcommittee_action = eosio::action_wrapper<"rmvcommittee"_n, &system_contract::rmvcommittee>; + using rmvreject_action = eosio::action_wrapper<"rmvreject"_n, &system_contract::rmvreject>; + using rmvcompleted_action = eosio::action_wrapper<"rmvcompleted"_n, &system_contract::rmvcompleted>; + using setwpsenv_action = eosio::action_wrapper<"setwpsenv"_n, &system_contract::setwpsenv>; + using rejectfund_action = eosio::action_wrapper<"rejectfund"_n, &system_contract::rejectfund>; + using voteproposal_action = eosio::action_wrapper<"voteproposal"_n, &system_contract::voteproposal>; private: // WAX specifics @@ -927,6 +1183,9 @@ namespace eosiosystem { symbol core_symbol()const; void update_ram_supply(); + //defined in wps.cpp + void update_wps_votes( const name& voter, const std::vector& proposals); + // defined in delegate_bandwidth.cpp void changebw( name from, const name& receiver, const asset& stake_net_quantity, const asset& stake_cpu_quantity, bool transfer ); diff --git a/contracts/eosio.system/src/delegate_bandwidth.cpp b/contracts/eosio.system/src/delegate_bandwidth.cpp index 6b9cf937..20106470 100755 --- a/contracts/eosio.system/src/delegate_bandwidth.cpp +++ b/contracts/eosio.system/src/delegate_bandwidth.cpp @@ -437,6 +437,11 @@ namespace eosiosystem { if( voter_itr->producers.size() || voter_itr->proxy ) { update_votes( voter, voter_itr->proxy, voter_itr->producers, false ); } + + auto wps_voter_itr = _wpsvoters.find( voter.value ); + if(wps_voter_itr != _wpsvoters.end()){ + update_wps_votes( voter, wps_voter_itr->proposals); + } } void system_contract::send_genesis_token( name from, name receiver, const asset tokens, bool add_backward_rewards ){ diff --git a/contracts/eosio.system/src/eosio.system.cpp b/contracts/eosio.system/src/eosio.system.cpp index 84bbd122..a05d06a3 100755 --- a/contracts/eosio.system/src/eosio.system.cpp +++ b/contracts/eosio.system/src/eosio.system.cpp @@ -12,12 +12,17 @@ namespace eosiosystem { system_contract::system_contract( name s, name code, datastream ds ) :native(s,code,ds), _voters(get_self(), get_self().value), + _wpsvoters(get_self(), get_self().value), _producers(get_self(), get_self().value), _producers2(get_self(), get_self().value), _global(get_self(), get_self().value), _global2(get_self(), get_self().value), _global3(get_self(), get_self().value), - _rammarket(get_self(), get_self().value) + _rammarket(get_self(), get_self().value), + _proposers(get_self(), get_self().value), + _proposals(get_self(), get_self().value), + _committees(get_self(), get_self().value), + _reviewers(get_self(), get_self().value) { //print( "construct system\n" ); _gstate = _global.exists() ? _global.get() : get_default_parameters(); diff --git a/contracts/eosio.system/src/wps.cpp b/contracts/eosio.system/src/wps.cpp new file mode 100644 index 00000000..bb2c5d00 --- /dev/null +++ b/contracts/eosio.system/src/wps.cpp @@ -0,0 +1,764 @@ +#include +#include +#include +#include +#include +#include +#include + +#include +#include + +#include + +namespace eosiosystem { + + using eosio::const_mem_fun; + using eosio::current_time_point; + using eosio::indexed_by; + using eosio::singleton; + using std::string; + using std::vector; + using std::set; + + void system_contract::regproposer( + name account, + const string& first_name, + const string& last_name, + const string& img_url, + const string& bio, + const string& country, + const string& telegram, + const string& website, + const string& linkedin) { + + // authority of the user's account is required + require_auth(account); + + //verify that the inputs are not too short + check(first_name.size() > 0, "first name should be more than 0 characters long"); + check(last_name.size() > 0, "last name should be more than 0 characters long"); + check(img_url.size() > 0, "not a valid image URL"); + check(bio.size() > 0, "bio should be more than 0 characters long"); + check(country.size() > 0, "country name should be more than 0 characters long"); + check(telegram.size() > 4, "not a valid Telegram username"); + check(website.size() > 0, "not a valid website URL"); + check(linkedin.size() > 0, "not a valid linkedin URL"); + + //verify that the inputs aren't too long + check(first_name.size() < 128, "first name should be shorter than 128 characters."); + check(last_name.size() < 128, "last name should be shorter than 128 characters."); + check(img_url.size() < 128, "image URL should be shorter than 128 characters."); + check(bio.size() < 256, "description should be shorter than 256 characters."); + check(country.size() < 64, "country name should be shorter than 64 characters."); + check(telegram.size() < 64, "telegram username should be shorter than 64 characters."); + check(website.size() < 128, "website URL should be shorter than 128 characters."); + check(linkedin.size() < 128, "linked URL should be shorter than 128 characters."); + + auto itr = _proposers.find(account.value); + // verify that the account doesn't already exist in the table + check(itr == _proposers.end(), "This account has already been registered as a proposer"); + + // add to the table + // storage is billed to the contract account + _proposers.emplace(account, [&](auto& proposer){ + proposer.account = account; + proposer.first_name = first_name; + proposer.last_name = last_name; + proposer.img_url = img_url; + proposer.bio = bio; + proposer.country = country; + proposer.telegram = telegram; + proposer.website = website; + proposer.linkedin = linkedin; + proposer.last_claim_time = time_point_sec(); + }); + } + + void system_contract::editproposer(name account, + const string& first_name, + const string& last_name, + const string& img_url, + const string& bio, + const string& country, + const string& telegram, + const string& website, + const string& linkedin) { + // authority of the user's account is required + require_auth(account); + + //verify that the inputs are not too short + check(first_name.size() > 0, "first name should be more than 0 characters long"); + check(last_name.size() > 0, "last name should be more than 0 characters long"); + check(img_url.size() > 0, "not a valid image URL"); + check(bio.size() > 0, "bio should be more than 0 characters long"); + check(country.size() > 0, "country name should be more than 0 characters long"); + check(telegram.size() > 4, "not a valid Telegram username"); + check(website.size() > 0, "not a valid website URL"); + check(linkedin.size() > 0, "not a valid linkedin URL"); + + //verify that the inputs aren't too long + check(first_name.size() < 128, "first name should be shorter than 128 characters."); + check(last_name.size() < 128, "last name should be shorter than 128 characters."); + check(img_url.size() < 128, "image URL should be shorter than 128 characters."); + check(bio.size() < 256, "description should be shorter than 256 characters."); + check(country.size() < 64, "country name should be shorter than 64 characters."); + check(telegram.size() < 64, "telegram username should be shorter than 64 characters."); + check(website.size() < 128, "website URL should be shorter than 128 characters."); + check(linkedin.size() < 128, "linked URL should be shorter than 128 characters."); + + auto itr = _proposers.find(account.value); + // verify that the account doesn't already exist in the table + check(itr != _proposers.end(), "Account not found in proposer table"); + + // modify value in the table + _proposers.modify(itr, same_payer, [&](auto& proposer) { + proposer.account = account; + proposer.first_name = first_name; + proposer.last_name = last_name; + proposer.img_url = img_url; + proposer.bio = bio; + proposer.country = country; + proposer.telegram = telegram; + proposer.website = website; + proposer.linkedin = linkedin; + }); + } + + void system_contract::rmvproposer(name account) { + // needs authority of the proposers's account + require_auth(account); + + // verify that the account already exists in the proposer table + auto itr = _proposers.find(account.value); + check(itr != _proposers.end(), "Account not found in proposer table"); + + _proposers.erase(itr); + } + + void system_contract::claimfunds(name account){ + // needs authority of the proposer account + require_auth(account); + + // verify that the account already exists in the proposer table + auto itr = _proposers.find(account.value); + check(itr != _proposers.end(), "Account not found in proposer table"); + + //auto idx_index = _proposals.get_index<"idx"_n>(); + auto itr_proposal = _proposals.find(account.value); + check(itr_proposal != _proposals.end(), "Proposal not found in proposal table"); + + time_point_sec current_time = current_time_point(); + wps_env_singleton _wps_env(get_self(), get_self().value); + auto wps_env = _wps_env.get(); + auto& proposal = (*itr_proposal); + + check(proposal.status == PROPOSAL_STATUS::APPROVED, "Proposal::status is not PROPOSAL_STATUS::APPROVED"); + check(proposal.iteration_of_funding <= proposal.total_iterations, "all funds for this proposal have already been claimed"); + + uint32_t funding_duration_seconds = proposal.duration * seconds_per_day; + uint32_t seconds_per_claim_interval = funding_duration_seconds / proposal.total_iterations; + time_point_sec start_funding_round = proposal.fund_start_time + + (uint32_t) (proposal.iteration_of_funding * seconds_per_claim_interval); + + check(current_time > start_funding_round, "Please wait until the end of this interval to claim funding"); + + asset transfer_amount = proposal.funding_goal / proposal.total_iterations; + + //inline action transfer, send funds to proposer + eosio::action( + eosio::permission_level{get_self() , "active"_n }, + "eosio.token"_n, "transfer"_n, + std::make_tuple( get_self(), account, transfer_amount, std::string("Your worker proposal has been approved.")) + ).send(); + + _proposers.modify(itr, same_payer, [&](auto& _proposer){ + _proposer.last_claim_time = current_time; + }); + + uint32_t past_iteration = proposal.iteration_of_funding; + + _proposals.modify(itr_proposal, same_payer, [&](auto& _proposal){ + _proposal.iteration_of_funding += 1; + }); + + if(past_iteration >= proposal.total_iterations){ + _proposals.modify(itr_proposal, same_payer, [&](auto& _proposal){ + _proposal.status = PROPOSAL_STATUS::COMPLETED; + }); + } + //change state based on count + } + + void system_contract::regproposal( + name proposer, + name committee, + uint16_t subcategory, + const string& title, + const string& summary, + const string& project_img_url, + const string& description, + const string& roadmap, + uint64_t duration, + const vector& members, + const asset& funding_goal, + uint32_t total_iterations + ){ + // authority of the user's account is required + require_auth(proposer); + + // verify that the committee account exists + check(is_account(committee), "committee account doesn't exist"); + + //verify that the inputs are not too short + //subcategory is not required + //eosio_assert(subcategory > 0, "subcategory should be an integer greater than 0"); + check(title.size() > 0, "title should be more than 0 characters long"); + check(summary.size() > 0, "summary should be more than 0 characters long"); + check(project_img_url.size() > 0, "URL should be more than 0 characters long"); + check(description.size() > 0, "description should be more than 0 characters long"); + check(roadmap.size() > 0, "roadmap should be more than 0 characters long"); + check(duration >= 30, "duration should be at least 30 days"); + check(members.size() > 0, "member should be more than 0"); + check(total_iterations >= 3, "total number of iterations must be at least 3"); + + wps_env_singleton _wps_env(get_self(), get_self().value); + auto env = _wps_env.get(); + + //verify that the inputs aren't too long + check(subcategory < 10, "invalid sub-category"); + check(title.size() < 256, "title should be shorter than 256 characters."); + check(summary.size() < 400, "subtitle should be shorter than 256 characters."); + check(project_img_url.size() < 128, "URL should be shorter than 128 characters."); + check(description.size() < 5000, "description should be shorter than 1024 characters."); + check(roadmap.size() < 2000, "financial_roadmap should be shorter than 256 characters."); + check(duration <= env.max_duration_of_funding, "this proposal is over the maximum duration"); + check(members.size() < 50, "members should be shorter than 50 characters."); + check(funding_goal.is_valid(), "invalid quantity" ); + check(funding_goal.amount > 0, "must request positive amount" ); + check(total_iterations < 100, "total iterations must be less than 100"); + + auto itr = _proposers.find(proposer.value); + // verify that the account is a registered proposer + check(itr != _proposers.end(), "This account is not a registered proposer"); + + auto proposal_itr = _proposals.find(proposer.value); + // verify that the account doesn't already exist in the table + check(proposal_itr == _proposals.end(), "This account has already registered a proposal"); + + auto committee_itr = _committees.find(committee.value); + // verify that the committee is on committee table + check(committee_itr != _committees.end(), "Account not found in committee table"); + + // add to the table + // storage is billed to the contract account + _proposals.emplace(proposer, [&](auto& proposal) { + proposal.proposer = proposer; + proposal.committee = committee; + proposal.category = (*committee_itr).category; + proposal.subcategory = subcategory; + proposal.title = title; + proposal.summary = summary; + proposal.project_img_url = project_img_url; + proposal.description = description; + proposal.roadmap = roadmap; + proposal.duration = duration; + proposal.members = members; + proposal.funding_goal = funding_goal; + proposal.id = _proposers.available_primary_key(); + proposal.status = PROPOSAL_STATUS::PENDING; //initialize status to pending + proposal.total_votes = 0; + proposal.vote_start_time = time_point_sec(); + proposal.fund_start_time = time_point_sec(); + proposal.iteration_of_funding = 1; + proposal.total_iterations = total_iterations; + }); + } + + void system_contract::editproposal( + name proposer, + name committee, + uint16_t subcategory, + const string& title, + const string& summary, + const string& project_img_url, + const string& description, + const string& roadmap, + uint64_t duration, + const vector& members, + const asset& funding_goal, + uint32_t total_iterations + ){ + // authority of the user's account is required + require_auth(proposer); + + // verify that the committee account exists + check(is_account(committee), "committee account doesn't exist"); + + //verify that the inputs are not too short + check(title.size() > 0, "title should be more than 0 characters long"); + check(summary.size() > 0, "summary should be more than 0 characters long"); + check(project_img_url.size() > 0, "URL should be more than 0 characters long"); + check(description.size() > 0, "description should be more than 0 characters long"); + check(roadmap.size() > 0, "roadmap should be more than 0 characters long"); + check(duration > 0, "duration should be longer than 0 days"); + check(members.size() > 0, "member should be more than 0"); + check(total_iterations >= 3, "total number of iterations must be at least 3"); + + wps_env_singleton _wps_env(get_self(), get_self().value); + auto env = _wps_env.get(); + + //verify that the inputs aren't too long + check(subcategory < 10, "invalid sub-category"); + check(title.size() < 256, "title should be shorter than 256 characters."); + check(summary.size() < 400, "subtitle should be shorter than 256 characters."); + check(project_img_url.size() < 128, "URL should be shorter than 128 characters."); + check(description.size() < 5000, "description should be shorter than 1024 characters."); + check(roadmap.size() < 2000, "financial_roadmap should be shorter than 256 characters."); + check(duration <= env.max_duration_of_funding, "duration maximum exceeded"); + check(members.size() < 50, "members should be shorter than 50 characters."); + check(funding_goal.is_valid(), "invalid quantity" ); + check(funding_goal.amount > 0, "must request positive amount" ); + check(total_iterations < 100, "total iterations must be less than 100"); + + auto itr = _proposers.find(proposer.value); + // verify that the account is a registered proposer + check(itr != _proposers.end(), "This account is not a registered proposer"); + + auto proposal_itr = _proposals.find(proposer.value); + // verify that the account already exists in the proposals table + check(proposal_itr != _proposals.end(), "Account not found in proposal table"); + + check((*proposal_itr).status == PROPOSAL_STATUS::PENDING, "Proposal::status is not PROPOSAL_STATUS::PENDING"); + + auto committee_itr = _committees.find(committee.value); + // verify that the committee is on committee table + check(committee_itr != _committees.end(), "Account not found in committee table"); + + // modify value in the table + _proposals.modify(proposal_itr, same_payer, [&](auto& proposal){ + proposal.proposer = proposer; + proposal.committee = committee; + proposal.category = (*committee_itr).category; + proposal.subcategory = subcategory; + proposal.title = title; + proposal.summary = summary; + proposal.project_img_url = project_img_url; + proposal.description = description; + proposal.roadmap = roadmap; + proposal.duration = duration; + proposal.members = members; + proposal.funding_goal = funding_goal; + proposal.total_iterations = total_iterations; + }); + } + + void system_contract::rmvproposal(name proposer){ + // needs authority of the proposers's account + require_auth(proposer); + + // verify that the account already exists in the proposer table + auto itr = _proposals.find(proposer.value); + check(itr != _proposals.end(), "Account not found in proposal table"); + + _proposals.erase(itr); + } + + void system_contract::regreviewer(name committee, name reviewer, const string& first_name, const string& last_name){ + //Require permission of committee account + require_auth(committee); + + //verify that the account exists + check(is_account(reviewer), "The reviewer account does not exist"); + + //verify that the inputs are not too short + check(first_name.size() > 0, "first name should be more than 0 characters long"); + check(last_name.size() > 0, "last name should be more than 0 characters long"); + + //verify that the inputs are not too long + check(first_name.size() < 128, "first name should be shorter than 128 characters."); + check(last_name.size() < 128, "last name should be shorter than 128 characters."); + + auto itr = _committees.find(committee.value); + // verify that the committee is on committee table + check(itr != _committees.end(), "Account not found in committee table"); + + auto reviewer_itr = _reviewers.find(reviewer.value); + // verify that the account doesn't already exist in the table + check(reviewer_itr == _reviewers.end(), "This account has already been registered as a reviewer"); + + //add to the table + _reviewers.emplace(committee, [&](auto& _reviewer){ + _reviewer.account = reviewer; + _reviewer.first_name = first_name; + _reviewer.last_name = last_name; + _reviewer.committee = committee; + }); + } + + void system_contract::editreviewer(name committee, name reviewer, const string& first_name, const string& last_name){ + //Require permission of committee account + require_auth(committee); + + //verify that the account exists + check(is_account(reviewer), "The reviewer account does not exist"); + + //verify that the inputs are not too short + check(first_name.size() > 0, "first name should be more than 0 characters long"); + check(last_name.size() > 0, "last name should be more than 0 characters long"); + + //verify that the inputs are not too long + check(first_name.size() < 128, "first name should be shorter than 128 characters."); + check(last_name.size() < 128, "last name should be shorter than 128 characters."); + + auto committee_itr = _committees.find(committee.value); + // verify that the committee is on committee table + check(committee_itr != _committees.end(), "Account not found in committee table"); + + auto itr = _reviewers.find(reviewer.value); + // verify that the account already exists in the table + check(itr != _reviewers.end(), "Account not found in reviewers table"); + // verify that the reviewer is part of the committee + check((*itr).committee == committee, "The given reviewer is not part of this committee"); + + //add to the table + _reviewers.modify(itr, same_payer, [&](auto& _reviewer){ + _reviewer.account = reviewer; + _reviewer.first_name = first_name; + _reviewer.last_name = last_name; + _reviewer.committee = committee; + }); + } + + void system_contract::rmvreviewer(name committee, name reviewer){ + // needs authority of the committee account + require_auth(committee); + + //verify that the account exists + check(is_account(reviewer), "The account does not exist"); + + auto committee_itr = _committees.find(committee.value); + // verify that the committee is on committee table + check(committee_itr != _committees.end(), "Account not found in committee table"); + + auto itr = _reviewers.find(reviewer.value); + // verify that the account already exists in the reviewers table + check(itr != _reviewers.end(), "Account not found in reviewers table"); + // verify that the reviewer is part of the committee + check((*itr).committee == committee, "The given reviewer is not part of this committee"); + + _reviewers.erase(itr); + } + + void system_contract::acceptprop(name reviewer, name proposer){ + require_auth(reviewer); + + check(is_account(proposer), "Proposal creator does not exist"); + + auto itr = _reviewers.find(reviewer.value); + check(itr != _reviewers.end(), "Account not found in reviewers table"); + + auto itr_proposal = _proposals.find(proposer.value); + check(itr_proposal != _proposals.end(), "Proposal not found in proposal table"); + check((*itr_proposal).status == PROPOSAL_STATUS::PENDING, "Proposal::status is not proposal_status::PENDING"); + check((*itr_proposal).committee == (*itr).committee, "Reviewer is not part of this proposal's responsible committee"); + + _proposals.modify(itr_proposal, same_payer, [&](auto& proposal){ + proposal.vote_start_time = current_time_point(); + proposal.status = PROPOSAL_STATUS::ON_VOTE; + }); + } + + void system_contract::rejectprop(name reviewer, name proposer, const string& reason){ + require_auth(reviewer); + + check(is_account(proposer), "Proposal creator does not exist"); + + check(reason.size() > 0, "must provide a brief reason"); + check(reason.size() < 256, "reason is too long"); + + auto itr = _reviewers.find(reviewer.value); + check(itr != _reviewers.end(), "Account not found in reviewers table"); + + auto itr_proposal = _proposals.find(proposer.value); + check(itr_proposal != _proposals.end(), "Proposal not found in proposal table"); + check((*itr_proposal).committee == (*itr).committee, "Reviewer is not part of this proposal's responsible committee"); + check(((*itr_proposal).status == PROPOSAL_STATUS::PENDING) || ((*itr_proposal).status == PROPOSAL_STATUS::ON_VOTE) + || ((*itr_proposal).status == PROPOSAL_STATUS::FINISHED_VOTING), "invalid proposal status"); + + _proposals.modify(itr_proposal, (*itr_proposal).proposer, [&](auto& proposal){ + proposal.status = PROPOSAL_STATUS::REJECTED; + }); + } + + void system_contract::approve(name reviewer, name proposer){ + require_auth(reviewer); + + check(is_account(proposer), "Proposal creator does not exist"); + + auto itr = _reviewers.find(reviewer.value); + check(itr != _reviewers.end(), "Account not found in reviewers table"); + + auto itr_proposal = _proposals.find(proposer.value); + check(itr_proposal != _proposals.end(), "Proposal not found in proposal table"); + check((*itr_proposal).committee==(*itr).committee, "Reviewer is not part of this proposal's responsible committee"); + check((*itr_proposal).status == PROPOSAL_STATUS::FINISHED_VOTING, "Proposal::status is not PROPOSAL_STATUS::FINISHED_VOTING"); + + _proposals.modify(itr_proposal, same_payer, [&](auto& proposal){ + proposal.fund_start_time = current_time_point(); + proposal.status = PROPOSAL_STATUS::APPROVED; + }); + } + + void system_contract::rmvreject(name reviewer, name proposer){ + require_auth(reviewer); + + check(is_account(proposer), "Proposal creator does not exist"); + + auto itr = _reviewers.find(reviewer.value); + check(itr != _reviewers.end(), "Account not found in reviewers table"); + + auto itr_proposal = _proposals.find(proposer.value); + check(itr_proposal != _proposals.end(), "Proposal not found in rejected proposal table"); + check((*itr_proposal).status == PROPOSAL_STATUS::REJECTED, "Proposal::status is not PROPOSAL_STATUS::REJECTED"); + check((*itr_proposal).committee==(*itr).committee, "Reviewer is not part of this proposal's responsible committee"); + + _proposals.erase(itr_proposal); + } + + void system_contract::rmvcompleted(name reviewer, name proposer){ + require_auth(reviewer); + + check(is_account(proposer), "Proposal creator does not exist"); + + auto itr = _reviewers.find(reviewer.value); + check(itr != _reviewers.end(), "Account not found in reviewers table"); + + auto itr_proposal = _proposals.find(proposer.value); + check(itr_proposal != _proposals.end(), "Proposal not found in completed proposals table"); + check((*itr_proposal).status == PROPOSAL_STATUS::COMPLETED, "Proposal::status is not PROPOSAL_STATUS::COMPLETED"); + check((*itr_proposal).committee==(*itr).committee, "Reviewer is not part of this proposal's responsible committee"); + + _proposals.erase(itr_proposal); + } + + void system_contract::setwpsenv( + uint32_t total_voting_percent, uint32_t duration_of_voting, + uint32_t max_duration_of_funding, uint32_t total_iteration_of_funding + ){ + //registration of committee requires contract account permissions + require_auth(get_self()); + + check(total_voting_percent > 0, "total_voting_percent should be more 0"); + check(duration_of_voting > 0, "duration_of_voting should be more than 0"); + check(max_duration_of_funding > 0, "max_duration_of_funding should be more than 0"); + check(total_iteration_of_funding > 0, "total_iteration_of_funding should be more than 0"); + + wpsenv env = wpsenv(); + + env.total_voting_percent = total_voting_percent; + env.duration_of_voting = duration_of_voting; + env.max_duration_of_funding = max_duration_of_funding; + env.total_iteration_of_funding = total_iteration_of_funding; + + wps_env_singleton _wps_env(get_self(), get_self().value); + + _wps_env.set( env, _self ); + } + + void system_contract::regcommittee(name committeeman, const string& category, bool is_oversight){ + //registration of committee requires contract account permissions + require_auth(get_self()); + + //verify that the committee account exists + check(is_account(committeeman), "committeeman account doesn't exist"); + + //verify that the size of the category string is not too long/short + check(category.size() > 0, "category should be more than 0 characters long"); + check(category.size() < 64, "category should be less than 64 characters long"); + + auto itr = _committees.find(committeeman.value); + // verify that the account doesn't already exist in the table + check(itr == _committees.end(), "This account has already been registered as a committee"); + + //add to the table + _committees.emplace(_self, [&](auto& committee){ + committee.committeeman = committeeman; + committee.category = category; + committee.is_oversight = is_oversight; + }); + } + + void system_contract::edcommittee(name committeeman, const string& category, bool is_oversight){ + //editing committee info requires contract account permissions + require_auth(get_self()); + + //verify that the committee account exists + check(is_account(committeeman), "committee account doesn't exist"); + + //verify that the size of the category string is not too long/short + check(category.size() > 0, "category should be more than 0 characters long"); + check(category.size() < 64, "category should be less than 64 characters long"); + + auto itr = _committees.find(committeeman.value); + // verify that the account doesn't already exist in the table + check(itr != _committees.end(), "Account not found in committee table"); + + //add to the table + _committees.modify(itr, same_payer, [&](auto& committee){ + committee.committeeman = committeeman; + committee.category = category; + committee.is_oversight = is_oversight; + }); + } + + void system_contract::rmvcommittee(name committeeman){ + require_auth(_self); + + check(is_account(committeeman), "committeeman account doesn't exist"); + + auto itr = _committees.find(committeeman.value); + + check(itr != _committees.end(), "Account not found in committee table"); + _committees.erase(itr); + } + + void system_contract::rejectfund(name committeeman, name proposer, const string& reason){ + require_auth(committeeman); + + check(is_account(proposer), "Proposal creator does not exist"); + + check(reason.size() > 0, "must provide a brief reason"); + check(reason.size() < 256, "reason is too long"); + + auto itr = _committees.find(committeeman.value); + // verify that the committee is on committee table + check(itr != _committees.end(), "Account not found in committee table"); + + auto itr_proposal = _proposals.find(proposer.value); + check(itr_proposal != _proposals.end(), "Proposal not found in proposal table"); + + check((*itr_proposal).committee == (*itr).committeeman || (*itr).is_oversight, "Committee is not associated with this proposal"); + check((*itr_proposal).status == PROPOSAL_STATUS::APPROVED, "Proposal::status is not PROPOSAL_STATUS::APPROVED"); + + _proposals.modify(itr_proposal, same_payer, [&](auto& _proposal){ + _proposal.status = PROPOSAL_STATUS::REJECTED; + }); + } + + double stake2vote_wps( int64_t staked ) { + // From voting.cpp + /// TODO subtract 2080 brings the large numbers closer to this decade + double weight = int64_t( (current_time_point().sec_since_epoch() - (block_timestamp::block_timestamp_epoch / 1000)) / (seconds_per_day * 7) ) / double( 13 ); + return double(staked) * std::pow( 2, weight ); + } + + void system_contract::voteproposal( const name& voter_name, const std::vector& proposals ) { + require_auth( voter_name ); + // vote_stake_updater( voter_name ); + update_wps_votes( voter_name, proposals ); + } + + void system_contract::update_wps_votes( const name& voter_name, const std::vector& proposals){ + //validate input + check( proposals.size() <= 1, "attempt to vote for too many proposals" ); + for( size_t i = 1; i < proposals.size(); ++i ) { + check( proposals[i-1] != proposals[i], "proposal votes must be unique" ); + } + + auto voter = _voters.find( voter_name.value ); + check( voter != _voters.end(), "user must stake before they can vote" ); /// staking creates voter object + + auto wpsvoter = _wpsvoters.find( voter_name.value ); + + + check( _gstate.total_activated_stake >= min_activated_stake, + "cannot update wps votes until the chain is activated (at least 15% of all tokens participate in voting)" ); + + auto new_vote_weight = stake2vote_wps( voter->staked ); + if( voter->is_proxy ) { + check(false, "Proxies can't vote for worker proposals"); + } + + std::map > proposal_deltas; + + if(wpsvoter != _wpsvoters.end()){ + if ( wpsvoter->last_vote_weight > 0 ) { + for( const auto& p : wpsvoter->proposals ) { + auto& d = proposal_deltas[p]; + d.first -= wpsvoter->last_vote_weight; + d.second = false; + } + } + } + + if( new_vote_weight >= 0 ) { + for( const auto& p : proposals ) { + auto& d = proposal_deltas[p]; + d.first += new_vote_weight; + d.second = true; + } + } + + const auto ct = current_time_point(); + for( const auto& pd : proposal_deltas ) { + auto pitr = _proposals.find( pd.first.value ); + if( pitr != _proposals.end() ) { + + check( (*pitr).status != PROPOSAL_STATUS::PENDING, "The proposal is not currently on vote" ); + + time_point_sec current_time = current_time_point(); + wps_env_singleton _wps_env(get_self(), get_self().value); + auto wps_env = _wps_env.get(); + uint32_t duration_of_voting = wps_env.duration_of_voting * seconds_per_day; + + if(time_point_sec(time_point(current_time - (*pitr).vote_start_time)) >= time_point_sec(duration_of_voting)) { + _proposals.modify(pitr, same_payer, [&](auto &proposal) { + proposal.status = PROPOSAL_STATUS::REJECTED; + }); + } + else{ + _proposals.modify( pitr, same_payer, [&]( auto& p ) { + p.total_votes += pd.second.first; + if ( p.total_votes < 0 ) { // floating point arithmetics can give small negative numbers + p.total_votes = 0; + } + }); + + auto total_activated_vote = stake2vote_wps(_gstate.total_activated_stake); + if((*pitr).total_votes > (double) (total_activated_vote / ((double) (100/wps_env.total_voting_percent)))){ + if((*pitr).status == PROPOSAL_STATUS::ON_VOTE){ + _proposals.modify( pitr, same_payer, [&]( auto& p ) { + p.status = PROPOSAL_STATUS::FINISHED_VOTING; + }); + } + } + } + } else { + if( pd.second.second ) { + check( false, ( pd.first.to_string() + "does not have an existing proposal" ).data() ); + } + } + } + + if(wpsvoter == _wpsvoters.end()){ + _wpsvoters.emplace(voter_name, [&](auto& wv){ + wv.owner = voter_name; + wv.proposals = proposals; + wv.last_vote_weight = new_vote_weight; + }); + } + else{ + _wpsvoters.modify(wpsvoter, same_payer, [&](auto& wv){ + wv.proposals = proposals; + wv.last_vote_weight = new_vote_weight; + }); + } + } + +} /// namespace eosiosystem + +// +// Created by Jae Chung on 2/8/2019. +// + diff --git a/tests/eosio.wps_tests.cpp b/tests/eosio.wps_tests.cpp new file mode 100644 index 00000000..4ca8db68 --- /dev/null +++ b/tests/eosio.wps_tests.cpp @@ -0,0 +1,811 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "eosio.system_tester.hpp" + +using namespace eosio_system; + +BOOST_AUTO_TEST_SUITE(eosio_wps_tests) + +class eosio_wps_tester : public eosio_system_tester { +public: + action_result regcommittee(name sender, name committeeman, const string& category, bool is_oversight) { + return push_action( + sender, + N(regcommittee), + mvo() + ("committeeman", committeeman) + ("category", category) + ("is_oversight", is_oversight) + ); + } + + action_result edcommittee(name sender, name committeeman, const string& category, bool is_oversight) { + return push_action( + sender, + N(edcommittee), + mvo() + ("committeeman", committeeman ) + ("category", category ) + ("is_oversight", is_oversight ) + ); + } + + action_result rmvcommittee(name sender, name committeeman) { + return push_action( + sender, + N(rmvcommittee), + mvo() + ("committeeman", committeeman) + ); + } + + action_result regreviewer(name sender, name committee, name reviewer, const string& first_name, const string& last_name) { + return push_action( + sender, + N(regreviewer), + mvo() + ("committee", committee) + ("reviewer", reviewer) + ("first_name", first_name) + ("last_name", last_name) + + ); + } + + action_result editreviewer(name sender, name committee, name reviewer, const string& first_name, const string& last_name) { + return push_action( + sender, + N(editreviewer), + mvo() + ("committee", committee) + ("reviewer", reviewer) + ("first_name", first_name) + ("last_name", last_name) + + ); + } + + action_result rmvreviewer(name sender, name committee, name reviewer) { + return push_action( + sender, + N(rmvreviewer), + mvo() + ("committee", committee) + ("reviewer", reviewer) + + ); + } + + action_result regproposer(name sender, + name account, + const string& first_name, + const string& last_name, + const string& img_url, + const string& bio, + const string& country, + const string& telegram, + const string& website, + const string& linkedin) { + return push_action( + sender, + N(regproposer), + mvo() + ("account", account) + ("first_name", first_name) + ("last_name", last_name) + ("img_url", img_url) + ("bio", bio) + ("country", country) + ("telegram", telegram) + ("website", website) + ("linkedin", linkedin) + ); + } + + action_result editproposer(name sender, + name account, + const string& first_name, + const string& last_name, + const string& img_url, + const string& bio, + const string& country, + const string& telegram, + const string& website, + const string& linkedin) { + return push_action( + sender, + N(editproposer), + mvo() + ("account", account) + ("first_name", first_name) + ("last_name", last_name) + ("img_url", img_url) + ("bio", bio) + ("country", country) + ("telegram", telegram) + ("website", website) + ("linkedin", linkedin) + ); + } + + action_result rmvproposer(name sender, + name account) { + return push_action( + sender, + N(rmvproposer), + mvo() + ("account", account) + ); + } + + action_result regproposal(name sender, + name proposer, + name committee, + uint16_t subcategory, + const string& title, + const string& summary, + const string& project_img_url, + const string& description, + const string& roadmap, + uint64_t duration, + const vector& members, + const asset& funding_goal, + uint32_t total_iterations) { + return push_action( + sender, + N(regproposal), + mvo() + ("proposer", proposer) + ("committee", committee) + ("subcategory", subcategory) + ("title", title) + ("summary", summary) + ("project_img_url", project_img_url) + ("description", description) + ("roadmap", roadmap) + ("duration", duration) + ("members", members) + ("funding_goal", funding_goal) + ("total_iterations", total_iterations) + ); + } + + action_result editproposal(name sender, + name proposer, + name committee, + uint16_t subcategory, + const string& title, + const string& summary, + const string& project_img_url, + const string& description, + const string& roadmap, + uint64_t duration, + const vector& members, + const asset& funding_goal, + uint32_t total_iterations) { + return push_action( + sender, + N(editproposal), + mvo() + ("proposer", proposer) + ("committee", committee) + ("subcategory", subcategory) + ("title", title) + ("summary", summary) + ("project_img_url", project_img_url) + ("description", description) + ("roadmap", roadmap) + ("duration", duration) + ("members", members) + ("funding_goal", funding_goal) + ("total_iterations", total_iterations) + ); + } + + action_result rmvproposal(name sender, + name proposer) { + return push_action( + sender, + N(rmvproposal), + mvo() + ("proposer", proposer) + ); + } + + action_result setwpsenv(name sender, uint32_t total_voting_percent, uint32_t duration_of_voting, + uint32_t max_duration_of_funding, uint32_t total_iteration_of_funding) { + return push_action( + sender, + N(setwpsenv), + mvo() + ("total_voting_percent", total_voting_percent) + ("duration_of_voting", duration_of_voting) + ("max_duration_of_funding", max_duration_of_funding) + ("total_iteration_of_funding", total_iteration_of_funding) + ); + } + + action_result acceptprop(name sender, name reviewer, name proposer) { + return push_action( + sender, + N(acceptprop), + mvo() + ("reviewer", reviewer) + ("proposer", proposer) + + ); + } + + action_result rejectprop(name sender, name reviewer, name proposer, const string& reason) { + return push_action( + sender, + N(rejectprop), + mvo() + ("reviewer", reviewer) + ("proposer", proposer) + ("reason", reason) + + ); + } + + action_result rejectfund(name sender, name committeeman, name proposer, const string& reason) { + return push_action( + sender, + N(rejectfund), + mvo() + ("committeeman", committeeman) + ("proposer", proposer) + ("reason", reason) + + ); + } + + action_result voteproposal(name sender, name voter_name, const std::vector& proposals) { + return push_action( + sender, + N(voteproposal), + mvo() + ("voter_name", voter_name) + ("proposals", proposals) + ); + } + + action_result approve(name sender, name reviewer, name proposer) { + return push_action( + sender, + N(approve), + mvo() + ("reviewer", reviewer) + ("proposer", proposer) + ); + } + + action_result claimfunds(name sender, name account) { + return push_action( + sender, + N(claimfunds), + mvo() + ("account", account) + ); + } + + fc::variant get_committee(const account_name& act) { + vector data = get_row_by_account(config::system_account_name, config::system_account_name, N(committees), act); + return data.empty() ? fc::variant() : abi_ser.binary_to_variant("committee", data, abi_serializer_max_time); + } + + fc::variant get_reviewer(const account_name& act) { + vector data = get_row_by_account(config::system_account_name, config::system_account_name, N(reviewers), act); + return data.empty() ? fc::variant() : abi_ser.binary_to_variant("reviewer", data, abi_serializer_max_time); + } + + fc::variant get_proposer(const account_name& act) { + vector data = get_row_by_account(config::system_account_name, config::system_account_name, N(proposers), act); + return data.empty() ? fc::variant() : abi_ser.binary_to_variant("proposer", data, abi_serializer_max_time); + } + + fc::variant get_proposal(const account_name& act) { + vector data = get_row_by_account(config::system_account_name, config::system_account_name, N(proposals), act); + return data.empty() ? fc::variant() : abi_ser.binary_to_variant("proposal", data, abi_serializer_max_time); + } +}; + +BOOST_FIXTURE_TEST_CASE(wpsenv_set, eosio_wps_tester) try { + create_account_with_resources(N(committee111), config::system_account_name, core_sym::from_string("100.0000"), false, +core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + BOOST_REQUIRE_EQUAL(error("missing authority of eosio"), setwpsenv(N(committee111), 5, 30, 500, 6)); + BOOST_REQUIRE_EQUAL(success(), setwpsenv(config::system_account_name, 5, 30, 500, 6)); +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(committee_reg_edit_rmv, eosio_wps_tester) try { + + create_account_with_resources(N(committee111), config::system_account_name, core_sym::from_string("100.0000"), false, +core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + + setwpsenv(config::system_account_name, 5, 30, 500, 6); + + BOOST_REQUIRE_EQUAL(error("missing authority of eosio"), regcommittee(N(committee111), N(committee111), "categoryX", true)); + BOOST_REQUIRE_EQUAL(success(), regcommittee(config::system_account_name, N(committee111), "categoryX", true)); + BOOST_REQUIRE_EQUAL(wasm_assert_msg("This account has already been registered as a committee"), regcommittee(config::system_account_name, N(committee111), "categoryX", true)); + + + produce_blocks(1); + auto committee = get_committee(N(committee111)); + BOOST_REQUIRE_EQUAL(committee["category"], "categoryX"); + + BOOST_REQUIRE_EQUAL(error("missing authority of eosio"), edcommittee(N(committee111), N(committee111), "categoryZ", true)); + edcommittee(config::system_account_name, N(committee111), "categoryY", true); + + produce_blocks(1); + + committee = get_committee(N(committee111)); + BOOST_REQUIRE_EQUAL(committee["category"], "categoryY"); + + BOOST_REQUIRE_EQUAL(error("missing authority of eosio"), rmvcommittee(N(committee111), N(committee111))); + BOOST_REQUIRE_EQUAL(success(), rmvcommittee(config::system_account_name, N(committee111))); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(reviewer_reg_edit_rmv, eosio_wps_tester) try { + + create_account_with_resources(N(committee111), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + create_account_with_resources(N(reviewer1111), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + + setwpsenv(config::system_account_name, 5, 30, 500, 6); + regcommittee(config::system_account_name, N(committee111), "categoryX", true); + + BOOST_REQUIRE_EQUAL(error("missing authority of committee111"), + regreviewer(N(reviewer1111), N(committee111), N(reviewer1111), "bob", "bob")); + + BOOST_REQUIRE_EQUAL(wasm_assert_msg("Account not found in committee table"), + regreviewer(N(reviewer1111), N(reviewer1111), N(reviewer1111), "bob", "bob")); + + BOOST_REQUIRE_EQUAL(success(), regreviewer(N(committee111), N(committee111), N(reviewer1111), "bob", "bob")); + + produce_blocks(1); + + BOOST_REQUIRE_EQUAL(wasm_assert_msg("This account has already been registered as a reviewer"), + regreviewer(N(committee111), N(committee111), N(reviewer1111), "bob", "bob")); + + auto reviewer = get_reviewer(N(reviewer1111)); + BOOST_REQUIRE_EQUAL(reviewer["last_name"], "bob"); + + BOOST_REQUIRE_EQUAL(wasm_assert_msg("Account not found in committee table"), + editreviewer(N(reviewer1111), N(reviewer1111), N(reviewer1111), "bob", "bob")); + + editreviewer(N(committee111), N(committee111), N(reviewer1111), "bob", "smith"); + + produce_blocks(1); + + reviewer = get_reviewer(N(reviewer1111)); + BOOST_REQUIRE_EQUAL(reviewer["last_name"], "smith"); + + BOOST_REQUIRE_EQUAL(wasm_assert_msg("Account not found in committee table"), + rmvreviewer(N(reviewer1111), N(reviewer1111), N(reviewer1111))); + + BOOST_REQUIRE_EQUAL(success(), rmvreviewer(N(committee111), N(committee111), N(reviewer1111))); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE(proposer_reg_edit_rmv, eosio_wps_tester) try { + +create_account_with_resources(N(proposer1111), config::system_account_name, core_sym::from_string("100.0000"), false, +core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); +create_account_with_resources(N(randomuser11), config::system_account_name, core_sym::from_string("100.0000"), false, +core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + +setwpsenv(config::system_account_name, 5, 30, 500, 6); + +BOOST_REQUIRE_EQUAL(error("missing authority of proposer1111"), + regproposer(N(randomuser11), N(proposer1111), "user", "one", "img_url", "bio", "country", "telegram", "website", "linkedin")); + +BOOST_REQUIRE_EQUAL(success(), + regproposer(N(proposer1111), N(proposer1111), "user", "one", "img_url", "bio", "country", "telegram", "website", "linkedin")); + +BOOST_REQUIRE_EQUAL(wasm_assert_msg("This account has already been registered as a proposer"), + regproposer(N(proposer1111), N(proposer1111), "user", "one", "img_url", "bio", "country", "telegram", "website", "linkedin")); + +produce_blocks(1); + +auto proposer = get_proposer(N(proposer1111)); + +BOOST_REQUIRE_EQUAL(proposer["first_name"], "user"); + +editproposer(N(proposer1111), N(proposer1111), "proposer", "one", "img_url", "bio", "country", "telegram", "website", "linkedin"); + +produce_blocks(1); + +proposer = get_proposer(N(proposer1111)); + +BOOST_REQUIRE_EQUAL(proposer["first_name"], "proposer"); + +BOOST_REQUIRE_EQUAL(wasm_assert_msg("Account not found in proposer table"), + rmvproposer(N(randomuser11), N(randomuser11))); + +BOOST_REQUIRE_EQUAL(success(), rmvproposer(N(proposer1111), N(proposer1111))); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE(proposal_reg_edit_rmv, eosio_wps_tester) try { + + create_account_with_resources(N(committee111), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + create_account_with_resources(N(reviewer1111), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + create_account_with_resources(N(proposer1111), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + create_account_with_resources(N(randomuser11), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + +setwpsenv(config::system_account_name, 5, 30, 500, 6); +regcommittee(config::system_account_name, N(committee111), "categoryX", true); +regreviewer(N(committee111), N(committee111), N(reviewer1111), "bob", "bob"); +regproposer(N(proposer1111), N(proposer1111), "user", "one", "img_url", "bio", "country", "telegram", "website", "linkedin"); + +BOOST_REQUIRE_EQUAL(error("missing authority of proposer1111"), + regproposal(N(randomuser11), N(proposer1111), N(committee111), 1, "title", "summary", "project_img_url", + "description", "roadmap", 30, {"user"}, core_sym::from_string("9000.0000"), 3)); + +BOOST_REQUIRE_EQUAL(success(), + regproposal(N(proposer1111), N(proposer1111), N(committee111), 1, "title", "summary", "project_img_url", + "description", "roadmap", 30, {"user"}, core_sym::from_string("9000.0000"), 3)); + +produce_blocks(1); + +BOOST_REQUIRE_EQUAL(wasm_assert_msg("This account has already registered a proposal"), + regproposal(N(proposer1111), N(proposer1111), N(committee111), 1, "title", "summary", "project_img_url", + "description", "roadmap", 30, {"user"}, core_sym::from_string("9000.0000"), 3)); + +auto proposal = get_proposal(N(proposer1111)); + +BOOST_REQUIRE_EQUAL(proposal["title"], "title"); + +BOOST_REQUIRE_EQUAL(success(), editproposal(N(proposer1111), N(proposer1111), N(committee111), 1, "First proposal", "summary", "project_img_url", +"description", "roadmap", 30, {"user"}, core_sym::from_string("9000.0000"), 3)); + +produce_blocks(1); + +proposal = get_proposal(N(proposer1111)); + +BOOST_REQUIRE_EQUAL(proposal["title"], "First proposal"); + +BOOST_REQUIRE_EQUAL(wasm_assert_msg("Account not found in proposal table"), rmvproposal(N(randomuser11), N(randomuser11))); + +BOOST_REQUIRE_EQUAL(success(), rmvproposal(N(proposer1111), N(proposer1111))); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE(reviewer_accept_reject, eosio_wps_tester) try { + +create_account_with_resources(N(committee111), config::system_account_name, core_sym::from_string("100.0000"), false, +core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); +create_account_with_resources(N(reviewer1111), config::system_account_name, core_sym::from_string("100.0000"), false, +core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); +create_account_with_resources(N(proposer1111), config::system_account_name, core_sym::from_string("100.0000"), false, +core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); +create_account_with_resources(N(randomuser11), config::system_account_name, core_sym::from_string("100.0000"), false, +core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + +setwpsenv(config::system_account_name, 5, 30, 500, 6); +regcommittee(config::system_account_name, N(committee111), "categoryX", true); +regreviewer(N(committee111), N(committee111), N(reviewer1111), "bob", "bob"); +regproposer(N(proposer1111), N(proposer1111), "user", "one", "img_url", "bio", "country", "telegram", "website", "linkedin"); +regproposal(N(proposer1111), N(proposer1111), N(committee111), 1, "title", "summary", "project_img_url", +"description", "roadmap", 30, {"user"}, core_sym::from_string("9000.0000"), 3); + +BOOST_REQUIRE_EQUAL(error("missing authority of reviewer1111"), + acceptprop(N(proposer1111), N(reviewer1111), N(proposer1111))); + +BOOST_REQUIRE_EQUAL(wasm_assert_msg("Account not found in reviewers table"), + acceptprop(N(proposer1111), N(proposer1111), N(proposer1111))); + +BOOST_REQUIRE_EQUAL(error("missing authority of reviewer1111"), + rejectprop(N(proposer1111), N(reviewer1111), N(proposer1111), "reason")); + +BOOST_REQUIRE_EQUAL(wasm_assert_msg("Account not found in reviewers table"), + rejectprop(N(proposer1111), N(proposer1111), N(proposer1111), "reason")); + +BOOST_REQUIRE_EQUAL(success(), + rejectprop(N(reviewer1111), N(reviewer1111), N(proposer1111), "reason")); + +produce_blocks(1); + +auto proposal = get_proposal(N(proposer1111)); + +BOOST_REQUIRE_EQUAL(proposal["status"], 2); + +BOOST_REQUIRE_EQUAL(success(), rmvproposal(N(proposer1111), N(proposer1111))); + +produce_blocks(1); + +regproposal(N(proposer1111), N(proposer1111), N(committee111), 1, "title", "summary", "project_img_url", +"description", "roadmap", 30, {"user"}, core_sym::from_string("9000.0000"), 3); + +produce_blocks(1); + +BOOST_REQUIRE_EQUAL(success(), acceptprop(N(reviewer1111), N(reviewer1111), N(proposer1111))); + +produce_blocks(1); + +proposal = get_proposal(N(proposer1111)); + +BOOST_REQUIRE_EQUAL(proposal["status"], 3); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE(proposal_vote_claim, eosio_wps_tester) try { + + create_account_with_resources(N(committee111), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + create_account_with_resources(N(reviewer1111), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + create_account_with_resources(N(proposer1111), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + create_account_with_resources(N(randomuser11), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + + cross_15_percent_threshold(); + + setwpsenv(config::system_account_name, 5, 30, 500, 6); + regcommittee(config::system_account_name, N(committee111), "categoryX", true); + regreviewer(N(committee111), N(committee111), N(reviewer1111), "bob", "bob"); + regproposer(N(proposer1111), N(proposer1111), "user", "one", "img_url", "bio", "country", "telegram", "website", "linkedin"); + regproposal(N(proposer1111), N(proposer1111), N(committee111), 1, "title", "summary", "project_img_url", + "description", "roadmap", 30, {"user"}, core_sym::from_string("9000.0000"), 3); + acceptprop(N(reviewer1111), N(reviewer1111), N(proposer1111)); + + + create_account_with_resources(N(smallvoter11), config::system_account_name, core_sym::from_string("100.0000"), false, +core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + + create_account_with_resources(N(bigvoter1111), config::system_account_name, core_sym::from_string("10000.0000"), false, +core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + + issue_and_transfer( "smallvoter11", core_sym::from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "smallvoter11", core_sym::from_string("100.0002"), core_sym::from_string("50.0001") ) ); + + issue_and_transfer( "bigvoter1111", core_sym::from_string("100000000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bigvoter1111", core_sym::from_string("50000000.0000"), core_sym::from_string("50000000.0000") ) ); + + produce_blocks(1); + + BOOST_REQUIRE_EQUAL(error("missing authority of smallvoter11"), voteproposal(N(proposer1111), N(smallvoter11), {N(proposer1111)})); + + BOOST_REQUIRE_EQUAL(success(), voteproposal(N(smallvoter11), N(smallvoter11), {N(proposer1111)})); + + produce_blocks(1); + + auto proposal = get_proposal(N(proposer1111)); + + BOOST_REQUIRE_EQUAL(proposal["status"], 3); + + BOOST_REQUIRE_EQUAL(success(), voteproposal(N(bigvoter1111), N(bigvoter1111), {N(proposer1111)})); + + produce_blocks(1); + + proposal = get_proposal(N(proposer1111)); + + BOOST_REQUIRE_EQUAL(proposal["status"], 4); + + BOOST_REQUIRE_EQUAL(error("missing authority of reviewer1111"), + approve(N(proposer1111), N(reviewer1111), N(proposer1111))); + + BOOST_REQUIRE_EQUAL(wasm_assert_msg("Account not found in reviewers table"), + approve(N(proposer1111), N(proposer1111), N(proposer1111))); + + BOOST_REQUIRE_EQUAL(success(), + approve(N(reviewer1111), N(reviewer1111), N(proposer1111))); + + produce_blocks(1); + + proposal = get_proposal(N(proposer1111)); + + BOOST_REQUIRE_EQUAL(proposal["status"], 5); + + produce_blocks(1); + + BOOST_REQUIRE_EQUAL(wasm_assert_msg("Please wait until the end of this interval to claim funding"), claimfunds(N(proposer1111), N(proposer1111))); + + produce_block( fc::days(10) ); + + BOOST_REQUIRE_EQUAL(error("missing authority of proposer1111"), claimfunds(N(randomuser11), N(proposer1111))); + + BOOST_REQUIRE_EQUAL(success(), claimfunds(N(proposer1111), N(proposer1111))); + + produce_blocks(1); + + BOOST_REQUIRE_EQUAL(wasm_assert_msg("Please wait until the end of this interval to claim funding"), claimfunds(N(proposer1111), N(proposer1111))); + + produce_block( fc::days(10) ); + + BOOST_REQUIRE_EQUAL(success(), claimfunds(N(proposer1111), N(proposer1111))); + + produce_blocks(1); + + BOOST_REQUIRE_EQUAL(wasm_assert_msg("Please wait until the end of this interval to claim funding"), claimfunds(N(proposer1111), N(proposer1111))); + + produce_block( fc::days(10) ); + + BOOST_REQUIRE_EQUAL(success(), claimfunds(N(proposer1111), N(proposer1111))); + + produce_blocks(1); + + proposal = get_proposal(N(proposer1111)); + + BOOST_REQUIRE_EQUAL(proposal["status"], 6); + + BOOST_REQUIRE_EQUAL(wasm_assert_msg("Proposal::status is not PROPOSAL_STATUS::APPROVED"), claimfunds(N(proposer1111), N(proposer1111))); + +} FC_LOG_AND_RETHROW() + + +BOOST_FIXTURE_TEST_CASE(proposal_reject_fund, eosio_wps_tester) try { + + create_account_with_resources(N(committee111), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + create_account_with_resources(N(reviewer1111), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + create_account_with_resources(N(proposer1111), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + create_account_with_resources(N(randomuser11), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + + cross_15_percent_threshold(); + + setwpsenv(config::system_account_name, 5, 30, 500, 6); + regcommittee(config::system_account_name, N(committee111), "categoryX", true); + regreviewer(N(committee111), N(committee111), N(reviewer1111), "bob", "bob"); + regproposer(N(proposer1111), N(proposer1111), "user", "one", "img_url", "bio", "country", "telegram", "website", "linkedin"); + regproposal(N(proposer1111), N(proposer1111), N(committee111), 1, "title", "summary", "project_img_url", + "description", "roadmap", 30, {"user"}, core_sym::from_string("9000.0000"), 3); + acceptprop(N(reviewer1111), N(reviewer1111), N(proposer1111)); + + create_account_with_resources(N(bigvoter1111), config::system_account_name, core_sym::from_string("10000.0000"), false, +core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + + issue_and_transfer( "bigvoter1111", core_sym::from_string("100000000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bigvoter1111", core_sym::from_string("50000000.0000"), core_sym::from_string("50000000.0000") ) ); + + produce_blocks(1); + + BOOST_REQUIRE_EQUAL(success(), voteproposal(N(bigvoter1111), N(bigvoter1111), {N(proposer1111)})); + + produce_blocks(1); + + BOOST_REQUIRE_EQUAL(success(), + approve(N(reviewer1111), N(reviewer1111), N(proposer1111))); + + produce_block( fc::days(11) ); + + BOOST_REQUIRE_EQUAL(success(), claimfunds(N(proposer1111), N(proposer1111))); + + BOOST_REQUIRE_EQUAL(error("missing authority of committee111"), rejectfund(N(proposer1111), N(committee111), N(proposer1111), "reason")); + BOOST_REQUIRE_EQUAL(wasm_assert_msg("Proposal creator does not exist"), rejectfund(N(committee111), N(committee111), N('dne'), "reason")); + BOOST_REQUIRE_EQUAL(wasm_assert_msg("Proposal not found in proposal table"), rejectfund(N(committee111), N(committee111), N(committee111), "reason")); + BOOST_REQUIRE_EQUAL(wasm_assert_msg("Account not found in committee table"), rejectfund(N(proposer1111), N(proposer1111), N(proposer1111), "reason")); + BOOST_REQUIRE_EQUAL(success(), rejectfund(N(committee111), N(committee111), N(proposer1111), "reason")); + + produce_block( fc::days(10) ); + + BOOST_REQUIRE_EQUAL(wasm_assert_msg("Proposal::status is not PROPOSAL_STATUS::APPROVED"), claimfunds(N(proposer1111), N(proposer1111))); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(proposal_vote_increase_stake, eosio_wps_tester) try { + + create_account_with_resources(N(committee111), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + create_account_with_resources(N(reviewer1111), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + create_account_with_resources(N(proposer1111), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + create_account_with_resources(N(randomuser11), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + + cross_15_percent_threshold(); + + setwpsenv(config::system_account_name, 5, 30, 500, 6); + regcommittee(config::system_account_name, N(committee111), "categoryX", true); + regreviewer(N(committee111), N(committee111), N(reviewer1111), "bob", "bob"); + regproposer(N(proposer1111), N(proposer1111), "user", "one", "img_url", "bio", "country", "telegram", "website", "linkedin"); + regproposal(N(proposer1111), N(proposer1111), N(committee111), 1, "title", "summary", "project_img_url", + "description", "roadmap", 30, {"user"}, core_sym::from_string("9000.0000"), 3); + acceptprop(N(reviewer1111), N(reviewer1111), N(proposer1111)); + + create_account_with_resources(N(bigvoter1111), config::system_account_name, core_sym::from_string("10000.0000"), false, +core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + + issue_and_transfer( "bigvoter1111", core_sym::from_string("100000000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bigvoter1111", core_sym::from_string("50000.0000"), core_sym::from_string("50000.0000") ) ); + + produce_blocks(1); + + BOOST_REQUIRE_EQUAL(success(), voteproposal(N(bigvoter1111), N(bigvoter1111), {N(proposer1111)})); + + produce_blocks(1); + + BOOST_REQUIRE_EQUAL(wasm_assert_msg("Proposal::status is not PROPOSAL_STATUS::FINISHED_VOTING"), + approve(N(reviewer1111), N(reviewer1111), N(proposer1111))); + + BOOST_REQUIRE_EQUAL( success(), stake( "bigvoter1111", core_sym::from_string("49950000.0000"), core_sym::from_string("49950000.0000") ) ); + + produce_blocks(1); + + BOOST_REQUIRE_EQUAL(success(), + approve(N(reviewer1111), N(reviewer1111), N(proposer1111))); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE(proposal_vote_decrease_stake, eosio_wps_tester) try { + + create_account_with_resources(N(committee111), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + create_account_with_resources(N(reviewer1111), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + create_account_with_resources(N(proposer1111), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + create_account_with_resources(N(randomuser11), config::system_account_name, core_sym::from_string("100.0000"), false, + core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + + cross_15_percent_threshold(); + + setwpsenv(config::system_account_name, 5, 30, 500, 6); + regcommittee(config::system_account_name, N(committee111), "categoryX", true); + regreviewer(N(committee111), N(committee111), N(reviewer1111), "bob", "bob"); + regproposer(N(proposer1111), N(proposer1111), "user", "one", "img_url", "bio", "country", "telegram", "website", "linkedin"); + regproposal(N(proposer1111), N(proposer1111), N(committee111), 1, "title", "summary", "project_img_url", + "description", "roadmap", 30, {"user"}, core_sym::from_string("9000.0000"), 3); + acceptprop(N(reviewer1111), N(reviewer1111), N(proposer1111)); + + create_account_with_resources(N(bigvoter1111), config::system_account_name, core_sym::from_string("10000.0000"), false, +core_sym::from_string("10.0000"), core_sym::from_string("10.0000")); + + issue_and_transfer( "bigvoter1111", core_sym::from_string("100000000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), stake( "bigvoter1111", core_sym::from_string("3700000.0000"), core_sym::from_string("3700000.0000") ) ); + + produce_blocks(1); + + BOOST_REQUIRE_EQUAL(success(), voteproposal(N(bigvoter1111), N(bigvoter1111), {N(proposer1111)})); + + produce_blocks(1); + + BOOST_REQUIRE_EQUAL(wasm_assert_msg("Proposal::status is not PROPOSAL_STATUS::FINISHED_VOTING"), + approve(N(reviewer1111), N(reviewer1111), N(proposer1111))); + + BOOST_REQUIRE_EQUAL( success(), unstake( "bigvoter1111", "bigvoter1111", core_sym::from_string("3700000.0000"), core_sym::from_string("3700000.0000") ) ); + + produce_blocks(1); + + BOOST_REQUIRE_EQUAL( success(), stake( "bigvoter1111", core_sym::from_string("1.0000"), core_sym::from_string("1.0000") ) ); + + produce_blocks(1); + + BOOST_REQUIRE_EQUAL(wasm_assert_msg("Proposal::status is not PROPOSAL_STATUS::FINISHED_VOTING"), + approve(N(reviewer1111), N(reviewer1111), N(proposer1111))); + + BOOST_REQUIRE_EQUAL( success(), stake( "bigvoter1111", core_sym::from_string("3750000.0000"), core_sym::from_string("3750000.0000") ) ); + + produce_blocks(1); + + BOOST_REQUIRE_EQUAL(success(), + approve(N(reviewer1111), N(reviewer1111), N(proposer1111))); + +} FC_LOG_AND_RETHROW() + +BOOST_AUTO_TEST_SUITE_END()