diff --git a/.github/workflows/ubuntu-2004.yml b/.github/workflows/ubuntu-2004.yml index 13ca5bcf84..ce94a058d3 100644 --- a/.github/workflows/ubuntu-2004.yml +++ b/.github/workflows/ubuntu-2004.yml @@ -15,6 +15,14 @@ jobs: name: Ubuntu 20.04 | Build runs-on: ubuntu-latest steps: + - name: Check for ccache disable + id: no-ccache + uses: saulmaldonado/skip-workflow@b934401f1ef10783ab5e7f25a78a31959a4fbad3 + with: + github-token: ${{ secrets.GITHUB_TOKEN }} + search: '["pull_request"]' + pr-message: 'body' + phrase: '[no-ccache]' - name: Timestamp id: ccache_cache_timestamp shell: cmake -P {0} @@ -27,6 +35,7 @@ jobs: submodules: recursive fetch-depth: 0 - name: Preserve ccache + if: ${{ !steps.no-ccache.outputs.skip }} uses: actions/cache@v1.1.0 with: path: .ccache @@ -34,6 +43,7 @@ jobs: restore-keys: | $ubuntu-20.04-ccache_make- - name: Build + if: ${{ !steps.no-ccache.outputs.skip }} run: | set -e export CCACHE_DIR=${GITHUB_WORKSPACE}/.ccache @@ -63,6 +73,30 @@ jobs: echo ===== tar -pczf build.tar.gz build tar -pczf usr_local.tar.gz installed + - name: Build without ccache + if: ${{ steps.no-ccache.outputs.skip }} + run: | + set -e + export CCACHE_DISABLE=1 + export DOCKER="docker run --rm -v ${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE} -w ${GITHUB_WORKSPACE} -e CCACHE_DISABLE --user $(id -u):$(id -g) ${UBUNTU_2004_IMAGE}" + export DOCKER_ROOT="docker run --rm -v ${GITHUB_WORKSPACE}:${GITHUB_WORKSPACE} -e CCACHE_DISABLE -w ${GITHUB_WORKSPACE} ${UBUNTU_2004_IMAGE}" + docker pull ${UBUNTU_2004_IMAGE} + echo ===== + mkdir build + ${DOCKER} bash -c "cd build && cmake -DCMAKE_BUILD_TYPE=Release -UCMAKE_CXX_COMPILER_LAUNCHER -UCMAKE_C_COMPILER_LAUNCHER .." + echo ===== + ${DOCKER} bash -c "cd build && make -j $(nproc)" + echo ===== + mkdir install + ${DOCKER_ROOT} bash -c "cd build && DESTDIR=${GITHUB_WORKSPACE}/installed make -j $(nproc) dev-install" + echo ===== + ls -la ${GITHUB_WORKSPACE} + echo ===== + ${DOCKER} build/bin/nodeos --version + ${DOCKER} build/bin/nodeos --full-version + echo ===== + tar -pczf build.tar.gz build + tar -pczf usr_local.tar.gz installed - name: Upload build uses: actions/upload-artifact@v1 with: diff --git a/programs/cleos/main.cpp b/programs/cleos/main.cpp index 5d8c018a81..3d73abf31c 100644 --- a/programs/cleos/main.cpp +++ b/programs/cleos/main.cpp @@ -96,22 +96,12 @@ Usage: ./cleos create account [OPTIONS] creator name OwnerKey ActiveKey #include #include #include -#include -#include #include #include -#include #include -#include #pragma pop_macro("N") -#include -#include -#include -#include -#include - #include #define CLI11_HAS_FILESYSTEM 0 @@ -164,9 +154,10 @@ std::string clean_output( std::string str ) { return fc::escape_string( str, nullptr, escape_control_chars ); } -string url = "http://127.0.0.1:8888/"; +string default_url = "http://127.0.0.1:8888/"; string default_wallet_url = "unix://" + (determine_home_directory() / "eosio-wallet" / (string(key_store_executable_name) + ".sock")).string(); string wallet_url; //to be set to default_wallet_url in main +std::map abi_files_override; bool no_verify = false; vector headers; @@ -282,7 +273,8 @@ class signing_keys_option { try { std::vector keys = json_keys.template as>(); signing_keys = std::move(keys); - } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid public key array format '${data}'", ("data", fc::json::to_string(json_keys, fc::time_point::maximum()))) + } EOS_RETHROW_EXCEPTIONS(public_key_type_exception, "Invalid public key array format '${data}'", + ("data", fc::json::to_string(json_keys, fc::time_point::maximum()))) } } return signing_keys; @@ -328,7 +320,7 @@ fc::variant call( const std::string& url, } catch(boost::system::system_error& e) { std::string exec_name; - if(url == ::url) { + if(url == ::default_url) { exec_name = node_executable_name; } else if(url == ::wallet_url) { exec_name = key_store_executable_name; @@ -342,18 +334,18 @@ fc::variant call( const std::string& url, template fc::variant call( const std::string& path, - const T& v ) { return call( url, path, fc::variant(v) ); } + const T& v ) { return call( ::default_url, path, fc::variant( v) ); } template<> fc::variant call( const std::string& url, const std::string& path) { return call( url, path, fc::variant() ); } eosio::chain_apis::read_only::get_consensus_parameters_results get_consensus_parameters() { - return call(url, get_consensus_parameters_func).as(); + return call(::default_url, get_consensus_parameters_func).as(); } eosio::chain_apis::read_only::get_info_results get_info() { - return call(url, get_info_func).as(); + return call(::default_url, get_info_func).as(); } string generate_nonce_string() { @@ -364,32 +356,34 @@ chain::action generate_nonce_action() { return chain::action( {}, config::null_account_name, name("nonce"), fc::raw::pack(fc::time_point::now().time_since_epoch().count())); } -auto abi_serializer_resolver_empty = [](const name& account) -> std::optional { - return std::optional(); -}; - //resolver for ABI serializer to decode actions in proposed transaction in multisig contract auto abi_serializer_resolver = [](const name& account) -> std::optional { static unordered_map > abi_cache; auto it = abi_cache.find( account ); if ( it == abi_cache.end() ) { - const auto raw_abi_result = call(get_raw_abi_func, fc::mutable_variant_object("account_name", account)); - const auto raw_abi_blob = raw_abi_result["abi"].as_blob().data; - std::optional abis; - if (raw_abi_blob.size() != 0) { - abis.emplace(fc::raw::unpack(raw_abi_blob), abi_serializer_max_time); + if (abi_files_override.find(account) != abi_files_override.end()) { + abis.emplace( fc::json::from_file(abi_files_override[account]).as(), abi_serializer::create_yield_function( abi_serializer_max_time )); } else { - std::cerr << "ABI for contract " << account.to_string() << " not found. Action data will be shown in hex only." << std::endl; + const auto raw_abi_result = call(get_raw_abi_func, fc::mutable_variant_object("account_name", account)); + const auto raw_abi_blob = raw_abi_result["abi"].as_blob().data; + if (raw_abi_blob.size() != 0) { + abis.emplace(fc::raw::unpack(raw_abi_blob), abi_serializer::create_yield_function( abi_serializer_max_time )); + } else { + std::cerr << "ABI for contract " << account.to_string() << " not found. Action data will be shown in hex only." << std::endl; + } } abi_cache.emplace( account, abis ); return abis; } - return it->second; }; +auto abi_serializer_resolver_empty = [](const name& account) -> std::optional { + return std::optional(); +}; + void prompt_for_wallet_password(string& pw, const string& name) { if(pw.size() == 0 && name != "SecureEnclave") { std::cout << localized("password: "); @@ -1018,7 +1012,7 @@ struct set_action_permission_subcommand { string requirementStr; set_action_permission_subcommand(CLI::App* actionRoot) { - auto permissions = actionRoot->add_subcommand("permission", localized("set parmaters dealing with account permissions")); + auto permissions = actionRoot->add_subcommand("permission", localized("Set paramaters dealing with account permissions")); permissions->add_option("account", accountStr, localized("The account to set/delete a permission authority for"))->required(); permissions->add_option("code", codeStr, localized("The account that owns the code for the action"))->required(); permissions->add_option("type", typeStr, localized("The type of the action"))->required(); @@ -1220,7 +1214,7 @@ struct create_account_subcommand { if( active_key_str.empty() ) { active = owner; - } else if ( active_key_str.find('{') != string::npos ) { + } else if ( active_key_str.find('{') != string::npos ) { try{ active = parse_json_authority_or_key(active_key_str); } EOS_RETHROW_EXCEPTIONS( explained_exception, "Invalid active authority: ${authority}", ("authority", owner_key_str) ) @@ -2704,7 +2698,26 @@ CLI::callback_t header_opt_callback = [](CLI::results_t res) { return true; }; +CLI::callback_t abi_files_overide_callback = [](CLI::results_t account_abis) { + for (vector::iterator itr = account_abis.begin(); itr != account_abis.end(); ++itr) { + size_t delim = itr->find(":"); + std::string acct_name, abi_path; + if (delim != std::string::npos) { + acct_name = itr->substr(0, delim); + abi_path = itr->substr(delim + 1); + } + if (acct_name.length() == 0 || abi_path.length() == 0) { + std::cerr << "please specify --abi-file in form of :.\n"; + return false; + } + abi_files_override[name(acct_name)] = abi_path; + } + return true; +}; + + int main( int argc, char** argv ) { + fc::logger::get(DEFAULT_LOGGER).set_log_level(fc::log_level::debug); context = eosio::client::http::create_http_context(); wallet_url = default_wallet_url; @@ -2717,9 +2730,10 @@ int main( int argc, char** argv ) { app.add_option( "--wallet-host", obsoleted_option_host_port, localized("The host where ${k} is running", ("k", key_store_executable_name)) )->group(""); app.add_option( "--wallet-port", obsoleted_option_host_port, localized("The port where ${k} is running", ("k", key_store_executable_name)) )->group(""); - app.add_option( "-u,--url", url, localized("The http/https URL where ${n} is running", ("n", node_executable_name)), true ); + app.add_option( "-u,--url", ::default_url, localized( "The http/https URL where ${n} is running", ("n", node_executable_name)), true ); app.add_option( "--wallet-url", wallet_url, localized("The http/https URL where ${k} is running", ("k", key_store_executable_name)), true ); + app.add_option( "--abi-file", abi_files_overide_callback, localized("In form of :, use a local abi file for serialization and deserialization instead of getting the abi data from the blockchain; repeat this option to pass multiple abi files for different contracts"))->type_size(0, 1000); app.add_option( "-r,--header", header_opt_callback, localized("Pass specific HTTP header; repeat this option to pass multiple headers")); app.add_flag( "-n,--no-verify", no_verify, localized("Don't verify peer certificate when using HTTPS")); app.add_flag( "--no-auto-" + string(key_store_executable_name), no_auto_keosd, localized("Don't automatically launch a ${k} if one is not currently running", ("k", key_store_executable_name))); @@ -2858,7 +2872,7 @@ int main( int argc, char** argv ) { }); // validate subcommand - auto validate = app.add_subcommand("validate", localized("Validate transactions")); + auto validate = app.add_subcommand("validate", localized("Validate transactions")); validate->require_subcommand(); // validate signatures @@ -3496,6 +3510,7 @@ int main( int argc, char** argv ) { string amount; string memo; bool pay_ram = false; + auto transfer = app.add_subcommand("transfer", localized("Transfer tokens from account to account")); transfer->add_option("sender", sender, localized("The account sending tokens"))->required(); transfer->add_option("recipient", recipient, localized("The account receiving tokens"))->required(); @@ -3529,27 +3544,27 @@ int main( int argc, char** argv ) { auto connect = net->add_subcommand("connect", localized("Start a new connection to a peer")); connect->add_option("host", new_host, localized("The hostname:port to connect to."))->required(); connect->callback([&] { - const auto& v = call(url, net_connect, new_host); + const auto& v = call(::default_url, net_connect, new_host); std::cout << fc::json::to_pretty_string(v) << std::endl; }); auto disconnect = net->add_subcommand("disconnect", localized("Close an existing connection")); disconnect->add_option("host", new_host, localized("The hostname:port to disconnect from."))->required(); disconnect->callback([&] { - const auto& v = call(url, net_disconnect, new_host); + const auto& v = call(::default_url, net_disconnect, new_host); std::cout << fc::json::to_pretty_string(v) << std::endl; }); auto status = net->add_subcommand("status", localized("Status of existing connection")); status->add_option("host", new_host, localized("The hostname:port to query status of connection"))->required(); status->callback([&] { - const auto& v = call(url, net_status, new_host); + const auto& v = call(::default_url, net_status, new_host); std::cout << fc::json::to_pretty_string(v) << std::endl; }); auto connections = net->add_subcommand("peers", localized("Status of all existing peers")); connections->callback([&] { - const auto& v = call(url, net_connections); + const auto& v = call(::default_url, net_connections); std::cout << fc::json::to_pretty_string(v) << std::endl; }); @@ -4245,7 +4260,7 @@ int main( int argc, char** argv ) { auto cancel = msig->add_subcommand("cancel", localized("Cancel proposed transaction")); add_standard_transaction_options_plus_signing(cancel, "canceler@active"); cancel->add_option("proposer", proposer, localized("The proposer name (string)"))->required(); - cancel->add_option("proposal_name", proposal_name, localized("proposal name (string)"))->required(); + cancel->add_option("proposal_name", proposal_name, localized("The proposal name (string)"))->required(); cancel->add_option("canceler", canceler, localized("The canceler name (string)")); cancel->callback([&]() { auto accountPermissions = get_account_permissions(tx_permission); diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index 85f9992baf..26f3fe7024 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -216,6 +216,7 @@ set_property(TEST nodeos_retry_transaction_lr_test PROPERTY LABELS long_running_ add_test(NAME cli_test COMMAND tests/cli_test.py WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) +set_property(TEST cli_test PROPERTY LABELS nonparallelizable_tests) add_test(NAME larger_lib_test COMMAND tests/large-lib-test.py WORKING_DIRECTORY ${CMAKE_BINARY_DIR}) set_property(TEST larger_lib_test PROPERTY LABELS nonparallelizable_tests) diff --git a/tests/cli_test.py b/tests/cli_test.py index bed27a34ae..ceb231824c 100755 --- a/tests/cli_test.py +++ b/tests/cli_test.py @@ -6,7 +6,19 @@ import subprocess import re +import os +import time +import shutil +import signal +from testUtils import Account +from testUtils import Utils +from Cluster import Cluster +from Node import Node +from Node import ReturnType +from WalletMgr import WalletMgr + +testSuccessful=False def nodeos_help_test(): """Test that nodeos help contains option descriptions""" @@ -38,7 +50,7 @@ def cli11_bugfix_test(): # Make sure that the command failed because of the connection error, # not the command line parsing error. - assert(b'Failed http request to nodeos' in completed_process.stderr) + assert(b'Connection refused' in completed_process.stderr) def cli11_optional_option_arg_test(): @@ -56,7 +68,6 @@ def cli11_optional_option_arg_test(): '-c', chain, '-k', key, '{}']) assert(b'signatures' in output) - def cleos_sign_test(): """Test that sign can on both regular and packed transactions""" chain = 'cf057bbfb72640471fd910bcb67639c22df9f92470936cddc1ade0e2f2e7dc4f' @@ -131,6 +142,282 @@ def cleos_sign_test(): assert(b'signatures' in output) assert(b'"signatures": []' not in output) +def processCleosCommand(cmd): + outs = None + errs = None + try: + popen=subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE) + outs, errs = popen.communicate() + popen.wait() + except subprocess.CalledProcessError as ex: + print(ex.output) + return outs, errs + +def cleos_abi_file_test(): + """Test option --abi-file """ + token_abi_path = os.path.abspath(os.getcwd() + '/../unittests/contracts/eosio.token/eosio.token.abi') + system_abi_path = os.path.abspath(os.getcwd() + '/../unittests/contracts/eosio.system/eosio.system.abi') + token_abi_file_arg = 'eosio.token' + ':' + token_abi_path + system_abi_file_arg = 'eosio' + ':' + system_abi_path + + # no option --abi-file + account = 'eosio.token' + action = 'transfer' + unpacked_action_data = '{"from":"aaa","to":"bbb","quantity":"10.0000 SYS","memo":"hello"}' + # use URL http://127.0.0.1:12345 to make sure cleos not to connect to any running nodeos + cmd = ['./programs/cleos/cleos', '-u', 'http://127.0.0.1:12345', 'convert', 'pack_action_data', account, action, unpacked_action_data] + outs, errs = processCleosCommand(cmd) + assert(b'Connection refused' in errs) + + # invalid option --abi-file + invalid_abi_arg = 'eosio.token' + ' ' + token_abi_path + cmd = ['./programs/cleos/cleos', '-u', 'http://127.0.0.1:12345', '--abi-file', invalid_abi_arg, 'convert', 'pack_action_data', account, action, unpacked_action_data] + outs, errs = processCleosCommand(cmd) + print(errs) + assert(b'please specify --abi-file in form of :.' in errs) + + # pack token transfer data + account = 'eosio.token' + action = 'transfer' + unpacked_action_data = '{"from":"aaa","to":"bbb","quantity":"10.0000 SYS","memo":"hello"}' + packed_action_data = '0000000000008c31000000000000ce39a08601000000000004535953000000000568656c6c6f' + cmd = ['./programs/cleos/cleos', '-u','http://127.0.0.1:12345', '--abi-file', token_abi_file_arg, 'convert', 'pack_action_data', account, action, unpacked_action_data] + outs, errs = processCleosCommand(cmd) + actual = outs.strip() + assert(actual.decode('utf-8') == packed_action_data) + + # unpack token transfer data + cmd = ['./programs/cleos/cleos', '-u','http://127.0.0.1:12345', '--abi-file', token_abi_file_arg, 'convert', 'unpack_action_data', account, action, packed_action_data] + outs, errs = processCleosCommand(cmd) + assert(b'"from": "aaa"' in outs) + assert(b'"to": "bbb"' in outs) + assert(b'"quantity": "10.0000 SYS"' in outs) + assert(b'"memo": "hello"' in outs) + + # pack account create data + account = 'eosio' + action = 'newaccount' + + unpacked_action_data = """{ + "creator": "eosio", + "name": "bob", + "owner": { + "threshold": 1, + "keys": [{ + "key": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", + "weight": 1 + } + ], + "accounts": [], + "waits": [] + }, + "active": { + "threshold": 1, + "keys": [{ + "key": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", + "weight": 1 + } + ], + "accounts": [], + "waits": [] + } + }""" + + cmd = ['./programs/cleos/cleos', '-u','http://127.0.0.1:12345', '--abi-file', system_abi_file_arg, 'convert', 'pack_action_data', account, action, unpacked_action_data] + packed_action_data = '0000000000ea30550000000000000e3d01000000010002c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf0100000001000000010002c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf01000000' + outs, errs = processCleosCommand(cmd) + actual = outs.strip() + assert(actual.decode('utf-8') == packed_action_data) + + # unpack account create data + cmd = ['./programs/cleos/cleos', '-u','http://127.0.0.1:12345', '--abi-file', system_abi_file_arg, 'convert', 'unpack_action_data', account, action, packed_action_data] + outs, errs = processCleosCommand(cmd) + assert(b'"creator": "eosio"' in outs) + assert(b'"name": "bob"' in outs) + + # pack transaction + unpacked_trx = """{ + "expiration": "2021-07-18T04:21:14", + "ref_block_num": 494, + "ref_block_prefix": 2118878731, + "max_net_usage_words": 0, + "max_cpu_usage_ms": 0, + "delay_sec": 0, + "context_free_actions": [], + "actions": [{ + "account": "eosio", + "name": "newaccount", + "authorization": [{ + "actor": "eosio", + "permission": "active" + } + ], + "data": { + "creator": "eosio", + "name": "bob", + "owner": { + "threshold": 1, + "keys": [{ + "key": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", + "weight": 1 + } + ], + "accounts": [], + "waits": [] + }, + "active": { + "threshold": 1, + "keys": [{ + "key": "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV", + "weight": 1 + } + ], + "accounts": [], + "waits": [] + } + }, + "hex_data": "0000000000ea30550000000000000e3d01000000010002c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf0100000001000000010002c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf01000000" + }, + { + "account": "eosio.token", + "name": "transfer", + "authorization": [{ + "actor": "aaa", + "permission": "active" + } + ], + "data": { + "from": "aaa", + "to": "bbb", + "quantity": "10.0000 SYS", + "memo": "hello" + }, + "hex_data": "0000000000008c31000000000000ce39a08601000000000004535953000000000568656c6c6f" + } + ], + "signatures": [ + "SIG_K1_K3LfbB7ZV2DNBu67iSn3yUMseTdiwoT49gAcwSZVT1QTvGXVHjkcvKqhentCW4FJngZJ1H9gBRSWgo9UPiWEXWHyKpXNCZ" + ], + "context_free_data": [] + }""" + + expected_output = b'3aacf360ee010b864b7e00000000020000000000ea305500409e9a2264b89a010000000000ea305500000000a8ed3232660000000000ea30550000000000000e3d01000000010002c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf0100000001000000010002c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf0100000000a6823403ea3055000000572d3ccdcd010000000000008c3100000000a8ed3232260000000000008c31000000000000ce39a08601000000000004535953000000000568656c6c6f00' + cmd = ['./programs/cleos/cleos', '-u','http://127.0.0.1:12345', '--abi-file', system_abi_file_arg, token_abi_file_arg, 'convert', 'pack_transaction', '--pack-action-data', unpacked_trx] + outs, errs = processCleosCommand(cmd) + assert(expected_output in outs) + + # unpack transaction + packed_trx = """{ + "signatures": [ + "SIG_K1_K3LfbB7ZV2DNBu67iSn3yUMseTdiwoT49gAcwSZVT1QTvGXVHjkcvKqhentCW4FJngZJ1H9gBRSWgo9UPiWEXWHyKpXNCZ" + ], + "compression": "none", + "packed_context_free_data": "", + "packed_trx": "3aacf360ee010b864b7e00000000020000000000ea305500409e9a2264b89a010000000000ea305500000000a8ed3232660000000000ea30550000000000000e3d01000000010002c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf0100000001000000010002c0ded2bc1f1305fb0faac5e6c03ee3a1924234985427b6167ca569d13df435cf0100000000a6823403ea3055000000572d3ccdcd010000000000008c3100000000a8ed3232260000000000008c31000000000000ce39a08601000000000004535953000000000568656c6c6f00" + }""" + cmd = ['./programs/cleos/cleos', '-u','http://127.0.0.1:12345', '--abi-file', system_abi_file_arg, token_abi_file_arg, 'convert', 'unpack_transaction', '--unpack-action-data', packed_trx] + outs, errs = processCleosCommand(cmd) + assert(b'"creator": "eosio"' in outs) + assert(b'"name": "bob"' in outs) + + assert(b'"from": "aaa"' in outs) + assert(b'"to": "bbb"' in outs) + assert(b'"quantity": "10.0000 SYS"' in outs) + assert(b'"memo": "hello"' in outs) + +def abi_file_with_nodeos_test(): + # push action token transfer with option `--abi-file` + global testSuccessful + try: + contractDir = os.path.abspath(os.getcwd() + "/../unittests/contracts/eosio.token") + # make a malicious abi file by switching 'from' and 'to' in eosio.token.abi + token_abi_path = os.path.abspath(os.getcwd() + '/../unittests/contracts/eosio.token/eosio.token.abi') + token_abi_file_arg = 'eosio.token' + ':' + token_abi_path + malicious_token_abi_path = os.path.abspath(os.getcwd() + '/../unittests/contracts/eosio.token/malicious.eosio.token.abi') + shutil.copyfile(token_abi_path, malicious_token_abi_path) + replaces = [["from", "malicious"], ["to", "from"], ["malicious", "to"]] + for replace in replaces: + with open(malicious_token_abi_path, 'r+') as f: + abi = f.read() + abi = re.sub(replace[0], replace[1], abi) + f.seek(0) + f.write(abi) + f.truncate() + + tries = 30 + while not Utils.arePortsAvailable(set(range(8888, 8889))): + Utils.Print("ERROR: Another process is listening on nodeos test port 8888. wait...") + if tries == 0: + assert False + tries -= 1 + time.sleep(2) + nodeId = 'bios' + data_dir = Utils.getNodeDataDir(nodeId) + assert not os.path.exists(data_dir), 'data_dir exists' + os.mkdir(data_dir) + walletMgr = WalletMgr(True) + walletMgr.launch() + node = Node('localhost', 8888, nodeId, cmd="./programs/nodeos/nodeos -e -p eosio --plugin eosio::trace_api_plugin --trace-no-abis --plugin eosio::producer_plugin --plugin eosio::producer_api_plugin --plugin eosio::chain_api_plugin --plugin eosio::chain_plugin --plugin eosio::http_plugin --access-control-allow-origin=* --http-validate-host=false --resource-monitor-not-shutdown-on-threshold-exceeded " + "--data-dir " + data_dir + " --config-dir " + data_dir, walletMgr=walletMgr) + node.verifyAlive() # Setting node state to not alive + node.relaunch(newChain=True, cachePopen=True) + accountNames = ["eosio", "eosio.token", "alice", "bob"] + accounts = [] + for name in accountNames: + account = Account(name) + account.ownerPrivateKey = account.activePrivateKey = "5KQwrPbwdL6PhXujxW37FSSQZ1JiwsST4cqQzDeyXtP79zkvFD3" + account.ownerPublicKey = account.activePublicKey = "EOS6MRyAjQq8ud7hVNYcfnVPJqcVpscN5So8BhtHuGYqET5GDW5CV" + accounts.append(account) + walletMgr.create('eosio', [accounts[0]]) + node.createAccount(accounts[1], accounts[0], stakedDeposit=0) + node.publishContract(accounts[1], contractDir, 'eosio.token.wasm', 'eosio.token.abi') + account = 'eosio.token' + action = 'create' + data = '{"issuer":"eosio.token","maximum_supply":"100000.0000 SYS","can_freeze":"0","can_recall":"0","can_whitelist":"0"}' + permission = '--permission eosio.token@active' + node.pushMessage(account, action, data, permission) + action = 'issue' + data = '{"from":"eosio.token","to":"eosio.token","quantity":"100000.0000 SYS","memo":"issue"}' + node.pushMessage(account, action, data, permission) + node.createAccount(accounts[2], accounts[0], stakedDeposit=0) + node.createAccount(accounts[3], accounts[0], stakedDeposit=0) + + node.transferFunds(accounts[1], accounts[2], '100.0000 SYS') + + node.processCleosCmd('set abi eosio.token ' + malicious_token_abi_path, 'set malicious eosio.token abi', returnType=ReturnType.raw) + + cmdArr = node._Node__transferFundsCmdArr(accounts[2], accounts[3], '25.0000 SYS', 'm', False, None, False, False, 90, False) + cmdArr.insert(6, '--print-request') + cmdArr.insert(7, '--abi-file') + cmdArr.insert(8, token_abi_file_arg) + Utils.runCmdArrReturnStr(cmdArr) + balance = node.getCurrencyBalance('eosio.token', 'alice') + assert balance == '75.0000 SYS\n' + testSuccessful=True + except Exception as e: + testSuccessful=False + Utils.Print(e.args) + finally: + if testSuccessful: + Utils.Print("Test succeeded.") + else: + Utils.Print("Test failed.") + if node: + if not node.killed: + if node.pid: + os.kill(node.pid, signal.SIGKILL) + if testSuccessful: + Utils.Print("Cleanup nodeos data.") + shutil.rmtree(data_dir) + + if malicious_token_abi_path: + if os.path.exists(malicious_token_abi_path): + os.remove(malicious_token_abi_path) + + walletMgr.killall() + if testSuccessful: + Utils.Print("Cleanup wallet data.") + walletMgr.cleanup() + nodeos_help_test() cleos_help_test(['--help']) @@ -142,3 +429,9 @@ def cleos_sign_test(): cli11_optional_option_arg_test() cleos_sign_test() + +cleos_abi_file_test() +abi_file_with_nodeos_test() + +errorCode = 0 if testSuccessful else 1 +exit(errorCode)