From a0632a0cb39b1b9b5ed06281660fb0b172cb9135 Mon Sep 17 00:00:00 2001 From: Bronek Kozicki Date: Tue, 25 Feb 2025 19:06:58 +0000 Subject: [PATCH] Add WithdrawalPolicy --- include/xrpl/protocol/Protocol.h | 3 +++ .../xrpl/protocol/detail/ledger_entries.macro | 4 +-- include/xrpl/protocol/detail/sfields.macro | 1 + .../xrpl/protocol/detail/transactions.macro | 3 +-- src/test/app/Vault_test.cpp | 26 +++++++++++++++++++ src/xrpld/app/tx/detail/VaultCreate.cpp | 13 ++++++++++ src/xrpld/app/tx/detail/VaultWithdraw.cpp | 4 +++ src/xrpld/ledger/detail/View.cpp | 2 -- 8 files changed, 50 insertions(+), 6 deletions(-) diff --git a/include/xrpl/protocol/Protocol.h b/include/xrpl/protocol/Protocol.h index 7ab18dd1dc..d4fe7838bf 100644 --- a/include/xrpl/protocol/Protocol.h +++ b/include/xrpl/protocol/Protocol.h @@ -118,6 +118,9 @@ std::uint64_t constexpr maxMPTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull; /** The maximum length of MPTokenMetadata */ std::size_t constexpr maxVaultDataLength = 256; +/** Vault withdrawal policies */ +std::uint8_t constexpr vaultStrategyFirstComeFirstServe = 1; + /** A ledger index. */ using LedgerIndex = std::uint32_t; diff --git a/include/xrpl/protocol/detail/ledger_entries.macro b/include/xrpl/protocol/detail/ledger_entries.macro index e68b0de474..2bd7e2ef52 100644 --- a/include/xrpl/protocol/detail/ledger_entries.macro +++ b/include/xrpl/protocol/detail/ledger_entries.macro @@ -480,9 +480,9 @@ LEDGER_ENTRY(ltVAULT, 0x0083, Vault, vault, ({ {sfAssetMaximum, soeDEFAULT}, {sfLossUnrealized, soeDEFAULT}, {sfMPTokenIssuanceID, soeREQUIRED}, // sfShare + {sfWithdrawalPolicy, soeREQUIRED}, // no ShareTotal ever (use MPTIssuance.sfOutstandingAmount) - // no PermissionedDomainID (use MPTIssuance.sfDomainID) - // no WithdrawalPolicy yet + // no PermissionedDomainID ever (use MPTIssuance.sfDomainID) })) #undef EXPAND diff --git a/include/xrpl/protocol/detail/sfields.macro b/include/xrpl/protocol/detail/sfields.macro index 05f1eaf0ed..b78a78363a 100644 --- a/include/xrpl/protocol/detail/sfields.macro +++ b/include/xrpl/protocol/detail/sfields.macro @@ -42,6 +42,7 @@ TYPED_SFIELD(sfTickSize, UINT8, 16) TYPED_SFIELD(sfUNLModifyDisabling, UINT8, 17) TYPED_SFIELD(sfHookResult, UINT8, 18) TYPED_SFIELD(sfWasLockingChainSend, UINT8, 19) +TYPED_SFIELD(sfWithdrawalPolicy, UINT8, 20) // 16-bit integers (common) TYPED_SFIELD(sfLedgerEntryType, UINT16, 1, SField::sMD_Never) diff --git a/include/xrpl/protocol/detail/transactions.macro b/include/xrpl/protocol/detail/transactions.macro index 4778bc3613..352e86c69d 100644 --- a/include/xrpl/protocol/detail/transactions.macro +++ b/include/xrpl/protocol/detail/transactions.macro @@ -471,7 +471,7 @@ TRANSACTION(ttVAULT_CREATE, 64, VaultCreate, ({ {sfAssetMaximum, soeOPTIONAL}, {sfMPTokenMetadata, soeOPTIONAL}, {sfDomainID, soeOPTIONAL}, // PermissionedDomainID - // no WithdrawalPolicy yet + {sfWithdrawalPolicy, soeOPTIONAL}, {sfData, soeOPTIONAL}, })) @@ -480,7 +480,6 @@ TRANSACTION(ttVAULT_SET, 65, VaultSet, ({ {sfVaultID, soeREQUIRED}, {sfAssetMaximum, soeOPTIONAL}, {sfDomainID, soeOPTIONAL}, // PermissionedDomainID - // no WithdrawalPolicy yet {sfData, soeOPTIONAL}, })) diff --git a/src/test/app/Vault_test.cpp b/src/test/app/Vault_test.cpp index 7c7952c198..6bd6e94be6 100644 --- a/src/test/app/Vault_test.cpp +++ b/src/test/app/Vault_test.cpp @@ -287,6 +287,32 @@ class Vault_test : public beast::unit_test::suite env(tx); }); + testCase([this]( + Env& env, + Account const& issuer, + Account const& owner, + Account const& depositor, + Asset const& asset, + Vault& vault) { + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + tx[sfWithdrawalPolicy] = 1; + testcase("explicitly select withdrawal policy"); + env(tx); + }); + + testCase([this]( + Env& env, + Account const& issuer, + Account const& owner, + Account const& depositor, + Asset const& asset, + Vault& vault) { + auto [tx, keylet] = vault.create({.owner = owner, .asset = asset}); + tx[sfWithdrawalPolicy] = 0; + testcase("invalid withdrawal policy"); + env(tx, ter(temMALFORMED)); + }); + testCase([this]( Env& env, Account const& issuer, diff --git a/src/xrpld/app/tx/detail/VaultCreate.cpp b/src/xrpld/app/tx/detail/VaultCreate.cpp index 918fd8a51d..724765b7e2 100644 --- a/src/xrpld/app/tx/detail/VaultCreate.cpp +++ b/src/xrpld/app/tx/detail/VaultCreate.cpp @@ -23,6 +23,7 @@ #include #include #include +#include #include #include #include @@ -51,6 +52,13 @@ VaultCreate::preflight(PreflightContext const& ctx) return temMALFORMED; } + if (auto const data = ctx.tx[~sfWithdrawalPolicy]) + { + // Enforce valid withdrawal policy + if (*data != vaultStrategyFirstComeFirstServe) + return temMALFORMED; + } + if (auto const domain = ctx.tx[~sfDomainID]) { if (*domain == beast::zero) @@ -176,6 +184,11 @@ VaultCreate::doApply() vault->at(sfMPTokenIssuanceID) = share; if (auto value = tx[~sfData]) vault->at(sfData) = *value; + // Required field, default to vaultStrategyFirstComeFirstServe + if (auto value = tx[~sfWithdrawalPolicy]) + vault->at(sfWithdrawalPolicy) = *value; + else + vault->at(sfWithdrawalPolicy) = vaultStrategyFirstComeFirstServe; // No `LossUnrealized`. view().insert(vault); diff --git a/src/xrpld/app/tx/detail/VaultWithdraw.cpp b/src/xrpld/app/tx/detail/VaultWithdraw.cpp index a5ae6d0982..14dae4ce26 100644 --- a/src/xrpld/app/tx/detail/VaultWithdraw.cpp +++ b/src/xrpld/app/tx/detail/VaultWithdraw.cpp @@ -51,6 +51,10 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx) if (!vault) return tecOBJECT_NOT_FOUND; + // Enforce valid withdrawal policy + if (vault->at(sfWithdrawalPolicy) != vaultStrategyFirstComeFirstServe) + return tefINTERNAL; + auto const assets = ctx.tx[sfAmount]; auto const asset = vault->at(sfAsset); auto const share = vault->at(sfMPTokenIssuanceID); diff --git a/src/xrpld/ledger/detail/View.cpp b/src/xrpld/ledger/detail/View.cpp index 65ab4c0ca7..99d2a4f865 100644 --- a/src/xrpld/ledger/detail/View.cpp +++ b/src/xrpld/ledger/detail/View.cpp @@ -2531,7 +2531,6 @@ assetsToSharesWithdraw( return shares; Number shareTotal = getShareTotal(view, vault); shares = shareTotal * (assets / assetTotal); - // TODO: Limit by withdrawal policy? return shares; } @@ -2549,7 +2548,6 @@ sharesToAssetsWithdraw( return assets; Number shareTotal = getShareTotal(view, vault); assets = assetTotal * (shares / shareTotal); - // TODO: Limit by withdrawal policy? return assets; }