Skip to content

Commit fb2eb35

Browse files
committed
Prohibit VaultCreate to assets without real issuer
1 parent 270355f commit fb2eb35

File tree

4 files changed

+92
-139
lines changed

4 files changed

+92
-139
lines changed

src/test/app/Vault_test.cpp

Lines changed: 79 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,8 @@
1717
*/
1818
//==============================================================================
1919

20+
#include <test/jtx/AMM.h>
21+
#include <test/jtx/AMMTest.h>
2022
#include <test/jtx/Account.h>
2123
#include <test/jtx/Env.h>
2224
#include <test/jtx/amount.h>
@@ -761,21 +763,71 @@ class Vault_test : public beast::unit_test::suite
761763
testCreateFailIOU()
762764
{
763765
using namespace test::jtx;
764-
Env env{*this, supported_amendments() | featureSingleAssetVault};
765-
Account issuer{"issuer"};
766-
Account owner{"owner"};
767-
Account depositor{"depositor"};
768-
env.fund(XRP(1000), issuer, owner, depositor);
769-
env.close();
770-
Vault vault{env};
771-
Asset asset = issuer["IOU"];
766+
{
767+
testcase("IOU fail create frozen");
768+
Env env{*this, supported_amendments() | featureSingleAssetVault};
769+
Account issuer{"issuer"};
770+
Account owner{"owner"};
771+
Account depositor{"depositor"};
772+
env.fund(XRP(1000), issuer, owner, depositor);
773+
env.close();
774+
Vault vault{env};
775+
Asset asset = issuer["IOU"];
772776

773-
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
777+
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
774778

775-
env(fset(issuer, asfGlobalFreeze));
776-
env.close();
777-
env(tx, ter(tecFROZEN));
778-
env.close();
779+
env(fset(issuer, asfGlobalFreeze));
780+
env.close();
781+
env(tx, ter(tecFROZEN));
782+
env.close();
783+
}
784+
785+
{
786+
testcase("IOU fail create vault for AMM LPToken");
787+
Env env{*this, supported_amendments() | featureSingleAssetVault};
788+
Account const gw("gateway");
789+
Account const alice("alice");
790+
Account const carol("carol");
791+
IOU const USD = gw["USD"];
792+
793+
auto const [asset1, asset2] =
794+
std::pair<STAmount, STAmount>(XRP(10000), USD(10000));
795+
auto tofund = [&](STAmount const& a) -> STAmount {
796+
if (a.native())
797+
{
798+
auto const defXRP = XRP(30000);
799+
if (a <= defXRP)
800+
return defXRP;
801+
return a + XRP(1000);
802+
}
803+
auto const defIOU = STAmount{a.issue(), 30000};
804+
if (a <= defIOU)
805+
return defIOU;
806+
return a + STAmount{a.issue(), 1000};
807+
};
808+
auto const toFund1 = tofund(asset1);
809+
auto const toFund2 = tofund(asset2);
810+
BEAST_EXPECT(asset1 <= toFund1 && asset2 <= toFund2);
811+
812+
if (!asset1.native() && !asset2.native())
813+
fund(env, gw, {alice, carol}, {toFund1, toFund2}, Fund::All);
814+
else if (asset1.native())
815+
fund(env, gw, {alice, carol}, toFund1, {toFund2}, Fund::All);
816+
else if (asset2.native())
817+
fund(env, gw, {alice, carol}, toFund2, {toFund1}, Fund::All);
818+
819+
AMM ammAlice(
820+
env, alice, asset1, asset2, CreateArg{.log = false, .tfee = 0});
821+
822+
Account const owner{"owner"};
823+
env.fund(XRP(1000000), owner);
824+
825+
Vault vault{env};
826+
auto [tx, k] =
827+
vault.create({.owner = owner, .asset = ammAlice.lptIssue()});
828+
env(tx, ter{tecWRONG_ASSET});
829+
env.close();
830+
}
779831
}
780832

781833
void
@@ -1065,7 +1117,7 @@ class Vault_test : public beast::unit_test::suite
10651117
});
10661118

