Skip to content

Commit

Permalink
Merge pull request #731 from steemit/102-asset-parsing
Browse files Browse the repository at this point in the history
Asset parsing corner cases
  • Loading branch information
Michael Vandeberg authored Jan 4, 2017
2 parents 888c056 + ba8e560 commit d013722
Show file tree
Hide file tree
Showing 2 changed files with 97 additions and 21 deletions.
78 changes: 57 additions & 21 deletions libraries/protocol/asset.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2,42 +2,66 @@
#include <boost/rational.hpp>
#include <boost/multiprecision/cpp_int.hpp>

/*
The bounds on asset serialization are as follows:
index : field
0 : decimals
1..6 : symbol
7 : \0
*/

namespace steemit { namespace protocol {
typedef boost::multiprecision::int128_t int128_t;

uint8_t asset::decimals()const {
uint8_t asset::decimals()const
{
auto a = (const char*)&symbol;
return a[0];
uint8_t result = uint8_t( a[0] );
FC_ASSERT( result < 15 );
return result;
}
void asset::set_decimals(uint8_t d){

void asset::set_decimals(uint8_t d)
{
FC_ASSERT( d < 15 );
auto a = (char*)&symbol;
a[0] = d;
}

std::string asset::symbol_name()const {
std::string asset::symbol_name()const
{
auto a = (const char*)&symbol;
assert( a[7] == 0 );
FC_ASSERT( a[7] == 0 );
return &a[1];
}

int64_t asset::precision()const {

int64_t asset::precision()const
{
static int64_t table[] = {
1, 10, 100, 1000, 10000,
100000, 1000000, 10000000, 100000000ll,
1000000000ll, 10000000000ll,
100000000000ll, 1000000000000ll,
10000000000000ll, 100000000000000ll
};
return table[ decimals() ];
uint8_t d = decimals();
return table[ d ];
}

string asset::to_string()const {
string result = fc::to_string(amount.value / precision());
if( decimals() )
string asset::to_string()const
{
int64_t prec = precision();
string result = fc::to_string(amount.value / prec);
if( prec > 1 )
{
auto fract = amount.value % precision();
result += "." + fc::to_string(precision() + fract).erase(0,1);
auto fract = amount.value % prec;
// prec is a power of ten, so for example when working with
// 7.005 we have fract = 5, prec = 1000. So prec+fract=1005
// has the correct number of zeros and we can simply trim the
// leading 1.
result += "." + fc::to_string(prec + fract).erase(0,1);
}
return result + " " + symbol_name();
}
Expand All @@ -50,31 +74,43 @@ namespace steemit { namespace protocol {
auto space_pos = s.find( " " );
auto dot_pos = s.find( "." );

FC_ASSERT( space_pos != std::string::npos );

asset result;
result.symbol = uint64_t(0);
auto sy = (char*)&result.symbol;
*sy = (char) dot_pos; // Mask due to undefined architecture behavior

auto intpart = s.substr( 0, dot_pos );
result.amount = fc::to_int64(intpart);
std::string fractpart;
if( dot_pos != std::string::npos )
{
FC_ASSERT( space_pos > dot_pos );

auto intpart = s.substr( 0, dot_pos );
auto fractpart = "1" + s.substr( dot_pos + 1, space_pos - dot_pos - 1 );
result.set_decimals( fractpart.size() - 1 );

result.amount = fc::to_int64( intpart );
result.amount.value *= result.precision();
result.amount.value += fc::to_int64(fractpart);
result.amount.value += fc::to_int64( fractpart );
result.amount.value -= result.precision();
}
else
{
auto intpart = s.substr( 0, space_pos );
result.amount = fc::to_int64( intpart );
result.set_decimals( 0 );
}
auto symbol = s.substr( space_pos + 1 );
size_t symbol_size = symbol.size();

if( symbol.size() )
memcpy( sy+1, symbol.c_str(), std::min(symbol.size(),size_t(6)) );
if( symbol_size > 0 )
{
FC_ASSERT( symbol_size <= 6 );
memcpy( sy+1, symbol.c_str(), symbol_size );
}

return result;
}
FC_CAPTURE_LOG_AND_RETHROW( (from) )
FC_CAPTURE_AND_RETHROW( (from) )
}

bool operator == ( const price& a, const price& b )
Expand Down
40 changes: 40 additions & 0 deletions tests/tests/serialization_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,7 @@ BOOST_AUTO_TEST_CASE( asset_test )
BOOST_CHECK_EQUAL( tmp.amount.value, 56 );

BOOST_CHECK( std::abs( steem.to_real() - 123.456 ) < 0.0005 );
BOOST_CHECK_EQUAL( steem.amount.value, 123456 );
BOOST_CHECK_EQUAL( steem.decimals(), 3 );
BOOST_CHECK_EQUAL( steem.symbol_name(), "TESTS" );
BOOST_CHECK_EQUAL( steem.to_string(), "123.456 TESTS" );
Expand All @@ -135,12 +136,51 @@ BOOST_AUTO_TEST_CASE( asset_test )
BOOST_CHECK_EQUAL( asset(50000, STEEM_SYMBOL).to_string(), "50.000 TESTS" );

BOOST_CHECK( std::abs( sbd.to_real() - 654.321 ) < 0.0005 );
BOOST_CHECK_EQUAL( sbd.amount.value, 654321 );
BOOST_CHECK_EQUAL( sbd.decimals(), 3 );
BOOST_CHECK_EQUAL( sbd.symbol_name(), "TBD" );
BOOST_CHECK_EQUAL( sbd.to_string(), "654.321 TBD" );
BOOST_CHECK_EQUAL( sbd.symbol, SBD_SYMBOL);
BOOST_CHECK_EQUAL( asset(50, SBD_SYMBOL).to_string(), "0.050 TBD" );
BOOST_CHECK_EQUAL( asset(50000, SBD_SYMBOL).to_string(), "50.000 TBD" );

BOOST_CHECK_THROW( steem.set_decimals(100), fc::exception );
char* steem_sy = (char*) &steem.symbol;
steem_sy[0] = 100;
BOOST_CHECK_THROW( steem.decimals(), fc::exception );
steem_sy[6] = 'A';
steem_sy[7] = 'A';

auto check_sym = []( const asset& a ) -> std::string
{
auto symbol = a.symbol_name();
wlog( "symbol_name is ${s}", ("s", symbol) );
return symbol;
};

BOOST_CHECK_THROW( check_sym(steem), fc::exception );
BOOST_CHECK_THROW( asset::from_string( "1.00000000000000000000 TESTS" ), fc::exception );
BOOST_CHECK_THROW( asset::from_string( "1.000TESTS" ), fc::exception );
BOOST_CHECK_THROW( asset::from_string( "1. 333 TESTS" ), fc::exception ); // Fails because symbol is '333 TESTS', which is too long
BOOST_CHECK_THROW( asset::from_string( "1 .333 TESTS" ), fc::exception );
asset unusual = asset::from_string( "1. 333 X" ); // Passes because symbol '333 X' is short enough
FC_ASSERT( unusual.decimals() == 0 );
FC_ASSERT( unusual.symbol_name() == "333 X" );
BOOST_CHECK_THROW( asset::from_string( "1 .333 X" ), fc::exception );
BOOST_CHECK_THROW( asset::from_string( "1 .333" ), fc::exception );
BOOST_CHECK_THROW( asset::from_string( "1 1.1" ), fc::exception );
BOOST_CHECK_THROW( asset::from_string( "11111111111111111111111111111111111111111111111 TESTS" ), fc::exception );
BOOST_CHECK_THROW( asset::from_string( "1.1.1 TESTS" ), fc::exception );
BOOST_CHECK_THROW( asset::from_string( "1.abc TESTS" ), fc::exception );
BOOST_CHECK_THROW( asset::from_string( " TESTS" ), fc::exception );
BOOST_CHECK_THROW( asset::from_string( "TESTS" ), fc::exception );
BOOST_CHECK_THROW( asset::from_string( "1.333" ), fc::exception );
BOOST_CHECK_THROW( asset::from_string( "1.333 " ), fc::exception );
BOOST_CHECK_THROW( asset::from_string( "" ), fc::exception );
BOOST_CHECK_THROW( asset::from_string( " " ), fc::exception );
BOOST_CHECK_THROW( asset::from_string( " " ), fc::exception );

BOOST_CHECK_EQUAL( asset::from_string( "100 TESTS" ).amount.value, 100 );
}
FC_LOG_AND_RETHROW()
}
Expand Down

0 comments on commit d013722

Please sign in to comment.