diff --git a/src/ripple/app/hook/impl/applyHook.cpp b/src/ripple/app/hook/impl/applyHook.cpp index 35e71c63b..ea55010aa 100644 --- a/src/ripple/app/hook/impl/applyHook.cpp +++ b/src/ripple/app/hook/impl/applyHook.cpp @@ -559,6 +559,56 @@ get_free_slot(hook::HookContext& hookCtx) return slot_into; } +// cu_ptr is a pointer into memory, bounds check is assumed to have already happened +inline +std::optional parseCurrency( + uint8_t* cu_ptr, uint32_t cu_len) +{ + if (cu_len == 20) + { + // normal 20 byte currency + return Currency::fromVoid(cu_ptr); + } + else if (cu_len == 3) + { + // 3 byte ascii currency + // need to check what data is in these three bytes, to ensure ISO4217 compliance + auto const validateChar = [](uint8_t c) -> bool + { + return + (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == '?' || c == '!' || c == '@' || + c == '#' || c == '$' || c == '%' || + c == '^' || c == '&' || c == '*' || + c == '<' || c == '>' || c == '(' || + c == ')' || c == '{' || c == '}' || + c == '[' || c == ']' || c == '|'; + }; + + if (!validateChar(*((uint8_t*)(cu_ptr + 0U))) || + !validateChar(*((uint8_t*)(cu_ptr + 1U))) || + !validateChar(*((uint8_t*)(cu_ptr + 2U)))) + return {}; + + uint8_t cur_buf[20] = + { + 0,0,0,0, + 0,0,0,0, + 0,0,0,0, + *((uint8_t*)(cu_ptr + 0U)), + *((uint8_t*)(cu_ptr + 1U)), + *((uint8_t*)(cu_ptr + 2U)), + 0,0,0,0,0 + }; + return Currency::fromVoid(cur_buf); + } + else + return {}; +} + + uint32_t hook:: computeHookStateOwnerCount(uint32_t hookStateCount) @@ -2639,48 +2689,9 @@ DEFINE_HOOK_FUNCTION( if (hi_len != 20 || lo_len != 20) return INVALID_ARGUMENT; - std::optional cur; - if (cu_len == 20) - { - // normal 20 byte currency - cur = Currency::fromVoid(memory + cu_ptr); - } - else if (cu_len == 3) - { - // 3 byte ascii currency - // need to check what data is in these three bytes, to ensure ISO4217 compliance - auto const validateChar = [](uint8_t c) -> bool - { - return - (c >= 'a' && c <= 'z') || - (c >= 'A' && c <= 'Z') || - (c >= '0' && c <= '9') || - c == '?' || c == '!' || c == '@' || - c == '#' || c == '$' || c == '%' || - c == '^' || c == '&' || c == '*' || - c == '<' || c == '>' || c == '(' || - c == ')' || c == '{' || c == '}' || - c == '[' || c == ']' || c == '|'; - }; - - if (!validateChar(*((uint8_t*)(cu_ptr + memory + 0U))) || - !validateChar(*((uint8_t*)(cu_ptr + memory + 1U))) || - !validateChar(*((uint8_t*)(cu_ptr + memory + 2U)))) - return INVALID_ARGUMENT; - - uint8_t cur_buf[20] = - { - 0,0,0,0, - 0,0,0,0, - 0,0,0,0, - *((uint8_t*)(cu_ptr + memory + 0U)), - *((uint8_t*)(cu_ptr + memory + 1U)), - *((uint8_t*)(cu_ptr + memory + 2U)), - 0,0,0,0,0 - }; - cur = Currency::fromVoid(cur_buf); - } - else + std::optional cur = + parseCurrency(memory + cu_ptr, cu_len); + if (!cur) return INVALID_ARGUMENT; auto kl = ripple::keylet::line( @@ -4372,6 +4383,51 @@ DEFINE_HOOK_FUNCTION( int64_t float1, uint32_t field_code) { HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx, hookCtx on current stack + + std::optional currency; + std::optional issuer; + + // bounds and argument checks + if (NOT_IN_BOUNDS(write_ptr, write_len, memory_length)) + return OUT_OF_BOUNDS; + + if (cread_len == 0) + { + if (cread_ptr != 0) + return INVALID_ARGUMENT; + } + else + { + if (cread_len != 20 && cread_len != 3) + return INVALID_ARGUMENT; + + if (NOT_IN_BOUNDS(cread_ptr, cread_len, memory_length)) + return OUT_OF_BOUNDS; + + currency = parseCurrency(memory + cread_ptr, cread_len); + + if (!currency) + return INVALID_ARGUMENT; + } + + if (iread_len == 0) + { + if (iread_ptr != 0) + return INVALID_ARGUMENT; + + } + else + { + if (iread_len != 20) + return INVALID_ARGUMENT; + + if (NOT_IN_BOUNDS(iread_ptr, iread_len, memory_length)) + return OUT_OF_BOUNDS; + + issuer = AccountID::fromVoid(memory + iread_ptr); + } + + RETURN_IF_INVALID_FLOAT(float1); uint16_t field = field_code & 0xFFFFU; @@ -4389,24 +4445,23 @@ DEFINE_HOOK_FUNCTION( int64_t bytes_written = 0; - if (NOT_IN_BOUNDS(write_ptr, write_len, memory_length)) - return OUT_OF_BOUNDS; - - if (!is_xrp && !is_short && (cread_ptr == 0 && cread_len == 0 && iread_ptr == 0 && iread_len == 0)) + if (issuer && !currency) return INVALID_ARGUMENT; - if (!is_xrp && !is_short) + if (!issuer && currency) + return INVALID_ARGUMENT; + + if (issuer) { - if (NOT_IN_BOUNDS(cread_ptr, cread_len, memory_length) || - NOT_IN_BOUNDS(iread_ptr, iread_len, memory_length)) - return OUT_OF_BOUNDS; - - if (cread_len != 20 || iread_len != 20) + if (is_xrp) return INVALID_ARGUMENT; - + if (is_short) + return INVALID_ARGUMENT; + bytes_needed += 40; - } + else if (!is_xrp && !is_short) + return INVALID_ARGUMENT; if (bytes_needed > write_len) return TOO_SMALL; @@ -4446,18 +4501,18 @@ DEFINE_HOOK_FUNCTION( uint8_t out[8]; if (is_xrp) { - // we need to normalize to exp -6 - while (exp < -6) - { - man /= 10; - exp++; - } + int32_t shift = -(exp); - while (exp > -6) - { - man *= 10; - exp--; - } + if (shift > 15) + return 0; + + if (shift < 0) + return XFL_OVERFLOW; + + if (shift > 0) + man /= power_of_ten[shift]; + + std::cout << "man: " << man << "\n"; out[0] = (neg ? 0b00000000U : 0b01000000U); out[0] += (uint8_t)((man >> 56U) & 0b111111U); @@ -4562,11 +4617,9 @@ DEFINE_HOOK_FUNCTION( } } - + bool is_xrp = (((*upto) & 0b10000000U) == 0); bool is_negative = (((*upto) & 0b01000000U) == 0); - int32_t exponent = (((*upto++) & 0b00111111U)) << 2U; - exponent += ((*upto)>>6U); - exponent -= 97; + uint64_t mantissa = (((uint64_t)(*upto++)) & 0b00111111U) << 48U; mantissa += ((uint64_t)*upto++) << 40U; mantissa += ((uint64_t)*upto++) << 32U; @@ -4574,6 +4627,19 @@ DEFINE_HOOK_FUNCTION( mantissa += ((uint64_t)*upto++) << 16U; mantissa += ((uint64_t)*upto++) << 8U; mantissa += ((uint64_t)*upto++); + + int32_t exponent = 0; + + if (is_xrp) + { + // exponent remains 0 + } + else + { + exponent = (((*upto++) & 0b00111111U)) << 2U; + exponent += ((*upto)>>6U); + exponent -= 97; + } if (mantissa == 0) return 0; diff --git a/src/test/app/SetHook_test.cpp b/src/test/app/SetHook_test.cpp index 3160848d8..bea383cde 100644 --- a/src/test/app/SetHook_test.cpp +++ b/src/test/app/SetHook_test.cpp @@ -3504,6 +3504,130 @@ public: void test_float_sto() { + testcase("Test float_sto"); + using namespace jtx; + Env env{*this, supported_amendments()}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + env.fund(XRP(10000), alice); + env.fund(XRP(10000), bob); + + { + TestHook hook = wasm[R"[test.hook]( + #include + extern int32_t _g (uint32_t id, uint32_t maxiter); + #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) + extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code); + extern int64_t hook_account (uint32_t, uint32_t); + extern int64_t float_sto ( + uint32_t write_ptr, + uint32_t write_len, + uint32_t cread_ptr, + uint32_t cread_len, + uint32_t iread_ptr, + uint32_t iread_len, + int64_t float1, + uint32_t field_code + ); + int64_t float_sto_set ( + uint32_t read_ptr, + uint32_t read_len + ); + #define OUT_OF_BOUNDS (-1) + #define INVALID_FLOAT (-10024) + #define INVALID_ARGUMENT (-7) + #define TOO_SMALL (-4) + #define XFL_OVERFLOW (-30) + #define ASSERT(x)\ + if (!(x))\ + rollback((uint32_t)#x, sizeof(#x), __LINE__); + + #define SBUF(x) x, sizeof(x) + #define sfAmount ((6U << 16U) + 1U) + uint8_t cur1[3] = {'U','S','D'}; + + #define BUFFER_EQUAL_20(buf1, buf2)\ + (\ + *(((uint64_t*)(buf1)) + 0) == *(((uint64_t*)(buf2)) + 0) &&\ + *(((uint64_t*)(buf1)) + 1) == *(((uint64_t*)(buf2)) + 1) &&\ + *(((uint32_t*)(buf1)) + 4) == *(((uint32_t*)(buf2)) + 4)) + + int64_t hook(uint32_t reserved ) + { + _g(1,1); + uint8_t cur2[20]; + for (int i =0; GUARD(20), i < 20; ++i) + cur2[i] = i; + + uint8_t iss[20]; + ASSERT(hook_account(SBUF(iss)) == 20); + + uint8_t buf[48]; + + // the three buffers must be bounds checked + ASSERT(float_sto(1000000, 50, 0,0,0,0,0,0) == OUT_OF_BOUNDS); + ASSERT(float_sto(0, 1000000, 0,0,0,0,0,0) == OUT_OF_BOUNDS); + ASSERT(float_sto(SBUF(buf), 1000000, 50, 0,0,0,0) == OUT_OF_BOUNDS); + ASSERT(float_sto(SBUF(buf), 0, 1000000, 0,0,0,0) == OUT_OF_BOUNDS); + ASSERT(float_sto(SBUF(buf), 0,0, 1000000, 50, 0,0) == OUT_OF_BOUNDS); + ASSERT(float_sto(SBUF(buf), 0,0, 0, 1000000, 0,0) == OUT_OF_BOUNDS); + + // zero issuer/currency pointers must be accompanied by 0 length + ASSERT(float_sto(SBUF(buf), 0, 1, 0,0, 0,0) == INVALID_ARGUMENT); + ASSERT(float_sto(SBUF(buf), 0, 0, 0,1, 0,0) == INVALID_ARGUMENT); + + // zero issuer/currency lengths mus tbe accompanied by 0 pointers + ASSERT(float_sto(SBUF(buf), 1, 0, 0,0, 0,0) == INVALID_ARGUMENT); + ASSERT(float_sto(SBUF(buf), 0, 0, 1,0, 0,0) == INVALID_ARGUMENT); + + // issuer without currency is invalid + ASSERT(float_sto(SBUF(buf), 0,0, SBUF(iss), 0, sfAmount) == INVALID_ARGUMENT); + + // currency without issuer is invalid + ASSERT(float_sto(SBUF(buf), SBUF(cur1), 0,0, 0, sfAmount) == INVALID_ARGUMENT); + + // currency and issuer with field code 0 = XRP is invalid + ASSERT(float_sto(SBUF(buf), SBUF(cur1), SBUF(iss), 0, 0) == INVALID_ARGUMENT); + + // invalid XFL + ASSERT(float_sto(SBUF(buf), SBUF(cur2), SBUF(iss), -1, sfAmount) == INVALID_FLOAT); + + // valid XFL, currency and issuer + { + // currency and issuer with field code not XRP is valid (XFL = 1234567.0) + ASSERT(float_sto(SBUF(buf), SBUF(cur2), SBUF(iss), 6198187654261802496ULL, sfAmount) == 48); + + // check the output contains the correct currency code + ASSERT(BUFFER_EQUAL_20(buf + 28, cur2)); + + // check the output contains the correct issuer + ASSERT(BUFFER_EQUAL_20(buf + 8, iss)); + + // check the field code is correct + ASSERT(buf[0] == 0x61U); // sfAmount + + // reverse the operation and check the XFL amount is correct + ASSERT(float_sto_set(SBUF(buf)) == 6198187654261802496ULL); + } + + + // RH TODO: xrp and short + + return accept(0,0,0); + + } + )[test.hook]"]; + + env(ripple::test::jtx::hook(alice, {{hso(hook, overrideFlag)}}, 0), + M("set float_sto"), + HSFEE); + env.close(); + + env(pay(bob, alice, XRP(1)), M("test float_sto"), fee(XRP(1))); + env.close(); + } } void @@ -9134,7 +9258,7 @@ public: test_state(); // test_state_foreign(); // - test_state_foreign_set(); + test_state_foreign_set(); // test_state_set(); // test_sto_emplace(); //