10671119
{
1068-
testcase("MPT maximum asset recursion");
1120+
testcase("MPT shares to a vault");
10691121

10701122
Env env{*this, supported_amendments() | featureSingleAssetVault};
10711123
Account owner{"owner"};
@@ -1074,119 +1126,27 @@ class Vault_test : public beast::unit_test::suite
10741126
env.close();
10751127
Vault vault{env};
10761128

1077-
struct VaultInfo
1078-
{
1079-
Keylet keylet;
1080-
PrettyAsset shares;
1081-
PrettyAsset assets;
1082-
VaultInfo(
1083-
Keylet const& keylet_,
1084-
PrettyAsset const& shares_,
1085-
PrettyAsset const& assets_)
1086-
: keylet(keylet_), shares(shares_), assets(assets_)
1087-
{
1088-
}
1089-
};
1090-
std::vector<VaultInfo> vaults;
1091-
10921129
MPTTester mptt{env, issuer, mptInitNoFund};
10931130
mptt.create(
10941131
{.flags = tfMPTCanTransfer | tfMPTCanLock | lsfMPTCanClawback |
10951132
tfMPTRequireAuth});
10961133
mptt.authorize({.account = owner});
10971134
mptt.authorize({.account = issuer, .holder = owner});
1098-
{
1099-
for (int i = 0; i < 5; ++i)
1100-
vaults.emplace_back(
1101-
keylet::vault(beast::zero), xrpIssue(), xrpIssue());
1102-
1103-
PrettyAsset asset = mptt.issuanceID();
1104-
env(pay(issuer, owner, asset(100)));
1105-
for (auto& v : vaults)
1106-
{
1107-
auto [tx, k] =
1108-
vault.create({.owner = owner, .asset = asset});
1109-
env(tx);
1110-
env.close();
1111-
1112-
v.keylet = k;
1113-
v.assets = asset;
1114-
v.shares = [&env, keylet = k, this]() -> Asset {
1115-
auto const vault = env.le(keylet);
1116-
BEAST_EXPECT(vault != nullptr);
1117-
return MPTIssue(vault->at(sfShareMPTID));
1118-
}();
1119-
asset = v.shares;
1120-
}
1121-
}
1122-
1123-
env(std::get<Json::Value>(vault.create(
1124-
{.owner = owner, .asset = vaults.back().shares})),
1125-
ter{tecLIMIT_EXCEEDED}); // exceeded maximum recursion depth
1126-
1127-
for (auto& v : vaults)
1128-
{
1129-
env(vault.deposit(
1130-
{.depositor = owner,
1131-
.id = v.keylet.key,
1132-
.amount = v.assets(100)}));
1133-
env.close();
1134-
}
1135-
1136-
{
1137-
testcase("MPT recursive authorization and lock check");
1138-
auto& v = vaults.back();
1139-
1140-
mptt.authorize(
1141-
{.account = issuer,
1142-
.holder = owner,
1143-
.flags = tfMPTUnauthorize});
1144-
1145-
env(vault.deposit(
1146-
{.depositor = owner,
1147-
.id = v.keylet.key,
1148-
.amount = v.assets(10)}),
1149-
ter{tecNO_AUTH});
1150-
env(vault.withdraw(
1151-
{.depositor = owner,
1152-
.id = v.keylet.key,
1153-
.amount = v.assets(10)}),
1154-
ter{tecNO_AUTH});
1155-
env.close();
1156-
1157-
mptt.authorize({.account = issuer, .holder = owner});
1158-
mptt.set(
1159-
{.account = issuer, .holder = owner, .flags = tfMPTLock});
1160-
1161-
env(vault.deposit(
1162-
{.depositor = owner,
1163-
.id = v.keylet.key,
1164-
.amount = v.assets(10)}),
1165-
ter{tecLOCKED});
1166-
env(vault.withdraw(
1167-
{.depositor = owner,
1168-
.id = v.keylet.key,
1169-
.amount = v.assets(10)}),
1170-
ter{tecLOCKED});
1171-
env.close();
1172-
1173-
mptt.set(
1174-
{.account = issuer, .holder = owner, .flags = tfMPTUnlock});
1175-
}
1135+
PrettyAsset asset = mptt.issuanceID();
1136+
env(pay(issuer, owner, asset(100)));
1137+
auto [tx1, k1] = vault.create({.owner = owner, .asset = asset});
1138+
env(tx1);
1139+
env.close();
11761140

1177-
for (auto i = vaults.rbegin(); i != vaults.rend(); ++i)
1178-
{
1179-
auto v = *i;
1180-
env(vault.withdraw(
1181-
{.depositor = owner,
1182-
.id = v.keylet.key,
1183-
.amount = v.shares(100)}));
1184-
}
1141+
auto const shares = [&env, keylet = k1, this]() -> Asset {
1142+
auto const vault = env.le(keylet);
1143+
BEAST_EXPECT(vault != nullptr);
1144+
return MPTIssue(vault->at(sfShareMPTID));
1145+
}();
11851146

1186-
for (auto& v : vaults)
1187-
{
1188-
env(vault.del({.owner = owner, .id = v.keylet.key}));
1189-
}
1147+
auto [tx2, k2] = vault.create({.owner = owner, .asset = shares});
1148+
env(tx2, ter{tecWRONG_ASSET});
1149+
env.close();
11901150
}
11911151
}
11921152

