diff --git a/src/ripple/app/tx/impl/CreateOffer.cpp b/src/ripple/app/tx/impl/CreateOffer.cpp index 9bb7532852f..4f1d9108bca 100644 --- a/src/ripple/app/tx/impl/CreateOffer.cpp +++ b/src/ripple/app/tx/impl/CreateOffer.cpp @@ -1107,6 +1107,12 @@ CreateOffer::applyGuts(Sandbox& sb, Sandbox& sbCancel) if (bImmediateOrCancel) { JLOG(j_.trace()) << "Immediate or cancel: offer canceled"; + if (!crossed && sb.rules().enabled(featureImmediateOfferKilled)) + // If the ImmediateOfferKilled amendment is enabled, any + // ImmediateOrCancel offer that transfers absolutely no funds + // returns tecKILLED rather than tesSUCCESS. Motivation for the + // change is here: https://github.com/ripple/rippled/issues/4115 + return {tecKILLED, false}; return {tesSUCCESS, true}; } diff --git a/src/ripple/protocol/Feature.h b/src/ripple/protocol/Feature.h index f0d0c8efbb5..9e51e723381 100644 --- a/src/ripple/protocol/Feature.h +++ b/src/ripple/protocol/Feature.h @@ -74,7 +74,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 = 50; +static constexpr std::size_t numFeatures = 51; /** Amendments that this server supports and the default voting behavior. Whether they are enabled depends on the Rules defined in the validated @@ -338,6 +338,7 @@ extern uint256 const featureExpandedSignerList; extern uint256 const fixNFTokenDirV1; extern uint256 const fixNFTokenNegOffer; extern uint256 const featureNonFungibleTokensV1_1; +extern uint256 const featureImmediateOfferKilled; } // namespace ripple diff --git a/src/ripple/protocol/impl/Feature.cpp b/src/ripple/protocol/impl/Feature.cpp index 4060067e30a..9a2d8feb4e7 100644 --- a/src/ripple/protocol/impl/Feature.cpp +++ b/src/ripple/protocol/impl/Feature.cpp @@ -442,6 +442,7 @@ REGISTER_FEATURE(ExpandedSignerList, Supported::yes, DefaultVote::no) REGISTER_FIX (fixNFTokenDirV1, Supported::yes, DefaultVote::no); REGISTER_FIX (fixNFTokenNegOffer, Supported::yes, DefaultVote::no); REGISTER_FEATURE(NonFungibleTokensV1_1, Supported::yes, DefaultVote::no); +REGISTER_FEATURE(ImmediateOfferKilled, Supported::yes, DefaultVote::no); // The following amendments have been active for at least two years. Their // pre-amendment code has been removed and the identifiers are deprecated. diff --git a/src/ripple/protocol/impl/TER.cpp b/src/ripple/protocol/impl/TER.cpp index c660b1cea3f..a845bdaeebc 100644 --- a/src/ripple/protocol/impl/TER.cpp +++ b/src/ripple/protocol/impl/TER.cpp @@ -77,7 +77,7 @@ transResults() MAKE_ERROR(tecINVARIANT_FAILED, "One or more invariants for the transaction were not satisfied."), MAKE_ERROR(tecEXPIRED, "Expiration time is passed."), MAKE_ERROR(tecDUPLICATE, "Ledger object already exists."), - MAKE_ERROR(tecKILLED, "FillOrKill offer killed."), + MAKE_ERROR(tecKILLED, "No funds transferred and no offer created."), MAKE_ERROR(tecHAS_OBLIGATIONS, "The account cannot be deleted since it has obligations."), MAKE_ERROR(tecTOO_SOON, "It is too early to attempt the requested operation. Please wait."), MAKE_ERROR(tecMAX_SEQUENCE_REACHED, "The maximum sequence number was reached."), diff --git a/src/test/app/Offer_test.cpp b/src/test/app/Offer_test.cpp index 200e1c4aa51..9f6e165bc16 100644 --- a/src/test/app/Offer_test.cpp +++ b/src/test/app/Offer_test.cpp @@ -950,9 +950,14 @@ class Offer_test : public beast::unit_test::suite env(pay(gw, alice, USD(1000)), ter(tesSUCCESS)); // No cross: - env(offer(alice, XRP(1000), USD(1000)), - txflags(tfImmediateOrCancel), - ter(tesSUCCESS)); + { + TER const expectedCode = features[featureImmediateOfferKilled] + ? static_cast(tecKILLED) + : static_cast(tesSUCCESS); + env(offer(alice, XRP(1000), USD(1000)), + txflags(tfImmediateOrCancel), + ter(expectedCode)); + } env.require( balance(alice, startBalance - f - f), @@ -5165,11 +5170,12 @@ class Offer_test : public beast::unit_test::suite FeatureBitset const flowCross{featureFlowCross}; FeatureBitset const takerDryOffer{fixTakerDryOfferRemoval}; FeatureBitset const rmSmallIncreasedQOffers{fixRmSmallIncreasedQOffers}; + FeatureBitset const immediateOfferKilled{featureImmediateOfferKilled}; - testAll(all - takerDryOffer); - testAll(all - flowCross - takerDryOffer); - testAll(all - flowCross); - testAll(all - rmSmallIncreasedQOffers); + testAll(all - takerDryOffer - immediateOfferKilled); + testAll(all - flowCross - takerDryOffer - immediateOfferKilled); + testAll(all - flowCross - immediateOfferKilled); + testAll(all - rmSmallIncreasedQOffers - immediateOfferKilled); testAll(all); testFalseAssert(); } @@ -5184,11 +5190,12 @@ class Offer_manual_test : public Offer_test FeatureBitset const all{supported_amendments()}; FeatureBitset const flowCross{featureFlowCross}; FeatureBitset const f1513{fix1513}; + FeatureBitset const immediateOfferKilled{featureImmediateOfferKilled}; FeatureBitset const takerDryOffer{fixTakerDryOfferRemoval}; - testAll(all - flowCross - f1513); - testAll(all - flowCross); - testAll(all - f1513); + testAll(all - flowCross - f1513 - immediateOfferKilled); + testAll(all - flowCross - immediateOfferKilled); + testAll(all - immediateOfferKilled); testAll(all); testAll(all - flowCross - takerDryOffer);