diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 000000000..11c809ca9 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,38 @@ +cmake_minimum_required(VERSION 3.5) +project(eosio_contracts VERSION 1.1.0) + +if(CMAKE_BUILD_TYPE STREQUAL "Debug") + set(TEST_BUILD_TYPE "Debug") + set(CMAKE_BUILD_TYPE "Release") +else() + set(TEST_BUILD_TYPE ${CMAKE_BUILD_TYPE}) +endif() +if(CXX_COMPILER STREQUAL "" OR NOT CXX_COMPILER) + set(CXX_COMPILER ${CMAKE_CXX_COMPILER}) +endif() + +if(EOSIO_INSTALL_PREFIX STREQUAL "" OR NOT EOSIO_INSTALL_PREFIX) + set(EOSIO_INSTALL_PREFIX "/usr/local") +endif() + +# if no wasm root is given use default path +if(WASM_ROOT STREQUAL "" OR NOT WASM_ROOT) + set(WASM_ROOT ${CMAKE_INSTALL_PREFIX}) +endif() + +list(APPEND CMAKE_MODULE_PATH ${WASM_ROOT}/lib/cmake) +include(EosioWasmToolchain) + +add_subdirectory(eosio.msig) +add_subdirectory(eosio.sudo) +add_subdirectory(eosio.system) +add_subdirectory(eosio.token) + +if (APPLE) + set(OPENSSL_ROOT "/usr/local/opt/openssl") +elseif (UNIX) + set(OPENSSL_ROOT "/usr/include/openssl") +endif() +set(SECP256K1_ROOT "/usr/local") + +include(UnitTestsExternalProject.txt) diff --git a/README.md b/README.md index e48049cba..4aebc8a61 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ # eosio.contracts -## Version : 1.0.0 +## Version : 1.1.0 The design of the EOSIO blockchain calls for a number of smart contracts that are run at a privileged permission level in order to support functions such as block producer registration and voting, token staking for CPU and network bandwidth, RAM purchasing, multi-sig, etc. These smart contracts are referred to as the system, token, msig and sudo contracts. @@ -16,14 +16,14 @@ The following unpriviledged contract(s) are also part of the system. Dependencies: * [eosio v1.0.8](https://github.com/eosio/eos/tree/v1.0.8) -* [eosio.wasmsdk v1.0](https://github.com/eosio/eosio.wasmsdk/tree/v1.0) +* [eosio.wasmsdk v1.0.0](https://github.com/eosio/eosio.wasmsdk/tree/v1.0.0) To build the contracts and the unit tests: * First, ensure that your __eosio__ is compiled to the core symbol for the EOSIO blockchain that intend to deploy to. * Second, make sure that you have ```sudo make install```ed __eosio__. -* Then just run the ```build.sh``` in the top directory to build all the contracts and the unit tests for these contracts. +* Then just run the ```build.sh``` in the top directory to build all the contracts and the unit tests for these contracts. After build: -* The unit tests executable is placed in the _build/tests_ directory and is named __unit_test__. +* The unit tests executable is placed in the _build/tests_ and is named __unit_test__. * 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. diff --git a/UnitTestsExternalProject.txt b/UnitTestsExternalProject.txt new file mode 100644 index 000000000..73ccd2d91 --- /dev/null +++ b/UnitTestsExternalProject.txt @@ -0,0 +1,14 @@ +include(ExternalProject) +find_package(Git REQUIRED) +include(GNUInstallDirs) + +ExternalProject_Add( + contracts_unit_tests + CMAKE_ARGS -DCMAKE_BUILD_TYPE=${TEST_BUILD_TYPE} -DROOT_DIR=${CMAKE_SOURCE_DIR} -DEOSIO_INSTALL_PREFIX=${EOSIO_INSTALL_PREFIX} -DOPENSSL_INSTALL_PREFIX=${OPENSSL_ROOT} -DSECP256K1_INSTALL_LIB=${SECP256K1_ROOT} -DBOOST_ROOT=${BOOST_ROOT}/include -DCMAKE_CXX_COMPILER=${CXX_COMPILER} + + SOURCE_DIR ${CMAKE_SOURCE_DIR}/tests + BINARY_DIR ${CMAKE_SOURCE_DIR}/build/tests + BUILD_ALWAYS 1 + TEST_COMMAND "" + INSTALL_COMMAND "" +) diff --git a/build.sh b/build.sh index 7fec92e42..815e9f277 100755 --- a/build.sh +++ b/build.sh @@ -1,19 +1,26 @@ #! /bin/bash -contracts=( "eosio.token" - "eosio.system" - "eosio.msig" - "eosio.sudo" ) +printf "\t=========== Building eosio.contracts ===========\n\n" + +RED='\033[0;31m' +NC='\033[0m' + +#if [ ! -d "/usr/local/eosio" ]; then +# printf "${RED}Error, please ensure that eosio is installed correctly!\n\n${NC}" +# exit -1 +#fi + +if [ ! -d "/usr/local/eosio.wasmsdk" ]; then + printf "${RED}Error, please ensure that eosio.wasmsdk is installed correctly!\n\n${NC}" + exit -1 +fi unamestr=`uname` if [[ "${unamestr}" == 'Darwin' ]]; then - PREFIX=/usr/local - BOOST=/usr/local/include - OPENSSL=/usr/local/opt/openssl + BOOST=/usr/local + CXX_COMPILER=g++ else - PREFIX=~/opt - BOOST=~/opt/boost/include - OPENSSL=/usr/include/openssl + BOOST=~/opt/boost OS_NAME=$( cat /etc/os-release | grep ^NAME | cut -d'=' -f2 | sed 's/\"//gI' ) case "$OS_NAME" in @@ -47,35 +54,9 @@ else esac fi -EOSIO_PREFIX=/usr/local/eosio - -export BOOST=${BOOST} -export PREFIX=${PREFIX} -export INSTALL_PREFIX=/usr/local/eosio - -### Build all the contracts - -for contract in "${contracts[@]}"; do - pushd ${contract} &> /dev/null - echo "Building ${contract}..." - CONTRACT_NAME="${contract}" - ./build.sh - popd &> /dev/null -done - - -if [ "$1" == "notests" ]; then - exit 0 -fi - -### Build the unit tests -root_dir=`pwd` -pushd tests &> /dev/null +CORES=`getconf _NPROCESSORS_ONLN` mkdir -p build pushd build &> /dev/null -cmake -DCMAKE_CXX_COMPILER="${CXX_COMPILER}" -DROOT_DIR="${root_dir}" -DEOSIO_INSTALL_PREFIX="${EOSIO_PREFIX}" -DOPENSSL_INSTALL_PREFIX="${OPENSSL}" -DSECP256K1_INSTALL_LIB="${EOSIO_PREFIX}" -DBOOST_ROOT="${BOOST}" ../ -make -j8 -cp unit_test ../../ -popd &> /dev/null -rm -r build +cmake -DCXX_COMPILER="${CXX_COMPILER}" -DBOOST_ROOT="${BOOST}" -DEOSIO_INSTALL_PREFIX=/usr/local ../ +make -j${CORES} popd &> /dev/null diff --git a/eosio.msig/CMakeLists.txt b/eosio.msig/CMakeLists.txt new file mode 100644 index 000000000..c5dd83e7e --- /dev/null +++ b/eosio.msig/CMakeLists.txt @@ -0,0 +1,12 @@ +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/abi/eosio.msig.abi" "${CMAKE_CURRENT_SOURCE_DIR}/bin/eosio.msig" COPYONLY) + +add_executable(eosio.msig.wasm ${CMAKE_CURRENT_SOURCE_DIR}/src/eosio.msig.cpp) +target_include_directories(eosio.msig.wasm + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include) + +set_target_properties(eosio.msig.wasm + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin/eosio.msig") + +#install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION ${WASM_ROOT}/eosio.wasmsdk/include) diff --git a/eosio.msig/bin/eosio.msig/.gitignore b/eosio.msig/bin/eosio.msig/.gitignore new file mode 100644 index 000000000..db5b01ccb --- /dev/null +++ b/eosio.msig/bin/eosio.msig/.gitignore @@ -0,0 +1,2 @@ +eosio.msig.abi +eosio.msig.wasm diff --git a/eosio.sudo/CMakeLists.txt b/eosio.sudo/CMakeLists.txt new file mode 100644 index 000000000..f38dae570 --- /dev/null +++ b/eosio.sudo/CMakeLists.txt @@ -0,0 +1,11 @@ +add_executable(eosio.sudo.wasm ${CMAKE_CURRENT_SOURCE_DIR}/src/eosio.sudo.cpp) +target_include_directories(eosio.sudo.wasm + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include) + +set_target_properties(eosio.sudo.wasm + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin/eosio.sudo") + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/abi/eosio.sudo.abi" "${CMAKE_CURRENT_SOURCE_DIR}/bin/eosio.sudo" COPYONLY) +#install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION ${WASM_ROOT}/eosio.wasmsdk/include) diff --git a/eosio.sudo/bin/eosio.sudo/.gitignore b/eosio.sudo/bin/eosio.sudo/.gitignore new file mode 100644 index 000000000..35898c3b5 --- /dev/null +++ b/eosio.sudo/bin/eosio.sudo/.gitignore @@ -0,0 +1,2 @@ +eosio.sudo.abi +eosio.sudo.wasm diff --git a/eosio.system/CMakeLists.txt b/eosio.system/CMakeLists.txt new file mode 100644 index 000000000..b0bc44312 --- /dev/null +++ b/eosio.system/CMakeLists.txt @@ -0,0 +1,13 @@ +add_executable(eosio.system.wasm ${CMAKE_CURRENT_SOURCE_DIR}/src/eosio.system.cpp) +target_include_directories(eosio.system.wasm + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include + ${CMAKE_CURRENT_SOURCE_DIR}/../eosio.token/include) + +set_target_properties(eosio.system.wasm + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin/eosio.system") + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/abi/eosio.system.abi" "${CMAKE_CURRENT_SOURCE_DIR}/bin/eosio.system" COPYONLY) + +#install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION ${WASM_ROOT}/eosio.wasmsdk/include) diff --git a/eosio.system/abi/eosio.system.abi b/eosio.system/abi/eosio.system.abi index 87937c787..4e30369a7 100644 --- a/eosio.system/abi/eosio.system.abi +++ b/eosio.system/abi/eosio.system.abi @@ -240,7 +240,15 @@ {"name":"max_inline_action_size", "type":"uint32"}, {"name":"max_inline_action_depth", "type":"uint16"}, {"name":"max_authority_depth", "type":"uint16"} - + ] + },{ + "name": "eosio_global_state2", + "fields": [ + {"name":"new_ram_per_block", "type":"uint16"}, + {"name":"last_ram_increase", "type":"block_timestamp_type"}, + {"name":"last_block_num", "type":"block_timestamp_type"}, + {"name":"reserved", "type":"float64"}, + {"name":"revision", "type":"uint8"} ] },{ "name": "eosio_global_state", @@ -294,6 +302,12 @@ "fields": [ {"name":"max_ram_size", "type":"uint64"} ] + },{ + "name": "setramrate", + "base": "", + "fields": [ + {"name":"bytes_per_block", "type":"uint16"} + ] },{ "name": "regproxy", "base": "", @@ -474,6 +488,10 @@ "name": "setram", "type": "setram", "ricardian_contract": "" + },{ + "name": "setramrate", + "type": "setramrate", + "ricardian_contract": "Sets the number of new bytes of ram to create per block and resyncs bancor base connector balance" },{ "name": "bidname", "type": "bidname", @@ -535,6 +553,12 @@ "index_type": "i64", "key_names" : [], "key_types" : [] + },{ + "name": "global2", + "type": "eosio_global_state2", + "index_type": "i64", + "key_names" : [], + "key_types" : [] },{ "name": "voters", "type": "voter_info", diff --git a/eosio.system/bin/eosio.system/.gitignore b/eosio.system/bin/eosio.system/.gitignore new file mode 100644 index 000000000..105891a48 --- /dev/null +++ b/eosio.system/bin/eosio.system/.gitignore @@ -0,0 +1,2 @@ +eosio.system.abi +eosio.system.wasm diff --git a/eosio.system/include/eosio.system/eosio.system.hpp b/eosio.system/include/eosio.system/eosio.system.hpp index a33238a1e..276a61e71 100644 --- a/eosio.system/include/eosio.system/eosio.system.hpp +++ b/eosio.system/include/eosio.system/eosio.system.hpp @@ -61,6 +61,22 @@ namespace eosiosystem { (last_producer_schedule_size)(total_producer_vote_weight)(last_name_close) ) }; + /** + * Defines new global state parameters added after version 1.0 + */ + struct eosio_global_state2 { + eosio_global_state2(){} + + uint16_t new_ram_per_block = 0; + block_timestamp last_ram_increase; + block_timestamp last_block_num; + double reserved = 0; + uint8_t revision = 0; ///< used to track version updates in the future. + + + EOSLIB_SERIALIZE( eosio_global_state2, (new_ram_per_block)(last_ram_increase)(last_block_num)(reserved)(revision) ) + }; + struct producer_info { account_name owner; double total_votes = 0; @@ -120,6 +136,7 @@ namespace eosiosystem { > producers_table; typedef eosio::singleton global_state_singleton; + typedef eosio::singleton global_state2_singleton; // static constexpr uint32_t max_inflation_rate = 5; // 5% annual inflation static constexpr uint32_t seconds_per_day = 24 * 3600; @@ -127,11 +144,13 @@ namespace eosiosystem { class system_contract : public native { private: - voters_table _voters; - producers_table _producers; - global_state_singleton _global; + voters_table _voters; + producers_table _producers; + global_state_singleton _global; + global_state2_singleton _global2; eosio_global_state _gstate; + eosio_global_state2 _gstate2; rammarket _rammarket; public: @@ -200,6 +219,7 @@ namespace eosiosystem { void unregprod( const account_name producer ); void setram( uint64_t max_ram_size ); + void setramrate( uint16_t bytes_per_block ); void voteproducer( const account_name voter, const account_name proxy, const std::vector& producers ); @@ -217,6 +237,7 @@ namespace eosiosystem { void bidname( account_name bidder, account_name newname, asset bid ); private: void update_elected_producers( block_timestamp timestamp ); + void update_ram_supply(); // Implementation details: diff --git a/eosio.system/src/delegate_bandwidth.cpp b/eosio.system/src/delegate_bandwidth.cpp index 9678c28e5..3b1599b7a 100644 --- a/eosio.system/src/delegate_bandwidth.cpp +++ b/eosio.system/src/delegate_bandwidth.cpp @@ -86,6 +86,7 @@ namespace eosiosystem { * This action will buy an exact amount of ram and bill the payer the current market price. */ void system_contract::buyrambytes( account_name payer, account_name receiver, uint32_t bytes ) { + auto itr = _rammarket.find(S(4,RAMCORE)); auto tmp = *itr; auto eosout = tmp.convert( asset(bytes,S(0,RAM)), CORE_SYMBOL ); @@ -105,6 +106,8 @@ namespace eosiosystem { void system_contract::buyram( account_name payer, account_name receiver, asset quant ) { require_auth( payer ); + update_ram_supply(); + eosio_assert( quant.amount > 0, "must purchase a positive amount" ); auto fee = quant; @@ -117,7 +120,7 @@ namespace eosiosystem { // quant_after_fee.amount should be > 0 if quant.amount > 1. // If quant.amount == 1, then quant_after_fee.amount == 0 and the next inline transfer will fail causing the buyram action to fail. - INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {payer,N(active)}, + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {{payer,N(active)},{N(eosio.ram),N(active)}}, { payer, N(eosio.ram), quant_after_fee, std::string("buy ram") } ); if( fee.amount > 0 ) { @@ -161,6 +164,8 @@ namespace eosiosystem { */ void system_contract::sellram( account_name account, int64_t bytes ) { require_auth( account ); + update_ram_supply(); + eosio_assert( bytes > 0, "cannot sell negative byte" ); user_resources_table userres( _self, account ); @@ -188,12 +193,11 @@ namespace eosiosystem { }); set_resource_limits( res_itr->owner, res_itr->ram_bytes, res_itr->net_weight.amount, res_itr->cpu_weight.amount ); - INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {N(eosio.ram),N(active)}, + INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {{N(eosio.ram),N(active)},{account,N(active)}}, { N(eosio.ram), account, asset(tokens_out), std::string("sell ram") } ); auto fee = ( tokens_out.amount + 199 ) / 200; /// .5% fee (round up) // since tokens_out.amount was asserted to be at least 2 earlier, fee.amount < tokens_out.amount - if( fee > 0 ) { INLINE_ACTION_SENDER(eosio::token, transfer)( N(eosio.token), {account,N(active)}, { account, N(eosio.ramfee), asset(fee), std::string("sell ram fee") } ); diff --git a/eosio.system/src/eosio.system.cpp b/eosio.system/src/eosio.system.cpp index a38a4a4f2..1f0b6a11f 100644 --- a/eosio.system/src/eosio.system.cpp +++ b/eosio.system/src/eosio.system.cpp @@ -14,10 +14,12 @@ namespace eosiosystem { _voters(_self,_self), _producers(_self,_self), _global(_self,_self), + _global2(_self,_self), _rammarket(_self,_self) { //print( "construct system\n" ); - _gstate = _global.exists() ? _global.get() : get_default_parameters(); + _gstate = _global.exists() ? _global.get() : get_default_parameters(); + _gstate2 = _global2.exists() ? _global2.get() : eosio_global_state2{}; auto itr = _rammarket.find(S(4,RAMCORE)); @@ -46,9 +48,8 @@ namespace eosiosystem { system_contract::~system_contract() { - //print( "destruct system\n" ); _global.set( _gstate, _self ); - //eosio_exit(0); + _global2.set( _gstate2, _self ); } void system_contract::setram( uint64_t max_ram_size ) { @@ -62,15 +63,47 @@ namespace eosiosystem { auto itr = _rammarket.find(S(4,RAMCORE)); /** - * Increase or decrease the amount of ram for sale based upon the change in max - * ram size. + * Increase the amount of ram for sale based upon the change in max ram size. */ _rammarket.modify( itr, 0, [&]( auto& m ) { m.base.balance.amount += delta; }); _gstate.max_ram_size = max_ram_size; - _global.set( _gstate, _self ); + } + + void system_contract::update_ram_supply() { + if( _gstate2.last_block_num <= _gstate2.last_ram_increase ) return; + + auto itr = _rammarket.find(S(4,RAMCORE)); + auto new_ram = (_gstate2.last_block_num.slot - _gstate2.last_ram_increase.slot)*_gstate2.new_ram_per_block; + _gstate.max_ram_size += new_ram; + + /** + * Increase the amount of ram for sale based upon the change in max ram size. + */ + _rammarket.modify( itr, 0, [&]( auto& m ) { + m.base.balance.amount += new_ram; + }); + _gstate2.last_ram_increase = _gstate2.last_block_num; + } + + /** + * Sets the rate of increase of RAM in bytes per block. It is capped by the uint16_t to + * a maximum rate of 3 TB per year. + * + * If update_ram_supply hasn't been called for the most recent block, then new ram will + * be allocated at the old rate up to the present block before switching the rate. + */ + void system_contract::setramrate( uint16_t bytes_per_block ) { + require_auth( _self ); + + _gstate2.new_ram_per_block = bytes_per_block; + if( _gstate2.last_ram_increase == block_timestamp() ) { + _gstate2.last_ram_increase = _gstate2.last_block_num; + } else { + update_ram_supply(); + } } void system_contract::setparams( const eosio::blockchain_parameters& params ) { @@ -189,7 +222,7 @@ EOSIO_ABI( eosiosystem::system_contract, // native.hpp (newaccount definition is actually in eosio.system.cpp) (newaccount)(updateauth)(deleteauth)(linkauth)(unlinkauth)(canceldelay)(onerror) // eosio.system.cpp - (setram)(setparams)(setpriv)(rmvproducer)(bidname) + (setram)(setramrate)(setparams)(setpriv)(rmvproducer)(bidname) // delegate_bandwidth.cpp (buyrambytes)(buyram)(sellram)(delegatebw)(undelegatebw)(refund) // voting.cpp diff --git a/eosio.system/src/exchange_state.cpp b/eosio.system/src/exchange_state.cpp index 621d3e714..b01169038 100644 --- a/eosio.system/src/exchange_state.cpp +++ b/eosio.system/src/exchange_state.cpp @@ -5,12 +5,11 @@ namespace eosiosystem { real_type R(supply.amount); real_type C(c.balance.amount+in.amount); - real_type F(c.weight/1000.0); + real_type F(c.weight); real_type T(in.amount); real_type ONE(1.0); real_type E = -R * (ONE - std::pow( ONE + T / C, F) ); - //print( "E: ", E, "\n"); int64_t issued = int64_t(E); supply.amount += issued; @@ -24,7 +23,7 @@ namespace eosiosystem { real_type R(supply.amount - in.amount); real_type C(c.balance.amount); - real_type F(1000.0/c.weight); + real_type F(1.0/c.weight); real_type E(in.amount); real_type ONE(1.0); @@ -36,7 +35,6 @@ namespace eosiosystem { // real_type T = C * std::expm1( F * std::log1p(E/R) ); real_type T = C * (std::pow( ONE + E/R, F) - ONE); - //print( "T: ", T, "\n"); int64_t out = int64_t(T); supply.amount -= in.amount; diff --git a/eosio.system/src/producer_pay.cpp b/eosio.system/src/producer_pay.cpp index 5ff9e450d..39fe64efc 100644 --- a/eosio.system/src/producer_pay.cpp +++ b/eosio.system/src/producer_pay.cpp @@ -21,6 +21,7 @@ namespace eosiosystem { using namespace eosio; require_auth(N(eosio)); + _gstate2.last_block_num = timestamp; /** until activated stake crosses this threshold no new rewards are paid */ if( _gstate.total_activated_stake < min_activated_stake ) diff --git a/eosio.token/CMakeLists.txt b/eosio.token/CMakeLists.txt new file mode 100644 index 000000000..39993201b --- /dev/null +++ b/eosio.token/CMakeLists.txt @@ -0,0 +1,11 @@ +add_executable(eosio.token.wasm ${CMAKE_CURRENT_SOURCE_DIR}/src/eosio.token.cpp) +target_include_directories(eosio.token.wasm + PUBLIC + ${CMAKE_CURRENT_SOURCE_DIR}/include) + +set_target_properties(eosio.token.wasm + PROPERTIES + RUNTIME_OUTPUT_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/bin/eosio.token") + +configure_file("${CMAKE_CURRENT_SOURCE_DIR}/abi/eosio.token.abi" "${CMAKE_CURRENT_SOURCE_DIR}/bin/eosio.token" COPYONLY) +#install(DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/include/ DESTINATION ${WASM_ROOT}/eosio.wasmsdk/include) diff --git a/eosio.token/abi/eosio.token.abi b/eosio.token/abi/eosio.token.abi index d769deb33..b7352d365 100644 --- a/eosio.token/abi/eosio.token.abi +++ b/eosio.token/abi/eosio.token.abi @@ -28,6 +28,20 @@ {"name":"quantity", "type":"asset"}, {"name":"memo", "type":"string"} ] + },{ + "name": "retire", + "base": "", + "fields": [ + {"name":"quantity", "type":"asset"}, + {"name":"memo", "type":"string"} + ] + },{ + "name": "close", + "base": "", + "fields": [ + {"name":"owner", "type":"account_name"}, + {"name":"symbol", "type":"symbol"}, + ] },{ "name": "account", "base": "", @@ -52,10 +66,18 @@ "name": "issue", "type": "issue", "ricardian_contract": "" + },{ + "name": "retire", + "type": "retire", + "ricardian_contract": "" }, { "name": "create", "type": "create", "ricardian_contract": "" + }, { + "name": "close", + "type": "close", + "ricardian_contract": "" } ], diff --git a/eosio.token/bin/eosio.token/.gitignore b/eosio.token/bin/eosio.token/.gitignore new file mode 100644 index 000000000..e55130074 --- /dev/null +++ b/eosio.token/bin/eosio.token/.gitignore @@ -0,0 +1,2 @@ +eosio.token.abi +eosio.token.wasm diff --git a/eosio.token/include/eosio.token/eosio.token.hpp b/eosio.token/include/eosio.token/eosio.token.hpp index 158751347..629524ad0 100644 --- a/eosio.token/include/eosio.token/eosio.token.hpp +++ b/eosio.token/include/eosio.token/eosio.token.hpp @@ -26,12 +26,15 @@ namespace eosio { void issue( account_name to, asset quantity, string memo ); + void retire( asset quantity, string memo ); + void transfer( account_name from, account_name to, asset quantity, string memo ); - - + + void close( account_name owner, symbol_type symbol ); + inline asset get_supply( symbol_name sym )const; inline asset get_balance( account_name owner, symbol_name sym )const; diff --git a/eosio.token/src/eosio.token.cpp b/eosio.token/src/eosio.token.cpp index d85c23620..ff25071d6 100644 --- a/eosio.token/src/eosio.token.cpp +++ b/eosio.token/src/eosio.token.cpp @@ -59,6 +59,31 @@ void token::issue( account_name to, asset quantity, string memo ) } } +void token::retire( asset quantity, string memo ) +{ + auto sym = quantity.symbol; + eosio_assert( sym.is_valid(), "invalid symbol name" ); + eosio_assert( memo.size() <= 256, "memo has more than 256 bytes" ); + + auto sym_name = sym.name(); + stats statstable( _self, sym_name ); + auto existing = statstable.find( sym_name ); + eosio_assert( existing != statstable.end(), "token with symbol does not exist" ); + const auto& st = *existing; + + require_auth( st.issuer ); + eosio_assert( quantity.is_valid(), "invalid quantity" ); + eosio_assert( quantity.amount > 0, "must retire positive quantity" ); + + eosio_assert( quantity.symbol == st.supply.symbol, "symbol precision mismatch" ); + + statstable.modify( st, 0, [&]( auto& s ) { + s.supply -= quantity; + }); + + sub_balance( st.issuer, quantity ); +} + void token::transfer( account_name from, account_name to, asset quantity, @@ -79,9 +104,10 @@ void token::transfer( account_name from, eosio_assert( quantity.symbol == st.supply.symbol, "symbol precision mismatch" ); eosio_assert( memo.size() <= 256, "memo has more than 256 bytes" ); + auto payer = has_auth( to ) ? to : from; sub_balance( from, quantity ); - add_balance( to, quantity, from ); + add_balance( to, quantity, payer ); } void token::sub_balance( account_name owner, asset value ) { @@ -90,14 +116,9 @@ void token::sub_balance( account_name owner, asset value ) { const auto& from = from_acnts.get( value.symbol.name(), "no balance object found" ); eosio_assert( from.balance.amount >= value.amount, "overdrawn balance" ); - - if( from.balance.amount == value.amount ) { - from_acnts.erase( from ); - } else { - from_acnts.modify( from, owner, [&]( auto& a ) { - a.balance -= value; + from_acnts.modify( from, owner, [&]( auto& a ) { + a.balance -= value; }); - } } void token::add_balance( account_name owner, asset value, account_name ram_payer ) @@ -115,6 +136,14 @@ void token::add_balance( account_name owner, asset value, account_name ram_payer } } +void token::close( account_name owner, symbol_type symbol ) { + accounts acnts( _self, owner ); + auto it = acnts.find( symbol.name() ); + eosio_assert( it != acnts.end(), "Balance row already deleted or never existed. Action won't have any effect." ); + eosio_assert( it->balance.amount == 0, "Cannot close because the balance is not zero." ); + acnts.erase( it ); +} + } /// namespace eosio -EOSIO_ABI( eosio::token, (create)(issue)(transfer) ) +EOSIO_ABI( eosio::token, (create)(issue)(transfer)(close)(retire) ) diff --git a/tests/CMakeLists.txt b/tests/CMakeLists.txt index a12561023..2a22a3ea4 100644 --- a/tests/CMakeLists.txt +++ b/tests/CMakeLists.txt @@ -2,7 +2,7 @@ cmake_minimum_required( VERSION 3.5 ) project( EOSIO_CONTRACTS_TESTS ) set( VERSION_MAJOR 1 ) set( VERSION_MINOR 0 ) -set( VERSION_PATCH 6 ) +set( VERSION_PATCH 0 ) enable_testing() @@ -40,9 +40,13 @@ find_package(Boost 1.67 REQUIRED COMPONENTS file(GLOB UNIT_TESTS "*.cpp") -find_library(libtester eosio_testing ) +find_library(libtester eosio_testing ${EOSIO_INSTALL_PREFIX}/lib) find_library(libchain eosio_chain ${EOSIO_INSTALL_PREFIX}/lib) -find_library(libfc fc ${EOSIO_INSTALL_PREFIX}/lib) +if ( "${CMAKE_BUILD_TYPE}" EQUAL "Debug" ) + find_library(libfc fc_debug ${EOSIO_INSTALL_PREFIX}/lib) +else() + find_library(libfc fc ${EOSIO_INSTALL_PREFIX}/lib) +endif() find_library(libbinaryen binaryen ${EOSIO_INSTALL_PREFIX}/lib) find_library(libwasm WASM ${EOSIO_INSTALL_PREFIX}/lib) find_library(libwast WAST ${EOSIO_INSTALL_PREFIX}/lib) @@ -60,6 +64,7 @@ find_library(libsecp256k1 secp256k1 ${SECP256K1_INSTALL_LIB}) add_executable( unit_test ${UNIT_TESTS} ) target_link_libraries( unit_test ${LLVM} + ${libtester} ${libchain} ${libfc} ${libbinaryen} @@ -75,7 +80,6 @@ target_link_libraries( unit_test ${libchainbase} ${libbuiltins} ${libsecp256k1} - ${libtester} LLVMX86Disassembler LLVMX86AsmParser @@ -113,6 +117,7 @@ target_link_libraries( unit_test target_include_directories( unit_test PUBLIC ${Boost_INCLUDE_DIRS} ${OPENSSL_INSTALL_PREFIX}/include + ${EOSIO_INSTALL_PREFIX} ${EOSIO_INSTALL_PREFIX}/include ${EOSIO_INSTALL_PREFIX}/include/eosio/wasm-jit/Include ${EOSIO_INSTALL_PREFIX}/include/eosio/softfloat/include diff --git a/tests/eosio.system_tester.hpp b/tests/eosio.system_tester.hpp index ff90f8d3d..7ce52211c 100644 --- a/tests/eosio.system_tester.hpp +++ b/tests/eosio.system_tester.hpp @@ -386,7 +386,11 @@ class eosio_system_tester : public TESTER { vector data = get_row_by_account( config::system_account_name, config::system_account_name, N(global), N(global) ); if (data.empty()) std::cout << "\nData is empty\n" << std::endl; return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "eosio_global_state", data ); + } + fc::variant get_global_state2() { + vector data = get_row_by_account( config::system_account_name, config::system_account_name, N(global2), N(global2) ); + return data.empty() ? fc::variant() : abi_ser.binary_to_variant( "eosio_global_state2", data ); } fc::variant get_refund_request( name account ) { diff --git a/tests/eosio.system_tests.cpp b/tests/eosio.system_tests.cpp index 03351a5e3..ac21342b7 100644 --- a/tests/eosio.system_tests.cpp +++ b/tests/eosio.system_tests.cpp @@ -68,7 +68,7 @@ BOOST_FIXTURE_TEST_CASE( buysell, eosio_system_tester ) try { wdump((init_bytes)(bought_bytes)(bytes) ); BOOST_REQUIRE_EQUAL( true, total["ram_bytes"].as_uint64() == init_bytes ); - BOOST_REQUIRE_EQUAL( core_from_string("99901248.0041"), get_balance( "alice1111111" ) ); + BOOST_REQUIRE_EQUAL( core_from_string("99901248.0048"), get_balance( "alice1111111" ) ); BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111", "alice1111111", core_from_string("100.0000") ) ); BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111", "alice1111111", core_from_string("100.0000") ) ); @@ -79,7 +79,7 @@ BOOST_FIXTURE_TEST_CASE( buysell, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111", "alice1111111", core_from_string("10.0000") ) ); BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111", "alice1111111", core_from_string("10.0000") ) ); BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111", "alice1111111", core_from_string("30.0000") ) ); - BOOST_REQUIRE_EQUAL( core_from_string("99900688.0041"), get_balance( "alice1111111" ) ); + BOOST_REQUIRE_EQUAL( core_from_string("99900688.0048"), get_balance( "alice1111111" ) ); auto newtotal = get_total_stake( "alice1111111" ); @@ -88,8 +88,7 @@ BOOST_FIXTURE_TEST_CASE( buysell, eosio_system_tester ) try { wdump((newbytes)(bytes)(bought_bytes) ); BOOST_REQUIRE_EQUAL( success(), sellram( "alice1111111", bought_bytes ) ); - BOOST_REQUIRE_EQUAL( core_from_string("99901242.4179"), get_balance( "alice1111111" ) ); - + BOOST_REQUIRE_EQUAL( core_from_string("99901242.4187"), get_balance( "alice1111111" ) ); newtotal = get_total_stake( "alice1111111" ); auto startbytes = newtotal["ram_bytes"].as_uint64(); @@ -103,7 +102,7 @@ BOOST_FIXTURE_TEST_CASE( buysell, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111", "alice1111111", core_from_string("100000.0000") ) ); BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111", "alice1111111", core_from_string("100000.0000") ) ); BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111", "alice1111111", core_from_string("300000.0000") ) ); - BOOST_REQUIRE_EQUAL( core_from_string("49301242.4179"), get_balance( "alice1111111" ) ); + BOOST_REQUIRE_EQUAL( core_from_string("49301242.4187"), get_balance( "alice1111111" ) ); auto finaltotal = get_total_stake( "alice1111111" ); auto endbytes = finaltotal["ram_bytes"].as_uint64(); @@ -113,7 +112,7 @@ BOOST_FIXTURE_TEST_CASE( buysell, eosio_system_tester ) try { BOOST_REQUIRE_EQUAL( success(), sellram( "alice1111111", bought_bytes ) ); - BOOST_REQUIRE_EQUAL( core_from_string("99396507.4142"), get_balance( "alice1111111" ) ); + BOOST_REQUIRE_EQUAL( core_from_string("99396507.4158"), get_balance( "alice1111111" ) ); } FC_LOG_AND_RETHROW() @@ -1380,7 +1379,6 @@ BOOST_FIXTURE_TEST_CASE(producer_pay, eosio_system_tester, * boost::unit_test::t } FC_LOG_AND_RETHROW() - BOOST_FIXTURE_TEST_CASE(multiple_producer_pay, eosio_system_tester, * boost::unit_test::tolerance(1e-10)) try { auto within_one = [](int64_t a, int64_t b) -> bool { return std::abs( a - b ) <= 1; }; @@ -1707,17 +1705,17 @@ BOOST_FIXTURE_TEST_CASE(producers_upgrade_system_contract, eosio_system_tester) for ( auto& x : producer_names ) { prod_perms.push_back( { name(x), config::active_name } ); } - //prepare system contract with different hash (contract differs in one byte) - string eosio_system_wast2 = contracts::system_wast(); - string msg = "producer votes must be unique and sorted"; - auto pos = eosio_system_wast2.find(msg); - BOOST_REQUIRE( pos != std::string::npos ); - msg[0] = 'P'; - eosio_system_wast2.replace( pos, msg.size(), msg ); transaction trx; { - auto code = wast_to_wasm( eosio_system_wast2 ); + //prepare system contract with different hash (contract differs in one byte) + auto code = contracts::system_wasm(); + string msg = "producer votes must be unique and sorted"; + auto it = std::search( code.begin(), code.end(), msg.begin(), msg.end() ); + BOOST_REQUIRE( it != code.end() ); + msg[0] = 'P'; + std::copy( msg.begin(), msg.end(), it ); + variant pretty_trx = fc::mutable_variant_object() ("expiration", "2020-01-01T00:30") ("ref_block_num", 2) @@ -2584,6 +2582,72 @@ BOOST_FIXTURE_TEST_CASE( setram_effect, eosio_system_tester ) try { } } FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( ram_inflation, eosio_system_tester ) try { + + const uint64_t init_max_ram_size = 64ll*1024 * 1024 * 1024; + + BOOST_REQUIRE_EQUAL( init_max_ram_size, get_global_state()["max_ram_size"].as_uint64() ); + produce_blocks(20); + BOOST_REQUIRE_EQUAL( init_max_ram_size, get_global_state()["max_ram_size"].as_uint64() ); + transfer( config::system_account_name, "alice1111111", core_from_string("1000.0000"), config::system_account_name ); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111", "alice1111111", core_from_string("100.0000") ) ); + produce_blocks(3); + BOOST_REQUIRE_EQUAL( init_max_ram_size, get_global_state()["max_ram_size"].as_uint64() ); + const uint16_t rate = 1000; + BOOST_REQUIRE_EQUAL( success(), push_action( config::system_account_name, N(setramrate), mvo()("bytes_per_block", rate) ) ); + BOOST_REQUIRE_EQUAL( rate, get_global_state2()["new_ram_per_block"].as() ); + // last time update_ram_supply called is in buyram, num of blocks since then is 1 + 3 = 4 + uint64_t cur_ram_size = get_global_state()["max_ram_size"].as_uint64(); + BOOST_REQUIRE_EQUAL( init_max_ram_size + 4 * rate, get_global_state()["max_ram_size"].as_uint64() ); + produce_blocks(10); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111", "alice1111111", core_from_string("100.0000") ) ); + BOOST_REQUIRE_EQUAL( cur_ram_size + 11 * rate, get_global_state()["max_ram_size"].as_uint64() ); + cur_ram_size = get_global_state()["max_ram_size"].as_uint64(); + produce_blocks(5); + BOOST_REQUIRE_EQUAL( cur_ram_size, get_global_state()["max_ram_size"].as_uint64() ); + BOOST_REQUIRE_EQUAL( success(), sellram( "alice1111111", 100 ) ); + BOOST_REQUIRE_EQUAL( cur_ram_size + 6 * rate, get_global_state()["max_ram_size"].as_uint64() ); + cur_ram_size = get_global_state()["max_ram_size"].as_uint64(); + produce_blocks(); + BOOST_REQUIRE_EQUAL( success(), buyrambytes( "alice1111111", "alice1111111", 100 ) ); + BOOST_REQUIRE_EQUAL( cur_ram_size + 2 * rate, get_global_state()["max_ram_size"].as_uint64() ); + + BOOST_REQUIRE_EQUAL( error("missing authority of eosio"), + push_action( "alice1111111", N(setramrate), mvo()("bytes_per_block", rate) ) ); + +} FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( eosioram_ramusage, eosio_system_tester ) try { + BOOST_REQUIRE_EQUAL( core_from_string("0.0000"), get_balance( "alice1111111" ) ); + transfer( "eosio", "alice1111111", core_from_string("1000.0000"), "eosio" ); + BOOST_REQUIRE_EQUAL( success(), stake( "eosio", "alice1111111", core_from_string("200.0000"), core_from_string("100.0000") ) ); + + const asset initial_ram_balance = get_balance(N(eosio.ram)); + const asset initial_ramfee_balance = get_balance(N(eosio.ramfee)); + BOOST_REQUIRE_EQUAL( success(), buyram( "alice1111111", "alice1111111", core_from_string("1000.0000") ) ); + + BOOST_REQUIRE_EQUAL( false, get_row_by_account( N(eosio.token), N(alice1111111), N(accounts), symbol().to_symbol_code() ).empty() ); + + //remove row + base_tester::push_action( N(eosio.token), N(close), N(alice1111111), mvo() + ( "owner", "alice1111111" ) + ( "symbol", symbol() ) + ); + BOOST_REQUIRE_EQUAL( true, get_row_by_account( N(eosio.token), N(alice1111111), N(accounts), symbol().to_symbol_code() ).empty() ); + + auto rlm = control->get_resource_limits_manager(); + auto eosioram_ram_usage = rlm.get_account_ram_usage(N(eosio.ram)); + auto alice_ram_usage = rlm.get_account_ram_usage(N(alice1111111)); + //std::cout << "Sellram" << std::endl; + BOOST_REQUIRE_EQUAL( success(), sellram( "alice1111111", 2048 ) ); + + //make sure that ram was billed to alice, not to eosio.ram + BOOST_REQUIRE_EQUAL( true, alice_ram_usage < rlm.get_account_ram_usage(N(alice1111111)) ); + BOOST_REQUIRE_EQUAL( eosioram_ram_usage, rlm.get_account_ram_usage(N(eosio.ram)) ); + +} FC_LOG_AND_RETHROW() + BOOST_AUTO_TEST_SUITE_END() void translate_fc_exception(const fc::exception &e) { diff --git a/tests/eosio.token_tests.cpp b/tests/eosio.token_tests.cpp index 0fcc7b62d..093d8d0f1 100644 --- a/tests/eosio.token_tests.cpp +++ b/tests/eosio.token_tests.cpp @@ -80,6 +80,14 @@ class eosio_token_tester : public tester { ); } + action_result retire( account_name issuer, asset quantity, string memo ) { + return push_action( issuer, N(retire), mvo() + ( "quantity", quantity) + ( "memo", memo) + ); + + } + action_result transfer( account_name from, account_name to, asset quantity, @@ -92,6 +100,14 @@ class eosio_token_tester : public tester { ); } + action_result close( account_name owner, + const string& symbolname ) { + return push_action( owner, N(close), mvo() + ( "owner", owner ) + ( "symbol", "0,CERO" ) + ); + } + abi_serializer abi_ser; }; @@ -217,6 +233,63 @@ BOOST_FIXTURE_TEST_CASE( issue_tests, eosio_token_tester ) try { } FC_LOG_AND_RETHROW() +BOOST_FIXTURE_TEST_CASE( retire_tests, eosio_token_tester ) try { + + auto token = create( N(alice), asset::from_string("1000.000 TKN")); + produce_blocks(1); + + BOOST_REQUIRE_EQUAL( success(), issue( N(alice), N(alice), asset::from_string("500.000 TKN"), "hola" ) ); + + auto stats = get_stats("3,TKN"); + REQUIRE_MATCHING_OBJECT( stats, mvo() + ("supply", "500.000 TKN") + ("max_supply", "1000.000 TKN") + ("issuer", "alice") + ); + + auto alice_balance = get_account(N(alice), "3,TKN"); + REQUIRE_MATCHING_OBJECT( alice_balance, mvo() + ("balance", "500.000 TKN") + ); + + BOOST_REQUIRE_EQUAL( success(), retire( N(alice), asset::from_string("200.000 TKN"), "hola" ) ); + stats = get_stats("3,TKN"); + REQUIRE_MATCHING_OBJECT( stats, mvo() + ("supply", "300.000 TKN") + ("max_supply", "1000.000 TKN") + ("issuer", "alice") + ); + alice_balance = get_account(N(alice), "3,TKN"); + REQUIRE_MATCHING_OBJECT( alice_balance, mvo() + ("balance", "300.000 TKN") + ); + + //should fail to retire more than current supply + BOOST_REQUIRE_EQUAL( wasm_assert_msg("overdrawn balance"), retire( N(alice), asset::from_string("500.000 TKN"), "hola" ) ); + + BOOST_REQUIRE_EQUAL( success(), transfer( N(alice), N(bob), asset::from_string("200.000 TKN"), "hola" ) ); + //should fail to retire since tokens are not on the issuer's balance + BOOST_REQUIRE_EQUAL( wasm_assert_msg("overdrawn balance"), retire( N(alice), asset::from_string("300.000 TKN"), "hola" ) ); + //transfer tokens back + BOOST_REQUIRE_EQUAL( success(), transfer( N(bob), N(alice), asset::from_string("200.000 TKN"), "hola" ) ); + + BOOST_REQUIRE_EQUAL( success(), retire( N(alice), asset::from_string("300.000 TKN"), "hola" ) ); + stats = get_stats("3,TKN"); + REQUIRE_MATCHING_OBJECT( stats, mvo() + ("supply", "0.000 TKN") + ("max_supply", "1000.000 TKN") + ("issuer", "alice") + ); + alice_balance = get_account(N(alice), "3,TKN"); + REQUIRE_MATCHING_OBJECT( alice_balance, mvo() + ("balance", "0.000 TKN") + ); + + //trying to retire tokens with zero supply + BOOST_REQUIRE_EQUAL( wasm_assert_msg("overdrawn balance"), retire( N(alice), asset::from_string("1.000 TKN"), "hola" ) ); + +} FC_LOG_AND_RETHROW() + BOOST_FIXTURE_TEST_CASE( transfer_tests, eosio_token_tester ) try { auto token = create( N(alice), asset::from_string("1000 CERO")); @@ -263,4 +336,32 @@ BOOST_FIXTURE_TEST_CASE( transfer_tests, eosio_token_tester ) try { } FC_LOG_AND_RETHROW() + +BOOST_FIXTURE_TEST_CASE( close_tests, eosio_token_tester ) try { + + auto token = create( N(alice), asset::from_string("1000 CERO")); + + auto alice_balance = get_account(N(alice), "0,CERO"); + BOOST_REQUIRE_EQUAL(true, alice_balance.is_null() ); + + BOOST_REQUIRE_EQUAL( success(), issue( N(alice), N(alice), asset::from_string("1000 CERO"), "hola" ) ); + + alice_balance = get_account(N(alice), "0,CERO"); + REQUIRE_MATCHING_OBJECT( alice_balance, mvo() + ("balance", "1000 CERO") + ); + + BOOST_REQUIRE_EQUAL( success(), transfer( N(alice), N(bob), asset::from_string("1000 CERO"), "hola" ) ); + + alice_balance = get_account(N(alice), "0,CERO"); + REQUIRE_MATCHING_OBJECT( alice_balance, mvo() + ("balance", "0 CERO") + ); + + BOOST_REQUIRE_EQUAL( success(), close( N(alice), "0,CERO" ) ); + alice_balance = get_account(N(alice), "0,CERO"); + BOOST_REQUIRE_EQUAL(true, alice_balance.is_null() ); + +} FC_LOG_AND_RETHROW() + BOOST_AUTO_TEST_SUITE_END()