Reorganize and extend unit tests

This commit is contained in:
Bronek Kozicki
2025-05-02 16:57:26 +01:00
parent 84abcba497
commit bbe49132a6

View File

@@ -77,6 +77,8 @@ class Vault_test : public beast::unit_test::suite
Vault& vault,
PrettyAsset const& asset) {
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
tx[sfData] = "AFEED00E";
tx[sfAssetsMaximum] = asset(100).number();
env(tx);
env.close();
BEAST_EXPECT(env.le(keylet));
@@ -151,6 +153,13 @@ class Vault_test : public beast::unit_test::suite
env(tx);
}
{
testcase(prefix + " set data");
auto tx = vault.set({.owner = owner, .id = keylet.key});
tx[sfData] = "0";
env(tx);
}
{
testcase(prefix + " fail to set domain on public vault");
auto tx = vault.set({.owner = owner, .id = keylet.key});
@@ -1140,10 +1149,12 @@ class Vault_test : public beast::unit_test::suite
Vault vault{env};
Asset asset = issuer["IOU"];
auto [tx, keylet] =
vault.create({.owner = owner, .asset = asset});
env(tx, ter(terNO_ACCOUNT));
env.close();
{
auto [tx, keylet] =
vault.create({.owner = owner, .asset = asset});
env(tx, ter(terNO_ACCOUNT));
env.close();
}
}
}
@@ -1516,6 +1527,28 @@ class Vault_test : public beast::unit_test::suite
mptt.destroy({.issuer = issuer, .id = mptt.issuanceID()});
env.close();
{
auto [tx, keylet] =
vault.create({.owner = depositor, .asset = asset});
env(tx, ter{tecOBJECT_NOT_FOUND});
}
{
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(10)});
env(tx, ter{tecOBJECT_NOT_FOUND});
}
{
auto tx = vault.withdraw(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(10)});
env(tx, ter{tecOBJECT_NOT_FOUND});
}
{
auto tx = vault.clawback(
{.issuer = issuer,
@@ -1524,6 +1557,8 @@ class Vault_test : public beast::unit_test::suite
.amount = asset(0)});
env(tx, ter{tecOBJECT_NOT_FOUND});
}
env(vault.del({.owner = owner, .id = keylet.key}));
});
testCase(
@@ -1707,6 +1742,444 @@ class Vault_test : public beast::unit_test::suite
}
}
void
testWithIOU()
{
auto testCase =
[&, this](
std::function<void(
Env & env,
Account const& owner,
Account const& issuer,
Account const& charlie,
std::function<AccountID(ripple::Keylet)> vaultAccount,
Vault& vault,
PrettyAsset const& asset,
std::function<MPTID(ripple::Keylet)> issuanceId,
std::function<PrettyAmount(ripple::Keylet)> vaultBalance)>
test) {
Env env{
*this, supported_amendments() | featureSingleAssetVault};
Account const owner{"owner"};
Account const issuer{"issuer"};
Account const charlie{"charlie"};
Vault vault{env};
env.fund(XRP(1000), issuer, owner, charlie);
env(fset(issuer, asfAllowTrustLineClawback));
env.close();
PrettyAsset const asset = issuer["IOU"];
env.trust(asset(1000), owner);
env(pay(issuer, owner, asset(200)));
env(rate(issuer, 1.25));
env.close();
auto const [tx, keylet] =
vault.create({.owner = owner, .asset = asset});
env(tx);
env.close();
auto const vaultAccount =
[&env](ripple::Keylet keylet) -> AccountID {
return env.le(keylet)->at(sfAccount);
};
auto const issuanceId = [&env](ripple::Keylet keylet) -> MPTID {
return env.le(keylet)->at(sfShareMPTID);
};
auto const vaultBalance = //
[&env, &vaultAccount, issue = asset.raw().get<Issue>()](
ripple::Keylet keylet) -> PrettyAmount {
auto const account = vaultAccount(keylet);
auto const sle = env.le(keylet::line(account, issue));
if (sle == nullptr)
return {
STAmount(issue, 0),
env.lookup(issue.account).name()};
auto amount = sle->getFieldAmount(sfBalance);
amount.setIssuer(issue.account);
if (account > issue.account)
amount.negate();
return {amount, env.lookup(issue.account).name()};
};
test(
env,
owner,
issuer,
charlie,
vaultAccount,
vault,
asset,
issuanceId,
vaultBalance);
};
testCase([&, this](
Env& env,
Account const& owner,
Account const& issuer,
Account const&,
auto vaultAccount,
Vault& vault,
PrettyAsset const& asset,
auto&&...) {
testcase("IOU cannot use different asset");
PrettyAsset const foo = issuer["FOO"];
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
env(tx);
env.close();
{
// Cannot create new trustline to a vault
auto tx = [&, account = vaultAccount(keylet)]() {
Json::Value jv;
jv[jss::Account] = issuer.human();
{
auto& ja = jv[jss::LimitAmount] =
foo(0).value().getJson(JsonOptions::none);
ja[jss::issuer] = toBase58(account);
}
jv[jss::TransactionType] = jss::TrustSet;
jv[jss::Flags] = tfSetFreeze;
return jv;
}();
env(tx, ter{tecNO_PERMISSION});
env.close();
}
{
auto tx = vault.deposit(
{.depositor = issuer, .id = keylet.key, .amount = foo(20)});
env(tx, ter{tecWRONG_ASSET});
env.close();
}
{
auto tx = vault.withdraw(
{.depositor = issuer, .id = keylet.key, .amount = foo(20)});
env(tx, ter{tecWRONG_ASSET});
env.close();
}
env(vault.del({.owner = owner, .id = keylet.key}));
env.close();
});
testCase([&, this](
Env& env,
Account const& owner,
Account const& issuer,
Account const& charlie,
auto vaultAccount,
Vault& vault,
PrettyAsset const& asset,
auto issuanceId,
auto) {
testcase("IOU frozen trust line to vault account");
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
env(tx);
env.close();
env(vault.deposit(
{.depositor = owner, .id = keylet.key, .amount = asset(100)}));
env.close();
Asset const share = Asset(issuanceId(keylet));
// Freeze the trustline to the vault
auto trustSet = [&, account = vaultAccount(keylet)]() {
Json::Value jv;
jv[jss::Account] = issuer.human();
{
auto& ja = jv[jss::LimitAmount] =
asset(0).value().getJson(JsonOptions::none);
ja[jss::issuer] = toBase58(account);
}
jv[jss::TransactionType] = jss::TrustSet;
jv[jss::Flags] = tfSetFreeze;
return jv;
}();
env(trustSet);
env.close();
{
// Note, the "frozen" state of the trust line to vault account
// is reported as "locked" state of the vault shares, because
// this state is attached to shares by means of the transitive
// isFrozen.
auto tx = vault.deposit(
{.depositor = owner,
.id = keylet.key,
.amount = asset(80)});
env(tx, ter{tecLOCKED});
}
{
auto tx = vault.withdraw(
{.depositor = owner,
.id = keylet.key,
.amount = asset(100)});
env(tx, ter{tecLOCKED});
// also when trying to withdraw to a 3rd party
tx[sfDestination] = charlie.human();
env(tx, ter{tecLOCKED});
env.close();
}
{
// Clawback works, even when locked
auto tx = vault.clawback(
{.issuer = issuer,
.id = keylet.key,
.holder = owner,
.amount = asset(50)});
env(tx);
env.close();
}
// Clear the frozen state
trustSet[jss::Flags] = tfClearFreeze;
env(trustSet);
env.close();
env(vault.withdraw(
{.depositor = owner, .id = keylet.key, .amount = share(50)}));
env(vault.del({.owner = owner, .id = keylet.key}));
env.close();
});
testCase([&, this](
Env& env,
Account const& owner,
Account const& issuer,
Account const& charlie,
auto,
Vault& vault,
PrettyAsset const& asset,
auto issuanceId,
auto vaultBalance) {
testcase("IOU transfer fees not applied");
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
env(tx);
env.close();
env(vault.deposit(
{.depositor = owner, .id = keylet.key, .amount = asset(100)}));
env.close();
auto const issue = asset.raw().get<Issue>();
Asset const share = Asset(issuanceId(keylet));
// transfer fees ignored on deposit
BEAST_EXPECT(env.balance(owner, issue) == asset(100));
BEAST_EXPECT(vaultBalance(keylet) == asset(100));
{
auto tx = vault.clawback(
{.issuer = issuer,
.id = keylet.key,
.holder = owner,
.amount = asset(50)});
env(tx);
env.close();
}
// transfer fees ignored on clawback
BEAST_EXPECT(env.balance(owner, issue) == asset(100));
BEAST_EXPECT(vaultBalance(keylet) == asset(50));
env(vault.withdraw(
{.depositor = owner, .id = keylet.key, .amount = share(20)}));
// transfer fees ignored on withdraw
BEAST_EXPECT(env.balance(owner, issue) == asset(120));
BEAST_EXPECT(vaultBalance(keylet) == asset(30));
{
auto tx = vault.withdraw(
{.depositor = owner,
.id = keylet.key,
.amount = share(30)});
tx[sfDestination] = charlie.human();
env(tx);
}
// transfer fees ignored on withdraw to 3rd party
BEAST_EXPECT(env.balance(owner, issue) == asset(120));
BEAST_EXPECT(env.balance(charlie, issue) == asset(30));
BEAST_EXPECT(vaultBalance(keylet) == asset(0));
env(vault.del({.owner = owner, .id = keylet.key}));
env.close();
});
testCase([&, this](
Env& env,
Account const& owner,
Account const& issuer,
Account const& charlie,
auto,
Vault& vault,
PrettyAsset const& asset,
auto&&...) {
testcase("IOU frozen trust line to depositor");
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
env(tx);
env.close();
env(vault.deposit(
{.depositor = owner, .id = keylet.key, .amount = asset(100)}));
env.close();
// Withdraw to 3rd party works
auto const withdrawToCharlie = [&](ripple::Keylet keylet) {
auto tx = vault.withdraw(
{.depositor = owner,
.id = keylet.key,
.amount = asset(10)});
tx[sfDestination] = charlie.human();
return tx;
}(keylet);
env(withdrawToCharlie);
// Freeze the owner
env(trust(issuer, asset(0), owner, tfSetFreeze));
env.close();
// Cannot withdraw
auto const withdraw = vault.withdraw(
{.depositor = owner, .id = keylet.key, .amount = asset(10)});
env(withdraw, ter{tecFROZEN});
// Cannot withdraw to 3rd party
env(withdrawToCharlie, ter{tecLOCKED});
env.close();
{
// Clawback still works
auto tx = vault.clawback(
{.issuer = issuer,
.id = keylet.key,
.holder = owner,
.amount = asset(0)});
env(tx);
env.close();
}
env(vault.del({.owner = owner, .id = keylet.key}));
env.close();
});
testCase([&, this](
Env& env,
Account const& owner,
Account const& issuer,
Account const& charlie,
auto,
Vault& vault,
PrettyAsset const& asset,
auto&&...) {
testcase("IOU frozen trust line to 3rd party");
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
env(tx);
env.close();
env(vault.deposit(
{.depositor = owner, .id = keylet.key, .amount = asset(100)}));
env.close();
// Withdraw to 3rd party works
auto const withdrawToCharlie = [&](ripple::Keylet keylet) {
auto tx = vault.withdraw(
{.depositor = owner,
.id = keylet.key,
.amount = asset(10)});
tx[sfDestination] = charlie.human();
return tx;
}(keylet);
env(withdrawToCharlie);
// Freeze the 3rd party
env(trust(issuer, asset(0), charlie, tfSetFreeze));
env.close();
// Can withdraw
auto const withdraw = vault.withdraw(
{.depositor = owner, .id = keylet.key, .amount = asset(10)});
env(withdraw);
env.close();
// Cannot withdraw to 3rd party
env(withdrawToCharlie, ter{tecFROZEN});
env.close();
env(vault.clawback(
{.issuer = issuer,
.id = keylet.key,
.holder = owner,
.amount = asset(0)}));
env.close();
env(vault.del({.owner = owner, .id = keylet.key}));
env.close();
});
testCase([&, this](
Env& env,
Account const& owner,
Account const& issuer,
Account const& charlie,
auto,
Vault& vault,
PrettyAsset const& asset,
auto&&...) {
testcase("IOU global freeze");
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
env(tx);
env.close();
env(vault.deposit(
{.depositor = owner, .id = keylet.key, .amount = asset(100)}));
env.close();
env(fset(issuer, asfGlobalFreeze));
env.close();
{
// Cannot withdraw
auto tx = vault.withdraw(
{.depositor = owner,
.id = keylet.key,
.amount = asset(10)});
env(tx, ter{tecFROZEN});
// Cannot withdraw to 3rd party
tx[sfDestination] = charlie.human();
env(tx, ter{tecFROZEN});
env.close();
}
// Clawback is permitted
env(vault.clawback(
{.issuer = issuer,
.id = keylet.key,
.holder = owner,
.amount = asset(0)}));
env.close();
env(vault.del({.owner = owner, .id = keylet.key}));
env.close();
});
}
void
testWithDomainCheck()
{
@@ -1895,7 +2368,27 @@ class Vault_test : public beast::unit_test::suite
tx = vault.withdraw(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(100)});
.amount = asset(50)});
env(tx);
tx = vault.clawback(
{.issuer = issuer,
.id = keylet.key,
.holder = depositor,
.amount = asset(0)});
env(tx);
tx = vault.clawback(
{.issuer = issuer,
.id = keylet.key,
.holder = owner,
.amount = asset(0)});
env(tx);
tx = vault.del({
.owner = owner,
.id = keylet.key,
});
env(tx);
}
}
@@ -2002,240 +2495,6 @@ class Vault_test : public beast::unit_test::suite
}
}
void
testWithIOU()
{
testcase("IOU");
Env env{*this, supported_amendments() | featureSingleAssetVault};
Account const owner{"owner"};
Account const issuer{"issuer"};
Account const charlie{"charlie"};
Vault vault{env};
env.fund(XRP(1000), issuer, owner, charlie);
env(fset(issuer, asfAllowTrustLineClawback));
env.close();
PrettyAsset const asset = issuer["IOU"];
env.trust(asset(1000), owner);
env(pay(issuer, owner, asset(200)));
env(rate(issuer, 1.25));
env.close();
auto const issue = asset.raw().get<Issue>();
auto const [tx, keylet] =
vault.create({.owner = owner, .asset = asset});
env(tx);
env.close();
auto const vaultAccount = [&env, keylet = keylet]() -> AccountID {
return env.le(keylet)->at(sfAccount);
}();
auto const issuanceId = [&env, keylet = keylet]() -> uint192 {
return env.le(keylet)->at(sfShareMPTID);
}();
auto const share = Asset(issuanceId);
auto const vaultBalance = //
[&, account = vaultAccount, this]() -> PrettyAmount {
auto const sle = env.le(keylet::line(account, issue));
BEAST_EXPECT(sle != nullptr);
auto amount = sle->getFieldAmount(sfBalance);
amount.setIssuer(issue.account);
if (account > issue.account)
amount.negate();
return {amount, env.lookup(issue.account).name()};
};
BEAST_EXPECT(vaultBalance() == asset(0));
{
testcase("IOU cannot update random trustline");
PrettyAsset const foo = issuer["FOO"];
auto tx = [&]() {
Json::Value jv;
jv[jss::Account] = issuer.human();
{
auto& ja = jv[jss::LimitAmount] =
foo(0).value().getJson(JsonOptions::none);
ja[jss::issuer] = toBase58(vaultAccount);
}
jv[jss::TransactionType] = jss::TrustSet;
jv[jss::Flags] = tfSetFreeze;
return jv;
}();
env(tx, ter{tecNO_PERMISSION});
env.close();
}
{
testcase("IOU cannot deposit when frozen");
env(vault.deposit(
{.depositor = owner, .id = keylet.key, .amount = asset(100)}));
env.close();
auto tx0 = [&]() {
Json::Value jv;
jv[jss::Account] = issuer.human();
{
auto& ja = jv[jss::LimitAmount] =
asset(0).value().getJson(JsonOptions::none);
ja[jss::issuer] = toBase58(vaultAccount);
}
jv[jss::TransactionType] = jss::TrustSet;
jv[jss::Flags] = tfSetFreeze;
return jv;
}();
env(tx0);
env.close();
// Note, the "frozen" state of the trust line is reported as
// "locked" state of the vault shares, because this state is
// attached to shares by means of the transitive isFrozen check.
env(vault.deposit(
{.depositor = owner,
.id = keylet.key,
.amount = asset(100)}),
ter{tecLOCKED});
env.close();
// Clawback works, even when locked
auto tx1 = vault.clawback(
{.issuer = issuer,
.id = keylet.key,
.holder = owner,
.amount = asset(0)});
env(tx1);
env.close();
tx0[jss::Flags] = tfClearFreeze;
env(tx0);
env.close();
env(pay(issuer, owner, asset(100)));
env.close();
}
{
testcase("IOU zero fee on deposit");
env(vault.deposit(
{.depositor = owner, .id = keylet.key, .amount = asset(100)}));
env.close();
BEAST_EXPECT(env.balance(owner, issue) == asset(100));
BEAST_EXPECT(vaultBalance() == asset(100));
}
{
testcase("IOU zero fee on withdraw");
env(vault.withdraw(
{.depositor = owner, .id = keylet.key, .amount = asset(60)}));
env.close();
BEAST_EXPECT(env.balance(owner, issue) == asset(160));
BEAST_EXPECT(vaultBalance() == asset(40));
}
{
testcase("IOU zero fee on withdraw for 3rd party");
auto tx = vault.withdraw(
{.depositor = owner, .id = keylet.key, .amount = asset(20)});
tx[sfDestination] = charlie.human();
env(tx);
env.close();
BEAST_EXPECT(env.balance(owner, issue) == asset(160));
BEAST_EXPECT(env.balance(charlie, issue) == asset(20));
BEAST_EXPECT(vaultBalance() == asset(20));
}
{
testcase("IOU froze trust line, cannot withdraw to 3rd party");
auto tx1 = test::jtx::pay(owner, charlie, STAmount{share, 10});
env(tx1, ter{tecNO_AUTH});
auto tx2 = test::jtx::pay(charlie, owner, STAmount{share, 10});
env(tx2, ter{tecNO_AUTH});
env.close();
env(trust(issuer, asset(0), owner, tfSetFreeze));
env.close();
// Since the vault is public, Charlie can simply create MPToken
// to gain authorization to receive its shares.
Json::Value jv;
jv[sfAccount] = charlie.human();
jv[sfTransactionType] = jss::MPTokenAuthorize;
jv[sfMPTokenIssuanceID] = to_string(issuanceId);
env(jv);
env.close();
auto tx = vault.withdraw(
{.depositor = owner, .id = keylet.key, .amount = asset(10)});
env(tx, ter{tecFROZEN});
tx[sfDestination] = charlie.human();
env(tx, ter{tecLOCKED}); // owner transitively locked via MPToken
env(tx1, ter{tecLOCKED});
env(tx2, ter{tecLOCKED});
env.close();
BEAST_EXPECT(env.balance(charlie, issue) == asset(20));
}
{
testcase("IOU unfroze trust line, can withdraw or pay");
env(trust(issuer, asset(500), owner, tfClearFreeze));
env.close();
auto tx = vault.withdraw(
{.depositor = owner, .id = keylet.key, .amount = asset(1)});
env(tx);
tx[sfDestination] = charlie.human();
env(tx);
auto tx1 = test::jtx::pay(owner, charlie, STAmount{share, 1});
env(tx1);
auto tx2 = test::jtx::pay(charlie, owner, STAmount{share, 1});
env(tx2);
}
{
testcase("IOU global freeze");
env(fset(issuer, asfGlobalFreeze));
env.close();
auto tx = vault.withdraw(
{.depositor = owner, .id = keylet.key, .amount = asset(1)});
env(tx, ter{tecFROZEN});
tx[sfDestination] = issuer.human();
env(tx, ter{tecFROZEN});
auto tx1 = test::jtx::pay(owner, charlie, STAmount{share, 10});
env(tx1, ter{tecLOCKED});
auto tx2 = test::jtx::pay(charlie, owner, STAmount{share, 10});
env(tx2, ter{tecLOCKED});
env.close();
// Clawback is permitted
auto tx3 = vault.clawback(
{.issuer = issuer,
.id = keylet.key,
.holder = owner,
.amount = asset(0)});
env(tx3);
env.close();
// Can delete an empty vault, even under asset is under global
// lock
auto tx4 = vault.del({.owner = owner, .id = keylet.key});
env(tx4);
env.close();
}
}
void
testFailedPseudoAccount()
{