diff --git a/src/ripple/app/hook/Enum.h b/src/ripple/app/hook/Enum.h index 9728b1040..e026ef130 100644 --- a/src/ripple/app/hook/Enum.h +++ b/src/ripple/app/hook/Enum.h @@ -358,6 +358,7 @@ enum ExitType : uint8_t { WASM_ERROR = 1, ROLLBACK = 2, ACCEPT = 3, + GAS_INSUFFICIENT = 4, }; const uint16_t max_state_modifications = 256; diff --git a/src/ripple/app/hook/applyHook.h b/src/ripple/app/hook/applyHook.h index 19fd319b8..f271fd5f6 100644 --- a/src/ripple/app/hook/applyHook.h +++ b/src/ripple/app/hook/applyHook.h @@ -816,7 +816,7 @@ public: << hookCtx.result.hookGas; } - hookCtx.result.exitType = hook_api::ExitType::WASM_ERROR; + hookCtx.result.exitType = hook_api::ExitType::GAS_INSUFFICIENT; return; } diff --git a/src/ripple/app/tx/impl/SetHook.cpp b/src/ripple/app/tx/impl/SetHook.cpp index d3b8f2bc4..910fcf7fc 100644 --- a/src/ripple/app/tx/impl/SetHook.cpp +++ b/src/ripple/app/tx/impl/SetHook.cpp @@ -1342,7 +1342,7 @@ validateGasHook( hook.isFieldPresent(sfHookCallbackGas)) return tecHOOK_INVALID; - auto const flags = defSLE->getFieldU32(sfFlags); + auto const flags = hook.getFlags(); if (((flags & hsfCOLLECT) && !hook.isFieldPresent(sfHookWeakGas)) || (!(flags & hsfCOLLECT) && hook.isFieldPresent(sfHookWeakGas))) return tecHOOK_INVALID; @@ -1446,6 +1446,14 @@ SetHook::setHook() std::optional newHookCanEmit; std::optional defHookCanEmit; + std::optional oldHookWeakGas; + std::optional newHookWeakGas; + std::optional defHookWeakGas; + + std::optional oldHookCallbackGas; + std::optional newHookCallbackGas; + std::optional defHookCallbackGas; + // 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 @@ -1514,6 +1522,23 @@ SetHook::setHook() oldHookCanEmit = oldHook->get().getFieldH256(sfHookCanEmit); else if (defHookCanEmit) oldHookCanEmit = *defHookCanEmit; + + if (oldDefSLE && oldDefSLE->isFieldPresent(sfHookWeakGas)) + defHookWeakGas = oldDefSLE->getFieldU32(sfHookWeakGas); + + if (oldHook && oldHook->get().isFieldPresent(sfHookWeakGas)) + oldHookWeakGas = oldHook->get().getFieldU32(sfHookWeakGas); + else if (defHookWeakGas) + oldHookWeakGas = *defHookWeakGas; + + if (oldDefSLE && oldDefSLE->isFieldPresent(sfHookCallbackGas)) + defHookCallbackGas = oldDefSLE->getFieldU32(sfHookCallbackGas); + + if (oldHook && oldHook->get().isFieldPresent(sfHookCallbackGas)) + oldHookCallbackGas = + oldHook->get().getFieldU32(sfHookCallbackGas); + else if (defHookCallbackGas) + oldHookCallbackGas = *defHookCallbackGas; } // in preparation for three way merge populate fields if they are @@ -1689,6 +1714,31 @@ SetHook::setHook() newHook.setFieldH256(sfHookCanEmit, *newHookCanEmit); } + if (newHookWeakGas) + { + if (defHookWeakGas.has_value() && + *defHookWeakGas == *newHookWeakGas) + { + if (newHook.isFieldPresent(sfHookWeakGas)) + newHook.makeFieldAbsent(sfHookWeakGas); + } + else + newHook.setFieldU32(sfHookWeakGas, *newHookWeakGas); + } + + if (newHookCallbackGas) + { + if (defHookCallbackGas.has_value() && + *defHookCallbackGas == *newHookCallbackGas) + { + if (newHook.isFieldPresent(sfHookCallbackGas)) + newHook.makeFieldAbsent(sfHookCallbackGas); + } + else + newHook.setFieldU32( + sfHookCallbackGas, *newHookCallbackGas); + } + // parameters if (hookSetObj->get().isFieldPresent(sfHookParameters) && hookSetObj->get().getFieldArray(sfHookParameters).empty()) @@ -2001,6 +2051,16 @@ SetHook::setHook() *defHookCanEmit == *newHookCanEmit)) newHook.setFieldH256(sfHookCanEmit, *newHookCanEmit); + if (newHookCallbackGas && + !(defHookCallbackGas.has_value() && + *defHookCallbackGas == *newHookCallbackGas)) + newHook.setFieldU32(sfHookCallbackGas, *newHookCallbackGas); + + if (newHookWeakGas && + !(defHookWeakGas.has_value() && + *defHookWeakGas == *newHookWeakGas)) + newHook.setFieldU32(sfHookWeakGas, *newHookWeakGas); + // parameters TER result = updateHookParameters( ctx, diff --git a/src/test/app/SetHook_test.cpp b/src/test/app/SetHook_test.cpp index 4ff64caa8..5b81b783b 100644 --- a/src/test/app/SetHook_test.cpp +++ b/src/test/app/SetHook_test.cpp @@ -14299,6 +14299,74 @@ public: env.close(); } + void + testGasTypeHookWeakGas(FeatureBitset features) + { + testcase("Test Gas-type Hook weak gas"); + using namespace jtx; + + auto const alice = Account{"alice"}; + auto const gw = Account{"gw"}; + auto const USD = gw["USD"]; + + for (auto const success : {true, false}) + { + Env env{*this, features}; + env.fund(XRP(10000), alice, gw); + env.close(); + + env(fset(gw, asfTshCollect)); + env.close(); + + auto const weakGas = success ? 1000000 : 1; + + Json::Value jv = hso(gas_accept_wasm, collectFlag); + jv[jss::HookApiVersion] = 1; + jv[sfHookWeakGas.jsonName] = weakGas; + + env(ripple::test::jtx::hook(gw, {{jv}}, 0), + M("test gas type hook weak gas installation"), + HSFEE); + env.close(); + + auto const balanceBefore = env.balance(gw); + + env(trust(alice, USD(1000000))); + env.close(); + + auto const balanceAfter = env.balance(gw); + BEAST_EXPECT(balanceBefore - balanceAfter == drops(weakGas)); + + auto const meta = env.meta(); + + BEAST_REQUIRE(meta); + BEAST_REQUIRE(meta->isFieldPresent(sfHookExecutions)); + BEAST_REQUIRE(meta->getFieldArray(sfHookExecutions).size() == 1); + + auto const& execution = meta->getFieldArray(sfHookExecutions)[0]; + BEAST_REQUIRE(execution.isFieldPresent(sfHookResult)); + BEAST_REQUIRE( + execution.getFieldU8(sfHookResult) == + (success ? hook_api::ExitType::ACCEPT + : hook_api::ExitType::GAS_INSUFFICIENT)); + BEAST_REQUIRE(execution.isFieldPresent(sfHookInstructionCost)); + BEAST_REQUIRE( + execution.getFieldU32(sfHookInstructionCost) == + (success ? 14 : 1)); + } + } + + void + testGasTypeHookCbakGas(FeatureBitset features) + { + testcase("Test Gas-type Hook cbak gas"); + using namespace jtx; + Env env{*this, features}; + auto const alice = Account{"alice"}; + env.fund(XRP(10000), alice); + env.close(); + } + void testGasExecutionSufficient(FeatureBitset features) { @@ -14568,8 +14636,11 @@ public: testWithFeatures(FeatureBitset features) { // Gas-type Hook tests + testGasTypeHookWeakGas(features); + return; testGasTypeHookDisabled(features); testGasTypeHookInstallation(features); + testGasTypeHookCbakGas(features); testGasTypeHookRejects_gFunction(features); testGasExecutionSufficient(features); testMultipleGasHooksSharedPool(features); @@ -14694,6 +14765,8 @@ public: // Gas-type Hook tests testGasTypeHookDisabled(features); testGasTypeHookInstallation(features); + testGasTypeHookWeakGas(features); + testGasTypeHookCbakGas(features); testGasTypeHookRejects_gFunction(features); testGasExecutionSufficient(features); testMultipleGasHooksSharedPool(features);