diff --git a/evolutiondex.cpp b/evolutiondex.cpp index c8c9ff5..efbd408 100644 --- a/evolutiondex.cpp +++ b/evolutiondex.cpp @@ -1,4 +1,5 @@ #include "evolutiondex.hpp" +#include "utils.hpp" using namespace evolution; @@ -31,17 +32,24 @@ void evolutiondex::closeext( const name& user, const name& to, const extended_sy } void evolutiondex::deposit(name from, name to, asset quantity, string memo) { + + constexpr string_view DEPOSIT_TO = "deposit to:"; + constexpr string_view EXCHANGE = "exchange:"; + if (from == get_self()) return; check(to == get_self(), "This transfer is not for evolutiondex"); check(quantity.amount >= 0, "quantity must be positive"); - if ( (memo.substr(0, 9)) == "exchange:") { - memoexchange(from, quantity, get_first_receiver(), memo.substr(9)); + + auto incoming = extended_asset{quantity, get_first_receiver()}; + + string_view memosv(memo); + if ( starts_with(memosv, EXCHANGE) ) { + memoexchange(from, incoming, memosv.substr(EXCHANGE.size()) ); } else { - if ( (memo.substr(0, 12)) == "deposit to: ") { - from = name(memo.substr(12)); + if ( starts_with(memosv, DEPOSIT_TO) ) { + from = name(trim(memosv.substr(DEPOSIT_TO.size()))); check(from != get_self(), "Donation not accepted"); } - extended_asset incoming = extended_asset{quantity, get_first_receiver()}; add_signed_ext_balance(from, incoming); } } @@ -147,36 +155,33 @@ void evolutiondex::exchange( name user, symbol_code through, asset asset1, asset }); } -// details has the structure "EVOTOKN,min_expected_asset;memo" -void evolutiondex::memoexchange(name user, asset quantity, name contract, string details){ - int comma = details.find(","); - check(comma != -1, "there must be a comma between evotoken symbol and min_expected_asset"); - int semicolon = details.find(";"); - check(semicolon > comma, "there must be a semicolon between min_expected_asset and memo"); - string symbol_code_string = details.substr(0, comma); - string min_expected_asset_string = details.substr(comma + 1, semicolon - comma - 1); - string memo = details.substr(semicolon + 1); - - int last_space = symbol_code_string.rfind(" "); - symbol_code_string = symbol_code_string.substr(last_space + 1); - auto through = symbol_code(symbol_code_string); - auto min_expected_asset = string_to_asset(min_expected_asset_string); +// details has the structure "EVOTOKN,min_expected_asset,memo" +void evolutiondex::memoexchange(name user, extended_asset amount, string_view details){ + auto quantity = amount.quantity; + auto contract = amount.contract; - stats statstable( get_self(), through.raw() ); - const auto& token = statstable.find( through.raw() ); + auto parts = split(details, ","); + check(parts.size() >= 2 && parts.size() <= 3, "Expected format 'EVOTOKEN,min_expected_asset,memo'"); + + auto evo_token = symbol_code(parts[0]); + auto min_expected = asset_from_string(parts[1]); + auto memo = std::string(parts.size() == 3 ? parts[2] : ""); + + stats statstable( get_self(), evo_token.raw() ); + const auto token = statstable.find( evo_token.raw() ); check ( token != statstable.end(), "token does not exist" ); bool in_first; if ((token->pool1.quantity.symbol == quantity.symbol) && - (token->pool2.quantity.symbol == min_expected_asset.symbol)) { + (token->pool2.quantity.symbol == min_expected.symbol)) { in_first = true; check(token->pool1.contract == contract, "you are transfering from an incorrect contract"); // testear estos mandando desde otros contratos. } else if ((token->pool2.quantity.symbol == quantity.symbol) && - (token->pool1.quantity.symbol == min_expected_asset.symbol)) { + (token->pool1.quantity.symbol == min_expected.symbol)) { in_first = false; check(token->pool2.contract == contract, "you are transfering from an incorrect contract"); } - else check(false, "symbol mismatch"); + else check(false, "symbol mismatch"); int64_t P_in, P_out; if (in_first) { P_in = token-> pool1.quantity.amount; @@ -187,7 +192,7 @@ void evolutiondex::memoexchange(name user, asset quantity, name contract, string } auto A_in = quantity.amount; int64_t A_out = compute(-A_in, P_out, P_in + A_in, token->fee); - check(min_expected_asset.amount <= -A_out, "available is less than expected"); + check(min_expected.amount <= -A_out, "available is less than expected"); extended_asset ext_asset1, ext_asset2, ext_asset_out; if (in_first) { ext_asset1 = extended_asset{A_in, token-> pool1.get_extended_symbol()}; @@ -203,7 +208,7 @@ void evolutiondex::memoexchange(name user, asset quantity, name contract, string a.pool2 += ext_asset2; }); action(permission_level{ get_self(), "active"_n }, ext_asset_out.contract, "transfer"_n, - std::make_tuple( get_self(), user, ext_asset_out.quantity, memo) ).send(); + std::make_tuple( get_self(), user, ext_asset_out.quantity, std::string(memo)) ).send(); } asset evolutiondex::string_to_asset(string input) { diff --git a/evolutiondex.hpp b/evolutiondex.hpp index e05c002..a427255 100644 --- a/evolutiondex.hpp +++ b/evolutiondex.hpp @@ -70,7 +70,7 @@ namespace evolution { void add_signed_ext_balance( const name& owner, const extended_asset& value ); void add_signed_liq(name user, asset to_buy, bool is_buying, asset max_asset1, asset max_asset2); - void memoexchange(name user, asset quantity, name contract, string details); + void memoexchange(name user, extended_asset amount, string_view details); int64_t compute(int64_t x, int64_t y, int64_t z, int fee); asset string_to_asset(string input); diff --git a/safe.hpp b/safe.hpp new file mode 100644 index 0000000..075cdec --- /dev/null +++ b/safe.hpp @@ -0,0 +1,221 @@ +#pragma once + +#include +/** +* This type is designed to provide automatic checks for +* integer overflow and default initialization. It will +* throw an exception on overflow conditions. +* +* It can only be used on built-in types. In particular, +* safe is buggy and should not be used. +* +* Implemented using spec from: +* https://www.securecoding.cert.org/confluence/display/c/INT32-C.+Ensure+that+operations+on+signed+integers+do+not+result+in+overflow +*/ +template +struct safe +{ + T value = 0; + + template + safe( O o ):value(o){} + safe(){} + safe( const safe& o ):value(o.value){} + + static safe min() + { + return std::numeric_limits::min(); + } + static safe max() + { + return std::numeric_limits::max(); + } + + friend safe operator + ( const safe& a, const safe& b ) + { + if( b.value > 0 && a.value > (std::numeric_limits::max() - b.value) ) check(false, "overflow_exception, (a)(b)" ); + if( b.value < 0 && a.value < (std::numeric_limits::min() - b.value) ) check(false, "underflow_exception, (a)(b)" ); + return safe( a.value + b.value ); + } + friend safe operator - ( const safe& a, const safe& b ) + { + if( b.value > 0 && a.value < (std::numeric_limits::min() + b.value) ) check(false, "underflow_exception, (a)(b)" ); + if( b.value < 0 && a.value > (std::numeric_limits::max() + b.value) ) check(false, "overflow_exception, (a)(b)" ); + return safe( a.value - b.value ); + } + + friend safe operator * ( const safe& a, const safe& b ) + { + if( a.value > 0 ) + { + if( b.value > 0 ) + { + if( a.value > (std::numeric_limits::max() / b.value) ) check(false, "overflow_exception, (a)(b)" ); + } + else + { + if( b.value < (std::numeric_limits::min() / a.value) ) check(false, "underflow_exception, (a)(b)" ); + } + } + else + { + if( b.value > 0 ) + { + if( a.value < (std::numeric_limits::min() / b.value) ) check(false, "underflow_exception, (a)(b)" ); + } + else + { + if( a.value != 0 && b.value < (std::numeric_limits::max() / a.value) ) check(false, "overflow_exception, (a)(b)" ); + } + } + + return safe( a.value * b.value ); + } + + friend safe operator / ( const safe& a, const safe& b ) + { + if( b.value == 0 ) check(false, "divide_by_zero_exception, (a)(b)" ); + if( a.value == std::numeric_limits::min() && b.value == -1 ) check(false, "overflow_exception, (a)(b)" ); + return safe( a.value / b.value ); + } + friend safe operator % ( const safe& a, const safe& b ) + { + if( b.value == 0 ) check(false, "divide_by_zero_exception, (a)(b)" ); + if( a.value == std::numeric_limits::min() && b.value == -1 ) check(false, "overflow_exception, (a)(b)" ); + return safe( a.value % b.value ); + } + + safe operator - ()const + { + if( value == std::numeric_limits::min() ) check(false, "overflow_exception, (*this)" ); + return safe( -value ); + } + + safe& operator += ( const safe& b ) + { + value = (*this + b).value; + return *this; + } + safe& operator -= ( const safe& b ) + { + value = (*this - b).value; + return *this; + } + safe& operator *= ( const safe& b ) + { + value = (*this * b).value; + return *this; + } + safe& operator /= ( const safe& b ) + { + value = (*this / b).value; + return *this; + } + safe& operator %= ( const safe& b ) + { + value = (*this % b).value; + return *this; + } + + safe& operator++() + { + *this += 1; + return *this; + } + safe operator++( int ) + { + safe bak = *this; + *this += 1; + return bak; + } + + safe& operator--() + { + *this -= 1; + return *this; + } + safe operator--( int ) + { + safe bak = *this; + *this -= 1; + return bak; + } + + friend bool operator == ( const safe& a, const safe& b ) + { + return a.value == b.value; + } + friend bool operator == ( const safe& a, const T& b ) + { + return a.value == b; + } + friend bool operator == ( const T& a, const safe& b ) + { + return a == b.value; + } + + friend bool operator < ( const safe& a, const safe& b ) + { + return a.value < b.value; + } + friend bool operator < ( const safe& a, const T& b ) + { + return a.value < b; + } + friend bool operator < ( const T& a, const safe& b ) + { + return a < b.value; + } + + friend bool operator > ( const safe& a, const safe& b ) + { + return a.value > b.value; + } + friend bool operator > ( const safe& a, const T& b ) + { + return a.value > b; + } + friend bool operator > ( const T& a, const safe& b ) + { + return a > b.value; + } + + friend bool operator != ( const safe& a, const safe& b ) + { + return !(a == b); + } + friend bool operator != ( const safe& a, const T& b ) + { + return !(a == b); + } + friend bool operator != ( const T& a, const safe& b ) + { + return !(a == b); + } + + friend bool operator <= ( const safe& a, const safe& b ) + { + return !(a > b); + } + friend bool operator <= ( const safe& a, const T& b ) + { + return !(a > b); + } + friend bool operator <= ( const T& a, const safe& b ) + { + return !(a > b); + } + + friend bool operator >= ( const safe& a, const safe& b ) + { + return !(a < b); + } + friend bool operator >= ( const safe& a, const T& b ) + { + return !(a < b); + } + friend bool operator >= ( const T& a, const safe& b ) + { + return !(a < b); + } +}; diff --git a/tests/evolutiondex_tests.cpp b/tests/evolutiondex_tests.cpp index 93e4509..30e42a7 100644 --- a/tests/evolutiondex_tests.cpp +++ b/tests/evolutiondex_tests.cpp @@ -231,7 +231,7 @@ static symbol EOS = symbol::from_string("4,EOS"); static symbol VOICE = symbol::from_string("4,VOICE"); static symbol TUSD = symbol::from_string("4,TUSD"); -BOOST_AUTO_TEST_SUITE(eosio_token_tests) +BOOST_AUTO_TEST_SUITE(evolutiondex_tests) BOOST_FIXTURE_TEST_CASE( evo_tests, evolutiondex_tester ) try { const auto& accnt2 = control->db().get( N(evolutiondex) ); @@ -552,10 +552,11 @@ BOOST_FIXTURE_TEST_CASE( memoexchange_test, evolutiondex_tester ) try { transfer( N(eosio.token), N(alice), N(evolutiondex), asset{4500000000000000000, TUSD}, ""); transfer( N(eosio.token), N(alice), N(bob), asset{30000000000000000, TUSD}, ""); - inittoken( N(alice), EVO, - extended_asset{asset{230584300921369, EOS}, N(eosio.token)}, - extended_asset{asset{961168601842738, VOICE}, N(eosio.token)}, - 12, N(wevotethefee)); + BOOST_REQUIRE_EQUAL( success(), + inittoken( N(alice), EVO, + extended_asset{asset{230584300921369, EOS}, N(eosio.token)}, + extended_asset{asset{461168601842738, VOICE}, N(eosio.token)}, + 12, N(wevotethefee)) ); BOOST_REQUIRE_EQUAL( wasm_assert_msg("symbol mismatch"), transfer( N(eosio.token), N(alice), N(evolutiondex), asset{40000, EOS}, @@ -584,13 +585,13 @@ BOOST_FIXTURE_TEST_CASE( memoexchange_test, evolutiondex_tester ) try { auto old_vec = system_balance(5199429); transfer( N(eosio.token), N(alice), N(evolutiondex), asset{40000, EOS}, - "exchange: EVO, 10000 VOICE; nothing to say"); + "exchange: EVO, 10000 VOICE, nothing to say"); auto new_vec = system_balance(5199429); BOOST_REQUIRE_EQUAL(is_increasing(old_vec, new_vec), true); old_vec = system_balance(293455877189); transfer( N(eosio.token), N(bob), N(evolutiondex), asset{3000000000000000000, TUSD}, - "exchange: ETUSD, 40000000000000 EOS;"); + "exchange: ETUSD, 40000000000000 EOS,"); new_vec = system_balance(293455877189); BOOST_REQUIRE_EQUAL(is_increasing(old_vec, new_vec), true); } FC_LOG_AND_RETHROW() diff --git a/utils.hpp b/utils.hpp new file mode 100644 index 0000000..c1c53ce --- /dev/null +++ b/utils.hpp @@ -0,0 +1,95 @@ +#pragma once +//#include +#include +#include +#include + +#include +#include "safe.hpp" + +string_view trim(string_view sv) { + sv.remove_prefix(std::min(sv.find_first_not_of(" "), sv.size())); // left trim + sv.remove_suffix(std::min(sv.size()-sv.find_last_not_of(" ")-1, sv.size())); // right trim + return sv; +} + +vector split(string_view str, string_view delims = " ") +{ + vector res; + std::size_t current, previous = 0; + current = str.find_first_of(delims); + while (current != std::string::npos) { + res.push_back(trim(str.substr(previous, current - previous))); + previous = current + 1; + current = str.find_first_of(delims, previous); + } + res.push_back(trim(str.substr(previous, current - previous))); + return res; +} + +bool starts_with(string_view sv, string_view s) { + return sv.size() >= s.size() && sv.compare(0, s.size(), s) == 0; +} + +template +void to_int(string_view sv, T& res) { + res = 0; + T p = 1; + for( auto itr = sv.rbegin(); itr != sv.rend(); ++itr ) { + check( *itr <= '9' && *itr >= '0', "invalid character"); + res += p * T(*itr-'0'); + p *= T(10); + } +} +template +void precision_from_decimals(int8_t decimals, T& p10) +{ + check(decimals <= 18, "precision should be <= 18"); + p10 = 1; + T p = decimals; + while( p > 0 ) { + p10 *= 10; --p; + } +} + +asset asset_from_string(string_view from) +{ + string_view s = trim(from); + + // Find space in order to split amount and symbol + auto space_pos = s.find(' '); + check(space_pos != string::npos, "Asset's amount and symbol should be separated with space"); + auto symbol_str = trim(s.substr(space_pos + 1)); + auto amount_str = s.substr(0, space_pos); + + // Ensure that if decimal point is used (.), decimal fraction is specified + auto dot_pos = amount_str.find('.'); + if (dot_pos != string::npos) { + check(dot_pos != amount_str.size() - 1, "Missing decimal fraction after decimal point"); + } + + // Parse symbol + uint8_t precision_digit = 0; + if (dot_pos != string::npos) { + precision_digit = amount_str.size() - dot_pos - 1; + } + + symbol sym = symbol(symbol_str, precision_digit); + + // Parse amount + safe int_part, fract_part; + if (dot_pos != string::npos) { + to_int(amount_str.substr(0, dot_pos), int_part); + to_int(amount_str.substr(dot_pos + 1), fract_part); + if (amount_str[0] == '-') fract_part *= -1; + } else { + to_int(amount_str, int_part); + } + + safe amount = int_part; + safe precision; precision_from_decimals(sym.precision(), precision); + amount *= precision; + amount += fract_part; + + return asset(amount.value, sym); +}