diff --git a/CMakeLists.txt b/CMakeLists.txt index adbdce0ae..da56661dd 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -2,9 +2,9 @@ cmake_minimum_required(VERSION 3.5) project(eosio_contracts) -set(VERSION_MAJOR 1) +set(VERSION_MAJOR 0) set(VERSION_MINOR 9) -set(VERSION_PATCH 1) +set(VERSION_PATCH 2) #set(VERSION_SUFFIX rc4) if (VERSION_SUFFIX) @@ -16,6 +16,7 @@ endif() include(ExternalProject) find_package(eosio.cdt) +message(STATUS "Using eosio.cdt in ${EOSIO_CDT_ROOT}") message(STATUS "Building eosio.contracts v${VERSION_FULL}") @@ -82,7 +83,7 @@ if(BUILD_TESTS) ExternalProject_Add( contracts_unit_tests LIST_SEPARATOR | # Use the alternate list separator - CMAKE_ARGS -DCMAKE_BUILD_TYPE=${TEST_BUILD_TYPE} -DCMAKE_PREFIX_PATH=${TEST_PREFIX_PATH} -DCMAKE_FRAMEWORK_PATH=${TEST_FRAMEWORK_PATH} -DCMAKE_MODULE_PATH=${TEST_MODULE_PATH} -DEOSIO_ROOT=${EOSIO_ROOT} -DLLVM_DIR=${LLVM_DIR} -DBOOST_ROOT=${BOOST_ROOT} + CMAKE_ARGS -Deosio.cdt_DIR=${eosio.cdt_DIR} -DCMAKE_BUILD_TYPE=${TEST_BUILD_TYPE} -DCMAKE_PREFIX_PATH=${TEST_PREFIX_PATH} -DCMAKE_FRAMEWORK_PATH=${TEST_FRAMEWORK_PATH} -DCMAKE_MODULE_PATH=${TEST_MODULE_PATH} -DEOSIO_ROOT=${EOSIO_ROOT} -DLLVM_DIR=${LLVM_DIR} -DBOOST_ROOT=${BOOST_ROOT} SOURCE_DIR ${CMAKE_SOURCE_DIR}/tests BINARY_DIR ${CMAKE_BINARY_DIR}/tests BUILD_ALWAYS 1 diff --git a/build.sh b/build.sh index 558736ccc..43a2c30b0 100755 --- a/build.sh +++ b/build.sh @@ -12,6 +12,8 @@ function usage() { exit 1 } +EOSIO_MAX_VERSION_MAJOR=1 +EOSIO_MAX_VERSION_MINOR=8 BUILD_TESTS=false if [ $# -ne 0 ]; then @@ -55,6 +57,9 @@ fi if [[ ${BUILD_TESTS} == true ]]; then # Prompt user for location of eosio. eosio-directory-prompt + + default-boost-directories; + echo "Using Boost installation at: ${BOOST_INSTALL_DIR}" fi # Prompt user for location of eosio.cdt. @@ -71,6 +76,7 @@ if [[ ${BUILD_TESTS} == true ]]; then # Include EOSIO_INSTALL_DIR in CMAKE_FRAMEWORK_PATH echo "Using EOSIO installation at: $EOSIO_INSTALL_DIR" export CMAKE_FRAMEWORK_PATH="${EOSIO_INSTALL_DIR}:${CMAKE_FRAMEWORK_PATH}" + export CMAKE_PREFIX_PATH="${BOOST_INSTALL_DIR}" fi printf "\t=========== Building eosio.contracts ===========\n\n" diff --git a/contracts/rem.msig/include/rem.msig/rem.msig.hpp b/contracts/rem.msig/include/rem.msig/rem.msig.hpp index 87d728825..a916a3f10 100644 --- a/contracts/rem.msig/include/rem.msig/rem.msig.hpp +++ b/contracts/rem.msig/include/rem.msig/rem.msig.hpp @@ -6,20 +6,23 @@ #include namespace eosio { + /// @cond IMPLEMENTATIONS + /** - * @defgroup eosiomsig rem.msig - * @ingroup eosiocontracts - * rem.msig contract defines the structures and actions needed to manage the proposals and approvals on blockchain. - * @{ + * The `rem.msig` system contract allows for creation of proposed transactions which require authorization from a list of accounts, approval of the proposed transactions by those accounts required to approve it, and finally, it also allows the execution of the approved transactions on the blockchain. + * + * In short, the workflow to propose, review, approve and then executed a transaction it can be described by the following: + * - first you create a transaction json file, + * - then you submit this proposal to the `rem.msig` contract, and you also insert the account permissions required to approve this proposal into the command that submits the proposal to the blockchain, + * - the proposal then gets stored on the blockchain by the `rem.msig` contract, and is accessible for review and approval to those accounts required to approve it, + * - after each of the appointed accounts required to approve the proposed transactions reviews and approves it, you can execute the proposed transaction. The `rem.msig` contract will execute it automatically, but not before validating that the transaction has not expired, it is not cancelled, and it has been signed by all the permissions in the initial proposal's required permission list. */ class [[eosio::contract("rem.msig")]] multisig : public contract { public: using contract::contract; /** - * Create proposal - * - * @details Creates a proposal containing one transaction. + * Propose action, creates a proposal containing one transaction. * Allows an account `proposer` to make a proposal `proposal_name` which has `requested` * permission levels expected to approve the proposal, and if approved by all expected * permission levels then `trx` transaction can we executed by this proposal. @@ -27,8 +30,7 @@ namespace eosio { * authorized by the provided keys and permissions, and if the proposal name doesn’t * already exist; if all validations pass the `proposal_name` and `trx` trasanction are * saved in the proposals table and the `requested` permission levels to the - * approvals table (for the `proposer` context). - * Storage changes are billed to `proposer`. + * approvals table (for the `proposer` context). Storage changes are billed to `proposer`. * * @param proposer - The account proposing a transaction * @param proposal_name - The name of the proposal (should be unique for proposer) @@ -36,18 +38,14 @@ namespace eosio { * @param trx - Proposed transaction */ [[eosio::action]] - void propose(ignore proposer, ignore proposal_name, - ignore> requested, ignore trx); + void propose(name proposer, name proposal_name, + std::vector requested, ignore trx); /** - * Approve proposal - * - * @details Approves an existing proposal - * Allows an account, the owner of `level` permission, to approve a proposal `proposal_name` + * Approve action approves an existing proposal. Allows an account, the owner of `level` permission, to approve a proposal `proposal_name` * proposed by `proposer`. If the proposal's requested approval list contains the `level` * permission then the `level` permission is moved from internal `requested_approvals` list to * internal `provided_approvals` list of the proposal, thus persisting the approval for - * the `proposal_name` proposal. - * Storage changes are billed to `proposer`. + * the `proposal_name` proposal. Storage changes are billed to `proposer`. * * @param proposer - The account proposing a transaction * @param proposal_name - The name of the proposal (should be unique for proposer) @@ -58,10 +56,7 @@ namespace eosio { void approve( name proposer, name proposal_name, permission_level level, const eosio::binary_extension& proposal_hash ); /** - * Revoke proposal - * - * @details Revokes an existing proposal - * This action is the reverse of the `approve` action: if all validations pass + * Unapprove action revokes an existing proposal. This action is the reverse of the `approve` action: if all validations pass * the `level` permission is erased from internal `provided_approvals` and added to the internal * `requested_approvals` list, and thus un-approve or revoke the proposal. * @@ -72,9 +67,7 @@ namespace eosio { [[eosio::action]] void unapprove( name proposer, name proposal_name, permission_level level ); /** - * Cancel proposal - * - * @details Cancels an existing proposal + * Cancel action cancels an existing proposal. * * @param proposer - The account proposing a transaction * @param proposal_name - The name of the proposal (should be an existing proposal) @@ -87,9 +80,7 @@ namespace eosio { [[eosio::action]] void cancel( name proposer, name proposal_name, name canceler ); /** - * Execute proposal - * - * @details Allows an `executer` account to execute a proposal. + * Exec action allows an `executer` account to execute a proposal. * * Preconditions: * - `executer` has authorization, @@ -108,9 +99,7 @@ namespace eosio { [[eosio::action]] void exec( name proposer, name proposal_name, name executer ); /** - * Invalidate proposal - * - * @details Allows an `account` to invalidate itself, that is, its name is added to + * Invalidate action allows an `account` to invalidate itself, that is, its name is added to * the invalidations table and this table will be cross referenced when exec is performed. * * @param account - The account invalidating the transaction @@ -124,52 +113,50 @@ namespace eosio { using cancel_action = eosio::action_wrapper<"cancel"_n, &multisig::cancel>; using exec_action = eosio::action_wrapper<"exec"_n, &multisig::exec>; using invalidate_action = eosio::action_wrapper<"invalidate"_n, &multisig::invalidate>; + }; - private: - struct [[eosio::table]] proposal { - name proposal_name; - std::vector packed_transaction; - - uint64_t primary_key()const { return proposal_name.value; } - }; - - typedef eosio::multi_index< "proposal"_n, proposal > proposals; + struct [[eosio::table, eosio::contract("rem.msig")]] proposal { + name proposal_name; + std::vector packed_transaction; + eosio::binary_extension< std::optional > earliest_exec_time; - struct [[eosio::table]] old_approvals_info { - name proposal_name; - std::vector requested_approvals; - std::vector provided_approvals; + uint64_t primary_key()const { return proposal_name.value; } + }; + typedef eosio::multi_index< "proposal"_n, proposal > proposals; - uint64_t primary_key()const { return proposal_name.value; } - }; - typedef eosio::multi_index< "approvals"_n, old_approvals_info > old_approvals; + struct [[eosio::table, eosio::contract("rem.msig")]] old_approvals_info { + name proposal_name; + std::vector requested_approvals; + std::vector provided_approvals; - struct approval { - permission_level level; - time_point time; - }; + uint64_t primary_key()const { return proposal_name.value; } + }; + typedef eosio::multi_index< "approvals"_n, old_approvals_info > old_approvals; - struct [[eosio::table]] approvals_info { - uint8_t version = 1; - name proposal_name; - //requested approval doesn't need to cointain time, but we want requested approval - //to be of exact the same size ad provided approval, in this case approve/unapprove - //doesn't change serialized data size. So, we use the same type. - std::vector requested_approvals; - std::vector provided_approvals; + struct approval { + permission_level level; + time_point time; + }; - uint64_t primary_key()const { return proposal_name.value; } - }; - typedef eosio::multi_index< "approvals2"_n, approvals_info > approvals; + struct [[eosio::table, eosio::contract("rem.msig")]] approvals_info { + uint8_t version = 1; + name proposal_name; + //requested approval doesn't need to contain time, but we want requested approval + //to be of exactly the same size as provided approval, in this case approve/unapprove + //doesn't change serialized data size. So, we use the same type. + std::vector requested_approvals; + std::vector provided_approvals; - struct [[eosio::table]] invalidation { - name account; - time_point last_invalidation_time; + uint64_t primary_key()const { return proposal_name.value; } + }; + typedef eosio::multi_index< "approvals2"_n, approvals_info > approvals; - uint64_t primary_key() const { return account.value; } - }; + struct [[eosio::table, eosio::contract("rem.msig")]] invalidation { + name account; + time_point last_invalidation_time; - typedef eosio::multi_index< "invals"_n, invalidation > invalidations; + uint64_t primary_key() const { return account.value; } }; - /** @}*/ // end of @defgroup eosiomsig rem.msig + typedef eosio::multi_index< "invals"_n, invalidation > invalidations; + } /// namespace eosio diff --git a/contracts/rem.msig/src/rem.msig.cpp b/contracts/rem.msig/src/rem.msig.cpp index 1a9dd6617..e77417acf 100644 --- a/contracts/rem.msig/src/rem.msig.cpp +++ b/contracts/rem.msig/src/rem.msig.cpp @@ -6,30 +6,61 @@ namespace eosio { -void multisig::propose( ignore proposer, - ignore proposal_name, - ignore> requested, +transaction_header get_trx_header(const char* ptr, size_t sz); +bool trx_is_authorized(const std::vector& approvals, const std::vector& packed_trx); + +template +std::vector get_approvals_and_adjust_table(name self, name proposer, name proposal_name, Function&& table_op) { + approvals approval_table( self, proposer.value ); + auto approval_table_iter = approval_table.find( proposal_name.value ); + std::vector approvals_vector; + invalidations invalidations_table( self, self.value ); + + if ( approval_table_iter != approval_table.end() ) { + approvals_vector.reserve( approval_table_iter->provided_approvals.size() ); + for ( const auto& permission : approval_table_iter->provided_approvals ) { + auto iter = invalidations_table.find( permission.level.actor.value ); + if ( iter == invalidations_table.end() || iter->last_invalidation_time < permission.time ) { + approvals_vector.push_back(permission.level); + } + } + table_op( approval_table, approval_table_iter ); + } else { + old_approvals old_approval_table( self, proposer.value ); + const auto& old_approvals_obj = old_approval_table.get( proposal_name.value, "proposal not found" ); + for ( const auto& permission : old_approvals_obj.provided_approvals ) { + auto iter = invalidations_table.find( permission.actor.value ); + if ( iter == invalidations_table.end() ) { + approvals_vector.push_back( permission ); + } + } + table_op( old_approval_table, old_approvals_obj ); + } + return approvals_vector; +} + +void multisig::propose( name proposer, + name proposal_name, + std::vector requested, ignore trx ) { - name _proposer; - name _proposal_name; - std::vector _requested; - transaction_header _trx_header; + require_auth( proposer ); + auto& ds = get_datastream(); - _ds >> _proposer >> _proposal_name >> _requested; + const char* trx_pos = ds.pos(); + size_t size = ds.remaining(); - const char* trx_pos = _ds.pos(); - size_t size = _ds.remaining(); - _ds >> _trx_header; - - require_auth( _proposer ); - check( _trx_header.expiration >= eosio::time_point_sec(current_time_point()), "transaction expired" ); - //check( trx_header.actions.size() > 0, "transaction must have at least one action" ); + transaction_header trx_header; + std::vector context_free_actions; + ds >> trx_header; + check( trx_header.expiration >= eosio::time_point_sec(current_time_point()), "transaction expired" ); + ds >> context_free_actions; + check( context_free_actions.empty(), "not allowed to `propose` a transaction with context-free actions" ); - proposals proptable( get_self(), _proposer.value ); - check( proptable.find( _proposal_name.value ) == proptable.end(), "proposal with the same name exists" ); + proposals proptable( get_self(), proposer.value ); + check( proptable.find( proposal_name.value ) == proptable.end(), "proposal with the same name exists" ); - auto packed_requested = pack(_requested); + auto packed_requested = pack(requested); auto res = check_transaction_authorization( trx_pos, size, (const char*)0, 0, @@ -41,19 +72,21 @@ void multisig::propose( ignore proposer, std::vector pkd_trans; pkd_trans.resize(size); memcpy((char*)pkd_trans.data(), trx_pos, size); - proptable.emplace( _proposer, [&]( auto& prop ) { - prop.proposal_name = _proposal_name; - prop.packed_transaction = pkd_trans; - }); - - approvals apptable( get_self(), _proposer.value ); - apptable.emplace( _proposer, [&]( auto& a ) { - a.proposal_name = _proposal_name; - a.requested_approvals.reserve( _requested.size() ); - for ( auto& level : _requested ) { - a.requested_approvals.push_back( approval{ level, time_point{ microseconds{0} } } ); - } - }); + + proptable.emplace( proposer, [&]( auto& prop ) { + prop.proposal_name = proposal_name; + prop.packed_transaction = pkd_trans; + prop.earliest_exec_time.emplace(); + }); + + approvals apptable( get_self(), proposer.value ); + apptable.emplace( proposer, [&]( auto& a ) { + a.proposal_name = proposal_name; + a.requested_approvals.reserve( requested.size() ); + for ( auto& level : requested ) { + a.requested_approvals.push_back( approval{ level, time_point{ microseconds{0} } } ); + } + }); } void multisig::approve( name proposer, name proposal_name, permission_level level, @@ -61,9 +94,10 @@ void multisig::approve( name proposer, name proposal_name, permission_level leve { require_auth( level ); + proposals proptable( get_self(), proposer.value ); + auto& prop = proptable.get( proposal_name.value, "proposal not found" ); + if( proposal_hash ) { - proposals proptable( get_self(), proposer.value ); - auto& prop = proptable.get( proposal_name.value, "proposal not found" ); assert_sha256( prop.packed_transaction.data(), prop.packed_transaction.size(), *proposal_hash ); } @@ -89,6 +123,21 @@ void multisig::approve( name proposer, name proposal_name, permission_level leve a.requested_approvals.erase( itr ); }); } + + transaction_header trx_header = get_trx_header(prop.packed_transaction.data(), prop.packed_transaction.size()); + + if( prop.earliest_exec_time.has_value() ) { + if( !prop.earliest_exec_time->has_value() ) { + auto table_op = [](auto&&, auto&&){}; + if( trx_is_authorized(get_approvals_and_adjust_table(get_self(), proposer, proposal_name, table_op), prop.packed_transaction) ) { + proptable.modify( prop, proposer, [&]( auto& p ) { + p.earliest_exec_time.emplace(time_point{ current_time_point() + eosio::seconds(trx_header.delay_sec.value)}); + }); + } + } + } else { + check( trx_header.delay_sec.value == 0, "old proposals are not allowed to have non-zero `delay_sec`; cancel and retry" ); + } } void multisig::unapprove( name proposer, name proposal_name, permission_level level ) { @@ -113,6 +162,23 @@ void multisig::unapprove( name proposer, name proposal_name, permission_level le a.provided_approvals.erase( itr ); }); } + + proposals proptable( get_self(), proposer.value ); + auto& prop = proptable.get( proposal_name.value, "proposal not found" ); + + if( prop.earliest_exec_time.has_value() ) { + if( prop.earliest_exec_time->has_value() ) { + auto table_op = [](auto&&, auto&&){}; + if( !trx_is_authorized(get_approvals_and_adjust_table(get_self(), proposer, proposal_name, table_op), prop.packed_transaction) ) { + proptable.modify( prop, proposer, [&]( auto& p ) { + p.earliest_exec_time.emplace(); + }); + } + } + } else { + transaction_header trx_header = get_trx_header(prop.packed_transaction.data(), prop.packed_transaction.size()); + check( trx_header.delay_sec.value == 0, "old proposals are not allowed to have non-zero `delay_sec`; cancel and retry" ); + } } void multisig::cancel( name proposer, name proposal_name, name canceler ) { @@ -144,46 +210,30 @@ void multisig::exec( name proposer, name proposal_name, name executer ) { proposals proptable( get_self(), proposer.value ); auto& prop = proptable.get( proposal_name.value, "proposal not found" ); + + datastream ds = {prop.packed_transaction.data(), prop.packed_transaction.size()}; transaction_header trx_header; - datastream ds( prop.packed_transaction.data(), prop.packed_transaction.size() ); + std::vector context_free_actions; + std::vector actions; ds >> trx_header; check( trx_header.expiration >= eosio::time_point_sec(current_time_point()), "transaction expired" ); + ds >> context_free_actions; + check( context_free_actions.empty(), "not allowed to `exec` a transaction with context-free actions" ); + ds >> actions; - approvals apptable( get_self(), proposer.value ); - auto apps_it = apptable.find( proposal_name.value ); - std::vector approvals; - invalidations inv_table( get_self(), get_self().value ); - if ( apps_it != apptable.end() ) { - approvals.reserve( apps_it->provided_approvals.size() ); - for ( auto& p : apps_it->provided_approvals ) { - auto it = inv_table.find( p.level.actor.value ); - if ( it == inv_table.end() || it->last_invalidation_time < p.time ) { - approvals.push_back(p.level); - } - } - apptable.erase(apps_it); + auto table_op = [](auto&& table, auto&& table_iter) { table.erase(table_iter); }; + bool ok = trx_is_authorized(get_approvals_and_adjust_table(get_self(), proposer, proposal_name, table_op), prop.packed_transaction); + check( ok, "transaction authorization failed" ); + + if ( prop.earliest_exec_time.has_value() && prop.earliest_exec_time->has_value() ) { + check( **prop.earliest_exec_time <= current_time_point(), "too early to execute" ); } else { - old_approvals old_apptable( get_self(), proposer.value ); - auto& apps = old_apptable.get( proposal_name.value, "proposal not found" ); - for ( auto& level : apps.provided_approvals ) { - auto it = inv_table.find( level.actor.value ); - if ( it == inv_table.end() ) { - approvals.push_back( level ); - } - } - old_apptable.erase(apps); + check( trx_header.delay_sec.value == 0, "old proposals are not allowed to have non-zero `delay_sec`; cancel and retry" ); } - auto packed_provided_approvals = pack(approvals); - auto res = check_transaction_authorization( - prop.packed_transaction.data(), prop.packed_transaction.size(), - (const char*)0, 0, - packed_provided_approvals.data(), packed_provided_approvals.size() - ); - - check( res > 0, "transaction authorization failed" ); - send_deferred( (uint128_t(proposer.value) << 64) | proposal_name.value, executer, - prop.packed_transaction.data(), prop.packed_transaction.size() ); + for (const auto& act : actions) { + act.send(); + } proptable.erase(prop); } @@ -204,4 +254,20 @@ void multisig::invalidate( name account ) { } } +transaction_header get_trx_header(const char* ptr, size_t sz) { + datastream ds = {ptr, sz}; + transaction_header trx_header; + ds >> trx_header; + return trx_header; +} + +bool trx_is_authorized(const std::vector& approvals, const std::vector& packed_trx) { + auto packed_approvals = pack(approvals); + return check_transaction_authorization( + packed_trx.data(), packed_trx.size(), + (const char*)0, 0, + packed_approvals.data(), packed_approvals.size() + ); +} + } /// namespace eosio diff --git a/scripts/helper.sh b/scripts/helper.sh index 7c391cf7e..d6ee8622f 100755 --- a/scripts/helper.sh +++ b/scripts/helper.sh @@ -22,6 +22,37 @@ function check-version-numbers() { exit 0 } +# Ensures passed in boost version values are supported. +function check-boost-version-numbers() { + CHECK_VERSION_MAJOR=$1 + CHECK_VERSION_MINOR=$2 + + if [[ ${BOOST_MIN_VERSION_MAJOR} && ${BOOST_MIN_VERSION_MAJOR} != "" ]]; then + if [[ $CHECK_VERSION_MAJOR -lt $BOOST_MIN_VERSION_MAJOR ]]; then + exit 1 + fi + fi + if [[ ${BOOST_MAX_VERSION_MAJOR} && ${BOOST_MAX_VERSION_MAJOR} != "" ]]; then + if [[ $CHECK_VERSION_MAJOR -gt $BOOST_MAX_VERSION_MAJOR ]]; then + exit 1 + fi + fi + if [[ $CHECK_VERSION_MAJOR -eq $BOOST_MIN_VERSION_MAJOR ]]; then + if [[ ${BOOST_MIN_VERSION_MINOR} && ${BOOST_MIN_VERSION_MINOR} != "" ]]; then + if [[ $CHECK_VERSION_MINOR -lt $BOOST_MIN_VERSION_MINOR ]]; then + exit 1 + fi + fi + fi + if [[ $CHECK_VERSION_MAJOR -eq $BOOST_MAX_VERSION_MAJOR ]]; then + if [[ ${BOOST_MAX_VERSION_MINOR} && ${BOOST_MAX_VERSION_MINOR} != "" ]]; then + if [[ $CHECK_VERSION_MINOR -gt $BOOST_MAX_VERSION_MINOR ]]; then + exit 1 + fi + fi + fi + exit 0 +} # Handles choosing which EOSIO directory to select when the default location is used. function default-eosio-directories() { @@ -46,7 +77,6 @@ function default-eosio-directories() { done } - # Prompts or sets default behavior for choosing EOSIO directory. function eosio-directory-prompt() { if [[ -z $EOSIO_DIR_PROMPT ]]; then @@ -83,6 +113,29 @@ function eosio-directory-prompt() { export EOSIO_INSTALL_DIR="${EOSIO_DIR_PROMPT:-${HOME}/eosio/${EOSIO_VERSION}}" } +# Handles choosing which Boost directory to select. +function default-boost-directories() { + REGEX='boost_[0-9]+([_][0-9]+)?+([_][0-9]+)?$' + ALL_BOOST_SUBDIRS=() + if [[ -d ${HOME}/eosio ]]; then + ALL_BOOST_SUBDIRS=($(ls ${EOSIO_INSTALL_DIR}/src | sort -V)) + fi + for ITEM in "${ALL_BOOST_SUBDIRS[@]}"; do + if [[ "$ITEM" =~ $REGEX ]]; then + DIR_MAJOR=$(echo $ITEM | cut -f2 -d '_') + DIR_MINOR=$(echo $ITEM | cut -f3 -d '_') + if $(check-boost-version-numbers $DIR_MAJOR $DIR_MINOR); then + PROMPT_BOOST_DIRS+=($ITEM) + fi + fi + done + for ITEM in "${PROMPT_BOOST_DIRS[@]}"; do + if [[ "$ITEM" =~ $REGEX ]]; then + BOOST_VERSION=$ITEM + fi + done + export BOOST_INSTALL_DIR=${EOSIO_INSTALL_DIR}/src/${BOOST_VERSION} +} # Prompts or default behavior for choosing EOSIO.CDT directory. function cdt-directory-prompt() { diff --git a/tests/contracts.hpp.in b/tests/contracts.hpp.in index 248100392..9c77fbcb0 100644 --- a/tests/contracts.hpp.in +++ b/tests/contracts.hpp.in @@ -49,6 +49,8 @@ struct contracts { static std::vector system_abi_old() { return read_abi("${CMAKE_SOURCE_DIR}/test_contracts/old_versions/v1.2.1/eosio.system/eosio.system.abi"); } static std::vector msig_wasm_old() { return read_wasm("${CMAKE_SOURCE_DIR}/test_contracts/old_versions/v1.2.1/eosio.msig/eosio.msig.wasm"); } static std::vector msig_abi_old() { return read_abi("${CMAKE_SOURCE_DIR}/test_contracts/old_versions/v1.2.1/eosio.msig/eosio.msig.abi"); } + static std::vector sendinline_wasm() {return read_wasm("${CMAKE_BINARY_DIR}/test_contracts/sendinline/sendinline.wasm"); } + static std::vector sendinline_abi() {return read_abi("${CMAKE_BINARY_DIR}/test_contracts/sendinline/sendinline.abi"); } }; }; }} //ns eosio::testing diff --git a/tests/eosio.msig_tests.cpp b/tests/eosio.msig_tests.cpp index 7e48f7e40..7b07fac62 100644 --- a/tests/eosio.msig_tests.cpp +++ b/tests/eosio.msig_tests.cpp @@ -1,6 +1,7 @@ #include #include #include +#include #include #include @@ -185,8 +186,7 @@ BOOST_FIXTURE_TEST_CASE( propose_approve_execute, eosio_msig_tester ) try { transaction_trace_ptr trace; control->applied_transaction.connect( [&]( std::tuple p ) { - const auto& t = std::get<0>(p); - if( t->scheduled ) { trace = t; } + trace = std::get<0>(p); } ); push_action( N(alice), N(exec), mvo() ("proposer", "alice") @@ -271,8 +271,7 @@ BOOST_FIXTURE_TEST_CASE( propose_approve_by_two, eosio_msig_tester ) try { transaction_trace_ptr trace; control->applied_transaction.connect( [&]( std::tuple p ) { - const auto& t = std::get<0>(p); - if( t->scheduled ) { trace = t; } + trace = std::get<0>(p); } ); push_action( N(alice), N(exec), mvo() @@ -304,6 +303,14 @@ BOOST_FIXTURE_TEST_CASE( propose_with_wrong_requested_auth, eosio_msig_tester ) BOOST_FIXTURE_TEST_CASE( big_transaction, eosio_msig_tester ) try { + //change `default_max_inline_action_size` to 512 KB + eosio::chain::chain_config params = control->get_global_properties().configuration; + params.max_inline_action_size = 512 * 1024; + base_tester::push_action( config::system_account_name, N(setparams), config::system_account_name, mutable_variant_object() + ("params", params) ); + + produce_blocks(); + vector perm = { { N(alice), config::active_name }, { N(bob), config::active_name } }; auto wasm = contracts::util::exchange_wasm(); @@ -354,8 +361,7 @@ BOOST_FIXTURE_TEST_CASE( big_transaction, eosio_msig_tester ) try { transaction_trace_ptr trace; control->applied_transaction.connect( [&]( std::tuple p ) { - const auto& t = std::get<0>(p); - if( t->scheduled ) { trace = t; } + trace = std::get<0>(p); } ); push_action( N(alice), N(exec), mvo() @@ -478,22 +484,36 @@ BOOST_FIXTURE_TEST_CASE( update_system_contract_all_approve, eosio_msig_tester ) ("level", permission_level{ N(carol), config::active_name }) ); // execute by alice to replace the rem system contract - transaction_trace_ptr trace; - control->applied_transaction.connect( - [&]( std::tuple p ) { - const auto& t = std::get<0>(p); - if( t->scheduled ) { trace = t; } - } ); - - push_action( N(alice), N(exec), mvo() - ("proposer", "alice") - ("proposal_name", "first") - ("executer", "alice") + transaction_trace_ptr trx_trace; + trx_trace = push_action( N(alice), N(exec), mvo() + ("proposer", "alice") + ("proposal_name", "first") + ("executer", "alice") ); -// BOOST_REQUIRE( bool(trace) ); -// BOOST_REQUIRE_EQUAL( 1, trace->action_traces.size() ); -// BOOST_REQUIRE_EQUAL( transaction_receipt::executed, trace->receipt->status ); + BOOST_REQUIRE( bool(trx_trace) ); + BOOST_REQUIRE( trx_trace->receipt.valid() ); + BOOST_REQUIRE_EQUAL( transaction_receipt::executed, trx_trace->receipt->status ); + BOOST_REQUIRE_EQUAL( 2, trx_trace->action_traces.size() ); + + BOOST_REQUIRE_EQUAL( fc::unsigned_int{1}, trx_trace->action_traces.at(0).action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{0}, trx_trace->action_traces.at(0).creator_action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{0}, trx_trace->action_traces.at(0).closest_unnotified_ancestor_action_ordinal ); + // EOSIO 1.8 N() macro returns a uint64_t rather than a struct name + BOOST_REQUIRE_EQUAL( name{N(rem.msig)}, action_name{trx_trace->action_traces.at(0).receiver} ); + BOOST_REQUIRE_EQUAL( name{N(rem.msig)}, name{trx_trace->action_traces.at(0).act.account} ); + BOOST_REQUIRE_EQUAL( name{N(exec)}, name{trx_trace->action_traces.at(0).act.name} ); + BOOST_REQUIRE_EQUAL( name{N(alice)}, name{trx_trace->action_traces.at(0).act.authorization[0].actor} ); + BOOST_REQUIRE_EQUAL( name{N(active)}, name{trx_trace->action_traces.at(0).act.authorization[0].permission} ); + + BOOST_REQUIRE_EQUAL( fc::unsigned_int{2}, trx_trace->action_traces.at(1).action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{1}, trx_trace->action_traces.at(1).creator_action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{1}, trx_trace->action_traces.at(1).closest_unnotified_ancestor_action_ordinal ); + BOOST_REQUIRE_EQUAL( name{N(rem)}, action_name{trx_trace->action_traces.at(1).receiver} ); + BOOST_REQUIRE_EQUAL( name{N(rem)}, name{trx_trace->action_traces.at(1).act.account} ); + BOOST_REQUIRE_EQUAL( name{N(setcode)}, name{trx_trace->action_traces.at(1).act.name} ); + BOOST_REQUIRE_EQUAL( name{N(rem)}, name{trx_trace->action_traces.at(1).act.authorization[0].actor} ); + BOOST_REQUIRE_EQUAL( name{N(active)}, name{trx_trace->action_traces.at(1).act.authorization[0].permission} ); // can't create account because system contract was replaced by the reject_all contract @@ -614,24 +634,38 @@ BOOST_FIXTURE_TEST_CASE( update_system_contract_major_approve, eosio_msig_tester ("proposal_name", "first") ("level", permission_level{ N(apple), config::active_name }) ); - // execute by alice to replace the rem system contract - transaction_trace_ptr trace; - control->applied_transaction.connect( - [&]( std::tuple p ) { - const auto& t = std::get<0>(p); - if( t->scheduled ) { trace = t; } - } ); // execute by another producer different from proposer - push_action( N(apple), N(exec), mvo() - ("proposer", "alice") - ("proposal_name", "first") - ("executer", "apple") + transaction_trace_ptr trx_trace; + trx_trace = push_action( N(apple), N(exec), mvo() + ("proposer", "alice") + ("proposal_name", "first") + ("executer", "apple") ); -// BOOST_REQUIRE( bool(trace) ); -// BOOST_REQUIRE_EQUAL( 1, trace->action_traces.size() ); -// BOOST_REQUIRE_EQUAL( transaction_receipt::executed, trace->receipt->status ); + BOOST_REQUIRE( bool(trx_trace) ); + BOOST_REQUIRE( trx_trace->receipt.valid() ); + BOOST_REQUIRE_EQUAL( transaction_receipt::executed, trx_trace->receipt->status ); + BOOST_REQUIRE_EQUAL( 2, trx_trace->action_traces.size() ); + + BOOST_REQUIRE_EQUAL( fc::unsigned_int{1}, trx_trace->action_traces.at(0).action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{0}, trx_trace->action_traces.at(0).creator_action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{0}, trx_trace->action_traces.at(0).closest_unnotified_ancestor_action_ordinal ); + // EOSIO 1.8 N() macro returns a uint64_t rather than a struct name + BOOST_REQUIRE_EQUAL( name{N(rem.msig)}, action_name{trx_trace->action_traces.at(0).receiver} ); + BOOST_REQUIRE_EQUAL( name{N(rem.msig)}, name{trx_trace->action_traces.at(0).act.account} ); + BOOST_REQUIRE_EQUAL( name{N(exec)}, name{trx_trace->action_traces.at(0).act.name} ); + BOOST_REQUIRE_EQUAL( name{N(apple)}, name{trx_trace->action_traces.at(0).act.authorization[0].actor} ); + BOOST_REQUIRE_EQUAL( name{N(active)}, name{trx_trace->action_traces.at(0).act.authorization[0].permission} ); + + BOOST_REQUIRE_EQUAL( fc::unsigned_int{2}, trx_trace->action_traces.at(1).action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{1}, trx_trace->action_traces.at(1).creator_action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{1}, trx_trace->action_traces.at(1).closest_unnotified_ancestor_action_ordinal ); + BOOST_REQUIRE_EQUAL( name{N(rem)}, action_name{trx_trace->action_traces.at(1).receiver} ); + BOOST_REQUIRE_EQUAL( name{N(rem)}, name{trx_trace->action_traces.at(1).act.account} ); + BOOST_REQUIRE_EQUAL( name{N(setcode)}, name{trx_trace->action_traces.at(1).act.name} ); + BOOST_REQUIRE_EQUAL( name{N(rem)}, name{trx_trace->action_traces.at(1).act.authorization[0].actor} ); + BOOST_REQUIRE_EQUAL( name{N(active)}, name{trx_trace->action_traces.at(1).act.authorization[0].permission} ); // can't create account because system contract was replaced by the reject_all contract @@ -720,8 +754,7 @@ BOOST_FIXTURE_TEST_CASE( propose_invalidate_approve, eosio_msig_tester ) try { transaction_trace_ptr trace; control->applied_transaction.connect( [&]( std::tuple p ) { - const auto& t = std::get<0>(p); - if( t->scheduled ) { trace = t; } + trace = std::get<0>(p); } ); push_action( N(bob), N(exec), mvo() @@ -763,8 +796,7 @@ BOOST_FIXTURE_TEST_CASE( approve_execute_old, eosio_msig_tester ) try { transaction_trace_ptr trace; control->applied_transaction.connect( [&]( std::tuple p ) { - const auto& t = std::get<0>(p); - if( t->scheduled ) { trace = t; } + trace = std::get<0>(p); } ); push_action( N(alice), N(exec), mvo() @@ -867,8 +899,7 @@ BOOST_FIXTURE_TEST_CASE( approve_by_two_old, eosio_msig_tester ) try { transaction_trace_ptr trace; control->applied_transaction.connect( [&]( std::tuple p ) { - const auto& t = std::get<0>(p); - if( t->scheduled ) { trace = t; } + trace = std::get<0>(p); } ); push_action( N(alice), N(exec), mvo() @@ -917,8 +948,7 @@ BOOST_FIXTURE_TEST_CASE( approve_with_hash, eosio_msig_tester ) try { transaction_trace_ptr trace; control->applied_transaction.connect( [&]( std::tuple p ) { - const auto& t = std::get<0>(p); - if( t->scheduled ) { trace = t; } + trace = std::get<0>(p); } ); push_action( N(alice), N(exec), mvo() @@ -974,4 +1004,142 @@ BOOST_FIXTURE_TEST_CASE( switch_proposal_and_fail_approve_with_hash, eosio_msig_ ); } FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( sendinline, eosio_msig_tester ) try { + create_accounts( {N(sendinline)} ); + set_code( N(sendinline), contracts::util::sendinline_wasm() ); + set_abi( N(sendinline), contracts::util::sendinline_abi().data() ); + + create_accounts( {N(wrongcon)} ); + set_code( N(wrongcon), contracts::util::sendinline_wasm() ); + set_abi( N(wrongcon), contracts::util::sendinline_abi().data() ); + produce_blocks(); + + action act = get_action( config::system_account_name, N(reqauth), {}, mvo()("from", "alice")); + + BOOST_REQUIRE_EXCEPTION( base_tester::push_action( N(sendinline), N(send), N(bob), mvo() + ("contract", "rem") + ("action_name", "reqauth") + ("auths", std::vector{ {N(alice), config::active_name} }) + ("payload", act.data) + ), + unsatisfied_authorization, + fc_exception_message_starts_with("transaction declares authority") + ); + + base_tester::push_action(config::system_account_name, N(updateauth), N(alice), mvo() + ("account", "alice") + ("permission", "perm") + ("parent", "active") + ("auth", authority{ 1, {}, {permission_level_weight{ {N(sendinline), config::active_name}, 1}}, {} }) + ); + produce_blocks(); + + base_tester::push_action( config::system_account_name, N(linkauth), N(alice), mvo() + ("account", "alice") + ("code", "rem") + ("type", "reqauth") + ("requirement", "perm") + ); + produce_blocks(); + + transaction_trace_ptr trx_trace; + trx_trace = base_tester::push_action( N(sendinline), N(send), N(bob), mvo() + ("contract", "rem") + ("action_name", "reqauth") + ("auths", std::vector{ {N(alice), N(perm)} }) + ("payload", act.data) + ); + produce_blocks(); + + BOOST_REQUIRE( bool(trx_trace) ); + BOOST_REQUIRE( trx_trace->receipt.valid() ); + BOOST_REQUIRE_EQUAL( transaction_receipt::executed, trx_trace->receipt->status ); + BOOST_REQUIRE_EQUAL( 2, trx_trace->action_traces.size() ); + + BOOST_REQUIRE_EQUAL( fc::unsigned_int{1}, trx_trace->action_traces.at(0).action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{0}, trx_trace->action_traces.at(0).creator_action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{0}, trx_trace->action_traces.at(0).closest_unnotified_ancestor_action_ordinal ); + // EOSIO 1.8 N() macro returns a uint64_t rather than a struct name + BOOST_REQUIRE_EQUAL( name{N(sendinline)}, action_name{trx_trace->action_traces.at(0).receiver} ); + BOOST_REQUIRE_EQUAL( name{N(sendinline)}, name{trx_trace->action_traces.at(0).act.account} ); + BOOST_REQUIRE_EQUAL( name{N(send)}, name{trx_trace->action_traces.at(0).act.name} ); + BOOST_REQUIRE_EQUAL( name{N(bob)}, name{trx_trace->action_traces.at(0).act.authorization[0].actor} ); + BOOST_REQUIRE_EQUAL( name{N(active)}, name{trx_trace->action_traces.at(0).act.authorization[0].permission} ); + + BOOST_REQUIRE_EQUAL( fc::unsigned_int{2}, trx_trace->action_traces.at(1).action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{1}, trx_trace->action_traces.at(1).creator_action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{1}, trx_trace->action_traces.at(1).closest_unnotified_ancestor_action_ordinal ); + BOOST_REQUIRE_EQUAL( name{N(rem)}, action_name{trx_trace->action_traces.at(1).receiver} ); + BOOST_REQUIRE_EQUAL( name{N(rem)}, name{trx_trace->action_traces.at(1).act.account} ); + BOOST_REQUIRE_EQUAL( name{N(reqauth)}, name{trx_trace->action_traces.at(1).act.name} ); + BOOST_REQUIRE_EQUAL( name{N(alice)}, name{trx_trace->action_traces.at(1).act.authorization[0].actor} ); + BOOST_REQUIRE_EQUAL( name{N(perm)}, name{trx_trace->action_traces.at(1).act.authorization[0].permission} ); + + + base_tester::push_action(config::system_account_name, updateauth::get_name(), N(sendinline), mvo() + ("account", "sendinline") + ("permission", name(config::active_name)) + ("parent", name(config::owner_name)) + ("auth", authority(1, {key_weight{get_public_key("sendinline", "active"), 1}}, { + permission_level_weight{{"sendinline", config::rem_code_name}, 1} + } + )) + ); + produce_blocks(); + + action approve_act = get_action(N(rem.msig), N(approve), {}, mvo() + ("proposer", "bob") + ("proposal_name", "first") + ("level", permission_level{N(sendinline), config::active_name}) + ); + + transaction trx = reqauth( N(alice), {permission_level{N(alice), N(perm)}}, abi_serializer_max_time ); + + base_tester::push_action( N(rem.msig), N(propose), N(bob), mvo() + ("proposer", "bob") + ("proposal_name", "first") + ("trx", trx) + ("requested", std::vector{{ N(sendinline), config::active_name }}) + ); + produce_blocks(); + + base_tester::push_action( N(sendinline), N(send), N(bob), mvo() + ("contract", "rem.msig") + ("action_name", "approve") + ("auths", std::vector{{N(sendinline), config::active_name}}) + ("payload", approve_act.data) + ); + produce_blocks(); + + trx_trace = base_tester::push_action( N(rem.msig), N(exec), N(bob), mvo() + ("proposer", "bob") + ("proposal_name", "first") + ("executer", "bob") + ); + + BOOST_REQUIRE( bool(trx_trace) ); + BOOST_REQUIRE( trx_trace->receipt.valid() ); + BOOST_REQUIRE_EQUAL( transaction_receipt::executed, trx_trace->receipt->status ); + BOOST_REQUIRE_EQUAL( 2, trx_trace->action_traces.size() ); + + BOOST_REQUIRE_EQUAL( fc::unsigned_int{1}, trx_trace->action_traces.at(0).action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{0}, trx_trace->action_traces.at(0).creator_action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{0}, trx_trace->action_traces.at(0).closest_unnotified_ancestor_action_ordinal ); + BOOST_REQUIRE_EQUAL( name{N(rem.msig)}, action_name{trx_trace->action_traces.at(0).receiver} ); + BOOST_REQUIRE_EQUAL( name{N(rem.msig)}, name{trx_trace->action_traces.at(0).act.account} ); + BOOST_REQUIRE_EQUAL( name{N(exec)}, name{trx_trace->action_traces.at(0).act.name} ); + BOOST_REQUIRE_EQUAL( name{N(bob)}, name{trx_trace->action_traces.at(0).act.authorization[0].actor} ); + BOOST_REQUIRE_EQUAL( name{N(active)}, name{trx_trace->action_traces.at(0).act.authorization[0].permission} ); + + BOOST_REQUIRE_EQUAL( fc::unsigned_int{2}, trx_trace->action_traces.at(1).action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{1}, trx_trace->action_traces.at(1).creator_action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{1}, trx_trace->action_traces.at(1).closest_unnotified_ancestor_action_ordinal ); + BOOST_REQUIRE_EQUAL( name{N(rem)}, action_name{trx_trace->action_traces.at(1).receiver} ); + BOOST_REQUIRE_EQUAL( name{N(rem)}, name{trx_trace->action_traces.at(1).act.account} ); + BOOST_REQUIRE_EQUAL( name{N(reqauth)}, name{trx_trace->action_traces.at(1).act.name} ); + BOOST_REQUIRE_EQUAL( name{N(alice)}, name{trx_trace->action_traces.at(1).act.authorization[0].actor} ); + BOOST_REQUIRE_EQUAL( name{N(perm)}, name{trx_trace->action_traces.at(1).act.authorization[0].permission} ); + +} FC_LOG_AND_RETHROW() + BOOST_AUTO_TEST_SUITE_END() diff --git a/tests/eosio.wrap_tests.cpp b/tests/eosio.wrap_tests.cpp index f0eb3f194..858451d9b 100644 --- a/tests/eosio.wrap_tests.cpp +++ b/tests/eosio.wrap_tests.cpp @@ -220,32 +220,42 @@ BOOST_FIXTURE_TEST_CASE( wrap_with_msig, eosio_wrap_tester ) try { vector traces; control->applied_transaction.connect( [&]( std::tuple p ) { - const auto& t = std::get<0>(p); - if( t->scheduled ) { - traces.push_back( t ); - } + traces.push_back( std::get<0>(p) ); } ); // Now the proposal should be ready to execute - push_action( N(rem.msig), N(exec), N(alice), mvo() - ("proposer", "carol") - ("proposal_name", "first") - ("executer", "alice") + transaction_trace_ptr trx_trace; + trx_trace = push_action( N(rem.msig), N(exec), N(alice), mvo() + ("proposer", "carol") + ("proposal_name", "first") + ("executer", "alice") ); - produce_block(); - - BOOST_REQUIRE_EQUAL( 2, traces.size() ); - - BOOST_REQUIRE_EQUAL( 1, traces[0]->action_traces.size() ); - BOOST_REQUIRE_EQUAL( N(rem.wrap), name{traces[0]->action_traces[0].act.account} ); - BOOST_REQUIRE_EQUAL( N(exec), name{traces[0]->action_traces[0].act.name} ); - BOOST_REQUIRE_EQUAL( transaction_receipt::executed, traces[0]->receipt->status ); - - BOOST_REQUIRE_EQUAL( 1, traces[1]->action_traces.size() ); - BOOST_REQUIRE_EQUAL( config::system_account_name, name{traces[1]->action_traces[0].act.account} ); - BOOST_REQUIRE_EQUAL( N(reqauth), name{traces[1]->action_traces[0].act.name} ); - BOOST_REQUIRE_EQUAL( transaction_receipt::executed, traces[1]->receipt->status ); + BOOST_REQUIRE( bool(trx_trace) ); + BOOST_REQUIRE( trx_trace->receipt.valid() ); + BOOST_REQUIRE_EQUAL( transaction_receipt::executed, trx_trace->receipt->status ); + BOOST_REQUIRE_EQUAL( 2, trx_trace->action_traces.size() ); + + BOOST_REQUIRE_EQUAL( fc::unsigned_int{1}, trx_trace->action_traces.at(0).action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{0}, trx_trace->action_traces.at(0).creator_action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{0}, trx_trace->action_traces.at(0).closest_unnotified_ancestor_action_ordinal ); + // EOSIO 1.8 N() macro returns a uint64_t rather than a struct name + BOOST_REQUIRE_EQUAL( name{N(rem.msig)}, action_name{trx_trace->action_traces.at(0).receiver} ); + BOOST_REQUIRE_EQUAL( name{N(rem.msig)}, name{trx_trace->action_traces.at(0).act.account} ); + BOOST_REQUIRE_EQUAL( name{N(exec)}, name{trx_trace->action_traces.at(0).act.name} ); + BOOST_REQUIRE_EQUAL( name{N(alice)}, name{trx_trace->action_traces.at(0).act.authorization[0].actor} ); + BOOST_REQUIRE_EQUAL( name{N(active)}, name{trx_trace->action_traces.at(0).act.authorization[0].permission} ); + + BOOST_REQUIRE_EQUAL( fc::unsigned_int{2}, trx_trace->action_traces.at(1).action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{1}, trx_trace->action_traces.at(1).creator_action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{1}, trx_trace->action_traces.at(1).closest_unnotified_ancestor_action_ordinal ); + BOOST_REQUIRE_EQUAL( name{N(rem.wrap)}, action_name{trx_trace->action_traces.at(1).receiver} ); + BOOST_REQUIRE_EQUAL( name{N(rem.wrap)}, name{trx_trace->action_traces.at(1).act.account} ); + BOOST_REQUIRE_EQUAL( name{N(exec)}, name{trx_trace->action_traces.at(1).act.name} ); + BOOST_REQUIRE_EQUAL( name{N(alice)}, name{trx_trace->action_traces.at(1).act.authorization[0].actor} ); + BOOST_REQUIRE_EQUAL( name{N(active)}, name{trx_trace->action_traces.at(1).act.authorization[0].permission} ); + BOOST_REQUIRE_EQUAL( name{N(rem.wrap)}, name{trx_trace->action_traces.at(1).act.authorization[1].actor} ); + BOOST_REQUIRE_EQUAL( name{N(active)}, name{trx_trace->action_traces.at(1).act.authorization[1].permission} ); } FC_LOG_AND_RETHROW() @@ -336,32 +346,42 @@ BOOST_FIXTURE_TEST_CASE( wrap_with_msig_producers_change, eosio_wrap_tester ) tr vector traces; control->applied_transaction.connect( [&]( std::tuple p ) { - const auto& t = std::get<0>(p); - if( t->scheduled ) { - traces.push_back( t ); - } + traces.push_back( std::get<0>(p) ); } ); // Now the proposal should be ready to execute - push_action( N(rem.msig), N(exec), N(alice), mvo() - ("proposer", "carol") - ("proposal_name", "first") - ("executer", "alice") + transaction_trace_ptr trx_trace; + trx_trace = push_action( N(rem.msig), N(exec), N(alice), mvo() + ("proposer", "carol") + ("proposal_name", "first") + ("executer", "alice") ); - produce_block(); - - BOOST_REQUIRE_EQUAL( 2, traces.size() ); - - BOOST_REQUIRE_EQUAL( 1, traces[0]->action_traces.size() ); - BOOST_REQUIRE_EQUAL( N(rem.wrap), name{traces[0]->action_traces[0].act.account} ); - BOOST_REQUIRE_EQUAL( N(exec), name{traces[0]->action_traces[0].act.name} ); - BOOST_REQUIRE_EQUAL( transaction_receipt::executed, traces[0]->receipt->status ); - - BOOST_REQUIRE_EQUAL( 1, traces[1]->action_traces.size() ); - BOOST_REQUIRE_EQUAL( config::system_account_name, name{traces[1]->action_traces[0].act.account} ); - BOOST_REQUIRE_EQUAL( N(reqauth), name{traces[1]->action_traces[0].act.name} ); - BOOST_REQUIRE_EQUAL( transaction_receipt::executed, traces[1]->receipt->status ); + BOOST_REQUIRE( bool(trx_trace) ); + BOOST_REQUIRE( trx_trace->receipt.valid() ); + BOOST_REQUIRE_EQUAL( transaction_receipt::executed, trx_trace->receipt->status ); + BOOST_REQUIRE_EQUAL( 2, trx_trace->action_traces.size() ); + + BOOST_REQUIRE_EQUAL( fc::unsigned_int{1}, trx_trace->action_traces.at(0).action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{0}, trx_trace->action_traces.at(0).creator_action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{0}, trx_trace->action_traces.at(0).closest_unnotified_ancestor_action_ordinal ); + // EOSIO 1.8 N() macro returns a uint64_t rather than a struct name + BOOST_REQUIRE_EQUAL( name{N(rem.msig)}, action_name{trx_trace->action_traces.at(0).receiver} ); + BOOST_REQUIRE_EQUAL( name{N(rem.msig)}, name{trx_trace->action_traces.at(0).act.account} ); + BOOST_REQUIRE_EQUAL( name{N(exec)}, name{trx_trace->action_traces.at(0).act.name} ); + BOOST_REQUIRE_EQUAL( name{N(alice)}, name{trx_trace->action_traces.at(0).act.authorization[0].actor} ); + BOOST_REQUIRE_EQUAL( name{N(active)}, name{trx_trace->action_traces.at(0).act.authorization[0].permission} ); + + BOOST_REQUIRE_EQUAL( fc::unsigned_int{2}, trx_trace->action_traces.at(1).action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{1}, trx_trace->action_traces.at(1).creator_action_ordinal ); + BOOST_REQUIRE_EQUAL( fc::unsigned_int{1}, trx_trace->action_traces.at(1).closest_unnotified_ancestor_action_ordinal ); + BOOST_REQUIRE_EQUAL( name{N(rem.wrap)}, action_name{trx_trace->action_traces.at(1).receiver} ); + BOOST_REQUIRE_EQUAL( name{N(rem.wrap)}, name{trx_trace->action_traces.at(1).act.account} ); + BOOST_REQUIRE_EQUAL( name{N(exec)}, name{trx_trace->action_traces.at(1).act.name} ); + BOOST_REQUIRE_EQUAL( name{N(alice)}, name{trx_trace->action_traces.at(1).act.authorization[0].actor} ); + BOOST_REQUIRE_EQUAL( name{N(active)}, name{trx_trace->action_traces.at(1).act.authorization[0].permission} ); + BOOST_REQUIRE_EQUAL( name{N(rem.wrap)}, name{trx_trace->action_traces.at(1).act.authorization[1].actor} ); + BOOST_REQUIRE_EQUAL( name{N(active)}, name{trx_trace->action_traces.at(1).act.authorization[1].permission} ); } FC_LOG_AND_RETHROW() diff --git a/tests/test_contracts/CMakeLists.txt b/tests/test_contracts/CMakeLists.txt new file mode 100644 index 000000000..87c42896a --- /dev/null +++ b/tests/test_contracts/CMakeLists.txt @@ -0,0 +1,2 @@ +add_subdirectory(sendinline) + diff --git a/tests/test_contracts/sendinline/CMakeLists.txt b/tests/test_contracts/sendinline/CMakeLists.txt new file mode 100644 index 000000000..9c846f4c0 --- /dev/null +++ b/tests/test_contracts/sendinline/CMakeLists.txt @@ -0,0 +1,4 @@ +find_package(eosio.cdt) +add_contract(sendinline sendinline ${CMAKE_CURRENT_SOURCE_DIR}/src/sendinline.cpp) +target_include_directories(sendinline.wasm PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include) +set_target_properties(sendinline.wasm PROPERTIES RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_BINARY_DIR}") diff --git a/tests/test_contracts/sendinline/src/sendinline.cpp b/tests/test_contracts/sendinline/src/sendinline.cpp new file mode 100644 index 000000000..88c0c01e1 --- /dev/null +++ b/tests/test_contracts/sendinline/src/sendinline.cpp @@ -0,0 +1,25 @@ +#include +#include +#include +#include +#include + +/// `eosio.code` is a virtual permission (there is no private or public +/// key associated with it). Therefore, this test tests how `eosio.msig` +/// contract reacts to a smart contract submitting a proposal and +/// approving/unnapproving itself. +class [[eosio::contract]] +sendinline : public eosio::contract { +public: + using contract::contract; + + [[eosio::action]] + void send( eosio::name contract, eosio::name action_name, std::vector auths, std::vector payload) { + eosio::action act; + act.account = contract; + act.name = action_name; + act.authorization = auths; + act.data = std::move(payload); + act.send(); + } +};