diff --git a/include/xrpl/hook/Enum.h b/include/xrpl/hook/Enum.h index 2acf371833..f1ebddc8ae 100644 --- a/include/xrpl/hook/Enum.h +++ b/include/xrpl/hook/Enum.h @@ -349,6 +349,12 @@ const uint8_t max_emit = 255; const uint8_t max_params = 16; const double fee_base_multiplier = 1.1f; +enum GuardRules : uint64_t { + HooksUpdate1 = 0x0001U, + Fix20250131 = 0x0002U, + AtomicEmit = 0x0004U, +}; + // RH NOTE: Find descriptions of api functions in ./impl/applyHook.cpp and // hookapi.h (include for hooks) this is a map of the api name to its return // code (vec[0] and its parameters vec[>0]) as wasm type codes @@ -434,5 +440,8 @@ static const std::map> import_whitelist{ // featureHooks1 static const std::map> import_whitelist_1{ {"xpop_slot", {0x7EU, 0x7FU, 0x7FU}}}; +// featureAtomicEmit +static const std::map> import_whitelist_2{ + {"emit_atomic", {0x7EU, 0x7FU, 0x7FU, 0x7FU, 0x7FU}}}; }; // namespace hook_api #endif diff --git a/include/xrpl/hook/Guard.h b/include/xrpl/hook/Guard.h index f395af4481..a25b377118 100644 --- a/include/xrpl/hook/Guard.h +++ b/include/xrpl/hook/Guard.h @@ -634,7 +634,7 @@ check_guard( } else if (fc_type == 10) // memory.copy { - if (rulesVersion & 0x02U) + if (rulesVersion & hook_api::GuardRules::Fix20250131) GUARD_ERROR("Memory.copy instruction is not allowed."); REQUIRE(2); @@ -642,7 +642,7 @@ check_guard( } else if (fc_type == 11) // memory.fill { - if (rulesVersion & 0x02U) + if (rulesVersion & hook_api::GuardRules::Fix20250131) GUARD_ERROR("Memory.fill instruction is not allowed."); ADVANCE(1); @@ -1028,12 +1028,19 @@ validateGuards( hook_api::import_whitelist.find(import_name) == hook_api::import_whitelist.end()) { - if (rulesVersion > 0 && + if (rulesVersion & hook_api::GuardRules::HooksUpdate1 && hook_api::import_whitelist_1.find(import_name) != hook_api::import_whitelist_1.end()) { // PASS, this is a version 1 api } + else if ( + rulesVersion & hook_api::GuardRules::AtomicEmit && + hook_api::import_whitelist_2.find(import_name) != + hook_api::import_whitelist_2.end()) + { + // PASS, this is a version 2 api + } else { GUARDLOG(hook::log::IMPORT_ILLEGAL) @@ -1258,12 +1265,20 @@ validateGuards( { for (auto const& [import_idx, api_name] : usage->second) { - auto const& api_signature = - hook_api::import_whitelist.find(api_name) != - hook_api::import_whitelist.end() - ? hook_api::import_whitelist.find(api_name)->second - : hook_api::import_whitelist_1.find(api_name) - ->second; + auto findInWhitelist = [&](auto const& whitelist) { + auto it = whitelist.find(api_name); + return it != whitelist.end() ? &it->second + : nullptr; + }; + + auto const* sig = + findInWhitelist(hook_api::import_whitelist); + if (!sig) + sig = findInWhitelist(hook_api::import_whitelist_1); + if (!sig) + sig = findInWhitelist(hook_api::import_whitelist_2); + + auto const& api_signature = *sig; if (!first_signature) { diff --git a/include/xrpl/hook/guard_checker.cpp b/include/xrpl/hook/guard_checker.cpp index f20d24617b..35675879d8 100644 --- a/include/xrpl/hook/guard_checker.cpp +++ b/include/xrpl/hook/guard_checker.cpp @@ -79,7 +79,10 @@ main(int argc, char** argv) close(fd); - auto result = validateGuards(hook, std::cout, "", 3); + auto const allRules = hook_api::GuardRules::HooksUpdate1 + + hook_api::GuardRules::Fix20250131 + hook_api::GuardRules::AtomicEmit; + + auto result = validateGuards(hook, std::cout, "", allRules); if (!result) { diff --git a/include/xrpl/protocol/Feature.h b/include/xrpl/protocol/Feature.h index 9bd1713a3c..a54bfdf402 100644 --- a/include/xrpl/protocol/Feature.h +++ b/include/xrpl/protocol/Feature.h @@ -80,7 +80,7 @@ namespace detail { // Feature.cpp. Because it's only used to reserve storage, and determine how // large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than // the actual number of amendments. A LogicError on startup will verify this. -static constexpr std::size_t numFeatures = 106; +static constexpr std::size_t numFeatures = 107; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated diff --git a/include/xrpl/protocol/TER.h b/include/xrpl/protocol/TER.h index 0973f0e58f..80b5b20f94 100644 --- a/include/xrpl/protocol/TER.h +++ b/include/xrpl/protocol/TER.h @@ -361,6 +361,7 @@ enum TECcodes : TERUnderlyingType { tecARRAY_TOO_LARGE = 196, tecLOCKED = 197, tecBAD_CREDENTIALS = 198, + tecHOOK_EMIT_FAILED = 199, tecLAST_POSSIBLE_ENTRY = 255, }; diff --git a/include/xrpl/protocol/detail/features.macro b/include/xrpl/protocol/detail/features.macro index 3953d1041b..b731ff0dbb 100644 --- a/include/xrpl/protocol/detail/features.macro +++ b/include/xrpl/protocol/detail/features.macro @@ -29,6 +29,7 @@ // If you add an amendment here, then do not forget to increment `numFeatures` // in include/xrpl/protocol/Feature.h. +XRPL_FEATURE(AtomicEmit, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo) XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo) XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo) diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index f7397fd381..df918a7522 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -91,6 +91,7 @@ LEDGER_ENTRY(ltHOOK_DEFINITION, 'D', HookDefinition, hook_definition, ({ {sfHookCallbackFee, soeOPTIONAL}, {sfPreviousTxnID, soeOPTIONAL}, {sfPreviousTxnLgrSeq, soeOPTIONAL}, + {sfHookAtomicEmitFee, soeOPTIONAL}, })) /** A ledger object containing a hook-emitted transaction from a previous hook execution. diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index 30d2ece74c..5dd0e6e08b 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -251,6 +251,7 @@ TYPED_SFIELD(sfPrice, AMOUNT, 28) TYPED_SFIELD(sfSignatureReward, AMOUNT, 29) TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30) TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31) +TYPED_SFIELD(sfHookAtomicEmitFee, AMOUNT, 32) // variable length (common) TYPED_SFIELD(sfPublicKey, VL, 1) diff --git a/src/libxrpl/protocol/InnerObjectFormats.cpp b/src/libxrpl/protocol/InnerObjectFormats.cpp index 06f38626db..a39d6cf466 100644 --- a/src/libxrpl/protocol/InnerObjectFormats.cpp +++ b/src/libxrpl/protocol/InnerObjectFormats.cpp @@ -96,7 +96,8 @@ InnerObjectFormats::InnerObjectFormats() {sfHookCanEmit, soeOPTIONAL}, {sfHookApiVersion, soeREQUIRED}, {sfFlags, soeREQUIRED}, - {sfFee, soeREQUIRED}}); + {sfFee, soeREQUIRED}, + {sfHookAtomicEmitFee, soeOPTIONAL}}); add(sfHook.jsonName, sfHook.getCode(), @@ -108,7 +109,8 @@ InnerObjectFormats::InnerObjectFormats() {sfHookOn, soeOPTIONAL}, {sfHookCanEmit, soeOPTIONAL}, {sfHookApiVersion, soeOPTIONAL}, - {sfFlags, soeOPTIONAL}}); + {sfFlags, soeOPTIONAL}, + {sfHookAtomicEmitFee, soeOPTIONAL}}); add(sfHookGrant.jsonName, sfHookGrant.getCode(), diff --git a/src/libxrpl/protocol/TER.cpp b/src/libxrpl/protocol/TER.cpp index 64f7c8ba25..a179c827df 100644 --- a/src/libxrpl/protocol/TER.cpp +++ b/src/libxrpl/protocol/TER.cpp @@ -123,6 +123,7 @@ transResults() MAKE_ERROR(tecARRAY_TOO_LARGE, "Array is too large."), MAKE_ERROR(tecLOCKED, "Fund is locked."), MAKE_ERROR(tecBAD_CREDENTIALS, "Bad credentials."), + MAKE_ERROR(tecHOOK_EMIT_FAILED, "Failed to emit transactions from Hook."), MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."), MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."), diff --git a/src/test/app/SetHook_test.cpp b/src/test/app/SetHook_test.cpp index 8836c88127..41a7aaf2c6 100644 --- a/src/test/app/SetHook_test.cpp +++ b/src/test/app/SetHook_test.cpp @@ -2933,6 +2933,457 @@ class SetHook0_test : public beast::unit_test::suite } } + Json::Value + getLastLedger(jtx::Env& env) + { + Json::Value params; + params[jss::ledger_index] = env.closed()->seq(); + params[jss::transactions] = true; + params[jss::expand] = true; + return env.rpc("json", "ledger", to_string(params)); + } + + void + test_emit_atomic(FeatureBitset features) + { + testcase("Test emit_atomic"); + using namespace jtx; + // Env env{*this, features}; + Env env{ + *this, envconfig(), features, nullptr, beast::severities::kInfo}; + + auto const alice = Account{"alice"}; + auto const bob = Account{"bob"}; + auto const charlie = Account{"charlie"}; + auto const nonActive = Account{"nonActive"}; + env.fund(XRP(10000), alice); + env.fund(XRP(10000), bob); + env.fund(XRP(10000), charlie); + + TestHook hook = wasm[R"[test.hook]( + #include + extern int32_t _g(uint32_t, uint32_t); + 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 emit_atomic (uint32_t, uint32_t, uint32_t, uint32_t); + extern int64_t etxn_reserve(uint32_t); + extern int64_t otxn_param(uint32_t, uint32_t, uint32_t, uint32_t); + extern int64_t hook_account(uint32_t, uint32_t); + extern int64_t otxn_field ( + uint32_t write_ptr, + uint32_t write_len, + uint32_t field_id + ); + #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) + #define OUT_OF_BOUNDS (-1) + #define ttPAYMENT 0 + #define tfCANONICAL 0x80000000UL + #define amAMOUNT 1U + #define amFEE 8U + #define atACCOUNT 1U + #define DOESNT_EXIST (-5) + #define atDESTINATION 3U + #define SBUF(x) (uint32_t)x,sizeof(x) + + #define PREREQUISITE_NOT_MET -9 + #define ENCODE_DROPS_SIZE 9 + #define ENCODE_DROPS(buf_out, drops, amount_type ) \ + {\ + uint8_t uat = amount_type; \ + uint64_t udrops = drops; \ + buf_out[0] = 0x60U +(uat & 0x0FU ); \ + buf_out[1] = 0b01000000 + (( udrops >> 56 ) & 0b00111111 ); \ + buf_out[2] = (udrops >> 48) & 0xFFU; \ + buf_out[3] = (udrops >> 40) & 0xFFU; \ + buf_out[4] = (udrops >> 32) & 0xFFU; \ + buf_out[5] = (udrops >> 24) & 0xFFU; \ + buf_out[6] = (udrops >> 16) & 0xFFU; \ + buf_out[7] = (udrops >> 8) & 0xFFU; \ + buf_out[8] = (udrops >> 0) & 0xFFU; \ + buf_out += ENCODE_DROPS_SIZE; \ + } + + #define _06_XX_ENCODE_DROPS(buf_out, drops, amount_type )\ + ENCODE_DROPS(buf_out, drops, amount_type ); + + #define ENCODE_DROPS_AMOUNT(buf_out, drops )\ + ENCODE_DROPS(buf_out, drops, amAMOUNT ); + #define _06_01_ENCODE_DROPS_AMOUNT(buf_out, drops )\ + ENCODE_DROPS_AMOUNT(buf_out, drops ); + + #define ENCODE_DROPS_FEE(buf_out, drops )\ + ENCODE_DROPS(buf_out, drops, amFEE ); + #define _06_08_ENCODE_DROPS_FEE(buf_out, drops )\ + ENCODE_DROPS_FEE(buf_out, drops ); + + #define ENCODE_TT_SIZE 3 + #define ENCODE_TT(buf_out, tt )\ + {\ + uint8_t utt = tt;\ + buf_out[0] = 0x12U;\ + buf_out[1] =(utt >> 8 ) & 0xFFU;\ + buf_out[2] =(utt >> 0 ) & 0xFFU;\ + buf_out += ENCODE_TT_SIZE; \ + } + #define _01_02_ENCODE_TT(buf_out, tt)\ + ENCODE_TT(buf_out, tt); + + + #define ENCODE_ACCOUNT_SIZE 22 + #define ENCODE_ACCOUNT(buf_out, account_id, account_type)\ + {\ + uint8_t uat = account_type;\ + buf_out[0] = 0x80U + uat;\ + buf_out[1] = 0x14U;\ + *(uint64_t*)(buf_out + 2) = *(uint64_t*)(account_id + 0);\ + *(uint64_t*)(buf_out + 10) = *(uint64_t*)(account_id + 8);\ + *(uint32_t*)(buf_out + 18) = *(uint32_t*)(account_id + 16);\ + buf_out += ENCODE_ACCOUNT_SIZE;\ + } + #define _08_XX_ENCODE_ACCOUNT(buf_out, account_id, account_type)\ + ENCODE_ACCOUNT(buf_out, account_id, account_type); + + #define ENCODE_ACCOUNT_SRC_SIZE 22 + #define ENCODE_ACCOUNT_SRC(buf_out, account_id)\ + ENCODE_ACCOUNT(buf_out, account_id, atACCOUNT); + #define _08_01_ENCODE_ACCOUNT_SRC(buf_out, account_id)\ + ENCODE_ACCOUNT_SRC(buf_out, account_id); + + #define ENCODE_ACCOUNT_DST_SIZE 22 + #define ENCODE_ACCOUNT_DST(buf_out, account_id)\ + ENCODE_ACCOUNT(buf_out, account_id, atDESTINATION); + #define _08_03_ENCODE_ACCOUNT_DST(buf_out, account_id)\ + ENCODE_ACCOUNT_DST(buf_out, account_id); + + #define ENCODE_ACCOUNT_OWNER_SIZE 22 + #define ENCODE_ACCOUNT_OWNER(buf_out, account_id) \ + ENCODE_ACCOUNT(buf_out, account_id, atOWNER); + #define _08_02_ENCODE_ACCOUNT_OWNER(buf_out, account_id) \ + ENCODE_ACCOUNT_OWNER(buf_out, account_id); + + #define ENCODE_UINT32_COMMON_SIZE 5U + #define ENCODE_UINT32_COMMON(buf_out, i, field)\ + {\ + uint32_t ui = i; \ + uint8_t uf = field; \ + buf_out[0] = 0x20U +(uf & 0x0FU); \ + buf_out[1] =(ui >> 24 ) & 0xFFU; \ + buf_out[2] =(ui >> 16 ) & 0xFFU; \ + buf_out[3] =(ui >> 8 ) & 0xFFU; \ + buf_out[4] =(ui >> 0 ) & 0xFFU; \ + buf_out += ENCODE_UINT32_COMMON_SIZE; \ + } + #define _02_XX_ENCODE_UINT32_COMMON(buf_out, i, field)\ + ENCODE_UINT32_COMMON(buf_out, i, field)\ + + #define ENCODE_UINT32_UNCOMMON_SIZE 6U + #define ENCODE_UINT32_UNCOMMON(buf_out, i, field)\ + {\ + uint32_t ui = i; \ + uint8_t uf = field; \ + buf_out[0] = 0x20U; \ + buf_out[1] = uf; \ + buf_out[2] =(ui >> 24 ) & 0xFFU; \ + buf_out[3] =(ui >> 16 ) & 0xFFU; \ + buf_out[4] =(ui >> 8 ) & 0xFFU; \ + buf_out[5] =(ui >> 0 ) & 0xFFU; \ + buf_out += ENCODE_UINT32_UNCOMMON_SIZE; \ + } + #define _02_XX_ENCODE_UINT32_UNCOMMON(buf_out, i, field)\ + ENCODE_UINT32_UNCOMMON(buf_out, i, field)\ + + #define ENCODE_LLS_SIZE 6U + #define ENCODE_LLS(buf_out, lls )\ + ENCODE_UINT32_UNCOMMON(buf_out, lls, 0x1B ); + #define _02_27_ENCODE_LLS(buf_out, lls )\ + ENCODE_LLS(buf_out, lls ); + + #define ENCODE_FLS_SIZE 6U + #define ENCODE_FLS(buf_out, fls )\ + ENCODE_UINT32_UNCOMMON(buf_out, fls, 0x1A ); + #define _02_26_ENCODE_FLS(buf_out, fls )\ + ENCODE_FLS(buf_out, fls ); + + #define ENCODE_TAG_SRC_SIZE 5 + #define ENCODE_TAG_SRC(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0x3U ); + #define _02_03_ENCODE_TAG_SRC(buf_out, tag )\ + ENCODE_TAG_SRC(buf_out, tag ); + + #define ENCODE_TAG_DST_SIZE 5 + #define ENCODE_TAG_DST(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0xEU ); + #define _02_14_ENCODE_TAG_DST(buf_out, tag )\ + ENCODE_TAG_DST(buf_out, tag ); + + #define ENCODE_SEQUENCE_SIZE 5 + #define ENCODE_SEQUENCE(buf_out, sequence )\ + ENCODE_UINT32_COMMON(buf_out, sequence, 0x4U ); + #define _02_04_ENCODE_SEQUENCE(buf_out, sequence )\ + ENCODE_SEQUENCE(buf_out, sequence ); + + #define ENCODE_FLAGS_SIZE 5 + #define ENCODE_FLAGS(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0x2U ); + #define _02_02_ENCODE_FLAGS(buf_out, tag )\ + ENCODE_FLAGS(buf_out, tag ); + + #define ENCODE_SIGNING_PUBKEY_SIZE 35 + #define ENCODE_SIGNING_PUBKEY(buf_out, pkey )\ + {\ + buf_out[0] = 0x73U;\ + buf_out[1] = 0x21U;\ + *(uint64_t*)(buf_out + 2) = *(uint64_t*)(pkey + 0);\ + *(uint64_t*)(buf_out + 10) = *(uint64_t*)(pkey + 8);\ + *(uint64_t*)(buf_out + 18) = *(uint64_t*)(pkey + 16);\ + *(uint64_t*)(buf_out + 26) = *(uint64_t*)(pkey + 24);\ + buf[34] = pkey[32];\ + buf_out += ENCODE_SIGNING_PUBKEY_SIZE;\ + } + + #define _07_03_ENCODE_SIGNING_PUBKEY(buf_out, pkey )\ + ENCODE_SIGNING_PUBKEY(buf_out, pkey ); + + #define ENCODE_SIGNING_PUBKEY_NULL_SIZE 35 + #define ENCODE_SIGNING_PUBKEY_NULL(buf_out )\ + {\ + buf_out[0] = 0x73U;\ + buf_out[1] = 0x21U;\ + *(uint64_t*)(buf_out+2) = 0;\ + *(uint64_t*)(buf_out+10) = 0;\ + *(uint64_t*)(buf_out+18) = 0;\ + *(uint64_t*)(buf_out+25) = 0;\ + buf_out += ENCODE_SIGNING_PUBKEY_NULL_SIZE;\ + } + + #define _07_03_ENCODE_SIGNING_PUBKEY_NULL(buf_out )\ + ENCODE_SIGNING_PUBKEY_NULL(buf_out ); + + extern int64_t etxn_fee_base ( + uint32_t read_ptr, + uint32_t read_len + ); + extern int64_t etxn_details ( + uint32_t write_ptr, + uint32_t write_len + ); + extern int64_t ledger_seq (void); + + #define PREPARE_PAYMENT_SIMPLE_SIZE 248U + #define PREPARE_PAYMENT_SIMPLE(buf_out_master, drops_amount_raw, to_address, dest_tag_raw, src_tag_raw)\ + {\ + uint8_t* buf_out = buf_out_master;\ + uint8_t acc[20];\ + uint64_t drops_amount = (drops_amount_raw);\ + uint32_t dest_tag = (dest_tag_raw);\ + uint32_t src_tag = (src_tag_raw);\ + uint32_t cls = (uint32_t)ledger_seq();\ + hook_account(SBUF(acc));\ + _01_02_ENCODE_TT (buf_out, ttPAYMENT ); /* uint16 | size 3 */ \ + _02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); /* uint32 | size 5 */ \ + _02_03_ENCODE_TAG_SRC (buf_out, src_tag ); /* uint32 | size 5 */ \ + _02_04_ENCODE_SEQUENCE (buf_out, 3 ); /* uint32 | size 5 */ \ + _02_14_ENCODE_TAG_DST (buf_out, dest_tag ); /* uint32 | size 5 */ \ + _02_26_ENCODE_FLS (buf_out, cls + 0 ); /* uint32 | size 6 */ \ + _02_27_ENCODE_LLS (buf_out, cls + 0 ); /* uint32 | size 6 */ \ + _06_01_ENCODE_DROPS_AMOUNT (buf_out, drops_amount ); /* amount | size 9 */ \ + uint8_t* fee_ptr = buf_out;\ + _06_08_ENCODE_DROPS_FEE (buf_out, 0 ); /* amount | size 9 */ \ + _07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out ); /* pk | size 35 */ \ + _08_01_ENCODE_ACCOUNT_SRC (buf_out, acc ); /* account | size 22 */ \ + _08_03_ENCODE_ACCOUNT_DST (buf_out, to_address ); /* account | size 22 */ \ + int64_t edlen = etxn_details((uint32_t)buf_out, PREPARE_PAYMENT_SIMPLE_SIZE); /* emitdet | size 1?? */ \ + int64_t fee = etxn_fee_base(buf_out_master, PREPARE_PAYMENT_SIMPLE_SIZE); \ + _06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \ + } + + #define UINT16_FROM_BUF(buf)\ + (((uint64_t)((buf)[0]) << 8U) +\ + ((uint64_t)((buf)[1]) << 0U)) + + #define BUFFER_EQUAL_32(buf1, buf2)\ + (\ + *(((uint64_t*)(buf1)) + 0) == *(((uint64_t*)(buf2)) + 0) &&\ + *(((uint64_t*)(buf1)) + 1) == *(((uint64_t*)(buf2)) + 1) &&\ + *(((uint64_t*)(buf1)) + 2) == *(((uint64_t*)(buf2)) + 2) &&\ + *(((uint64_t*)(buf1)) + 3) == *(((uint64_t*)(buf2)) + 3) &&\ + *(((uint64_t*)(buf1)) + 4) == *(((uint64_t*)(buf2)) + 4) &&\ + *(((uint64_t*)(buf1)) + 5) == *(((uint64_t*)(buf2)) + 5) &&\ + *(((uint64_t*)(buf1)) + 6) == *(((uint64_t*)(buf2)) + 6) &&\ + *(((uint64_t*)(buf1)) + 7) == *(((uint64_t*)(buf2)) + 7)) + + #define ASSERT(x)\ + if (!(x))\ + rollback((uint32_t)#x,sizeof(#x),__LINE__) + + #define sfDestination ((8U << 16U) + 3U) + + extern int64_t etxn_generation(void); + extern int64_t otxn_generation(void); + extern int64_t otxn_burden(void); + extern int64_t etxn_burden(void); + + // int64_t cbak(uint32_t r) + // { + // // on callback we emit 2 more txns + // uint8_t bob[20]; + // ASSERT(otxn_field(SBUF(bob), sfDestination) == 20); + + // ASSERT(otxn_generation() + 1 == etxn_generation()); + + // ASSERT(etxn_burden() == PREREQUISITE_NOT_MET); + + // ASSERT(etxn_reserve(2) == 2); + + // ASSERT(otxn_burden() > 0); + // ASSERT(etxn_burden() == otxn_burden() * 2); + + // uint8_t tx[PREPARE_PAYMENT_SIMPLE_SIZE]; + // PREPARE_PAYMENT_SIMPLE(tx, 1000, bob, 0, 0); + + // uint8_t hash1[32]; + // ASSERT(emit_atomic(SBUF(hash1), SBUF(tx)) == 32); + + // ASSERT(etxn_details(tx + 132, 138) == 138); + // uint8_t hash2[32]; + // ASSERT(emit_atomic(SBUF(hash2), SBUF(tx)) == 32); + + // ASSERT(!BUFFER_EQUAL_32(hash1, hash2)); + + // return accept(0,0,0); + // } + + int64_t hook(uint32_t r) + { + _g(1,1); + + etxn_reserve(1); + + // bounds checks + ASSERT(emit_atomic(1000000, 32, 0, 32) == OUT_OF_BOUNDS); + ASSERT(emit_atomic(0,1000000, 0, 32) == OUT_OF_BOUNDS); + ASSERT(emit_atomic(0,32, 1000000, 32) == OUT_OF_BOUNDS); + ASSERT(emit_atomic(0,32, 0, 1000000) == OUT_OF_BOUNDS); + + ASSERT(otxn_generation() == 0); + ASSERT(otxn_burden == 1); + + uint8_t bob[20]; + ASSERT(otxn_param(SBUF(bob), "bob", 3) == 20); + + uint8_t tx[PREPARE_PAYMENT_SIMPLE_SIZE]; + PREPARE_PAYMENT_SIMPLE(tx, 1000, bob, 0, 0); + + uint8_t hash[32]; + ASSERT(emit_atomic(SBUF(hash), SBUF(tx)) == 32); + + return accept(0,0,0); + } + )[test.hook]"]; + + auto hookObj = hso(hook, overrideFlag); + // hookObj[jss::HookOn] = + // "FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFF7FFFFFFFFFFFFFFFFFFBFFF" + // "FF"; // Invoke + hookObj[sfHookAtomicEmitFee.jsonName] = "1000"; + env(ripple::test::jtx::hook(charlie, {{hookObj}}, 0), + M("set emit_atomic"), + HSFEE); + env.close(); + + // Json::Value invoke; + // invoke[jss::TransactionType] = "Invoke"; + // invoke[jss::Account] = alice.human(); + // invoke[jss::Destination] = charlie.human(); + + Json::Value invoke; + invoke[jss::TransactionType] = "Payment"; + invoke[jss::Account] = alice.human(); + invoke[jss::Destination] = charlie.human(); + invoke[jss::Amount] = "1234"; + + Json::Value params{Json::arrayValue}; + params[0U][jss::HookParameter][jss::HookParameterName] = + strHex(std::string("bob")); + params[0U][jss::HookParameter][jss::HookParameterValue] = + strHex(nonActive.id()); + + invoke[jss::HookParameters] = params; + + env(invoke, M("test emit_atomic"), fee(XRP(1)), ter(tesSUCCESS)); + env.close(); + + bool const fixV2 = env.current()->rules().enabled(fixXahauV2); + + std::optional emithash; + { + auto meta = env.meta(); // meta can close + + printf( + "txs: %s\n", + getLastLedger(env)[jss::result][jss::ledger][jss::transactions] + .toStyledString() + .c_str()); + + // ensure hook execution occured + BEAST_REQUIRE(meta); + BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions)); + + auto const hookEmissions = meta->getFieldArray(sfHookEmissions); + BEAST_EXPECT( + hookEmissions[0u].isFieldPresent(sfEmitNonce) == fixV2 ? true + : false); + BEAST_EXPECT( + hookEmissions[0u].getAccountID(sfHookAccount) == alice.id()); + + auto const hookExecutions = meta->getFieldArray(sfHookExecutions); + BEAST_REQUIRE(hookExecutions.size() == 1); + + // ensure there was one emitted txn + BEAST_EXPECT(hookExecutions[0].getFieldU16(sfHookEmitCount) == 1); + + BEAST_REQUIRE(meta->isFieldPresent(sfAffectedNodes)); + + BEAST_REQUIRE(meta->getFieldArray(sfAffectedNodes).size() == 3); + + for (auto const& node : meta->getFieldArray(sfAffectedNodes)) + { + SField const& metaType = node.getFName(); + uint16_t nodeType = node.getFieldU16(sfLedgerEntryType); + if (metaType == sfCreatedNode && nodeType == ltEMITTED_TXN) + { + BEAST_REQUIRE(node.isFieldPresent(sfNewFields)); + + auto const& nf = const_cast(node) + .getField(sfNewFields) + .downcast(); + + auto const& et = const_cast(nf) + .getField(sfEmittedTxn) + .downcast(); + + auto const& em = const_cast(et) + .getField(sfEmitDetails) + .downcast(); + + BEAST_EXPECT(em.getFieldU32(sfEmitGeneration) == 1); + BEAST_EXPECT(em.getFieldU64(sfEmitBurden) == 1); + + Blob txBlob = et.getSerializer().getData(); + auto const tx = std::make_unique( + Slice{txBlob.data(), txBlob.size()}); + emithash = tx->getTransactionID(); + + break; + } + } + + BEAST_REQUIRE(emithash); + BEAST_EXPECT( + emithash == hookEmissions[0u].getFieldH256(sfEmittedTxnID)); + } + } + void test_etxn_details(FeatureBitset features) { @@ -12734,117 +13185,118 @@ class SetHook0_test : public beast::unit_test::suite void testWithFeatures(FeatureBitset features) { - testHooksOwnerDir(features); - testHooksDisabled(features); - testTxStructure(features); - testInferHookSetOperation(); - testParams(features); - testGrants(features); - testHookCanEmit(features); - - testDelete(features); - testInstall(features); - testCreate(features); - testWithTickets(features); - - testUpdate(features); - - testNSDelete(features); - testNSDeletePartial(features); - testPageCap(features); - - testFillCopy(features); - - testWasm(features); - test_accept(features); - test_rollback(features); - - testGuards(features); - - test_emit(features); // - // test_etxn_burden(features); // tested above - // test_etxn_generation(features); // tested above - // test_otxn_burden(features); // tested above - // test_otxn_generation(features); // tested above - test_etxn_details(features); // - test_etxn_fee_base(features); // - test_etxn_nonce(features); // - test_etxn_reserve(features); // - test_fee_base(features); // - - test_otxn_field(features); // - - test_ledger_keylet(features); // - - test_float_compare(features); // - test_float_divide(features); // - test_float_int(features); // - test_float_invert(features); // - test_float_log(features); // - test_float_mantissa(features); // - test_float_mulratio(features); // - test_float_multiply(features); // - test_float_negate(features); // - test_float_one(features); // - test_float_root(features); // - test_float_set(features); // - test_float_sign(features); // - test_float_sto(features); // - test_float_sto_set(features); // - test_float_sum(features); // - - test_hook_account(features); // - test_hook_again(features); // - test_hook_hash(features); // - test_hook_param(features); // - test_hook_param_set(features); // - test_hook_pos(features); // - test_hook_skip(features); // - - test_ledger_last_hash(features); // - test_ledger_last_time(features); // - test_ledger_nonce(features); // - test_ledger_seq(features); // - - test_meta_slot(features); // - test_xpop_slot(features); // - - test_otxn_id(features); // - test_otxn_slot(features); // - test_otxn_type(features); // - test_otxn_param(features); // - - test_slot(features); // - test_slot_clear(features); // - test_slot_count(features); // - test_slot_float(features); // - test_slot_set(features); // - test_slot_size(features); // - test_slot_subarray(features); // - test_slot_subfield(features); // - test_slot_type(features); // - - test_state(features); // - test_state_foreign(features); // - test_state_foreign_set(features); // - test_state_foreign_set_max(features); // - test_state_set(features); // - - test_sto_emplace(features); // - test_sto_erase(features); // - test_sto_subarray(features); // - test_sto_subfield(features); // - test_sto_validate(features); // - - test_trace(features); // - test_trace_float(features); // - test_trace_num(features); // - - test_util_accid(features); // - test_util_keylet(features); // - test_util_raddr(features); // - test_util_sha512h(features); // - test_util_verify(features); // + // testHooksOwnerDir(features); + // testHooksDisabled(features); + // testTxStructure(features); + // testInferHookSetOperation(); + // testParams(features); + // testGrants(features); + // testHookCanEmit(features); + + // testDelete(features); + // testInstall(features); + // testCreate(features); + // testWithTickets(features); + + // testUpdate(features); + + // testNSDelete(features); + // testNSDeletePartial(features); + // testPageCap(features); + + // testFillCopy(features); + + // testWasm(features); + // test_accept(features); + // test_rollback(features); + + // testGuards(features); + + // test_emit(features); // + test_emit_atomic(features); // + // // test_etxn_burden(features); // tested above + // // test_etxn_generation(features); // tested above + // // test_otxn_burden(features); // tested above + // // test_otxn_generation(features); // tested above + // test_etxn_details(features); // + // test_etxn_fee_base(features); // + // test_etxn_nonce(features); // + // test_etxn_reserve(features); // + // test_fee_base(features); // + + // test_otxn_field(features); // + + // test_ledger_keylet(features); // + + // test_float_compare(features); // + // test_float_divide(features); // + // test_float_int(features); // + // test_float_invert(features); // + // test_float_log(features); // + // test_float_mantissa(features); // + // test_float_mulratio(features); // + // test_float_multiply(features); // + // test_float_negate(features); // + // test_float_one(features); // + // test_float_root(features); // + // test_float_set(features); // + // test_float_sign(features); // + // test_float_sto(features); // + // test_float_sto_set(features); // + // test_float_sum(features); // + + // test_hook_account(features); // + // test_hook_again(features); // + // test_hook_hash(features); // + // test_hook_param(features); // + // test_hook_param_set(features); // + // test_hook_pos(features); // + // test_hook_skip(features); // + + // test_ledger_last_hash(features); // + // test_ledger_last_time(features); // + // test_ledger_nonce(features); // + // test_ledger_seq(features); // + + // test_meta_slot(features); // + // test_xpop_slot(features); // + + // test_otxn_id(features); // + // test_otxn_slot(features); // + // test_otxn_type(features); // + // test_otxn_param(features); // + + // test_slot(features); // + // test_slot_clear(features); // + // test_slot_count(features); // + // test_slot_float(features); // + // test_slot_set(features); // + // test_slot_size(features); // + // test_slot_subarray(features); // + // test_slot_subfield(features); // + // test_slot_type(features); // + + // test_state(features); // + // test_state_foreign(features); // + // test_state_foreign_set(features); // + // test_state_foreign_set_max(features); // + // test_state_set(features); // + + // test_sto_emplace(features); // + // test_sto_erase(features); // + // test_sto_subarray(features); // + // test_sto_subfield(features); // + // test_sto_validate(features); // + + // test_trace(features); // + // test_trace_float(features); // + // test_trace_num(features); // + + // test_util_accid(features); // + // test_util_keylet(features); // + // test_util_raddr(features); // + // test_util_sha512h(features); // + // test_util_verify(features); // } public: @@ -13031,11 +13483,11 @@ SETHOOK_TEST(4, false) SETHOOK_TEST(5, true) BEAST_DEFINE_TESTSUITE_PRIO(SetHook0, app, ripple, 2); -BEAST_DEFINE_TESTSUITE_PRIO(SetHook1, app, ripple, 2); -BEAST_DEFINE_TESTSUITE_PRIO(SetHook2, app, ripple, 2); -BEAST_DEFINE_TESTSUITE_PRIO(SetHook3, app, ripple, 2); -BEAST_DEFINE_TESTSUITE_PRIO(SetHook4, app, ripple, 2); -BEAST_DEFINE_TESTSUITE_PRIO(SetHook5, app, ripple, 2); +// BEAST_DEFINE_TESTSUITE_PRIO(SetHook1, app, ripple, 2); +// BEAST_DEFINE_TESTSUITE_PRIO(SetHook2, app, ripple, 2); +// BEAST_DEFINE_TESTSUITE_PRIO(SetHook3, app, ripple, 2); +// BEAST_DEFINE_TESTSUITE_PRIO(SetHook4, app, ripple, 2); +// BEAST_DEFINE_TESTSUITE_PRIO(SetHook5, app, ripple, 2); } // namespace test } // namespace ripple #undef M diff --git a/src/test/app/SetHook_wasm.h b/src/test/app/SetHook_wasm.h index a7e75c827c..04e257343d 100644 --- a/src/test/app/SetHook_wasm.h +++ b/src/test/app/SetHook_wasm.h @@ -979,6 +979,480 @@ std::map> wasm = { }}, /* ==== WASM: 6 ==== */ + {R"[test.hook]( + #include + extern int32_t _g(uint32_t, uint32_t); + 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 emit_atomic (uint32_t, uint32_t, uint32_t, uint32_t); + extern int64_t etxn_reserve(uint32_t); + extern int64_t otxn_param(uint32_t, uint32_t, uint32_t, uint32_t); + extern int64_t hook_account(uint32_t, uint32_t); + extern int64_t otxn_field ( + uint32_t write_ptr, + uint32_t write_len, + uint32_t field_id + ); + #define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1) + #define OUT_OF_BOUNDS (-1) + #define ttPAYMENT 0 + #define tfCANONICAL 0x80000000UL + #define amAMOUNT 1U + #define amFEE 8U + #define atACCOUNT 1U + #define DOESNT_EXIST (-5) + #define atDESTINATION 3U + #define SBUF(x) (uint32_t)x,sizeof(x) + + #define PREREQUISITE_NOT_MET -9 + #define ENCODE_DROPS_SIZE 9 + #define ENCODE_DROPS(buf_out, drops, amount_type ) \ + {\ + uint8_t uat = amount_type; \ + uint64_t udrops = drops; \ + buf_out[0] = 0x60U +(uat & 0x0FU ); \ + buf_out[1] = 0b01000000 + (( udrops >> 56 ) & 0b00111111 ); \ + buf_out[2] = (udrops >> 48) & 0xFFU; \ + buf_out[3] = (udrops >> 40) & 0xFFU; \ + buf_out[4] = (udrops >> 32) & 0xFFU; \ + buf_out[5] = (udrops >> 24) & 0xFFU; \ + buf_out[6] = (udrops >> 16) & 0xFFU; \ + buf_out[7] = (udrops >> 8) & 0xFFU; \ + buf_out[8] = (udrops >> 0) & 0xFFU; \ + buf_out += ENCODE_DROPS_SIZE; \ + } + + #define _06_XX_ENCODE_DROPS(buf_out, drops, amount_type )\ + ENCODE_DROPS(buf_out, drops, amount_type ); + + #define ENCODE_DROPS_AMOUNT(buf_out, drops )\ + ENCODE_DROPS(buf_out, drops, amAMOUNT ); + #define _06_01_ENCODE_DROPS_AMOUNT(buf_out, drops )\ + ENCODE_DROPS_AMOUNT(buf_out, drops ); + + #define ENCODE_DROPS_FEE(buf_out, drops )\ + ENCODE_DROPS(buf_out, drops, amFEE ); + #define _06_08_ENCODE_DROPS_FEE(buf_out, drops )\ + ENCODE_DROPS_FEE(buf_out, drops ); + + #define ENCODE_TT_SIZE 3 + #define ENCODE_TT(buf_out, tt )\ + {\ + uint8_t utt = tt;\ + buf_out[0] = 0x12U;\ + buf_out[1] =(utt >> 8 ) & 0xFFU;\ + buf_out[2] =(utt >> 0 ) & 0xFFU;\ + buf_out += ENCODE_TT_SIZE; \ + } + #define _01_02_ENCODE_TT(buf_out, tt)\ + ENCODE_TT(buf_out, tt); + + + #define ENCODE_ACCOUNT_SIZE 22 + #define ENCODE_ACCOUNT(buf_out, account_id, account_type)\ + {\ + uint8_t uat = account_type;\ + buf_out[0] = 0x80U + uat;\ + buf_out[1] = 0x14U;\ + *(uint64_t*)(buf_out + 2) = *(uint64_t*)(account_id + 0);\ + *(uint64_t*)(buf_out + 10) = *(uint64_t*)(account_id + 8);\ + *(uint32_t*)(buf_out + 18) = *(uint32_t*)(account_id + 16);\ + buf_out += ENCODE_ACCOUNT_SIZE;\ + } + #define _08_XX_ENCODE_ACCOUNT(buf_out, account_id, account_type)\ + ENCODE_ACCOUNT(buf_out, account_id, account_type); + + #define ENCODE_ACCOUNT_SRC_SIZE 22 + #define ENCODE_ACCOUNT_SRC(buf_out, account_id)\ + ENCODE_ACCOUNT(buf_out, account_id, atACCOUNT); + #define _08_01_ENCODE_ACCOUNT_SRC(buf_out, account_id)\ + ENCODE_ACCOUNT_SRC(buf_out, account_id); + + #define ENCODE_ACCOUNT_DST_SIZE 22 + #define ENCODE_ACCOUNT_DST(buf_out, account_id)\ + ENCODE_ACCOUNT(buf_out, account_id, atDESTINATION); + #define _08_03_ENCODE_ACCOUNT_DST(buf_out, account_id)\ + ENCODE_ACCOUNT_DST(buf_out, account_id); + + #define ENCODE_ACCOUNT_OWNER_SIZE 22 + #define ENCODE_ACCOUNT_OWNER(buf_out, account_id) \ + ENCODE_ACCOUNT(buf_out, account_id, atOWNER); + #define _08_02_ENCODE_ACCOUNT_OWNER(buf_out, account_id) \ + ENCODE_ACCOUNT_OWNER(buf_out, account_id); + + #define ENCODE_UINT32_COMMON_SIZE 5U + #define ENCODE_UINT32_COMMON(buf_out, i, field)\ + {\ + uint32_t ui = i; \ + uint8_t uf = field; \ + buf_out[0] = 0x20U +(uf & 0x0FU); \ + buf_out[1] =(ui >> 24 ) & 0xFFU; \ + buf_out[2] =(ui >> 16 ) & 0xFFU; \ + buf_out[3] =(ui >> 8 ) & 0xFFU; \ + buf_out[4] =(ui >> 0 ) & 0xFFU; \ + buf_out += ENCODE_UINT32_COMMON_SIZE; \ + } + #define _02_XX_ENCODE_UINT32_COMMON(buf_out, i, field)\ + ENCODE_UINT32_COMMON(buf_out, i, field)\ + + #define ENCODE_UINT32_UNCOMMON_SIZE 6U + #define ENCODE_UINT32_UNCOMMON(buf_out, i, field)\ + {\ + uint32_t ui = i; \ + uint8_t uf = field; \ + buf_out[0] = 0x20U; \ + buf_out[1] = uf; \ + buf_out[2] =(ui >> 24 ) & 0xFFU; \ + buf_out[3] =(ui >> 16 ) & 0xFFU; \ + buf_out[4] =(ui >> 8 ) & 0xFFU; \ + buf_out[5] =(ui >> 0 ) & 0xFFU; \ + buf_out += ENCODE_UINT32_UNCOMMON_SIZE; \ + } + #define _02_XX_ENCODE_UINT32_UNCOMMON(buf_out, i, field)\ + ENCODE_UINT32_UNCOMMON(buf_out, i, field)\ + + #define ENCODE_LLS_SIZE 6U + #define ENCODE_LLS(buf_out, lls )\ + ENCODE_UINT32_UNCOMMON(buf_out, lls, 0x1B ); + #define _02_27_ENCODE_LLS(buf_out, lls )\ + ENCODE_LLS(buf_out, lls ); + + #define ENCODE_FLS_SIZE 6U + #define ENCODE_FLS(buf_out, fls )\ + ENCODE_UINT32_UNCOMMON(buf_out, fls, 0x1A ); + #define _02_26_ENCODE_FLS(buf_out, fls )\ + ENCODE_FLS(buf_out, fls ); + + #define ENCODE_TAG_SRC_SIZE 5 + #define ENCODE_TAG_SRC(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0x3U ); + #define _02_03_ENCODE_TAG_SRC(buf_out, tag )\ + ENCODE_TAG_SRC(buf_out, tag ); + + #define ENCODE_TAG_DST_SIZE 5 + #define ENCODE_TAG_DST(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0xEU ); + #define _02_14_ENCODE_TAG_DST(buf_out, tag )\ + ENCODE_TAG_DST(buf_out, tag ); + + #define ENCODE_SEQUENCE_SIZE 5 + #define ENCODE_SEQUENCE(buf_out, sequence )\ + ENCODE_UINT32_COMMON(buf_out, sequence, 0x4U ); + #define _02_04_ENCODE_SEQUENCE(buf_out, sequence )\ + ENCODE_SEQUENCE(buf_out, sequence ); + + #define ENCODE_FLAGS_SIZE 5 + #define ENCODE_FLAGS(buf_out, tag )\ + ENCODE_UINT32_COMMON(buf_out, tag, 0x2U ); + #define _02_02_ENCODE_FLAGS(buf_out, tag )\ + ENCODE_FLAGS(buf_out, tag ); + + #define ENCODE_SIGNING_PUBKEY_SIZE 35 + #define ENCODE_SIGNING_PUBKEY(buf_out, pkey )\ + {\ + buf_out[0] = 0x73U;\ + buf_out[1] = 0x21U;\ + *(uint64_t*)(buf_out + 2) = *(uint64_t*)(pkey + 0);\ + *(uint64_t*)(buf_out + 10) = *(uint64_t*)(pkey + 8);\ + *(uint64_t*)(buf_out + 18) = *(uint64_t*)(pkey + 16);\ + *(uint64_t*)(buf_out + 26) = *(uint64_t*)(pkey + 24);\ + buf[34] = pkey[32];\ + buf_out += ENCODE_SIGNING_PUBKEY_SIZE;\ + } + + #define _07_03_ENCODE_SIGNING_PUBKEY(buf_out, pkey )\ + ENCODE_SIGNING_PUBKEY(buf_out, pkey ); + + #define ENCODE_SIGNING_PUBKEY_NULL_SIZE 35 + #define ENCODE_SIGNING_PUBKEY_NULL(buf_out )\ + {\ + buf_out[0] = 0x73U;\ + buf_out[1] = 0x21U;\ + *(uint64_t*)(buf_out+2) = 0;\ + *(uint64_t*)(buf_out+10) = 0;\ + *(uint64_t*)(buf_out+18) = 0;\ + *(uint64_t*)(buf_out+25) = 0;\ + buf_out += ENCODE_SIGNING_PUBKEY_NULL_SIZE;\ + } + + #define _07_03_ENCODE_SIGNING_PUBKEY_NULL(buf_out )\ + ENCODE_SIGNING_PUBKEY_NULL(buf_out ); + + extern int64_t etxn_fee_base ( + uint32_t read_ptr, + uint32_t read_len + ); + extern int64_t etxn_details ( + uint32_t write_ptr, + uint32_t write_len + ); + extern int64_t ledger_seq (void); + + #define PREPARE_PAYMENT_SIMPLE_SIZE 248U + #define PREPARE_PAYMENT_SIMPLE(buf_out_master, drops_amount_raw, to_address, dest_tag_raw, src_tag_raw)\ + {\ + uint8_t* buf_out = buf_out_master;\ + uint8_t acc[20];\ + uint64_t drops_amount = (drops_amount_raw);\ + uint32_t dest_tag = (dest_tag_raw);\ + uint32_t src_tag = (src_tag_raw);\ + uint32_t cls = (uint32_t)ledger_seq();\ + hook_account(SBUF(acc));\ + _01_02_ENCODE_TT (buf_out, ttPAYMENT ); /* uint16 | size 3 */ \ + _02_02_ENCODE_FLAGS (buf_out, tfCANONICAL ); /* uint32 | size 5 */ \ + _02_03_ENCODE_TAG_SRC (buf_out, src_tag ); /* uint32 | size 5 */ \ + _02_04_ENCODE_SEQUENCE (buf_out, 3 ); /* uint32 | size 5 */ \ + _02_14_ENCODE_TAG_DST (buf_out, dest_tag ); /* uint32 | size 5 */ \ + _02_26_ENCODE_FLS (buf_out, cls + 0 ); /* uint32 | size 6 */ \ + _02_27_ENCODE_LLS (buf_out, cls + 0 ); /* uint32 | size 6 */ \ + _06_01_ENCODE_DROPS_AMOUNT (buf_out, drops_amount ); /* amount | size 9 */ \ + uint8_t* fee_ptr = buf_out;\ + _06_08_ENCODE_DROPS_FEE (buf_out, 0 ); /* amount | size 9 */ \ + _07_03_ENCODE_SIGNING_PUBKEY_NULL (buf_out ); /* pk | size 35 */ \ + _08_01_ENCODE_ACCOUNT_SRC (buf_out, acc ); /* account | size 22 */ \ + _08_03_ENCODE_ACCOUNT_DST (buf_out, to_address ); /* account | size 22 */ \ + int64_t edlen = etxn_details((uint32_t)buf_out, PREPARE_PAYMENT_SIMPLE_SIZE); /* emitdet | size 1?? */ \ + int64_t fee = etxn_fee_base(buf_out_master, PREPARE_PAYMENT_SIMPLE_SIZE); \ + _06_08_ENCODE_DROPS_FEE (fee_ptr, fee ); \ + } + + #define UINT16_FROM_BUF(buf)\ + (((uint64_t)((buf)[0]) << 8U) +\ + ((uint64_t)((buf)[1]) << 0U)) + + #define BUFFER_EQUAL_32(buf1, buf2)\ + (\ + *(((uint64_t*)(buf1)) + 0) == *(((uint64_t*)(buf2)) + 0) &&\ + *(((uint64_t*)(buf1)) + 1) == *(((uint64_t*)(buf2)) + 1) &&\ + *(((uint64_t*)(buf1)) + 2) == *(((uint64_t*)(buf2)) + 2) &&\ + *(((uint64_t*)(buf1)) + 3) == *(((uint64_t*)(buf2)) + 3) &&\ + *(((uint64_t*)(buf1)) + 4) == *(((uint64_t*)(buf2)) + 4) &&\ + *(((uint64_t*)(buf1)) + 5) == *(((uint64_t*)(buf2)) + 5) &&\ + *(((uint64_t*)(buf1)) + 6) == *(((uint64_t*)(buf2)) + 6) &&\ + *(((uint64_t*)(buf1)) + 7) == *(((uint64_t*)(buf2)) + 7)) + + #define ASSERT(x)\ + if (!(x))\ + rollback((uint32_t)#x,sizeof(#x),__LINE__) + + #define sfDestination ((8U << 16U) + 3U) + + extern int64_t etxn_generation(void); + extern int64_t otxn_generation(void); + extern int64_t otxn_burden(void); + extern int64_t etxn_burden(void); + + // int64_t cbak(uint32_t r) + // { + // // on callback we emit 2 more txns + // uint8_t bob[20]; + // ASSERT(otxn_field(SBUF(bob), sfDestination) == 20); + + // ASSERT(otxn_generation() + 1 == etxn_generation()); + + // ASSERT(etxn_burden() == PREREQUISITE_NOT_MET); + + // ASSERT(etxn_reserve(2) == 2); + + // ASSERT(otxn_burden() > 0); + // ASSERT(etxn_burden() == otxn_burden() * 2); + + // uint8_t tx[PREPARE_PAYMENT_SIMPLE_SIZE]; + // PREPARE_PAYMENT_SIMPLE(tx, 1000, bob, 0, 0); + + // uint8_t hash1[32]; + // ASSERT(emit_atomic(SBUF(hash1), SBUF(tx)) == 32); + + // ASSERT(etxn_details(tx + 132, 138) == 138); + // uint8_t hash2[32]; + // ASSERT(emit_atomic(SBUF(hash2), SBUF(tx)) == 32); + + // ASSERT(!BUFFER_EQUAL_32(hash1, hash2)); + + // return accept(0,0,0); + // } + + int64_t hook(uint32_t r) + { + _g(1,1); + + etxn_reserve(1); + + // bounds checks + ASSERT(emit_atomic(1000000, 32, 0, 32) == OUT_OF_BOUNDS); + ASSERT(emit_atomic(0,1000000, 0, 32) == OUT_OF_BOUNDS); + ASSERT(emit_atomic(0,32, 1000000, 32) == OUT_OF_BOUNDS); + ASSERT(emit_atomic(0,32, 0, 1000000) == OUT_OF_BOUNDS); + + ASSERT(otxn_generation() == 0); + ASSERT(otxn_burden == 1); + + uint8_t bob[20]; + ASSERT(otxn_param(SBUF(bob), "bob", 3) == 20); + + uint8_t tx[PREPARE_PAYMENT_SIMPLE_SIZE]; + PREPARE_PAYMENT_SIMPLE(tx, 1000, bob, 0, 0); + + uint8_t hash[32]; + ASSERT(emit_atomic(SBUF(hash), SBUF(tx)) == 32); + + return accept(0,0,0); + } + )[test.hook]", + { + 0x00U, 0x61U, 0x73U, 0x6DU, 0x01U, 0x00U, 0x00U, 0x00U, 0x01U, 0x25U, + 0x06U, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7FU, 0x60U, 0x01U, 0x7FU, + 0x01U, 0x7EU, 0x60U, 0x04U, 0x7FU, 0x7FU, 0x7FU, 0x7FU, 0x01U, 0x7EU, + 0x60U, 0x03U, 0x7FU, 0x7FU, 0x7EU, 0x01U, 0x7EU, 0x60U, 0x00U, 0x01U, + 0x7EU, 0x60U, 0x02U, 0x7FU, 0x7FU, 0x01U, 0x7EU, 0x02U, 0xCFU, 0x01U, + 0x0CU, 0x03U, 0x65U, 0x6EU, 0x76U, 0x02U, 0x5FU, 0x67U, 0x00U, 0x00U, + 0x03U, 0x65U, 0x6EU, 0x76U, 0x0CU, 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, + 0x72U, 0x65U, 0x73U, 0x65U, 0x72U, 0x76U, 0x65U, 0x00U, 0x01U, 0x03U, + 0x65U, 0x6EU, 0x76U, 0x0BU, 0x65U, 0x6DU, 0x69U, 0x74U, 0x5FU, 0x61U, + 0x74U, 0x6FU, 0x6DU, 0x69U, 0x63U, 0x00U, 0x02U, 0x03U, 0x65U, 0x6EU, + 0x76U, 0x08U, 0x72U, 0x6FU, 0x6CU, 0x6CU, 0x62U, 0x61U, 0x63U, 0x6BU, + 0x00U, 0x03U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0FU, 0x6FU, 0x74U, 0x78U, + 0x6EU, 0x5FU, 0x67U, 0x65U, 0x6EU, 0x65U, 0x72U, 0x61U, 0x74U, 0x69U, + 0x6FU, 0x6EU, 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0BU, 0x6FU, + 0x74U, 0x78U, 0x6EU, 0x5FU, 0x62U, 0x75U, 0x72U, 0x64U, 0x65U, 0x6EU, + 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0AU, 0x6FU, 0x74U, 0x78U, + 0x6EU, 0x5FU, 0x70U, 0x61U, 0x72U, 0x61U, 0x6DU, 0x00U, 0x02U, 0x03U, + 0x65U, 0x6EU, 0x76U, 0x0AU, 0x6CU, 0x65U, 0x64U, 0x67U, 0x65U, 0x72U, + 0x5FU, 0x73U, 0x65U, 0x71U, 0x00U, 0x04U, 0x03U, 0x65U, 0x6EU, 0x76U, + 0x0CU, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x5FU, 0x61U, 0x63U, 0x63U, 0x6FU, + 0x75U, 0x6EU, 0x74U, 0x00U, 0x05U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0CU, + 0x65U, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x64U, 0x65U, 0x74U, 0x61U, 0x69U, + 0x6CU, 0x73U, 0x00U, 0x05U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x0DU, 0x65U, + 0x74U, 0x78U, 0x6EU, 0x5FU, 0x66U, 0x65U, 0x65U, 0x5FU, 0x62U, 0x61U, + 0x73U, 0x65U, 0x00U, 0x05U, 0x03U, 0x65U, 0x6EU, 0x76U, 0x06U, 0x61U, + 0x63U, 0x63U, 0x65U, 0x70U, 0x74U, 0x00U, 0x03U, 0x03U, 0x02U, 0x01U, + 0x01U, 0x05U, 0x03U, 0x01U, 0x00U, 0x02U, 0x06U, 0x21U, 0x05U, 0x7FU, + 0x01U, 0x41U, 0xC0U, 0x8AU, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0xB9U, + 0x0AU, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, 0x0BU, 0x7FU, 0x00U, + 0x41U, 0xC0U, 0x8AU, 0x04U, 0x0BU, 0x7FU, 0x00U, 0x41U, 0x80U, 0x08U, + 0x0BU, 0x07U, 0x08U, 0x01U, 0x04U, 0x68U, 0x6FU, 0x6FU, 0x6BU, 0x00U, + 0x0CU, 0x0AU, 0xDCU, 0x86U, 0x00U, 0x01U, 0xD8U, 0x86U, 0x00U, 0x03U, + 0x01U, 0x7FU, 0x01U, 0x7EU, 0x03U, 0x7FU, 0x23U, 0x80U, 0x80U, 0x80U, + 0x80U, 0x00U, 0x41U, 0xC0U, 0x02U, 0x6BU, 0x22U, 0x01U, 0x24U, 0x80U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x41U, 0x01U, 0x41U, 0x01U, 0x10U, 0x80U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x41U, 0x01U, 0x10U, 0x81U, 0x80U, + 0x80U, 0x80U, 0x00U, 0x1AU, 0x02U, 0x40U, 0x41U, 0xC0U, 0x84U, 0x3DU, + 0x41U, 0x20U, 0x41U, 0x00U, 0x41U, 0x20U, 0x10U, 0x82U, 0x80U, 0x80U, + 0x80U, 0x00U, 0x42U, 0x7FU, 0x51U, 0x0DU, 0x00U, 0x41U, 0x80U, 0x88U, + 0x80U, 0x80U, 0x00U, 0x41U, 0x31U, 0x42U, 0xADU, 0x02U, 0x10U, 0x83U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x00U, + 0x41U, 0xC0U, 0x84U, 0x3DU, 0x41U, 0x00U, 0x41U, 0x20U, 0x10U, 0x82U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x7FU, 0x51U, 0x0DU, 0x00U, 0x41U, + 0xB1U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x2FU, 0x42U, 0xAEU, 0x02U, + 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, + 0x41U, 0x00U, 0x41U, 0x20U, 0x41U, 0xC0U, 0x84U, 0x3DU, 0x41U, 0x20U, + 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x7FU, 0x51U, 0x0DU, + 0x00U, 0x41U, 0xE0U, 0x88U, 0x80U, 0x80U, 0x00U, 0x41U, 0x30U, 0x42U, + 0xAFU, 0x02U, 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, + 0x02U, 0x40U, 0x41U, 0x00U, 0x41U, 0x20U, 0x41U, 0x00U, 0x41U, 0xC0U, + 0x84U, 0x3DU, 0x10U, 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x7FU, + 0x51U, 0x0DU, 0x00U, 0x41U, 0x90U, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, + 0x2FU, 0x42U, 0xB0U, 0x02U, 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, + 0x1AU, 0x0BU, 0x02U, 0x40U, 0x10U, 0x84U, 0x80U, 0x80U, 0x80U, 0x00U, + 0x50U, 0x0DU, 0x00U, 0x41U, 0xBFU, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, + 0x17U, 0x42U, 0xB2U, 0x02U, 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, + 0x1AU, 0x0BU, 0x02U, 0x40U, 0x41U, 0x81U, 0x80U, 0x80U, 0x80U, 0x00U, + 0x41U, 0x01U, 0x46U, 0x0DU, 0x00U, 0x41U, 0xD6U, 0x89U, 0x80U, 0x80U, + 0x00U, 0x41U, 0x11U, 0x42U, 0xB3U, 0x02U, 0x10U, 0x83U, 0x80U, 0x80U, + 0x80U, 0x00U, 0x1AU, 0x0BU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0xA0U, + 0x02U, 0x6AU, 0x41U, 0x14U, 0x41U, 0xE7U, 0x89U, 0x80U, 0x80U, 0x00U, + 0x41U, 0x03U, 0x10U, 0x86U, 0x80U, 0x80U, 0x80U, 0x00U, 0x42U, 0x14U, + 0x51U, 0x0DU, 0x00U, 0x41U, 0xEBU, 0x89U, 0x80U, 0x80U, 0x00U, 0x41U, + 0x26U, 0x42U, 0xB6U, 0x02U, 0x10U, 0x83U, 0x80U, 0x80U, 0x80U, 0x00U, + 0x1AU, 0x0BU, 0x10U, 0x87U, 0x80U, 0x80U, 0x80U, 0x00U, 0x21U, 0x02U, + 0x20U, 0x01U, 0x41U, 0x14U, 0x10U, 0x88U, 0x80U, 0x80U, 0x80U, 0x00U, + 0x1AU, 0x20U, 0x01U, 0x41U, 0xC9U, 0x00U, 0x6AU, 0x41U, 0x00U, 0x3AU, + 0x00U, 0x00U, 0x20U, 0x01U, 0x41U, 0xE1U, 0x80U, 0x01U, 0x3BU, 0x00U, + 0x43U, 0x20U, 0x01U, 0x20U, 0x02U, 0xA7U, 0x22U, 0x03U, 0x3AU, 0x00U, + 0x42U, 0x20U, 0x01U, 0x41U, 0xA0U, 0x36U, 0x3BU, 0x00U, 0x3DU, 0x20U, + 0x01U, 0x20U, 0x03U, 0x3AU, 0x00U, 0x3CU, 0x20U, 0x01U, 0x41U, 0xA0U, + 0x34U, 0x3BU, 0x00U, 0x37U, 0x20U, 0x01U, 0x41U, 0x00U, 0x36U, 0x00U, + 0x33U, 0x20U, 0x01U, 0x41U, 0x83U, 0xDCU, 0x00U, 0x3BU, 0x00U, 0x31U, + 0x20U, 0x01U, 0x42U, 0x80U, 0x80U, 0x80U, 0x80U, 0xC0U, 0x04U, 0x37U, + 0x00U, 0x29U, 0x20U, 0x01U, 0x41U, 0x23U, 0x3AU, 0x00U, 0x28U, 0x20U, + 0x01U, 0x42U, 0x92U, 0x80U, 0x80U, 0x90U, 0x82U, 0x10U, 0x37U, 0x03U, + 0x20U, 0x20U, 0x01U, 0x41U, 0x00U, 0x36U, 0x00U, 0x45U, 0x20U, 0x01U, + 0x20U, 0x03U, 0x41U, 0x08U, 0x76U, 0x22U, 0x04U, 0x3AU, 0x00U, 0x41U, + 0x20U, 0x01U, 0x20U, 0x03U, 0x41U, 0x10U, 0x76U, 0x22U, 0x05U, 0x3AU, + 0x00U, 0x40U, 0x20U, 0x01U, 0x20U, 0x03U, 0x41U, 0x18U, 0x76U, 0x22U, + 0x03U, 0x3AU, 0x00U, 0x3FU, 0x20U, 0x01U, 0x20U, 0x04U, 0x3AU, 0x00U, + 0x3BU, 0x20U, 0x01U, 0x20U, 0x05U, 0x3AU, 0x00U, 0x3AU, 0x20U, 0x01U, + 0x20U, 0x03U, 0x3AU, 0x00U, 0x39U, 0x20U, 0x01U, 0x41U, 0x20U, 0x6AU, + 0x41U, 0x3FU, 0x6AU, 0x42U, 0x00U, 0x37U, 0x03U, 0x00U, 0x20U, 0x01U, + 0x41U, 0xE7U, 0x00U, 0x6AU, 0x42U, 0x00U, 0x37U, 0x03U, 0x00U, 0x20U, + 0x01U, 0x41U, 0xEEU, 0x00U, 0x6AU, 0x42U, 0x00U, 0x37U, 0x00U, 0x00U, + 0x20U, 0x01U, 0x42U, 0x83U, 0xD0U, 0xA3U, 0x83U, 0x04U, 0x37U, 0x01U, + 0x4AU, 0x20U, 0x01U, 0x41U, 0x00U, 0x36U, 0x00U, 0x51U, 0x20U, 0x01U, + 0x41U, 0xF3U, 0xC2U, 0x00U, 0x3BU, 0x00U, 0x55U, 0x20U, 0x01U, 0x42U, + 0x00U, 0x37U, 0x03U, 0x57U, 0x20U, 0x01U, 0x41U, 0x81U, 0x29U, 0x3BU, + 0x01U, 0x78U, 0x20U, 0x01U, 0x41U, 0x83U, 0x29U, 0x3BU, 0x01U, 0x8EU, + 0x01U, 0x20U, 0x01U, 0x20U, 0x01U, 0x29U, 0x03U, 0x00U, 0x37U, 0x03U, + 0x7AU, 0x20U, 0x01U, 0x20U, 0x01U, 0x29U, 0x03U, 0x08U, 0x37U, 0x03U, + 0x82U, 0x01U, 0x20U, 0x01U, 0x20U, 0x01U, 0x28U, 0x02U, 0x10U, 0x36U, + 0x02U, 0x8AU, 0x01U, 0x20U, 0x01U, 0x20U, 0x01U, 0x29U, 0x03U, 0xA0U, + 0x02U, 0x37U, 0x03U, 0x90U, 0x01U, 0x20U, 0x01U, 0x20U, 0x01U, 0x29U, + 0x03U, 0xA8U, 0x02U, 0x37U, 0x03U, 0x98U, 0x01U, 0x20U, 0x01U, 0x20U, + 0x01U, 0x28U, 0x02U, 0xB0U, 0x02U, 0x36U, 0x02U, 0xA0U, 0x01U, 0x20U, + 0x01U, 0x41U, 0xA4U, 0x01U, 0x6AU, 0x41U, 0xF8U, 0x01U, 0x10U, 0x89U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x20U, 0x01U, 0x41U, + 0x20U, 0x6AU, 0x41U, 0xF8U, 0x01U, 0x10U, 0x8AU, 0x80U, 0x80U, 0x80U, + 0x00U, 0x22U, 0x02U, 0x3CU, 0x00U, 0x54U, 0x20U, 0x01U, 0x41U, 0xE8U, + 0x00U, 0x3AU, 0x00U, 0x4CU, 0x20U, 0x01U, 0x20U, 0x02U, 0x42U, 0x08U, + 0x88U, 0x3CU, 0x00U, 0x53U, 0x20U, 0x01U, 0x20U, 0x02U, 0x42U, 0x10U, + 0x88U, 0x3CU, 0x00U, 0x52U, 0x20U, 0x01U, 0x20U, 0x02U, 0x42U, 0x18U, + 0x88U, 0x3CU, 0x00U, 0x51U, 0x20U, 0x01U, 0x20U, 0x02U, 0x42U, 0x20U, + 0x88U, 0x3CU, 0x00U, 0x50U, 0x20U, 0x01U, 0x20U, 0x02U, 0x42U, 0x28U, + 0x88U, 0x3CU, 0x00U, 0x4FU, 0x20U, 0x01U, 0x20U, 0x02U, 0x42U, 0x30U, + 0x88U, 0x3CU, 0x00U, 0x4EU, 0x20U, 0x01U, 0x20U, 0x02U, 0x42U, 0x38U, + 0x88U, 0xA7U, 0x41U, 0x3FU, 0x71U, 0x41U, 0xC0U, 0x00U, 0x72U, 0x3AU, + 0x00U, 0x4DU, 0x02U, 0x40U, 0x20U, 0x01U, 0x41U, 0x20U, 0x20U, 0x01U, + 0x41U, 0x20U, 0x6AU, 0x41U, 0xF8U, 0x01U, 0x10U, 0x82U, 0x80U, 0x80U, + 0x80U, 0x00U, 0x42U, 0x20U, 0x51U, 0x0DU, 0x00U, 0x41U, 0x91U, 0x8AU, + 0x80U, 0x80U, 0x00U, 0x41U, 0x28U, 0x42U, 0xBCU, 0x02U, 0x10U, 0x83U, + 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x0BU, 0x41U, 0x00U, 0x41U, 0x00U, + 0x42U, 0x00U, 0x10U, 0x8BU, 0x80U, 0x80U, 0x80U, 0x00U, 0x21U, 0x02U, + 0x20U, 0x01U, 0x41U, 0xC0U, 0x02U, 0x6AU, 0x24U, 0x80U, 0x80U, 0x80U, + 0x80U, 0x00U, 0x20U, 0x02U, 0x0BU, 0x0BU, 0xC1U, 0x02U, 0x01U, 0x00U, + 0x41U, 0x80U, 0x08U, 0x0BU, 0xB9U, 0x02U, 0x65U, 0x6DU, 0x69U, 0x74U, + 0x5FU, 0x61U, 0x74U, 0x6FU, 0x6DU, 0x69U, 0x63U, 0x28U, 0x31U, 0x30U, + 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x2CU, 0x20U, 0x33U, 0x32U, 0x2CU, + 0x20U, 0x30U, 0x2CU, 0x20U, 0x33U, 0x32U, 0x29U, 0x20U, 0x3DU, 0x3DU, + 0x20U, 0x4FU, 0x55U, 0x54U, 0x5FU, 0x4FU, 0x46U, 0x5FU, 0x42U, 0x4FU, + 0x55U, 0x4EU, 0x44U, 0x53U, 0x00U, 0x65U, 0x6DU, 0x69U, 0x74U, 0x5FU, + 0x61U, 0x74U, 0x6FU, 0x6DU, 0x69U, 0x63U, 0x28U, 0x30U, 0x2CU, 0x31U, + 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x2CU, 0x20U, 0x30U, 0x2CU, + 0x20U, 0x33U, 0x32U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x4FU, 0x55U, + 0x54U, 0x5FU, 0x4FU, 0x46U, 0x5FU, 0x42U, 0x4FU, 0x55U, 0x4EU, 0x44U, + 0x53U, 0x00U, 0x65U, 0x6DU, 0x69U, 0x74U, 0x5FU, 0x61U, 0x74U, 0x6FU, + 0x6DU, 0x69U, 0x63U, 0x28U, 0x30U, 0x2CU, 0x33U, 0x32U, 0x2CU, 0x20U, + 0x31U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x2CU, 0x20U, 0x33U, + 0x32U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x4FU, 0x55U, 0x54U, 0x5FU, + 0x4FU, 0x46U, 0x5FU, 0x42U, 0x4FU, 0x55U, 0x4EU, 0x44U, 0x53U, 0x00U, + 0x65U, 0x6DU, 0x69U, 0x74U, 0x5FU, 0x61U, 0x74U, 0x6FU, 0x6DU, 0x69U, + 0x63U, 0x28U, 0x30U, 0x2CU, 0x33U, 0x32U, 0x2CU, 0x20U, 0x30U, 0x2CU, + 0x20U, 0x31U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x30U, 0x29U, 0x20U, + 0x3DU, 0x3DU, 0x20U, 0x4FU, 0x55U, 0x54U, 0x5FU, 0x4FU, 0x46U, 0x5FU, + 0x42U, 0x4FU, 0x55U, 0x4EU, 0x44U, 0x53U, 0x00U, 0x6FU, 0x74U, 0x78U, + 0x6EU, 0x5FU, 0x67U, 0x65U, 0x6EU, 0x65U, 0x72U, 0x61U, 0x74U, 0x69U, + 0x6FU, 0x6EU, 0x28U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x30U, 0x00U, + 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x62U, 0x75U, 0x72U, 0x64U, 0x65U, + 0x6EU, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x31U, 0x00U, 0x62U, 0x6FU, 0x62U, + 0x00U, 0x6FU, 0x74U, 0x78U, 0x6EU, 0x5FU, 0x70U, 0x61U, 0x72U, 0x61U, + 0x6DU, 0x28U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x62U, 0x6FU, 0x62U, + 0x29U, 0x2CU, 0x20U, 0x22U, 0x62U, 0x6FU, 0x62U, 0x22U, 0x2CU, 0x20U, + 0x33U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x32U, 0x30U, 0x00U, 0x65U, + 0x6DU, 0x69U, 0x74U, 0x5FU, 0x61U, 0x74U, 0x6FU, 0x6DU, 0x69U, 0x63U, + 0x28U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x68U, 0x61U, 0x73U, 0x68U, + 0x29U, 0x2CU, 0x20U, 0x53U, 0x42U, 0x55U, 0x46U, 0x28U, 0x74U, 0x78U, + 0x29U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, + }}, + + /* ==== WASM: 7 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -1083,7 +1557,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 7 ==== */ + /* ==== WASM: 8 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -1180,7 +1654,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 8 ==== */ + /* ==== WASM: 9 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -1303,7 +1777,7 @@ std::map> wasm = { 0x4EU, 0x59U, 0x5FU, 0x4EU, 0x4FU, 0x4EU, 0x43U, 0x45U, 0x53U, 0x00U, }}, - /* ==== WASM: 9 ==== */ + /* ==== WASM: 10 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -1383,7 +1857,7 @@ std::map> wasm = { 0x54U, 0x00U, }}, - /* ==== WASM: 10 ==== */ + /* ==== WASM: 11 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -1428,7 +1902,7 @@ std::map> wasm = { 0x30U, 0x00U, }}, - /* ==== WASM: 11 ==== */ + /* ==== WASM: 12 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -1747,7 +2221,7 @@ std::map> wasm = { 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, }}, - /* ==== WASM: 12 ==== */ + /* ==== WASM: 13 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -2533,7 +3007,7 @@ std::map> wasm = { 0x37U, 0x36U, 0x33U, 0x4CU, 0x4CU, 0x20U, 0x29U, 0x00U, }}, - /* ==== WASM: 13 ==== */ + /* ==== WASM: 14 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -2923,7 +3397,7 @@ std::map> wasm = { 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, }}, - /* ==== WASM: 14 ==== */ + /* ==== WASM: 15 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -3104,7 +3578,7 @@ std::map> wasm = { 0x38U, 0x4CU, 0x4CU, 0x29U, 0x00U, }}, - /* ==== WASM: 15 ==== */ + /* ==== WASM: 16 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -3293,7 +3767,7 @@ std::map> wasm = { 0x29U, 0x00U, }}, - /* ==== WASM: 16 ==== */ + /* ==== WASM: 17 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -3532,7 +4006,7 @@ std::map> wasm = { 0x00U, 0x42U, 0x00U, 0x10U, 0x85U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, }}, - /* ==== WASM: 17 ==== */ + /* ==== WASM: 18 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -4234,7 +4708,7 @@ std::map> wasm = { 0x38U, 0x35U, 0x35U, 0x32U, 0x55U, 0x29U, 0x00U, }}, - /* ==== WASM: 18 ==== */ + /* ==== WASM: 19 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -5579,7 +6053,7 @@ std::map> wasm = { 0x20U, 0x29U, 0x00U, }}, - /* ==== WASM: 19 ==== */ + /* ==== WASM: 20 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -5681,7 +6155,7 @@ std::map> wasm = { 0x84U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, }}, - /* ==== WASM: 20 ==== */ + /* ==== WASM: 21 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -5724,7 +6198,7 @@ std::map> wasm = { 0x00U, 0x0BU, }}, - /* ==== WASM: 21 ==== */ + /* ==== WASM: 22 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -5872,7 +6346,7 @@ std::map> wasm = { 0x34U, 0x34U, 0x4CU, 0x4CU, 0x2CU, 0x20U, 0x33U, 0x29U, 0x00U, }}, - /* ==== WASM: 22 ==== */ + /* ==== WASM: 23 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -6200,7 +6674,7 @@ std::map> wasm = { 0x38U, 0x34U, 0x39U, 0x30U, 0x4CU, 0x4CU, 0x00U, }}, - /* ==== WASM: 23 ==== */ + /* ==== WASM: 24 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -6405,7 +6879,7 @@ std::map> wasm = { 0x10U, 0x85U, 0x80U, 0x80U, 0x80U, 0x00U, 0x0BU, }}, - /* ==== WASM: 24 ==== */ + /* ==== WASM: 25 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -7089,7 +7563,7 @@ std::map> wasm = { 0x20U, 0x3DU, 0x3DU, 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 25 ==== */ + /* ==== WASM: 26 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -7384,7 +7858,7 @@ std::map> wasm = { 0x32U, 0x34U, 0x31U, 0x36U, 0x55U, 0x4CU, 0x4CU, 0x00U, }}, - /* ==== WASM: 26 ==== */ + /* ==== WASM: 27 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -8097,7 +8571,7 @@ std::map> wasm = { 0x31U, 0x33U, 0x33U, 0x38U, 0x20U, 0x29U, 0x00U, }}, - /* ==== WASM: 27 ==== */ + /* ==== WASM: 28 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -8182,7 +8656,7 @@ std::map> wasm = { 0x30U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x32U, 0x30U, 0x00U, }}, - /* ==== WASM: 28 ==== */ + /* ==== WASM: 29 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -8244,7 +8718,7 @@ std::map> wasm = { 0x80U, 0x80U, 0x00U, 0x0BU, }}, - /* ==== WASM: 29 ==== */ + /* ==== WASM: 30 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -8321,7 +8795,7 @@ std::map> wasm = { 0x31U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, }}, - /* ==== WASM: 30 ==== */ + /* ==== WASM: 31 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -8398,7 +8872,7 @@ std::map> wasm = { 0x31U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, }}, - /* ==== WASM: 31 ==== */ + /* ==== WASM: 32 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -8655,7 +9129,7 @@ std::map> wasm = { 0x00U, 0x00U, }}, - /* ==== WASM: 32 ==== */ + /* ==== WASM: 33 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -8813,7 +9287,7 @@ std::map> wasm = { 0x04U, 0x00U, 0x00U, }}, - /* ==== WASM: 33 ==== */ + /* ==== WASM: 34 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9166,7 +9640,7 @@ std::map> wasm = { 0x00U, 0x2AU, 0x04U, 0x00U, 0x00U, 0x31U, 0x04U, 0x00U, 0x00U, }}, - /* ==== WASM: 34 ==== */ + /* ==== WASM: 35 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9199,7 +9673,7 @@ std::map> wasm = { 0x82U, 0x80U, 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x0BU, }}, - /* ==== WASM: 35 ==== */ + /* ==== WASM: 36 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9426,7 +9900,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 36 ==== */ + /* ==== WASM: 37 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9457,7 +9931,7 @@ std::map> wasm = { 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x0BU, }}, - /* ==== WASM: 37 ==== */ + /* ==== WASM: 38 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9768,7 +10242,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 38 ==== */ + /* ==== WASM: 39 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9845,7 +10319,7 @@ std::map> wasm = { 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, }}, - /* ==== WASM: 39 ==== */ + /* ==== WASM: 40 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9879,7 +10353,7 @@ std::map> wasm = { 0x80U, 0x80U, 0x00U, 0x1AU, 0x20U, 0x01U, 0x0BU, }}, - /* ==== WASM: 40 ==== */ + /* ==== WASM: 41 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -9966,7 +10440,7 @@ std::map> wasm = { 0x32U, 0x00U, }}, - /* ==== WASM: 41 ==== */ + /* ==== WASM: 42 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10000,7 +10474,7 @@ std::map> wasm = { 0x0BU, }}, - /* ==== WASM: 42 ==== */ + /* ==== WASM: 43 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10140,7 +10614,7 @@ std::map> wasm = { 0x54U, 0x5FU, 0x4DU, 0x45U, 0x54U, 0x00U, }}, - /* ==== WASM: 43 ==== */ + /* ==== WASM: 44 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10330,7 +10804,7 @@ std::map> wasm = { 0x31U, 0x34U, 0x00U, }}, - /* ==== WASM: 44 ==== */ + /* ==== WASM: 45 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10484,7 +10958,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 45 ==== */ + /* ==== WASM: 46 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10645,7 +11119,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 46 ==== */ + /* ==== WASM: 47 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10802,7 +11276,7 @@ std::map> wasm = { 0x53U, 0x4CU, 0x4FU, 0x54U, 0x53U, 0x00U, }}, - /* ==== WASM: 47 ==== */ + /* ==== WASM: 48 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -10887,7 +11361,7 @@ std::map> wasm = { 0x74U, 0x79U, 0x70U, 0x65U, 0x28U, 0x29U, 0x00U, }}, - /* ==== WASM: 48 ==== */ + /* ==== WASM: 49 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -11144,7 +11618,7 @@ std::map> wasm = { 0x00U, 0x00U, }}, - /* ==== WASM: 49 ==== */ + /* ==== WASM: 50 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -11382,7 +11856,7 @@ std::map> wasm = { 0x3EU, 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 50 ==== */ + /* ==== WASM: 51 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -11478,7 +11952,7 @@ std::map> wasm = { 0x53U, 0x54U, 0x00U, }}, - /* ==== WASM: 51 ==== */ + /* ==== WASM: 52 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -11588,7 +12062,7 @@ std::map> wasm = { 0x20U, 0x31U, 0x00U, }}, - /* ==== WASM: 52 ==== */ + /* ==== WASM: 53 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -11718,7 +12192,7 @@ std::map> wasm = { 0x30U, 0x30U, 0x30U, 0x4CU, 0x4CU, 0x00U, }}, - /* ==== WASM: 53 ==== */ + /* ==== WASM: 54 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -11992,7 +12466,7 @@ std::map> wasm = { 0x30U, 0x00U, }}, - /* ==== WASM: 54 ==== */ + /* ==== WASM: 55 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -12129,7 +12603,7 @@ std::map> wasm = { 0x2CU, 0x20U, 0x73U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x31U, 0x00U, }}, - /* ==== WASM: 55 ==== */ + /* ==== WASM: 56 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -12423,7 +12897,7 @@ std::map> wasm = { 0x5FU, 0x53U, 0x4CU, 0x4FU, 0x54U, 0x53U, 0x00U, }}, - /* ==== WASM: 56 ==== */ + /* ==== WASM: 57 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -12657,7 +13131,7 @@ std::map> wasm = { 0x5FU, 0x53U, 0x4CU, 0x4FU, 0x54U, 0x53U, 0x00U, }}, - /* ==== WASM: 57 ==== */ + /* ==== WASM: 58 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -12953,7 +13427,7 @@ std::map> wasm = { 0x2CU, 0x20U, 0x31U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 58 ==== */ + /* ==== WASM: 59 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -13157,7 +13631,7 @@ std::map> wasm = { 0x6EU, 0x74U, 0x32U, 0x22U, 0x20U, 0x2BU, 0x20U, 0x69U, 0x29U, 0x00U, }}, - /* ==== WASM: 59 ==== */ + /* ==== WASM: 60 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -13274,7 +13748,7 @@ std::map> wasm = { 0x69U, 0x29U, 0x00U, }}, - /* ==== WASM: 60 ==== */ + /* ==== WASM: 61 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -13383,7 +13857,7 @@ std::map> wasm = { 0x6EU, 0x74U, 0x65U, 0x6EU, 0x74U, 0x32U, 0x22U, 0x29U, 0x00U, }}, - /* ==== WASM: 61 ==== */ + /* ==== WASM: 62 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -13656,7 +14130,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 62 ==== */ + /* ==== WASM: 63 ==== */ {R"[test.hook]( #include #define sfInvoiceID ((5U << 16U) + 17U) @@ -13875,7 +14349,7 @@ std::map> wasm = { 0x30U, 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, }}, - /* ==== WASM: 63 ==== */ + /* ==== WASM: 64 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -13987,7 +14461,7 @@ std::map> wasm = { 0x58U, 0x49U, 0x53U, 0x54U, 0x00U, }}, - /* ==== WASM: 64 ==== */ + /* ==== WASM: 65 ==== */ {R"[test.hook]( #include #define sfInvoiceID ((5U << 16U) + 17U) @@ -14123,7 +14597,7 @@ std::map> wasm = { 0x3DU, 0x20U, 0x33U, 0x32U, 0x00U, }}, - /* ==== WASM: 65 ==== */ + /* ==== WASM: 66 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -14341,7 +14815,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 66 ==== */ + /* ==== WASM: 67 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -14453,7 +14927,7 @@ std::map> wasm = { 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 67 ==== */ + /* ==== WASM: 68 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -14547,7 +15021,7 @@ std::map> wasm = { 0x61U, 0x64U, 0x5BU, 0x69U, 0x5DU, 0x00U, }}, - /* ==== WASM: 68 ==== */ + /* ==== WASM: 69 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -14680,7 +15154,7 @@ std::map> wasm = { 0x64U, 0x5BU, 0x69U, 0x5DU, 0x00U, }}, - /* ==== WASM: 69 ==== */ + /* ==== WASM: 70 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -14768,7 +15242,7 @@ std::map> wasm = { 0x61U, 0x74U, 0x61U, 0x29U, 0x00U, }}, - /* ==== WASM: 70 ==== */ + /* ==== WASM: 71 ==== */ {R"[test.hook]( #include #define sfInvoiceID ((5U << 16U) + 17U) @@ -14879,7 +15353,7 @@ std::map> wasm = { 0x20U, 0x33U, 0x32U, 0x00U, }}, - /* ==== WASM: 71 ==== */ + /* ==== WASM: 72 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -15491,7 +15965,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 72 ==== */ + /* ==== WASM: 73 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -15841,7 +16315,7 @@ std::map> wasm = { 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 73 ==== */ + /* ==== WASM: 74 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -15977,7 +16451,7 @@ std::map> wasm = { 0x00U, }}, - /* ==== WASM: 74 ==== */ + /* ==== WASM: 75 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -16150,7 +16624,7 @@ std::map> wasm = { 0x54U, 0x5FU, 0x45U, 0x58U, 0x49U, 0x53U, 0x54U, 0x00U, }}, - /* ==== WASM: 75 ==== */ + /* ==== WASM: 76 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -16298,7 +16772,7 @@ std::map> wasm = { 0x30U, 0x00U, 0x22U, 0x00U, 0x00U, 0x00U, 0x00U, }}, - /* ==== WASM: 76 ==== */ + /* ==== WASM: 77 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -16395,7 +16869,7 @@ std::map> wasm = { 0x3DU, 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 77 ==== */ + /* ==== WASM: 78 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -16454,7 +16928,7 @@ std::map> wasm = { 0x4FU, 0x46U, 0x5FU, 0x42U, 0x4FU, 0x55U, 0x4EU, 0x44U, 0x53U, 0x00U, }}, - /* ==== WASM: 78 ==== */ + /* ==== WASM: 79 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -16513,7 +16987,7 @@ std::map> wasm = { 0x4EU, 0x44U, 0x53U, 0x00U, }}, - /* ==== WASM: 79 ==== */ + /* ==== WASM: 80 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -18342,7 +18816,7 @@ std::map> wasm = { 0x53U, 0x4DU, 0x41U, 0x4CU, 0x4CU, 0x00U, }}, - /* ==== WASM: 80 ==== */ + /* ==== WASM: 81 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -19622,7 +20096,7 @@ std::map> wasm = { 0x29U, 0x2CU, 0x20U, 0x30U, 0x2CU, 0x30U, 0x20U, 0x29U, 0x29U, 0x00U, }}, - /* ==== WASM: 81 ==== */ + /* ==== WASM: 82 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -22555,7 +23029,7 @@ std::map> wasm = { 0x4FU, 0x4FU, 0x5FU, 0x53U, 0x4DU, 0x41U, 0x4CU, 0x4CU, 0x00U, }}, - /* ==== WASM: 82 ==== */ + /* ==== WASM: 83 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -24520,7 +24994,7 @@ std::map> wasm = { 0x54U, 0x4FU, 0x4FU, 0x5FU, 0x53U, 0x4DU, 0x41U, 0x4CU, 0x4CU, 0x00U, }}, - /* ==== WASM: 83 ==== */ + /* ==== WASM: 84 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -24805,7 +25279,7 @@ std::map> wasm = { 0x29U, 0x20U, 0x3DU, 0x3DU, 0x20U, 0x30U, 0x00U, }}, - /* ==== WASM: 84 ==== */ + /* ==== WASM: 85 ==== */ {R"[test.hook]( #include extern int32_t _g(uint32_t, uint32_t); @@ -25392,7 +25866,7 @@ std::map> wasm = { 0x4EU, 0x5FU, 0x46U, 0x41U, 0x49U, 0x4CU, 0x55U, 0x52U, 0x45U, 0x00U, }}, - /* ==== WASM: 85 ==== */ + /* ==== WASM: 86 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -25421,7 +25895,7 @@ std::map> wasm = { 0x0BU, }}, - /* ==== WASM: 86 ==== */ + /* ==== WASM: 87 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -25453,7 +25927,7 @@ std::map> wasm = { 0x20U, 0x52U, 0x65U, 0x6AU, 0x65U, 0x63U, 0x74U, 0x65U, 0x64U, 0x00U, }}, - /* ==== WASM: 87 ==== */ + /* ==== WASM: 88 ==== */ {R"[test.hook]( (module (type (;0;) (func (param i32 i32 i64) (result i64))) @@ -25480,7 +25954,7 @@ std::map> wasm = { 0x41U, 0x00U, 0x41U, 0x00U, 0x42U, 0x00U, 0x10U, 0x00U, 0x0BU, }}, - /* ==== WASM: 88 ==== */ + /* ==== WASM: 89 ==== */ {R"[test.hook]( (module (type (;0;) (func (param i32 i32) (result i32))) @@ -25533,7 +26007,7 @@ std::map> wasm = { 0x00U, 0x1AU, 0x0BU, }}, - /* ==== WASM: 89 ==== */ + /* ==== WASM: 90 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -32176,7 +32650,7 @@ std::map> wasm = { 0x39U, 0x30U, 0x31U, 0x32U, 0x33U, 0x00U, }}, - /* ==== WASM: 90 ==== */ + /* ==== WASM: 91 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); @@ -32222,7 +32696,7 @@ std::map> wasm = { 0x0BU, 0x06U, 0x76U, 0x61U, 0x6CU, 0x75U, 0x65U, 0x00U, }}, - /* ==== WASM: 91 ==== */ + /* ==== WASM: 92 ==== */ {R"[test.hook]( #include extern int32_t _g (uint32_t id, uint32_t maxiter); diff --git a/src/xrpld/app/hook/applyHook.h b/src/xrpld/app/hook/applyHook.h index 30f69ed27f..9201727bac 100644 --- a/src/xrpld/app/hook/applyHook.h +++ b/src/xrpld/app/hook/applyHook.h @@ -177,6 +177,13 @@ DECLARE_HOOK_FUNCTION( uint32_t write_len, uint32_t read_ptr, uint32_t read_len); +DECLARE_HOOK_FUNCTION( + int64_t, + emit_atomic, + uint32_t write_ptr, + uint32_t write_len, + uint32_t read_ptr, + uint32_t read_len); DECLARE_HOOK_FUNCTION(int64_t, float_set, int32_t exponent, int64_t mantissa); DECLARE_HOOK_FUNCTION(int64_t, float_multiply, int64_t float1, int64_t float2); @@ -454,6 +461,7 @@ apply( std::map, std::vector>> const& hookParamOverrides, HookStateMap& stateMap, + XRPAmount& atomicEmitFeeRemaining, ripple::ApplyContext& applyCtx, ripple::AccountID const& account, /* the account the hook is INSTALLED ON not always the otxn account */ @@ -489,7 +497,10 @@ struct HookResult std::queue> emittedTxn{}; // etx stored here until accept/rollback + std::queue> + emittedAtomicTxn{}; // etx stored here until accept/rollback HookStateMap& stateMap; + XRPAmount& atomicEmitFeeRemaining; uint16_t changedStateCount = 0; std::map< ripple::uint256, // hook hash @@ -581,6 +592,14 @@ finalizeHookResult( ripple::ApplyContext&, bool doEmit); +bool +emitAtomicTransactions( + ripple::Application& app, + ripple::OpenView& view, + ripple::uint256 const& parentTxnId, + ripple::STArray const& hookEmissions, + beast::Journal j); + // write state map to ledger ripple::TER finalizeHookState( @@ -813,6 +832,7 @@ class HookExecutor ADD_HOOK_FUNCTION(util_keylet, ctx); ADD_HOOK_FUNCTION(emit, ctx); + ADD_HOOK_FUNCTION(emit_atomic, ctx); ADD_HOOK_FUNCTION(etxn_burden, ctx); ADD_HOOK_FUNCTION(etxn_fee_base, ctx); ADD_HOOK_FUNCTION(etxn_details, ctx); diff --git a/src/xrpld/app/hook/detail/applyHook.cpp b/src/xrpld/app/hook/detail/applyHook.cpp index 9205901e72..2f3cc730de 100644 --- a/src/xrpld/app/hook/detail/applyHook.cpp +++ b/src/xrpld/app/hook/detail/applyHook.cpp @@ -5,6 +5,7 @@ #include #include #include +#include #include #include #include @@ -1300,6 +1301,7 @@ hook::apply( std::map, std::vector>> const& hookParamOverrides, HookStateMap& stateMap, + XRPAmount& atomicEmitFeeRemaining, ApplyContext& applyCtx, ripple::AccountID const& account, /* the account the hook is INSTALLED ON not always the otxn account */ @@ -1324,6 +1326,7 @@ hook::apply( .otxnAccount = applyCtx.tx.getAccountID(sfAccount), .hookNamespace = hookNamespace, .stateMap = stateMap, + .atomicEmitFeeRemaining = atomicEmitFeeRemaining, .changedStateCount = 0, .hookParamOverrides = hookParamOverrides, .hookParams = hookParams, @@ -2018,10 +2021,9 @@ hook::finalizeHookResult( if (doEmit) { - DBG_PRINTF("emitted txn count: %d\n", hookResult.emittedTxn.size()); - for (; hookResult.emittedTxn.size() > 0; hookResult.emittedTxn.pop()) - { - auto& tpTrans = hookResult.emittedTxn.front(); + auto const insertEmittedTxn = + [&](std::shared_ptr tpTrans, + bool atomic) -> TER { auto& id = tpTrans->getID(); JLOG(j.trace()) << "HookEmit[" << HR_ACC() << "]: " << id; @@ -2049,7 +2051,7 @@ hook::finalizeHookResult( ptr->add(s); SerialIter sit(s.slice()); - sleEmitted->emplace_back(ripple::STObject(sit, sfEmittedTxn)); + sleEmitted->set(ripple::STObject(sit, sfEmittedTxn)); auto page = applyCtx.view().dirInsert( keylet::emittedDir(), emittedId, [&](SLE::ref sle) { (*sle)[sfFlags] = lsfEmittedDir; @@ -2058,6 +2060,7 @@ hook::finalizeHookResult( if (page) { (*sleEmitted)[sfOwnerNode] = *page; + (*sleEmitted)[sfFlags] = atomic ? 1 : 0; applyCtx.view().insert(sleEmitted); } else @@ -2069,6 +2072,24 @@ hook::finalizeHookResult( return tecDIR_FULL; } } + return tesSUCCESS; + }; + + DBG_PRINTF("emitted txn count: %d\n", hookResult.emittedTxn.size()); + for (; hookResult.emittedTxn.size() > 0; hookResult.emittedTxn.pop()) + { + auto& tpTrans = hookResult.emittedTxn.front(); + insertEmittedTxn(tpTrans, false); + } + + DBG_PRINTF( + "emitted atomic txn count: %d\n", + hookResult.emittedAtomicTxn.size()); + for (; hookResult.emittedAtomicTxn.size() > 0; + hookResult.emittedAtomicTxn.pop()) + { + auto& tpTrans = hookResult.emittedAtomicTxn.front(); + insertEmittedTxn(tpTrans, true); } } @@ -2134,6 +2155,57 @@ hook::finalizeHookResult( return tesSUCCESS; } +bool +hook::emitAtomicTransactions( + ripple::Application& app, + ripple::OpenView& view, + ripple::uint256 const& parentTxnId, + ripple::STArray const& hookEmissions, + beast::Journal j_) +{ + for (auto const& emission : hookEmissions) + { + auto const& etxnId = emission.getFieldH256(sfEmittedTxnID); + auto const& keylet = keylet::emittedTxn(etxnId); + + auto sleItem = view.read(keylet); + if (!sleItem) + { + continue; + } + + LedgerEntryType const nodeType{ + safe_cast((*sleItem)[sfLedgerEntryType])}; + + if (nodeType != ltEMITTED_TXN) + { + JLOG(j_.warn()) + << "EmittedTxn processing: emitted directory contained " + "non ltEMITTED_TXN type"; + // RH TODO: if this ever happens the entry should be + // gracefully removed (somehow) + continue; + } + + auto const& emitted = const_cast(*sleItem) + .getField(sfEmittedTxn) + .downcast(); + + auto s = std::make_shared(); + emitted.add(*s); + SerialIter sitTrans(s->slice()); + + auto const& stpTrans = std::make_shared(std::ref(sitTrans)); + + auto const result = ripple::apply( + app, view, parentTxnId, *stpTrans, tapATOMIC_EMIT, j_); + + if (!result.applied || !isTesSuccess(result.ter)) + return false; + } + return true; +} + /* Retrieve the state into write_ptr identified by the key in kread_ptr */ DEFINE_HOOK_FUNCTION( int64_t, @@ -3354,7 +3426,9 @@ DEFINE_HOOK_FUNCTION( if (hookCtx.expected_etxn_count < 0) return PREREQUISITE_NOT_MET; - if (hookCtx.result.emittedTxn.size() >= hookCtx.expected_etxn_count) + if (hookCtx.result.emittedTxn.size() + + hookCtx.result.emittedAtomicTxn.size() >= + hookCtx.expected_etxn_count) return TOO_MANY_EMITTED_TXN; ripple::Blob blob{memory + read_ptr, memory + read_ptr + read_len}; @@ -3687,6 +3761,386 @@ DEFINE_HOOK_FUNCTION( HOOK_TEARDOWN(); } +/* Emit a transaction from this hook. Transaction must be in STObject form, + * fully formed and valid. XRPLD does not modify transactions it only checks + * them for validity. */ +DEFINE_HOOK_FUNCTION( + int64_t, + emit_atomic, + uint32_t write_ptr, + uint32_t write_len, + uint32_t read_ptr, + uint32_t read_len) +{ + HOOK_SETUP(); // populates memory_ctx, memory, memory_length, applyCtx, + // hookCtx on current stack + + if (NOT_IN_BOUNDS(read_ptr, read_len, memory_length)) + return OUT_OF_BOUNDS; + + if (NOT_IN_BOUNDS(write_ptr, write_len, memory_length)) + return OUT_OF_BOUNDS; + + if (write_len < 32) + return TOO_SMALL; + + auto& app = hookCtx.applyCtx.app; + + // Doesn't allow to call Callbackable Hook + if (hookCtx.result.hasCallback || hookCtx.result.isCallback) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: emit_atomic: Not allowed in Callbackable Hook."; + return EMISSION_FAILURE; + } + + // Doesn't allow to call in Weak Execution + if (!hookCtx.result.isStrong) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: emit_atomic: Not allowed in Weak Execution."; + return EMISSION_FAILURE; + } + + if (hookCtx.expected_etxn_count < 0) + return PREREQUISITE_NOT_MET; + + if (hookCtx.result.emittedTxn.size() + + hookCtx.result.emittedAtomicTxn.size() >= + hookCtx.expected_etxn_count) + return TOO_MANY_EMITTED_TXN; + + ripple::Blob blob{memory + read_ptr, memory + read_ptr + read_len}; + std::shared_ptr stpTrans; + try + { + stpTrans = std::make_shared( + SerialIter{memory + read_ptr, read_len}); + } + catch (std::exception& e) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() << "]: Failed " << e.what() + << "\n"; + return EMISSION_FAILURE; + } + + if (isPseudoTx(*stpTrans)) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: Attempted to emit pseudo txn."; + return EMISSION_FAILURE; + } + + ripple::TxType txType = stpTrans->getTxnType(); + + ripple::uint256 const& hookCanEmit = hookCtx.result.hookCanEmit; + if (!hook::canEmit(txType, hookCanEmit)) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: Hook cannot emit this txn."; + return EMISSION_FAILURE; + } + + // check the emitted txn is valid + /* Emitted TXN rules + * 0. Account must match the hook account + * 1. Sequence: 0 + * 2. PubSigningKey: 000000000000000 + * 3. sfEmitDetails present and valid + * 4. No sfTxnSignature + * 5. LastLedgerSeq == current ledger + * 6. FirstLedgerSeq == current ledger + * 7. Fee must be correctly high + * 8. The generation cannot be higher than 10 + */ + + // rule 0: account must match the hook account + if (!stpTrans->isFieldPresent(sfAccount) || + stpTrans->getAccountID(sfAccount) != hookCtx.result.account) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: sfAccount does not match hook account"; + return EMISSION_FAILURE; + } + + // rule 1: sfSequence must be present and 0 + // if (!stpTrans->isFieldPresent(sfSequence) || + // stpTrans->getFieldU32(sfSequence) != 0) + // { + // JLOG(j.trace()) << "HookEmit[" << HC_ACC() + // << "]: sfSequence missing or non-zero"; + // return EMISSION_FAILURE; + // } + + // rule 2: sfSigningPubKey must be present and 00...00 + if (!stpTrans->isFieldPresent(sfSigningPubKey)) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: sfSigningPubKey missing"; + return EMISSION_FAILURE; + } + + auto const pk = stpTrans->getSigningPubKey(); + if (pk.size() != 33 && pk.size() != 0) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: sfSigningPubKey present but wrong size" + << " expecting 33 bytes"; + return EMISSION_FAILURE; + } + + for (int i = 0; i < pk.size(); ++i) + if (pk[i] != 0) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: sfSigningPubKey present but non-zero."; + return EMISSION_FAILURE; + } + + // rule 2.a: no signers + if (stpTrans->isFieldPresent(sfSigners)) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: sfSigners not allowed in emitted txns."; + return EMISSION_FAILURE; + } + + // rule 2.b: ticketseq cannot be used + if (stpTrans->isFieldPresent(sfTicketSequence)) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: sfTicketSequence not allowed in emitted txns."; + return EMISSION_FAILURE; + } + + // rule 2.c sfAccountTxnID not allowed + if (stpTrans->isFieldPresent(sfAccountTxnID)) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: sfAccountTxnID not allowed in emitted txns."; + return EMISSION_FAILURE; + } + + // rule 3: sfEmitDetails must be present and valid + if (!stpTrans->isFieldPresent(sfEmitDetails)) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: sfEmitDetails missing."; + return EMISSION_FAILURE; + } + + auto const& emitDetails = const_cast(*stpTrans) + .getField(sfEmitDetails) + .downcast(); + + if (!emitDetails.isFieldPresent(sfEmitGeneration) || + !emitDetails.isFieldPresent(sfEmitBurden) || + !emitDetails.isFieldPresent(sfEmitParentTxnID) || + !emitDetails.isFieldPresent(sfEmitNonce) || + !emitDetails.isFieldPresent(sfEmitHookHash)) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: sfEmitDetails malformed."; + return EMISSION_FAILURE; + } + + // rule 8: emit generation cannot exceed 10 + if (emitDetails.getFieldU32(sfEmitGeneration) >= 10) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: sfEmitGeneration was 10 or more."; + return EMISSION_FAILURE; + } + + uint32_t gen = emitDetails.getFieldU32(sfEmitGeneration); + uint64_t bur = emitDetails.getFieldU64(sfEmitBurden); + ripple::uint256 const& pTxnID = emitDetails.getFieldH256(sfEmitParentTxnID); + ripple::uint256 const& nonce = emitDetails.getFieldH256(sfEmitNonce); + + if (emitDetails.isFieldPresent(sfEmitCallback)) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: callback not supported yet in emit_atomic."; + return EMISSION_FAILURE; + } + + auto const& hash = emitDetails.getFieldH256(sfEmitHookHash); + + uint32_t gen_proper = etxn_generation(hookCtx, frameCtx); + + if (gen != gen_proper) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: sfEmitGeneration provided in EmitDetails " + << "not correct (" << gen << ") " + << "should be " << gen_proper; + return EMISSION_FAILURE; + } + + uint64_t bur_proper = etxn_burden(hookCtx, frameCtx); + if (bur != bur_proper) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: sfEmitBurden provided in EmitDetails " + << "was not correct (" << bur << ") " + << "should be " << bur_proper; + return EMISSION_FAILURE; + } + + if (pTxnID != applyCtx.tx.getTransactionID()) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: sfEmitParentTxnID provided in EmitDetails " + << "was not correct"; + return EMISSION_FAILURE; + } + + if (hookCtx.nonce_used.find(nonce) == hookCtx.nonce_used.end()) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: sfEmitNonce provided in EmitDetails " + << "was not generated by nonce api"; + return EMISSION_FAILURE; + } + + if (hash != hookCtx.result.hookHash) + { + JLOG(j.trace()) + << "HookEmit[" << HC_ACC() + << "]: sfEmitHookHash must be the hash of the emitting hook"; + return EMISSION_FAILURE; + } + + // rule 4: sfTxnSignature must be absent + if (stpTrans->isFieldPresent(sfTxnSignature)) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: sfTxnSignature is present but should not be"; + return EMISSION_FAILURE; + } + + // rule 5: LastLedgerSeq must be present and after current ledger + if (!stpTrans->isFieldPresent(sfLastLedgerSequence)) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: sfLastLedgerSequence missing"; + return EMISSION_FAILURE; + } + + uint32_t tx_lls = stpTrans->getFieldU32(sfLastLedgerSequence); + uint32_t ledgerSeq = view.info().seq; + + if (tx_lls != ledgerSeq) + { + JLOG(j.trace()) + << "HookEmit[" << HC_ACC() + << "]: sfLastLedgerSequence cannot be equal to current seq"; + return EMISSION_FAILURE; + } + + // rule 6 + if (!stpTrans->isFieldPresent(sfFirstLedgerSequence) || + stpTrans->getFieldU32(sfFirstLedgerSequence) != ledgerSeq) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: sfFirstLedgerSequence must be present and " + << "= LastLedgerSequence"; + return EMISSION_FAILURE; + } + + // rule 7 check the emitted txn pays the appropriate fee + int64_t minfee = etxn_fee_base(hookCtx, frameCtx, read_ptr, read_len); + + if (minfee < 0) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: Fee could not be calculated"; + return EMISSION_FAILURE; + } + + if (!stpTrans->isFieldPresent(sfFee)) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: Fee missing from emitted tx"; + return EMISSION_FAILURE; + } + + int64_t fee = stpTrans->getFieldAmount(sfFee).xrp().drops(); + if (fee < minfee) + { + JLOG(j.trace()) + << "HookEmit[" << HC_ACC() + << "]: Fee on emitted txn is less than the minimum required fee"; + return EMISSION_FAILURE; + } + + if (hookCtx.result.atomicEmitFeeRemaining < XRPAmount(fee)) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: Fee on emitted txn is greater than the atomic " + "emit fee remaining"; + return EMISSION_FAILURE; + } + + std::string reason; + auto tpTrans = std::make_shared(stpTrans, reason, app); + if (tpTrans->getStatus() != NEW) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: tpTrans->getStatus() != NEW"; + return EMISSION_FAILURE; + } + + // preflight the transaction + auto preflightResult = ripple::preflight( + applyCtx.app, + applyCtx.view().rules(), + *stpTrans, + ripple::ApplyFlags::tapPREFLIGHT_EMIT, + j); + + if (!isTesSuccess(preflightResult.ter)) + { + JLOG(j.trace()) << "HookEmit[" << HC_ACC() + << "]: Transaction preflight failure: " + << preflightResult.ter; + return EMISSION_FAILURE; + } + + auto const& txID = tpTrans->getID(); + + if (txID.size() > write_len) + return TOO_SMALL; + + if (NOT_IN_BOUNDS(write_ptr, txID.size(), memory_length)) + return OUT_OF_BOUNDS; + + auto const write_txid = [&]() -> int64_t { + WRITE_WASM_MEMORY_AND_RETURN( + write_ptr, + txID.size(), + txID.data(), + txID.size(), + memory, + memory_length); + }; + + int64_t result = write_txid(); + + if (result == 32) + { + hookCtx.result.atomicEmitFeeRemaining -= XRPAmount(fee); + XRPL_ASSERT( + hookCtx.result.atomicEmitFeeRemaining >= XRPAmount(0), + "atomicEmitFeeRemaining is negative"); + + hookCtx.result.emittedAtomicTxn.push(tpTrans); + } + + return result; + HOOK_TEARDOWN(); +} + // When implemented will return the hash of the current hook DEFINE_HOOK_FUNCTION( int64_t, diff --git a/src/xrpld/app/tx/apply.h b/src/xrpld/app/tx/apply.h index cf8183b3c3..8b36ecb3c8 100644 --- a/src/xrpld/app/tx/apply.h +++ b/src/xrpld/app/tx/apply.h @@ -129,6 +129,15 @@ apply( ApplyFlags flags, beast::Journal journal); +ApplyResult +apply( + Application& app, + OpenView& view, + uint256 const& parentBatchId, + STTx const& tx, + ApplyFlags flags, + beast::Journal journal); + /** Enum class for return value from `applyTransaction` @see applyTransaction diff --git a/src/xrpld/app/tx/detail/ApplyContext.cpp b/src/xrpld/app/tx/detail/ApplyContext.cpp index d162f533e9..efcc12b60f 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.cpp +++ b/src/xrpld/app/tx/detail/ApplyContext.cpp @@ -47,22 +47,31 @@ ApplyContext::ApplyContext( , parentBatchId_(parentBatchId) { XRPL_ASSERT( - parentBatchId.has_value() == ((flags_ & tapBATCH) == tapBATCH), - "Parent Batch ID should be set if batch apply flag is set"); - view_.emplace(&base_, flags_); + parentBatchId.has_value() == + ((flags_ & (tapBATCH | tapATOMIC_EMIT)) > 0), + "Parent Batch ID should be set if batch or AtomicEmit flag is set"); + view_.emplace(&base_.view(), flags_); } void ApplyContext::discard() { - view_.emplace(&base_, flags_); + base_.discard(); + view_.emplace(&base_.view(), flags_); +} + +void +ApplyContext::finalize() +{ + base_.commit(); + view_.emplace(&base_.view(), flags_); } std::optional ApplyContext::apply(TER ter) { return view_->apply( - base_, tx, ter, parentBatchId_, flags_ & tapDRY_RUN, journal); + base_.view(), tx, ter, parentBatchId_, flags_ & tapDRY_RUN, journal); } std::size_t @@ -78,7 +87,7 @@ ApplyContext::visit(std::function const&, std::shared_ptr const&)> const& func) { - view_->visit(base_, func); + view_->visit(base_.view(), func); } TER diff --git a/src/xrpld/app/tx/detail/ApplyContext.h b/src/xrpld/app/tx/detail/ApplyContext.h index d71ab55908..57b300e344 100644 --- a/src/xrpld/app/tx/detail/ApplyContext.h +++ b/src/xrpld/app/tx/detail/ApplyContext.h @@ -23,6 +23,8 @@ #include #include #include +#include + #include #include #include @@ -64,7 +66,8 @@ class ApplyContext journal) { XRPL_ASSERT( - (flags & tapBATCH) == 0, "Batch apply flag should not be set"); + (flags & (tapBATCH | tapATOMIC_EMIT)) == 0, + "Batch or AtomicEmit flag should not be set"); } Application& app; @@ -73,6 +76,12 @@ class ApplyContext XRPAmount const baseFee; beast::Journal const journal; + OpenView& + openView() + { + return base_.view(); + } + ApplyView& view() { @@ -109,6 +118,10 @@ class ApplyContext void discard(); + /** Finalize changes. */ + void + finalize(); + /** Apply the transaction result to the base. */ std::optional apply(TER); @@ -134,7 +147,7 @@ class ApplyContext generateProvisionalMeta() { return view_->generateProvisionalMeta( - base_, tx, parentBatchId_, journal); + base_.view(), tx, parentBatchId_, journal); } /** Applies all invariant checkers one by one. @@ -169,7 +182,7 @@ class ApplyContext XRPAmount const fee, std::index_sequence); - OpenView& base_; + OpenViewSandbox base_; ApplyFlags flags_; std::optional view_; diff --git a/src/xrpld/app/tx/detail/Change.cpp b/src/xrpld/app/tx/detail/Change.cpp index c18503e4c9..2a2515705b 100644 --- a/src/xrpld/app/tx/detail/Change.cpp +++ b/src/xrpld/app/tx/detail/Change.cpp @@ -585,12 +585,21 @@ Change::activateXahauGenesis() for (auto const& [hookOn, wasmBytes, params] : genesis_hooks) { std::ostringstream loggerStream; - auto result = validateGuards( + auto rulesVersion = + (ctx_.view().rules().enabled(featureHooksUpdate1) + ? hook_api::GuardRules::HooksUpdate1 + : 0U) + + (ctx_.view().rules().enabled(fix20250131) + ? hook_api::GuardRules::Fix20250131 + : 0U) + + (ctx_.view().rules().enabled(featureAtomicEmit) + ? hook_api::GuardRules::AtomicEmit + : 0U); + auto const result = validateGuards( wasmBytes, // wasm to verify loggerStream, "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh", - (ctx_.view().rules().enabled(featureHooksUpdate1) ? 1 : 0) + - (ctx_.view().rules().enabled(fix20250131) ? 2 : 0)); + rulesVersion); if (!result) { diff --git a/src/xrpld/app/tx/detail/SetHook.cpp b/src/xrpld/app/tx/detail/SetHook.cpp index 81a90bc168..3fc35bd66c 100644 --- a/src/xrpld/app/tx/detail/SetHook.cpp +++ b/src/xrpld/app/tx/detail/SetHook.cpp @@ -227,7 +227,8 @@ SetHook::inferOperation(STObject const& hookSetObj) !hookSetObj.isFieldPresent(sfHookOn) && !hookSetObj.isFieldPresent(sfHookCanEmit) && !hookSetObj.isFieldPresent(sfHookApiVersion) && - !hookSetObj.isFieldPresent(sfFlags)) + !hookSetObj.isFieldPresent(sfFlags) && + !hookSetObj.isFieldPresent(sfHookAtomicEmitFee)) return hsoNOOP; uint32_t flags = hookSetObj.isFieldPresent(sfFlags) @@ -263,7 +264,8 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj) hookSetObj.isFieldPresent(sfHookCanEmit) || hookSetObj.isFieldPresent(sfHookApiVersion) || !hookSetObj.isFieldPresent(sfFlags) || - !hookSetObj.isFieldPresent(sfHookNamespace)) + !hookSetObj.isFieldPresent(sfHookNamespace) || + !hookSetObj.isFieldPresent(sfHookAtomicEmitFee)) { JLOG(ctx.j.trace()) << "HookSet(" << hook::log::NSDELETE_FIELD << ")[" << HS_ACC() @@ -293,7 +295,8 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj) hookSetObj.isFieldPresent(sfHookCanEmit) || hookSetObj.isFieldPresent(sfHookApiVersion) || hookSetObj.isFieldPresent(sfHookNamespace) || - !hookSetObj.isFieldPresent(sfFlags)) + !hookSetObj.isFieldPresent(sfFlags) || + !hookSetObj.isFieldPresent(sfHookAtomicEmitFee)) { JLOG(ctx.j.trace()) << "HookSet(" << hook::log::DELETE_FIELD << ")[" << HS_ACC() @@ -392,6 +395,18 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj) return false; } + if (hookSetObj.isFieldPresent(sfHookAtomicEmitFee)) + { + auto const fee = hookSetObj.getFieldAmount(sfHookAtomicEmitFee); + if (!isXRP(fee)) + return false; + + // TODO: Update maxFee + XRPAmount maxFee{1'000'000}; + if (fee < beast::zero || fee > maxFee) + return false; + } + // namespace may be valid, if the user so chooses // hookon may be present if the user so chooses // flags may be present if the user so chooses @@ -462,6 +477,19 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj) // pass } + // validate sfHookAtomicEmitFee + if (hookSetObj.isFieldPresent(sfHookAtomicEmitFee)) + { + auto const fee = hookSetObj.getFieldAmount(sfHookAtomicEmitFee); + if (!isXRP(fee)) + return false; + + // TODO: Update maxFee + XRPAmount maxFee{1'000'000}; + if (fee < beast::zero || fee > maxFee) + return false; + } + // finally validate web assembly byte code { if (!hookSetObj.isFieldPresent(sfCreateCode)) @@ -485,12 +513,21 @@ SetHook::validateHookSetEntry(SetHookCtx& ctx, STObject const& hookSetObj) hsacc = ss.str(); } + auto rulesVersion = (ctx.rules.enabled(featureHooksUpdate1) + ? hook_api::GuardRules::HooksUpdate1 + : 0U) + + (ctx.rules.enabled(fix20250131) + ? hook_api::GuardRules::Fix20250131 + : 0U) + + (ctx.rules.enabled(featureAtomicEmit) + ? hook_api::GuardRules::AtomicEmit + : 0U); + auto result = validateGuards( hook, // wasm to verify logger, hsacc, - (ctx.rules.enabled(featureHooksUpdate1) ? 1 : 0) + - (ctx.rules.enabled(fix20250131) ? 2 : 0)); + rulesVersion); if (ctx.j.trace()) { @@ -728,6 +765,10 @@ SetHook::preflight(PreflightContext const& ctx) hookSetObj.isFieldPresent(sfHookCanEmit)) return temDISABLED; + if (!ctx.rules.enabled(featureAtomicEmit) && + hookSetObj.isFieldPresent(sfHookAtomicEmitFee)) + return temDISABLED; + for (auto const& hookSetElement : hookSetObj) { auto const& name = hookSetElement.getFName(); @@ -736,7 +777,7 @@ SetHook::preflight(PreflightContext const& ctx) name != sfHookNamespace && name != sfHookParameters && name != sfHookOn && name != sfHookGrants && name != sfHookApiVersion && name != sfFlags && - name != sfHookCanEmit) + name != sfHookCanEmit && name != sfHookAtomicEmitFee) { JLOG(ctx.j.trace()) << "HookSet(" << hook::log::HOOK_INVALID_FIELD << ")[" @@ -1242,6 +1283,10 @@ SetHook::setHook() std::optional newHookCanEmit; std::optional defHookCanEmit; + std::optional oldHookAtomicEmitFee; + std::optional newHookAtomicEmitFee; + std::optional defHookAtomicEmitFee; + // when hsoCREATE is invoked it populates this variable in case the hook // definition already exists and the operation falls through into a // hsoINSTALL operation instead @@ -1310,6 +1355,16 @@ SetHook::setHook() oldHookCanEmit = oldHook->get().getFieldH256(sfHookCanEmit); else if (defHookCanEmit) oldHookCanEmit = *defHookCanEmit; + + if (oldDefSLE && oldDefSLE->isFieldPresent(sfHookAtomicEmitFee)) + defHookAtomicEmitFee = + oldDefSLE->getFieldAmount(sfHookAtomicEmitFee); + + if (oldHook && oldHook->get().isFieldPresent(sfHookAtomicEmitFee)) + oldHookAtomicEmitFee = + oldHook->get().getFieldAmount(sfHookAtomicEmitFee); + else if (defHookAtomicEmitFee) + oldHookAtomicEmitFee = *defHookAtomicEmitFee; } // in preparation for three way merge populate fields if they are @@ -1329,6 +1384,10 @@ SetHook::setHook() if (hookSetObj->get().isFieldPresent(sfHookCanEmit)) newHookCanEmit = hookSetObj->get().getFieldH256(sfHookCanEmit); + if (hookSetObj->get().isFieldPresent(sfHookAtomicEmitFee)) + newHookAtomicEmitFee = + hookSetObj->get().getFieldAmount(sfHookAtomicEmitFee); + if (hookSetObj->get().isFieldPresent(sfHookNamespace)) { newNamespace = hookSetObj->get().getFieldH256(sfHookNamespace); @@ -1485,6 +1544,17 @@ SetHook::setHook() newHook.setFieldH256(sfHookCanEmit, *newHookCanEmit); } + // set the hookAtomeicFee field if it differs from definition + if (newHookAtomicEmitFee) + { + if (defHookAtomicEmitFee.has_value() && + *defHookAtomicEmitFee == *newHookAtomicEmitFee) + { + if (newHook.isFieldPresent(sfHookAtomicEmitFee)) + newHook.makeFieldAbsent(sfHookAtomicEmitFee); + } + } + // parameters if (hookSetObj->get().isFieldPresent(sfHookParameters) && hookSetObj->get().getFieldArray(sfHookParameters).empty()) @@ -1660,6 +1730,11 @@ SetHook::setHook() sfHookCallbackFee, XRPAmount{ hook::computeExecutionFee(maxInstrCountCbak)}); + if (hookSetObj->get().isFieldPresent(sfHookAtomicEmitFee)) + newHookDef->setFieldAmount( + sfHookAtomicEmitFee, + hookSetObj->get().getFieldAmount( + sfHookAtomicEmitFee)); if (flags) newHookDef->setFieldU32(sfFlags, newFlags); diff --git a/src/xrpld/app/tx/detail/Transactor.cpp b/src/xrpld/app/tx/detail/Transactor.cpp index 5efb740b12..f4415a60c9 100644 --- a/src/xrpld/app/tx/detail/Transactor.cpp +++ b/src/xrpld/app/tx/detail/Transactor.cpp @@ -289,15 +289,23 @@ Transactor::calculateHookChainFee( if (hook::canHook(tx.getTxnType(), hookOn) && (!collectCallsOnly || (flags & hook::hsfCOLLECT))) { - XRPAmount const toAdd{hookDef->getFieldAmount(sfFee).xrp().drops()}; + XRPAmount const toAddFee{ + hookDef->getFieldAmount(sfFee).xrp().drops()}; + + XRPAmount const toAddAtomicEmitFee{ + hookObj.isFieldPresent(sfHookAtomicEmitFee) + ? hookObj.getFieldAmount(sfHookAtomicEmitFee).xrp().drops() + : hookDef->isFieldPresent(sfHookAtomicEmitFee) + ? hookDef->getFieldAmount(sfHookAtomicEmitFee).xrp().drops() + : 0}; // this overflow should never happen, if somehow it does // fee is set to the largest possible valid xrp value to force // fail the transaction - if (fee + toAdd < fee) + if (fee + toAddFee + toAddAtomicEmitFee < fee) fee = XRPAmount{INITIAL_XRP.drops()}; else - fee += toAdd; + fee += toAddFee + toAddAtomicEmitFee; } } @@ -493,7 +501,7 @@ Transactor::checkFee(PreclaimContext const& ctx, XRPAmount baseFee) if (feePaid < feeDue) { - JLOG(ctx.j.trace()) + JLOG(ctx.j.fatal()) << "Insufficient fee paid: " << to_string(feePaid) << "/" << to_string(feeDue); return telINSUF_FEE_P; @@ -1368,6 +1376,111 @@ Transactor::reset(XRPAmount fee) return {ter, fee}; } +std::pair +Transactor::checkInvariants(TER result, XRPAmount fee) +{ + // Check invariants: if `tecINVARIANT_FAILED` is not returned, we can + // proceed to apply the tx + result = ctx_.checkInvariants(result, fee); + + if (result == tecINVARIANT_FAILED) + { + // if invariants checking failed again, reset the context and + // attempt to only claim a fee. + auto const resetResult = reset(fee); + if (!isTesSuccess(resetResult.first)) + result = resetResult.first; + + fee = resetResult.second; + + // Check invariants again to ensure the fee claiming doesn't + // violate invariants. + if (isTesSuccess(result) || isTecClaim(result)) + result = ctx_.checkInvariants(result, fee); + } + + return {result, fee}; +} + +void +Transactor::balanceRewards(TER result) +{ + TxMeta metaRaw = ctx_.generateProvisionalMeta(); + metaRaw.setResult(result, 0); + STObject const meta = metaRaw.getAsObject(); + + uint32_t lgrCur = view().seq(); + + bool const has240819 = view().rules().enabled(fix240819); + bool const has240911 = view().rules().enabled(fix240911); + + auto const& sfRewardFields = + *(ripple::SField::knownCodeToField.at(917511 - has240819)); + + // iterate all affected balances + for (auto const& node : meta.getFieldArray(sfAffectedNodes)) + { + SField const& metaType = node.getFName(); + uint16_t nodeType = node.getFieldU16(sfLedgerEntryType); + + // we only care about ltACCOUNT_ROOT objects being modified or + // created + if (nodeType != ltACCOUNT_ROOT || metaType == sfDeletedNode) + continue; + + if (!node.isFieldPresent(sfRewardFields) || + !node.isFieldPresent(sfLedgerIndex)) + continue; + + auto sle = view().peek( + Keylet{ltACCOUNT_ROOT, node.getFieldH256(sfLedgerIndex)}); + + if (!sle) + continue; + + if (!sle->isFieldPresent(sfRewardLgrFirst) || + !sle->isFieldPresent(sfRewardLgrLast) || + !sle->isFieldPresent(sfRewardAccumulator)) + continue; + + STObject& finalFields = (const_cast(node)) + .getField(sfRewardFields) + .downcast(); + + if (!finalFields.isFieldPresent(sfBalance)) + continue; + + uint64_t bal = + finalFields.getFieldAmount(sfBalance).xrp().drops() / 1'000'000; + + if (bal == 0) + continue; + + uint32_t lgrLast = sle->getFieldU32(sfRewardLgrLast); + + uint32_t lgrElapsed = lgrCur - lgrLast; + + // overflow safety + if (!has240911 && + (lgrElapsed > lgrCur || lgrElapsed > lgrLast || lgrElapsed == 0)) + continue; + if (has240911 && (lgrElapsed > lgrCur || lgrElapsed == 0)) + continue; + + uint64_t accum = sle->getFieldU64(sfRewardAccumulator); + uint64_t accumNew = accum + bal * ((uint64_t)lgrElapsed); + + // check for overflow + if (accumNew < accum) + continue; + + sle->setFieldU64(sfRewardAccumulator, accumNew); + sle->setFieldU32(sfRewardLgrLast, lgrCur); + + view().update(sle); + } +} + TER Transactor::executeHookChain( std::shared_ptr const& hookSLE, @@ -1419,6 +1532,13 @@ Transactor::executeHookChain( uint256 hookCanEmit = hook::getHookCanEmit(hookObj, hookDef); + XRPAmount atomicEmitFeeRemaining = + hookObj.isFieldPresent(sfHookAtomicEmitFee) + ? hookObj.getFieldAmount(sfHookAtomicEmitFee).xrp() + : hookDef->isFieldPresent(sfHookAtomicEmitFee) + ? hookDef->getFieldAmount(sfHookAtomicEmitFee).xrp() + : XRPAmount(0); + uint32_t flags = (hookObj.isFieldPresent(sfFlags) ? hookObj.getFieldU32(sfFlags) : hookDef->getFieldU32(sfFlags)); @@ -1460,6 +1580,7 @@ Transactor::executeHookChain( parameters, hookParamOverrides, stateMap, + atomicEmitFeeRemaining, ctx_, account, hasCallback, @@ -1587,6 +1708,8 @@ Transactor::doHookCallback( uint256 hookCanEmit = hook::getHookCanEmit(hookObj, hookDef); + XRPAmount atomicEmitFeeRemaining = XRPAmount(0); + // fetch the namespace either from the hook object of, if absent, the // hook def uint256 const& ns = @@ -1618,6 +1741,7 @@ Transactor::doHookCallback( parameters, {}, stateMap, + atomicEmitFeeRemaining, ctx_, callbackAccountID, true, @@ -1867,6 +1991,8 @@ Transactor::doAgainAsWeak( uint256 hookCanEmit = hook::getHookCanEmit(hookObj, hookDef); + XRPAmount atomicEmitFeeRemaining{0}; + // fetch the namespace either from the hook object of, if absent, the // hook def uint256 const& ns = @@ -1893,6 +2019,7 @@ Transactor::doAgainAsWeak( parameters, {}, stateMap, + atomicEmitFeeRemaining, ctx_, hookAccountID, hookDef->isFieldPresent(sfHookCallbackFee), @@ -2164,25 +2291,10 @@ Transactor::operator()() if (applied) { - // Check invariants: if `tecINVARIANT_FAILED` is not returned, we can - // proceed to apply the tx - result = ctx_.checkInvariants(result, fee); - - if (result == tecINVARIANT_FAILED) - { - // if invariants checking failed again, reset the context and - // attempt to only claim a fee. - auto const resetResult = reset(fee); - if (!isTesSuccess(resetResult.first)) - result = resetResult.first; - - fee = resetResult.second; + auto const invariantsResult = checkInvariants(result, fee); - // Check invariants again to ensure the fee claiming doesn't - // violate invariants. - if (isTesSuccess(result) || isTecClaim(result)) - result = ctx_.checkInvariants(result, fee); - } + result = invariantsResult.first; + fee = invariantsResult.second; // We ran through the invariant checker, which can, in some cases, // return a tef error code. Don't apply the transaction in that case. @@ -2192,81 +2304,7 @@ Transactor::operator()() if (applied && view().rules().enabled(featureBalanceRewards)) { - TxMeta metaRaw = ctx_.generateProvisionalMeta(); - metaRaw.setResult(result, 0); - STObject const meta = metaRaw.getAsObject(); - - uint32_t lgrCur = view().seq(); - - bool const has240819 = view().rules().enabled(fix240819); - bool const has240911 = view().rules().enabled(fix240911); - - auto const& sfRewardFields = - *(ripple::SField::knownCodeToField.at(917511 - has240819)); - - // iterate all affected balances - for (auto const& node : meta.getFieldArray(sfAffectedNodes)) - { - SField const& metaType = node.getFName(); - uint16_t nodeType = node.getFieldU16(sfLedgerEntryType); - - // we only care about ltACCOUNT_ROOT objects being modified or - // created - if (nodeType != ltACCOUNT_ROOT || metaType == sfDeletedNode) - continue; - - if (!node.isFieldPresent(sfRewardFields) || - !node.isFieldPresent(sfLedgerIndex)) - continue; - - auto sle = view().peek( - Keylet{ltACCOUNT_ROOT, node.getFieldH256(sfLedgerIndex)}); - - if (!sle) - continue; - - if (!sle->isFieldPresent(sfRewardLgrFirst) || - !sle->isFieldPresent(sfRewardLgrLast) || - !sle->isFieldPresent(sfRewardAccumulator)) - continue; - - STObject& finalFields = (const_cast(node)) - .getField(sfRewardFields) - .downcast(); - - if (!finalFields.isFieldPresent(sfBalance)) - continue; - - uint64_t bal = - finalFields.getFieldAmount(sfBalance).xrp().drops() / 1'000'000; - - if (bal == 0) - continue; - - uint32_t lgrLast = sle->getFieldU32(sfRewardLgrLast); - - uint32_t lgrElapsed = lgrCur - lgrLast; - - // overflow safety - if (!has240911 && - (lgrElapsed > lgrCur || lgrElapsed > lgrLast || - lgrElapsed == 0)) - continue; - if (has240911 && (lgrElapsed > lgrCur || lgrElapsed == 0)) - continue; - - uint64_t accum = sle->getFieldU64(sfRewardAccumulator); - uint64_t accumNew = accum + bal * ((uint64_t)lgrElapsed); - - // check for overflow - if (accumNew < accum) - continue; - - sle->setFieldU64(sfRewardAccumulator, accumNew); - sle->setFieldU32(sfRewardLgrLast, lgrCur); - - view().update(sle); - } + balanceRewards(result); } // Post-application (Weak TSH/AAW) Hooks are executed here. @@ -2352,6 +2390,53 @@ Transactor::operator()() applied = false; } + if (metadata && metadata->hasHookEmissions()) + { + OpenView emittedTxnsView(batch_view, ctx_.openView()); + bool const emitResult = hook::emitAtomicTransactions( + ctx_.app, + emittedTxnsView, + metadata->getTxID(), + metadata->getHookEmissions(), + j_); + printf("emitResult: %d\n", emitResult); + if (emitResult) + { + emittedTxnsView.apply(ctx_.openView()); + } + else + { + // reset context + result = tecHOOK_EMIT_FAILED; + auto const resetResult = reset(fee); + if (!isTesSuccess(resetResult.first)) + result = resetResult.first; + fee = resetResult.second; + + // InvariantCheck + auto const invariantsResult = checkInvariants(result, fee); + result = invariantsResult.first; + fee = invariantsResult.second; + + // BalanceRewards + balanceRewards(result); + // Add Hooks metadata (use metadata) + + // apply + metadata = ctx_.apply(result); + } + } + + ctx_.finalize(); + + if (ctx_.flags() & tapATOMIC_EMIT && !isTesSuccess(result)) + { + JLOG(j_.trace()) << "HookEmit[]: Atomic emit failed: " + << transToken(result); + JLOG(j_.trace()) << "HookEmit[]: Atomic emit failed: " + << ctx_.tx.getFullText(); + } + JLOG(j_.trace()) << (applied ? "applied " : "not applied ") << transToken(result); diff --git a/src/xrpld/app/tx/detail/Transactor.h b/src/xrpld/app/tx/detail/Transactor.h index 79576c53a4..3117e8f045 100644 --- a/src/xrpld/app/tx/detail/Transactor.h +++ b/src/xrpld/app/tx/detail/Transactor.h @@ -57,7 +57,8 @@ struct PreflightContext , j(j_) { XRPL_ASSERT( - (flags_ & tapBATCH) == tapBATCH, "Batch apply flag should be set"); + (flags_ & (tapBATCH | tapATOMIC_EMIT)) > 0, + "Batch or AtomicEmit flag should be set"); } PreflightContext( @@ -69,7 +70,8 @@ struct PreflightContext : app(app_), tx(tx_), rules(rules_), flags(flags_), j(j_) { XRPL_ASSERT( - (flags_ & tapBATCH) == 0, "Batch apply flag should not be set"); + (flags_ & (tapBATCH | tapATOMIC_EMIT)) == 0, + "Batch or AtomicEmit flag should not be set"); } PreflightContext& @@ -105,8 +107,9 @@ struct PreclaimContext , j(j_) { XRPL_ASSERT( - parentBatchId.has_value() == ((flags_ & tapBATCH) == tapBATCH), - "Parent Batch ID should be set if batch apply flag is set"); + parentBatchId.has_value() == + ((flags_ & (tapBATCH | tapATOMIC_EMIT)) > 0), + "Parent Batch ID should be set if batch or AtomicEmit flag is set"); } PreclaimContext( @@ -126,7 +129,8 @@ struct PreclaimContext j_) { XRPL_ASSERT( - (flags_ & tapBATCH) == 0, "Batch apply flag should not be set"); + (flags_ & (tapBATCH | tapATOMIC_EMIT)) == 0, + "Batch or AtomicEmit flag should not be set"); } PreclaimContext& @@ -312,6 +316,12 @@ class Transactor std::pair reset(XRPAmount fee); + std::pair + checkInvariants(TER result, XRPAmount fee); + + void + balanceRewards(TER result); + TER consumeSeqProxy(SLE::pointer const& sleAccount); TER diff --git a/src/xrpld/ledger/ApplyView.h b/src/xrpld/ledger/ApplyView.h index 65ff5816bb..1aaaa3e5d2 100644 --- a/src/xrpld/ledger/ApplyView.h +++ b/src/xrpld/ledger/ApplyView.h @@ -49,7 +49,10 @@ enum ApplyFlags : std::uint32_t { // Transaction shouldn't be applied // Signatures shouldn't be checked - tapDRY_RUN = 0x1000 + tapDRY_RUN = 0x1000, + + // Transaction is executing as part of a atomic emit + tapATOMIC_EMIT = 0x2000, }; constexpr ApplyFlags diff --git a/src/xrpld/ledger/OpenViewSandbox.h b/src/xrpld/ledger/OpenViewSandbox.h new file mode 100644 index 0000000000..ceff6b6efc --- /dev/null +++ b/src/xrpld/ledger/OpenViewSandbox.h @@ -0,0 +1,105 @@ +//------------------------------------------------------------------------------ +/* + This file is part of rippled: https://github.com/ripple/rippled + Copyright (c) 2025 Ripple Labs Inc. + + Permission to use, copy, modify, and/or distribute this software for any + purpose with or without fee is hereby granted, provided that the above + copyright notice and this permission notice appear in all copies. + + THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES + WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF + MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR + ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES + WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN + ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF + OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. +*/ +//============================================================================== + +#ifndef RIPPLE_LEDGER_OPENVIEWSANDBOX_H_INCLUDED +#define RIPPLE_LEDGER_OPENVIEWSANDBOX_H_INCLUDED + +#include + +#include + +namespace ripple { + +class OpenViewSandbox +{ +private: + OpenView& parent_; + std::unique_ptr sandbox_; + +public: + using key_type = ReadView::key_type; + + OpenViewSandbox(OpenView& parent) + : parent_(parent) + , sandbox_(std::make_unique(batch_view, parent)) + { + } + + void + rawErase(std::shared_ptr const& sle) + { + sandbox_->rawErase(sle); + } + + void + rawInsert(std::shared_ptr const& sle) + { + sandbox_->rawInsert(sle); + } + + void + rawReplace(std::shared_ptr const& sle) + { + sandbox_->rawReplace(sle); + } + + void + rawDestroyXRP(XRPAmount const& fee) + { + sandbox_->rawDestroyXRP(fee); + } + + void + rawTxInsert( + key_type const& key, + std::shared_ptr const& txn, + std::shared_ptr const& metaData) + { + sandbox_->rawTxInsert(key, txn, metaData); + } + + void + commit() + { + sandbox_->apply(parent_); + sandbox_ = std::make_unique(batch_view, parent_); + } + + void + discard() + { + sandbox_ = std::make_unique(batch_view, parent_); + } + + OpenView const& + view() const + { + return *sandbox_; + } + + OpenView& + view() + { + return *sandbox_; + } +}; + +} // namespace ripple + +#endif \ No newline at end of file