src/xrpld/app/tx/detail/VaultCreate.cpp

Lines changed: 6 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -110,16 +110,13 @@ VaultCreate::preclaim(PreclaimContext const& ctx)
110110
return tecNO_AUTH;
111111
}
112112

113-
// Check for excessive vault shares recursion, which is reported by
114-
// requireAuth as tecLIMIT_EXCEEDED. The vault owner might not be
115-
// permissioned to hold assets but that's OK, it only means that the owner
116-
// won't be able to withdraw from or deposit into the vault, or hold shares.
117-
if (asset.holds<MPTIssue>())
113+
// Check for pseudo-account issuers - we do not want a vault to hold such
114+
// assets (e.g. MPT shares to other vaults or AMM LPTokens) as they would be
115+
// impossible to clawback (should the need arise)
116+
if (!asset.native())
118117
{
119-
auto const mptIssue = asset.get<MPTIssue>();
120-
if (auto const ter = requireAuth(ctx.view, mptIssue, account, 1);
121-
ter == tecLIMIT_EXCEEDED)
122-
return ter;
118+
if (isPseudoAccount(ctx.view, asset.getIssuer()))
119+
return tecWRONG_ASSET;
123120
}
124121

125122
// Cannot create Vault for an Asset frozen for the vault owner

src/xrpld/ledger/View.h

Lines changed: 5 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,9 @@ isFrozen(
156156
int depth = 0);
157157

158158
/**
159-
* isFrozen check is recursive for MPT shares in a vault, descending to assets
160-
* in the vault. These assets could be themselves MPT shares in another vault.
161-
* For this reason we limit depth of check, up to maxAssetCheckDepth. If this
162-
* depth is exceeded, we return true (i.e. the asset is considered frozen).
159+
* isFrozen check is recursive for MPT shares in a vault, descending to
160+
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
161+
* purely defensive, as we currently do not allow such vaults to be created.
163162
*/
164163
[[nodiscard]] inline bool
165164
isFrozen(
@@ -718,11 +717,8 @@ requireAuth(ReadView const& view, Issue const& issue, AccountID const& account);
718717
* doApply. This will ensure that any expired credentials are deleted.
719718
*
720719
* requireAuth check is recursive for MPT shares in a vault, descending to
721-
* assets in the vault. These assets could be themselves MPT shares in another
722-
* vault. For this reason we limit depth of check, up to maxAssetCheckDepth.
723-
* This function will return tecLIMIT_EXCEEDED if maximum depth is exceeded.
724-
* Transaction VaultCreate checks for this error code, to prevent such vaults
725-
* being created.
720+
* assets in the vault, up to maxAssetCheckDepth recursion depth. This is
721+
* purely defensive, as we currently do not allow such vaults to be created.
726722
*/
727723
[[nodiscard]] TER
728724
requireAuth(

src/xrpld/ledger/detail/View.cpp

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -328,7 +328,7 @@ isVaultPseudoAccountFrozen(
328328
return false; // not a Vault pseudo-account, common case
329329

330330
if (depth >= maxAssetCheckDepth)
331-
return true; // fail at maximum 2^maxAssetCheckDepth checks
331+
return true; // LCOV_EXCL_LINE
332332

333333
auto const vault =
334334
view.read(keylet::vault(mptIssuer->getFieldH256(sfVaultID)));
@@ -2298,7 +2298,7 @@ requireAuth(
22982298
return tefINTERNAL; // LCOV_EXCL_LINE
22992299

23002300
if (depth >= maxAssetCheckDepth)
2301-
return tecLIMIT_EXCEEDED; // VaultCreate looks for this code
2301+
return tecINTERNAL; // LCOV_EXCL_LINE
23022302

23032303
auto const asset = sleVault->at(sfAsset);
23042304
if (auto const err = std::visit(

0 commit comments

Comments
 (0)