1510 bool const fixEnabled = features[fixCleanup3_1_3];
1518 {{
"permissioned domain with no rules."}},
1524 fixEnabled ? failTers : badTers);
1526 testcase <<
"PermissionedDomain 2";
1531 {{
"permissioned domain bad credentials size " +
std::to_string(kTooBig)}},
1537 fixEnabled ? failTers : badTers);
1539 testcase <<
"PermissionedDomain 3";
1542 {{
"permissioned domain credentials aren't sorted"}},
1550 cred.setAccountID(sfIssuer, a2);
1552 cred.setFieldVL(sfCredentialType,
Slice(credType.c_str(), credType.size()));
1555 slePd->setFieldArray(sfAcceptedCredentials,
credentials);
1556 ac.view().update(slePd);
1561 fixEnabled ? failTers : badTers);
1563 testcase <<
"PermissionedDomain 4";
1566 {{
"permissioned domain credentials aren't unique"}},
1574 cred.setAccountID(sfIssuer, a2);
1575 cred.setFieldVL(sfCredentialType,
Slice(
"cred_type", 9));
1578 slePd->setFieldArray(sfAcceptedCredentials,
credentials);
1579 ac.view().update(slePd);
1584 fixEnabled ? failTers : badTers);
1586 testcase <<
"PermissionedDomain Set 1";
1589 {{
"permissioned domain with no rules."}},
1597 slePd->setFieldArray(sfAcceptedCredentials,
credentials);
1598 ac.view().update(slePd);
1605 fixEnabled ? failTers : badTers);
1607 testcase <<
"PermissionedDomain Set 2";
1610 {{
"permissioned domain bad credentials size " +
std::to_string(kTooBig)}},
1622 cred.setAccountID(sfIssuer, a2);
1624 cred.setFieldVL(sfCredentialType,
Slice(credType.c_str(), credType.size()));
1628 slePd->setFieldArray(sfAcceptedCredentials,
credentials);
1629 ac.view().update(slePd);
1636 fixEnabled ? failTers : badTers);
1638 testcase <<
"PermissionedDomain Set 3";
1641 {{
"permissioned domain credentials aren't sorted"}},
1652 cred.setAccountID(sfIssuer, a2);
1654 cred.setFieldVL(sfCredentialType,
Slice(credType.c_str(), credType.size()));
1658 slePd->setFieldArray(sfAcceptedCredentials,
credentials);
1659 ac.view().update(slePd);
1666 fixEnabled ? failTers : badTers);
1668 testcase <<
"PermissionedDomain Set 4";
1671 {{
"permissioned domain credentials aren't unique"}},
1682 cred.setAccountID(sfIssuer, a2);
1683 cred.setFieldVL(sfCredentialType,
Slice(
"cred_type", 9));
1686 slePd->setFieldArray(sfAcceptedCredentials,
credentials);
1687 ac.view().update(slePd);
1694 fixEnabled ? failTers : badTers);
1699 {
"transaction affected more than 1 permissioned domain entry."}};
1703 {
"domain object modified, but not deleted by "}};
1706 {
"domain object(s) affected by an unauthorized transaction."}};
1709 testcase <<
"PermissionedDomain set 2 domains ";
1712 fixEnabled ? badMoreThan1 : emptyV,
1720 fixEnabled ? failTers : goodTers);
1724 testcase <<
"PermissionedDomain del 2 domains";
1726 Env env1(*
this, features);
1741 fixEnabled ? badMoreThan1 : emptyV,
1743 auto sle1 = ac.
view().
peek({ltPERMISSIONED_DOMAIN, pd1});
1744 auto sle2 = ac.
view().
peek({ltPERMISSIONED_DOMAIN, pd2});
1751 fixEnabled ? failTers : goodTers);
1755 testcase <<
"PermissionedDomain set 0 domains ";
1758 fixEnabled ? badNoDomains : emptyV,
1762 fixEnabled ? badTers : goodTers);
1766 testcase <<
"PermissionedDomain del 0 domains";
1768 Env env1(*
this, features);
1783 fixEnabled ? badNoDomains : emptyV,
1787 fixEnabled ? badTers : goodTers);
1791 testcase <<
"PermissionedDomain set, delete domain";
1793 Env env1(*
this, features);
1807 fixEnabled ? badDeleted : emptyV,
1809 auto sle1 = ac.
view().
peek({ltPERMISSIONED_DOMAIN, pd1});
1815 fixEnabled ? failTers : goodTers);
1819 testcase <<
"PermissionedDomain del, create domain ";
1822 fixEnabled ? badNotDeleted : emptyV,
1829 fixEnabled ? failTers : goodTers);
1833 testcase <<
"PermissionedDomain invalid tx";
1836 fixEnabled ? badTx : emptyV,
2833 struct AccountAmount
2856 auto const mptIssuanceID = (*sleVault)[sfShareMPTID];
2862 if (args.lossUnrealized)
2863 (*sleVault)[sfLossUnrealized] = *args.lossUnrealized;
2864 if (args.assetsMaximum)
2865 (*sleVault)[sfAssetsMaximum] = *args.assetsMaximum;
2868 if (args.assetsTotal)
2869 (*sleVault)[sfAssetsTotal] = *(*sleVault)[sfAssetsTotal] + *args.assetsTotal;
2870 if (args.assetsAvailable)
2872 (*sleVault)[sfAssetsAvailable] =
2873 *(*sleVault)[sfAssetsAvailable] + *args.assetsAvailable;
2877 if (args.sharesTotal)
2879 (*sleShares)[sfOutstandingAmount] =
2880 *(*sleShares)[sfOutstandingAmount] + *args.sharesTotal;
2884 auto const assets = *(*sleVault)[sfAsset];
2885 auto const pseudoId = *(*sleVault)[sfAccount];
2886 if (args.vaultAssets)
2888 if (assets.native())
2891 if (!slePseudoAccount)
2893 (*slePseudoAccount)[sfBalance] =
2894 *(*slePseudoAccount)[sfBalance] + *args.vaultAssets;
2895 ac.
update(slePseudoAccount);
2899 auto const mptId = assets.get<
MPTIssue>().getMptID();
2903 (*sleMPToken)[sfMPTAmount] = *(*sleMPToken)[sfMPTAmount] + *args.vaultAssets;
2912 if (args.accountAssets)
2914 auto const& pair = *args.accountAssets;
2915 if (assets.native())
2920 (*sleAccount)[sfBalance] = *(*sleAccount)[sfBalance] + pair.amount;
2925 auto const mptID = assets.get<
MPTIssue>().getMptID();
2929 (*sleMPToken)[sfMPTAmount] = *(*sleMPToken)[sfMPTAmount] + pair.amount;
2938 if (args.accountShares)
2940 auto const& pair = *args.accountShares;
2944 (*sleMPToken)[sfMPTAmount] = *(*sleMPToken)[sfMPTAmount] + pair.amount;
2950 static constexpr auto kArgs = [](
AccountID id,
int adjustment,
auto fn) -> Adjustments {
2951 Adjustments sample = {
2952 .assetsTotal = adjustment,
2953 .assetsAvailable = adjustment,
2954 .lossUnrealized = 0,
2955 .sharesTotal = adjustment,
2956 .vaultAssets = adjustment,
2958 AccountAmount{.account = id, .amount = -adjustment},
2960 AccountAmount{.account = id, .amount = adjustment}};
2967 auto const precloseXrp = [&](
Account const& a1,
Account const& a2,
Env& env) ->
bool {
2968 env.fund(
XRP(1000), a3, a4);
2969 Vault const vault{env};
2970 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
2972 env(vault.deposit({.depositor = a1, .id = keylet.key, .amount = XRP(10)}));
2973 env(vault.deposit({.depositor = a2, .id = keylet.key, .amount = XRP(10)}));
2974 env(vault.deposit({.depositor = a3, .id = keylet.key, .amount = XRP(10)}));
2978 testcase <<
"Vault general checks";
2980 {
"vault deletion succeeded without deleting a vault"},
2986 ac.view().
update(sleVault);
2993 Vault const vault{env};
2994 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
3000 {
"vault updated by a wrong transaction type",
3001 "deleted Vault without deleting its pseudo-account"},
3007 ac.view().
erase(sleVault);
3014 Vault const vault{env};
3015 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
3021 {
"vault updated by a wrong transaction type"},
3027 ac.view().
update(sleVault);
3034 Vault const vault{env};
3035 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
3041 {
"vault updated by a wrong transaction type"},
3043 auto const sequence = ac.view().
seq();
3046 auto const vaultPage = ac.view().
dirInsert(
3048 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
3049 sleVault->setAccountID(sfAccount, a1.
id());
3050 ac.view().
insert(sleVault);
3058 {
"vault deleted by a wrong transaction type",
3059 "deleted Vault without deleting its pseudo-account"},
3065 ac.view().
erase(sleVault);
3072 Vault const vault{env};
3073 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
3079 {
"vault operation updated more than single vault",
3080 "deleted Vault without deleting its pseudo-account"},
3088 ac.view().
erase(sleVault);
3096 ac.view().
erase(sleVault);
3104 Vault const vault{env};
3106 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
3110 auto [tx, _] = vault.create({.owner = a2, .asset =
xrpIssue()});
3117 {
"vault operation updated more than single vault"},
3119 auto const sequence = ac.view().
seq();
3120 auto const insertVault = [&](
Account const a) {
3123 auto const vaultPage = ac.view().
dirInsert(
3125 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
3126 sleVault->setAccountID(sfAccount, a.id());
3127 ac.view().
insert(sleVault);
3138 {
"deleted vault must also delete shares",
3139 "deleted Vault without deleting its pseudo-account"},
3145 ac.view().
erase(sleVault);
3152 Vault const vault{env};
3153 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
3159 {
"deleted vault must have no shares outstanding",
3160 "deleted vault must have no assets outstanding",
3161 "deleted vault must have no assets available"},
3170 ac.view().
erase(sleVault);
3171 ac.view().
erase(sleShares);
3178 Vault const vault{env};
3179 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
3181 env(vault.deposit({.depositor = a1, .id = keylet.key, .amount = XRP(10)}));
3186 {
"vault operation succeeded without modifying a vault"},
3197 sleShares->setFieldH256(sfDomainID,
uint256(13));
3198 ac.view().
update(sleShares);
3208 {
"vault operation succeeded without modifying a vault"},
3214 Vault const vault{env};
3215 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
3221 {
"vault operation succeeded without modifying a vault"},
3227 Vault const vault{env};
3228 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
3234 {
"vault operation succeeded without modifying a vault"},
3240 Vault const vault{env};
3241 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
3247 {
"vault operation succeeded without modifying a vault"},
3253 Vault const vault{env};
3254 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
3260 {
"vault operation succeeded without modifying a vault"},
3266 Vault const vault{env};
3267 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
3273 {
"updated vault must have shares"},
3279 (*sleVault)[sfAssetsMaximum] = 200;
3280 ac.view().
update(sleVault);
3285 ac.view().
erase(sleShares);
3292 Vault const vault{env};
3293 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
3299 {
"vault operation succeeded without updating shares",
3300 "assets available must not be greater than assets outstanding"},
3306 (*sleVault)[sfAssetsTotal] = 9;
3307 ac.view().
update(sleVault);
3314 Vault const vault{env};
3315 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
3317 env(vault.deposit({.depositor = a1, .id = keylet.key, .amount = XRP(10)}));
3322 {
"set must not change assets outstanding",
3323 "set must not change assets available",
3324 "set must not change shares outstanding",
3325 "set must not change vault balance",
3326 "assets available must not be negative",
3327 "assets available must not be greater than assets outstanding",
3328 "assets outstanding must not be negative"},
3335 if (!slePseudoAccount)
3337 (*slePseudoAccount)[sfBalance] = *(*slePseudoAccount)[sfBalance] - 10;
3338 ac.view().
update(slePseudoAccount);
3344 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
3347 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {
3348 sample.assetsAvailable = (kDropsPerXrp * -100).value();
3349 sample.assetsTotal = (kDropsPerXrp * -200).value();
3350 sample.sharesTotal = -1;
3360 {
"violation of vault immutable data"},
3367 ac.view().
update(sleVault);
3376 {
"violation of vault immutable data"},
3382 sleVault->setAccountID(sfAccount, a2.id());
3383 ac.view().
update(sleVault);
3392 {
"violation of vault immutable data"},
3398 (*sleVault)[sfShareMPTID] =
MPTID(42);
3399 ac.view().
update(sleVault);
3408 {
"vault transaction must not change loss unrealized",
3409 "set must not change assets outstanding"},
3412 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {
3413 sample.lossUnrealized = 13;
3414 sample.assetsTotal = 20;
3424 {
"loss unrealized must not exceed the difference "
3425 "between assets outstanding and available",
3426 "vault transaction must not change loss unrealized"},
3429 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 100, [&](Adjustments& sample) {
3430 sample.lossUnrealized = 13;
3435 ttVAULT_DEPOSIT, [](
STObject& tx) { tx.setFieldAmount(sfAmount,
XRPAmount(200)); }},
3445 {
"loss unrealized must not be negative"},
3448 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {
3449 sample.lossUnrealized = -1;
3465 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {
3466 sample.lossUnrealized = -1;
3476 {
"set assets outstanding must not exceed assets maximum"},
3479 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {
3480 sample.assetsMaximum = 1;
3490 {
"assets maximum must not be negative"},
3493 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {
3494 sample.assetsMaximum = -1;
3504 {
"set must not change shares outstanding",
3505 "updated zero sized vault must have no assets outstanding",
3506 "updated zero sized vault must have no assets available"},
3512 ac.view().
update(sleVault);
3516 (*sleShares)[sfOutstandingAmount] = 0;
3517 ac.view().
update(sleShares);
3527 {
"updated shares must not exceed maximum"},
3536 (*sleShares)[sfMaximumAmount] = 10;
3537 ac.view().
update(sleShares);
3539 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [](Adjustments&) {}));
3548 {
"updated shares must not exceed maximum"},
3551 kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [](Adjustments&) {}));
3560 ac.view().
update(sleShares);
3572 "created vault must be empty",
3573 "updated zero sized vault must have no assets outstanding",
3574 "create operation must not have updated a vault",
3581 (*sleVault)[sfAssetsTotal] = 9;
3582 ac.view().
update(sleVault);
3589 Vault const vault{env};
3590 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
3597 "created vault must be empty",
3598 "updated zero sized vault must have no assets available",
3599 "assets available must not be greater than assets outstanding",
3600 "create operation must not have updated a vault",
3607 (*sleVault)[sfAssetsAvailable] = 9;
3608 ac.view().
update(sleVault);
3615 Vault const vault{env};
3616 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
3623 "created vault must be empty",
3624 "loss unrealized must not exceed the difference between assets "
3625 "outstanding and available",
3626 "vault transaction must not change loss unrealized",
3627 "create operation must not have updated a vault",
3634 (*sleVault)[sfLossUnrealized] = 1;
3635 ac.view().
update(sleVault);
3642 Vault const vault{env};
3643 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
3650 "created vault must be empty",
3651 "create operation must not have updated a vault",
3661 ac.view().
update(sleVault);
3662 (*sleShares)[sfOutstandingAmount] = 9;
3663 ac.view().
update(sleShares);
3670 Vault const vault{env};
3671 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
3678 "assets maximum must not be negative",
3679 "create operation must not have updated a vault",
3686 (*sleVault)[sfAssetsMaximum] =
Number(-1);
3687 ac.view().
update(sleVault);
3694 Vault const vault{env};
3695 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
3701 {
"create operation must not have updated a vault",
3702 "shares issuer and vault pseudo-account must be the same",
3703 "shares issuer must be a pseudo-account",
3704 "shares issuer pseudo-account must point back to the vault"},
3713 ac.view().
update(sleVault);
3714 (*sleShares)[sfIssuer] = a1.
id();
3715 ac.view().
update(sleShares);
3722 Vault const vault{env};
3723 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
3729 {
"vault created by a wrong transaction type",
"account root created illegally"},
3734 auto const sequence = ac.view().
seq();
3737 auto const vaultPage = ac.view().
dirInsert(
3739 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
3744 sleAccount->setAccountID(sfAccount, pseudoId);
3745 sleAccount->setFieldAmount(sfBalance,
STAmount{});
3750 sleAccount->setFieldU32(sfSequence, seqno);
3751 sleAccount->setFieldU32(
3752 sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
3753 sleAccount->setFieldH256(sfVaultID, vaultKeylet.key);
3754 ac.view().
insert(sleAccount);
3756 auto const sharesMptId =
makeMptID(sequence, pseudoId);
3759 auto const sharesPage = ac.view().
dirInsert(
3761 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
3763 sleShares->at(sfFlags) = 0;
3764 sleShares->at(sfIssuer) = pseudoId;
3765 sleShares->at(sfOutstandingAmount) = 0;
3766 sleShares->at(sfSequence) = sequence;
3768 sleVault->at(sfAccount) = pseudoId;
3769 sleVault->at(sfFlags) = 0;
3770 sleVault->at(sfSequence) = sequence;
3771 sleVault->at(sfOwner) = a1.
id();
3772 sleVault->at(sfAssetsTotal) =
Number(0);
3773 sleVault->at(sfAssetsAvailable) =
Number(0);
3774 sleVault->at(sfLossUnrealized) =
Number(0);
3775 sleVault->at(sfShareMPTID) = sharesMptId;
3778 ac.view().
insert(sleVault);
3779 ac.view().
insert(sleShares);
3787 {
"shares issuer and vault pseudo-account must be the same",
3788 "shares issuer pseudo-account must point back to the vault"},
3790 auto const sequence = ac.view().
seq();
3793 auto const vaultPage = ac.view().
dirInsert(
3795 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
3800 sleAccount->setAccountID(sfAccount, pseudoId);
3801 sleAccount->setFieldAmount(sfBalance,
STAmount{});
3806 sleAccount->setFieldU32(sfSequence, seqno);
3807 sleAccount->setFieldU32(
3808 sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
3811 sleAccount->setFieldH256(sfVaultID,
uint256(42));
3812 ac.view().
insert(sleAccount);
3814 auto const sharesMptId =
makeMptID(sequence, pseudoId);
3817 auto const sharesPage = ac.view().
dirInsert(
3819 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
3821 sleShares->at(sfFlags) = 0;
3822 sleShares->at(sfIssuer) = pseudoId;
3823 sleShares->at(sfOutstandingAmount) = 0;
3824 sleShares->at(sfSequence) = sequence;
3828 sleVault->at(sfAccount) = a2.id();
3829 sleVault->at(sfFlags) = 0;
3830 sleVault->at(sfSequence) = sequence;
3831 sleVault->at(sfOwner) = a1.
id();
3832 sleVault->at(sfAssetsTotal) =
Number(0);
3833 sleVault->at(sfAssetsAvailable) =
Number(0);
3834 sleVault->at(sfLossUnrealized) =
Number(0);
3835 sleVault->at(sfShareMPTID) = sharesMptId;
3838 ac.view().
insert(sleVault);
3839 ac.view().
insert(sleShares);
3847 {
"shares issuer and vault pseudo-account must be the same",
"shares issuer must exist"},
3849 auto const sequence = ac.view().
seq();
3852 auto const vaultPage = ac.view().
dirInsert(
3854 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
3856 auto const sharesMptId =
makeMptID(sequence, a2.id());
3859 auto const sharesPage = ac.view().
dirInsert(
3861 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
3863 sleShares->at(sfFlags) = 0;
3865 sleShares->at(sfIssuer) =
AccountID(42);
3866 sleShares->at(sfOutstandingAmount) = 0;
3867 sleShares->at(sfSequence) = sequence;
3869 sleVault->at(sfAccount) = a2.id();
3870 sleVault->at(sfFlags) = 0;
3871 sleVault->at(sfSequence) = sequence;
3872 sleVault->at(sfOwner) = a1.
id();
3873 sleVault->at(sfAssetsTotal) =
Number(0);
3874 sleVault->at(sfAssetsAvailable) =
Number(0);
3875 sleVault->at(sfLossUnrealized) =
Number(0);
3876 sleVault->at(sfShareMPTID) = sharesMptId;
3879 ac.view().
insert(sleVault);
3880 ac.view().
insert(sleShares);
3889 {
"deposit must change vault balance"},
3892 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [](Adjustments& sample) {
3893 sample.vaultAssets.reset();
3902 {
"deposit assets outstanding must not exceed assets maximum"},
3905 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 200, [&](Adjustments& sample) {
3906 sample.assetsMaximum = 1;
3911 ttVAULT_DEPOSIT, [](
STObject& tx) { tx.setFieldAmount(sfAmount,
XRPAmount(200)); }},
3921 {
"deposit must increase vault balance",
"deposit must change depositor balance"},
3929 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
3932 return kAdjust(ac.view(),
keylet, kArgs(a3.
id(), -10, [&](Adjustments& sample) {
3933 sample.accountAssets->amount = -100;
3941 tx[sfAccount] = a3.
id();
3947 {
"deposit must increase vault balance",
3948 "deposit must decrease depositor balance",
3949 "deposit must change vault and depositor balance by equal amount",
3950 "deposit and assets outstanding must add up",
3951 "deposit and assets available must add up"},
3959 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 10;
3962 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [&](Adjustments& sample) {
3963 sample.vaultAssets = -20;
3964 sample.accountAssets->amount = 10;
3974 {
"deposit must change depositor balance"},
3982 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 10;
3985 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [&](Adjustments& sample) {
3986 sample.accountAssets->amount = 0;
3996 {
"deposit must change depositor shares"},
3999 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [&](Adjustments& sample) {
4000 sample.accountShares.reset();
4010 {
"deposit must change vault shares"},
4014 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [](Adjustments& sample) {
4015 sample.sharesTotal = 0;
4025 {
"deposit must increase depositor shares",
4026 "deposit must change depositor and vault shares by equal amount",
4027 "deposit must not change vault balance by more than deposited "
4031 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [&](Adjustments& sample) {
4032 sample.accountShares->amount = -5;
4033 sample.sharesTotal = -10;
4043 {
"deposit and assets outstanding must add up"},
4046 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 2000;
4050 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [&](Adjustments& sample) {
4051 sample.assetsTotal = 11;
4059 tx[sfDelegate] = a3.
id();
4067 {
"deposit and assets outstanding must add up",
4068 "deposit and assets available must add up"},
4071 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [&](Adjustments& sample) {
4072 sample.assetsTotal = 7;
4073 sample.assetsAvailable = 7;
4084 {
"withdrawal must change vault balance"},
4087 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [](Adjustments& sample) {
4088 sample.vaultAssets.reset();
4101 {
"withdrawal must change one destination balance"},
4109 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
4112 return kAdjust(ac.view(),
keylet, kArgs(a3.
id(), -10, [&](Adjustments& sample) {
4113 sample.accountAssets->amount = -100;
4121 tx[sfAccount] = a3.
id();
4130 "withdrawal must change vault and destination balance by equal amount",
4131 "withdrawal must decrease vault balance",
4132 "withdrawal must increase destination balance",
4133 "withdrawal and assets outstanding must add up",
4134 "withdrawal and assets available must add up",
4143 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 10;
4146 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
4147 sample.vaultAssets = 10;
4148 sample.accountAssets->amount = -20;
4158 {
"withdrawal must change one destination balance"},
4161 if (!kAdjust(ac.view(),
keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
4162 *sample.vaultAssets -= 5;
4168 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 5;
4173 STTx{ttVAULT_WITHDRAW, [&](
STObject& tx) { tx.setAccountID(sfDestination, a3.
id()); }},
4179 {
"withdrawal must change depositor shares"},
4182 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
4183 sample.accountShares.reset();
4193 {
"withdrawal must change vault shares"},
4196 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), -10, [](Adjustments& sample) {
4197 sample.sharesTotal = 0;
4207 {
"withdrawal must decrease depositor shares",
4208 "withdrawal must change depositor and vault shares by equal "
4212 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
4213 sample.accountShares->amount = 5;
4214 sample.sharesTotal = 10;
4224 {
"withdrawal and assets outstanding must add up",
4225 "withdrawal and assets available must add up"},
4228 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
4229 sample.assetsTotal = -15;
4230 sample.assetsAvailable = -15;
4240 {
"withdrawal and assets outstanding must add up"},
4243 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 2000;
4247 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
4248 sample.assetsTotal = -7;
4256 tx[sfDelegate] = a3.
id();
4263 auto const precloseMpt = [&](
Account const& a1,
Account const& a2,
Env& env) ->
bool {
4264 env.fund(
XRP(1000), a3, a4);
4269 jv[sfAccount] = a3.
human();
4270 jv[sfTransactionType] = jss::MPTokenIssuanceCreate;
4271 jv[sfFlags] = tfMPTCanTransfer;
4276 auto const mptID =
makeMptID(env.seq(a3) - 1, a3);
4281 jv[sfAccount] = a1.
human();
4282 jv[sfTransactionType] = jss::MPTokenAuthorize;
4283 jv[sfMPTokenIssuanceID] =
to_string(mptID);
4285 jv[sfAccount] = a2.human();
4287 jv[sfAccount] = a4.
human();
4294 env(
pay(a3, a1, asset(1000)));
4295 env(
pay(a3, a2, asset(1000)));
4296 env(
pay(a3, a4, asset(1000)));
4300 Vault const vault{env};
4301 auto [tx,
keylet] = vault.create({.owner = a1, .asset = asset});
4303 env(vault.deposit({.depositor = a1, .id = keylet.key, .amount = asset(10)}));
4304 env(vault.deposit({.depositor = a2, .id = keylet.key, .amount = asset(10)}));
4305 env(vault.deposit({.depositor = a4, .id = keylet.key, .amount = asset(10)}));
4310 {
"withdrawal must decrease depositor shares",
4311 "withdrawal must change depositor and vault shares by equal "
4316 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
4317 sample.accountShares->amount = 5;
4321 STTx{ttVAULT_WITHDRAW, [&](
STObject& tx) { tx[sfAccount] = a3.
id(); }},
4328 {
"clawback must change vault balance"},
4332 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), -1, [&](Adjustments& sample) {
4333 sample.vaultAssets.reset();
4337 STTx{ttVAULT_CLAWBACK, [&](
STObject& tx) { tx[sfAccount] = a3.
id(); }},
4343 {
"clawback may only be performed by the asset issuer"},
4346 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {}));
4355 {
"clawback may only be performed by the asset issuer"},
4359 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {}));
4362 STTx{ttVAULT_CLAWBACK, [&](
STObject& tx) { tx[sfAccount] = a4.
id(); }},
4367 {
"clawback must decrease vault balance",
4368 "clawback must decrease holder shares",
4369 "clawback must change vault shares"},
4373 return kAdjust(ac.view(),
keylet, kArgs(a4.
id(), 10, [&](Adjustments& sample) {
4374 sample.sharesTotal = 0;
4381 tx[sfAccount] = a3.
id();
4382 tx[sfHolder] = a4.
id();
4388 {
"clawback must change holder shares"},
4392 return kAdjust(ac.view(),
keylet, kArgs(a4.
id(), -10, [&](Adjustments& sample) {
4393 sample.accountShares.reset();
4400 tx[sfAccount] = a3.
id();
4401 tx[sfHolder] = a4.
id();
4407 {
"clawback must change holder and vault shares by equal amount",
4408 "clawback and assets outstanding must add up",
4409 "clawback and assets available must add up"},
4413 return kAdjust(ac.view(),
keylet, kArgs(a4.
id(), -10, [&](Adjustments& sample) {
4414 sample.accountShares->amount = -8;
4415 sample.assetsTotal = -7;
4416 sample.assetsAvailable = -7;
4423 tx[sfAccount] = a3.
id();
4424 tx[sfHolder] = a4.
id();
4447 auto const precloseClosedEnded = [&](
std::int32_t advanceBySub,
bool doDeposit) {
4448 return [&, advanceBySub, doDeposit](
4450 env.fund(
XRP(1000), a3, a4);
4451 auto const sub = env.now().time_since_epoch().count() + 60;
4453 Vault const vault{env};
4454 auto [tx,
keylet] = vault.create(
4457 .vaultKind = closedEnded,
4458 .subscriptionDate = sub,
4459 .redemptionDate = red});
4461 closedEndedKeylet =
keylet;
4464 env(vault.deposit({.depositor = a1, .id = keylet.key, .amount = XRP(10)}));
4465 env(vault.deposit({.depositor = a2, .id = keylet.key, .amount = XRP(10)}));
4466 env(vault.deposit({.depositor = a3, .id = keylet.key, .amount = XRP(10)}));
4468 if (advanceBySub >= 0)
4469 env.close(tp{d{sub + advanceBySub}});
4477 auto const insertBareClosedEndedVault =
4483 auto const sequence = ac.view().
seq();
4486 auto const vaultPage = ac.view().
dirInsert(
4490 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
4494 sleAccount->setAccountID(sfAccount, pseudoId);
4495 sleAccount->setFieldAmount(sfBalance,
STAmount{});
4496 sleAccount->setFieldU32(sfSequence, 0);
4497 sleAccount->setFieldU32(sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
4498 sleAccount->setFieldH256(sfVaultID, vaultKeylet.key);
4499 ac.view().
insert(sleAccount);
4501 auto const sharesMptId =
makeMptID(sequence, pseudoId);
4504 auto const sharesPage = ac.view().
dirInsert(
4508 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
4509 sleShares->at(sfFlags) = 0;
4510 sleShares->at(sfIssuer) = pseudoId;
4511 sleShares->at(sfOutstandingAmount) = 0;
4512 sleShares->at(sfSequence) = sequence;
4514 sleVault->at(sfAccount) = pseudoId;
4515 sleVault->at(sfFlags) = 0;
4516 sleVault->at(sfSequence) = sequence;
4517 sleVault->at(sfOwner) = owner.id();
4519 sleVault->at(sfAssetsTotal) =
Number(0);
4520 sleVault->at(sfAssetsAvailable) =
Number(0);
4521 sleVault->at(sfLossUnrealized) =
Number(0);
4522 sleVault->at(sfShareMPTID) = sharesMptId;
4524 sleVault->at(sfVaultKind) = closedEnded;
4525 if (subscriptionDate)
4526 sleVault->at(sfSubscriptionDate) = *subscriptionDate;
4528 sleVault->at(sfRedemptionDate) = *redemptionDate;
4530 ac.view().
insert(sleVault);
4531 ac.view().
insert(sleShares);
4535 testcase <<
"Vault create closed-ended";
4539 {
"closed-ended vault must have SubscriptionDate and RedemptionDate"},
4541 return insertBareClosedEndedVault(ac, a1, std::nullopt, std::nullopt);
4550 {
"closed-ended vault RedemptionDate - SubscriptionDate must be "
4551 "within [MIN_INVESTMENT_PERIOD, MAX_INVESTMENT_PERIOD)"},
4555 return insertBareClosedEndedVault(ac, a1, sub, red);
4564 {
"closed-ended vault RedemptionDate - SubscriptionDate must be "
4565 "within [MIN_INVESTMENT_PERIOD, MAX_INVESTMENT_PERIOD)"},
4569 return insertBareClosedEndedVault(ac, a1, sub, red);
4577 {
"closed-ended vault RedemptionDate - SubscriptionDate must be "
4578 "within [MIN_INVESTMENT_PERIOD, MAX_INVESTMENT_PERIOD)"},
4582 return insertBareClosedEndedVault(ac, a1, sub, red);
4588 testcase <<
"Vault deposit closed-ended";
4593 {
"deposit only allowed in Subscription or NoPhase"},
4596 ac.view(), closedEndedKeylet, kArgs(a2.id(), 10, [](Adjustments&) {}));
4601 precloseClosedEnded(1,
true),
4604 testcase <<
"Vault withdrawal closed-ended";
4608 {
"withdrawal not allowed during Investment phase"},
4611 ac.view(), closedEndedKeylet, kArgs(a2.id(), -10, [](Adjustments&) {}));
4616 precloseClosedEnded(1,
true),
4624 {
"loan origination only allowed in Investment phase"},
4626 auto sleVault = ac.view().
peek(closedEndedKeylet);
4629 ac.view().
update(sleVault);
4635 precloseClosedEnded(-1,
false));
4637 testcase <<
"Vault loan set - closed-ended final payment past "
4647 {
"closed-ended loan final payment must precede RedemptionDate"},
4652 auto sleVault = ac.view().
peek(closedEndedKeylet);
4655 ac.view().
update(sleVault);
4659 auto sleBroker = ac.view().
peek(closedEndedBrokerKeylet);
4662 std::uint32_t const loanSeq = sleBroker->at(sfLoanSequence);
4669 sleLoan->at(sfLoanBrokerID) = closedEndedBrokerKeylet.
key;
4670 sleLoan->at(sfLoanSequence) = loanSeq;
4671 sleLoan->at(sfBorrower) = a1.
id();
4672 sleLoan->at(sfStartDate) = closedEndedRed;
4673 sleLoan->at(sfPaymentInterval) = 60;
4674 sleLoan->at(sfPaymentRemaining) = 1;
4675 sleLoan->at(sfTotalValueOutstanding) =
Number(100);
4676 sleLoan->at(sfPeriodicPayment) =
Number(1);
4677 ac.view().
insert(sleLoan);
4684 auto const sub = env.now().time_since_epoch().count() + 60;
4686 closedEndedRed = red;
4688 Vault const vault{env};
4689 auto [tx,
keylet] = vault.create(
4692 .vaultKind = closedEnded,
4693 .subscriptionDate = sub,
4694 .redemptionDate = red});
4696 closedEndedKeylet =
keylet;
4699 closedEndedBrokerKeylet =
4705 env.close(tp{d{sub + 1}});
4717 auto const nonCanonicalMPTAmount = [&](
SField const& field) {
4720 nonCanonicalMPTIssue,
4726 auto const negativeMPTAmount = [&](
SField const& field) {
4729 auto const nonCanonicalMPTPayment = [&]() {
4731 tx.setFieldAmount(sfAmount, nonCanonicalMPTAmount(sfAmount));
4740 nonCanonicalMPTPayment(),
4744 {{
"ledger entry contains non-canonical MPT or XRP amount"}},
4752 sleNew->setAccountID(sfAccount, a1.
id());
4753 sleNew->setAccountID(sfDestination, a2.id());
4754 sleNew->setFieldAmount(sfSendMax, nonCanonicalMPTAmount(sfSendMax));
4755 ac.view().insert(sleNew);
4760 {{
"ledger entry contains non-canonical MPT or XRP amount"}},
4768 sleNew->setAccountID(sfAccount, a1.
id());
4769 sleNew->setAccountID(sfDestination, a2.id());
4770 sleNew->setFieldAmount(sfSendMax, negativeMPTAmount(sfSendMax));
4771 ac.view().insert(sleNew);
4777 {{
"OutstandingAmount overflow"}},
4786 sleNew->setFieldU64(sfOutstandingAmount, 110);
4787 sleNew->setFieldU64(sfMaximumAmount, 100);
4788 ac.view().insert(sleNew);
4794 {{
"invalid OutstandingAmount balance"}},
4803 sleNew->setFieldU64(sfOutstandingAmount, 100);
4804 sleNew->setFieldU64(sfMaximumAmount, 100);
4805 ac.view().insert(sleNew);
4808 sleNew->setFieldU64(sfMPTAmount, 90);
4809 ac.view().insert(sleNew);
4820 return update(
id, ac, a1);
4827 env.fund(
XRP(1'000), gw);
4829 {.env = env, .issuer = gw, .holders = {a1}, .pay = 100, .maxAmt = 100});
4835 "invalid OutstandingAmount balance",
4840 sle->setFieldU64(sfMPTAmount, 101);
4849 sle->setFieldU64(sfOutstandingAmount, 101);
4857 Account const issuer{
"issuer"};
4858 Account const holder{
"holder"};
4860 env.
fund(
XRP(1'000), issuer, holder, other);
4861 auto const usd = issuer[
"USD"];
4862 env.
trust(usd(100), holder);
4863 env(
pay(issuer, holder, usd(100)));
4870 {{
"Invariant failed: trustline clawback balance change is invalid"}},
4878 if (holder.
id() > issuer.
id())
4880 sle->setFieldAmount(sfBalance, balance);
4888 tx[sfAccount] = issuer.
id();
4897 Account const issuer{
"issuer"};
4898 Account const holder{
"holder"};
4900 env.
fund(
XRP(1'000), issuer, holder, other);
4901 auto const usd = issuer[
"USD"];
4902 env.
trust(usd(100), holder);
4903 env(
pay(issuer, holder, usd(100)));
4924 tx[sfAccount] = issuer.
id();
4933 Account const issuer{
"issuer"};
4934 Account const holder{
"holder"};
4936 env.
fund(
XRP(1'000), issuer, holder, other);
4937 auto const usd = issuer[
"USD"];
4938 env.
trust(usd(100), holder);
4939 env(
pay(issuer, holder, usd(100)));
4946 {{
"Invariant failed: trustline clawback balance change is invalid"}},
4954 if (holder.
id() > issuer.
id())
4956 sle->setFieldAmount(sfBalance, balance);
4964 tx[sfAccount] = issuer.
id();
4973 Account const issuer{
"issuer"};
4974 Account const holder{
"holder"};
4976 env.
fund(
XRP(1'000), issuer, holder, other);
4978 {.env = env, .issuer = issuer, .holders = {holder}, .pay = 100, .maxAmt = 100});
4985 {{
"Invariant failed: MPT clawback balance change is invalid"}},
4989 if (!sleToken || !sleIssuance)
4992 sleToken->setFieldU64(sfMPTAmount, 80);
4993 sleIssuance->setFieldU64(sfOutstandingAmount, 80);
5002 tx[sfAccount] = issuer.
id();
5003 tx[sfHolder] = holder.
id();
5012 Account const issuer{
"issuer"};
5013 Account const holder{
"holder"};
5015 env.
fund(
XRP(1'000), issuer, holder, other);
5016 auto const usd = issuer[
"USD"];
5017 env.
trust(usd(100), holder);
5018 env(
pay(issuer, holder, usd(100)));
5020 {.env = env, .issuer = issuer, .holders = {holder}, .pay = 100, .maxAmt = 100});
5027 {{
"Invariant failed: trustline and MPToken both changed"}},
5029 auto const sleLine =
5033 if (!sleLine || !sleToken || !sleIssuance)
5037 if (holder.
id() > issuer.
id())
5039 sleLine->setFieldAmount(sfBalance, balance);
5040 sleToken->setFieldU64(sfMPTAmount, 90);
5041 sleIssuance->setFieldU64(sfOutstandingAmount, 90);
5051 tx[sfAccount] = issuer.
id();
5052 tx[sfHolder] = holder.
id();
5063 Account const issuer{
"issuer"};
5064 Account const holder{
"holder"};
5066 env.
fund(
XRP(1'000), issuer, holder, other);
5067 auto const usd = issuer[
"USD"];
5068 auto const eur = issuer[
"EUR"];
5069 env.
trust(eur(100), holder);
5070 env(
pay(issuer, holder, eur(100)));
5077 {{
"Invariant failed: trustline clawback changed the wrong line"}},
5084 if (holder.
id() > issuer.
id())
5086 sle->setFieldAmount(sfBalance, balance);
5094 tx[sfAccount] = issuer.
id();
5103 Account const issuer{
"issuer"};
5104 Account const holder{
"holder"};
5106 env.
fund(
XRP(1'000), issuer, holder, other);
5107 auto const usd = issuer[
"USD"];
5108 env.
trust(usd(100), holder);
5109 env(
pay(issuer, holder, usd(100)));
5116 {{
"Invariant failed: trustline or MPT balance is negative"}},
5124 if (holder.
id() < issuer.
id())
5126 sle->setFieldAmount(sfBalance, balance);
5134 tx[sfAccount] = issuer.
id();
5144 Account const issuer{
"issuer"};
5145 Account const holder{
"holder"};
5147 env.
fund(
XRP(1'000), issuer, holder, other);
5148 auto const usd = issuer[
"USD"];
5150 {.env = env, .issuer = issuer, .holders = {holder}, .pay = 100, .maxAmt = 100});
5157 {{
"Invariant failed: trustline clawback changed the wrong line"}},
5161 if (!sleToken || !sleIssuance)
5163 sleToken->setFieldU64(sfMPTAmount, 90);
5164 sleIssuance->setFieldU64(sfOutstandingAmount, 90);
5173 tx[sfAccount] = issuer.
id();
5182 Account const issuer{
"issuer"};
5183 Account const holder{
"holder"};
5185 env.
fund(
XRP(1'000), issuer, holder, other);
5186 auto const usd = issuer[
"USD"];
5187 env.
trust(usd(100), holder);
5188 env(
pay(issuer, holder, usd(100)));
5195 {{
"Invariant failed: trustline clawback amount is invalid"}},
5202 if (holder.
id() > issuer.
id())
5204 sle->setFieldAmount(sfBalance, balance);
5212 tx[sfAccount] = issuer.
id();
5221 Account const issuer{
"issuer"};
5222 Account const holder{
"holder"};
5224 env.
fund(
XRP(1'000), issuer, holder, other);
5226 {.env = env, .issuer = issuer, .holders = {holder}, .pay = 100, .maxAmt = 100});
5233 {{
"Invariant failed: MPT clawback missing holder"}},
5237 if (!sleToken || !sleIssuance)
5239 sleToken->setFieldU64(sfMPTAmount, 90);
5240 sleIssuance->setFieldU64(sfOutstandingAmount, 90);
5249 tx[sfAccount] = issuer.
id();
5258 Account const issuer{
"issuer"};
5259 Account const holder{
"holder"};
5261 env.
fund(
XRP(1'000), issuer, holder, other);
5263 {.env = env, .issuer = issuer, .holders = {holder}, .pay = 100, .maxAmt = 100});
5270 {{
"Invariant failed: MPT clawback token is missing"}},
5274 if (!sleToken || !sleIssuance)
5277 sleIssuance->setFieldU64(sfOutstandingAmount, 0);
5286 tx[sfAccount] = issuer.
id();
5287 tx[sfHolder] = holder.
id();
5296 Account const issuer{
"issuer"};
5297 Account const holder{
"holder"};
5299 env.
fund(
XRP(1'000), issuer, holder, other);
5303 .holders = {holder, other},
5312 {{
"Invariant failed: MPT clawback changed the wrong token"}},
5316 if (!sleToken || !sleIssuance)
5318 sleToken->setFieldU64(sfMPTAmount, 90);
5319 sleIssuance->setFieldU64(sfOutstandingAmount, 190);
5328 tx[sfAccount] = issuer.
id();
5329 tx[sfHolder] = holder.
id();
5338 Account const issuer{
"issuer"};
5339 Account const holder{
"holder"};
5341 env.
fund(
XRP(1'000), issuer, holder, other);
5343 {.env = env, .issuer = issuer, .holders = {holder}, .pay = 100, .maxAmt = 100});
5350 {{
"Invariant failed: MPT clawback amount is invalid"}},
5354 if (!sleToken || !sleIssuance)
5356 sleToken->setFieldU64(sfMPTAmount, 90);
5357 sleIssuance->setFieldU64(sfOutstandingAmount, 90);
5366 tx[sfAccount] = issuer.
id();
5367 tx[sfHolder] = holder.
id();
5379 for (
auto const& [tx, nTokens] :
tests)
5382 {{
std::string(
"MPToken created for the MPT issuer")}},
5388 auto seq = sle->getFieldU32(sfSequence);
5389 for (
int i = 0; i < nTokens; ++i)
5408 for (
auto const& tx : {ttAMM_WITHDRAW, ttAMM_CLAWBACK})
5413 {{
"MPT authorize succeeded but created/deleted bad number of mptokens"}},
5415 for (
auto const& a : {a1, a2, a3})
5429 env.fund(
XRP(1'000), gw, a3);
5430 MPTTester const mpt({.env = env, .issuer = gw, .holders = {a1, a2, a3}});
5440 {{
"sfReferenceHolding set on a new MPTokenIssuance by a "
5441 "non-VaultCreate transaction"}},
5448 sleNew->setFieldH256(sfReferenceHolding,
uint256{1});
5463 {{
"sfReferenceHolding was modified on an existing "
5464 "MPTokenIssuance"}},
5473 sleIssuance->setFieldH256(sfReferenceHolding,
uint256{2});
5481 Account const issuer{
"issuer"};
5482 env.fund(
XRP(10'000), issuer);
5485 mptt.
create({.flags = tfMPTCanTransfer | tfMPTCanLock});
5490 Vault const vault{env};
5491 auto [tx,
keylet] = vault.create({.owner = a1, .asset = asset});
5505 {{
"vault pseudo-account holding deleted by a "
5506 "non-VaultDelete transaction"}},
5511 auto const sleIssuance =
5513 if (!sleIssuance || !sleIssuance->isFieldPresent(sfReferenceHolding))
5526 Account const issuer{
"issuer"};
5527 env.fund(
XRP(10'000), issuer);
5530 mptt.
create({.flags = tfMPTCanTransfer | tfMPTCanLock});
5535 Vault const vault{env};
5536 auto [tx,
keylet] = vault.create({.owner = a1, .asset = asset});
5549 for (
auto const enabled : {
true,
false})
5551 for (
auto const& [tx, crossCurrencyPayment] : invalidTransferTests)
5553 for (
auto const flag :
5560 auto const isSuccess = !enabled || flag == 0 ||
5561 (tx == ttPAYMENT && !crossCurrencyPayment && (flag == ~lsfMPTCanTrade)) ||
5562 (tx == ttAMM_WITHDRAW &&
5563 (flag == ~lsfMPTCanTrade || flag == ~lsfMPTCanTransfer));
5568 {{isSuccess ?
"" :
"invalid MPToken transfer between holders"}},
5574 sle->at(sfMPTAmount) = v;
5581 auto const flags = issuanceSle->at(sfFlags);
5582 if (flag == lsfMPTLocked)
5584 issuanceSle->at(sfFlags) = flags | lsfMPTLocked;
5586 else if (flag != 0u)
5588 issuanceSle->at(sfFlags) = flags & flag;
5590 issuanceSle->at(sfOutstandingAmount) = 200;
5592 return update(a1, 101) && update(a2, 99);
5598 if (crossCurrencyPayment)
5607 env.fund(
XRP(1'000), gw);
5609 {.env = env, .issuer = gw, .holders = {a1, a2}, .pay = 100});
5613 env.disableFeature(featureMPTokensV2);
5629 auto const setupVault = [&](
Account const& a1,
5632 env.fund(
XRP(1'000), gw);
5633 env.trust(gw[
"IOU"](10'000), a1);
5634 env.trust(gw[
"IOU"](10'000), a2);
5636 env(
pay(gw, a1, gw[
"IOU"](500)));
5637 env(
pay(gw, a2, gw[
"IOU"](500)));
5640 Vault const vault{env};
5641 auto [
createTx, vaultKeylet] = vault.create({.owner = a1, .asset = gw[
"IOU"]});
5645 {.depositor = a1, .id = vaultKeylet.key, .amount = gw[
"IOU"](100)}));
5647 {.depositor = a2, .id = vaultKeylet.key, .amount = gw[
"IOU"](100)}));
5650 return {env.le(vaultKeylet)->at(sfShareMPTID), env.le(vaultKeylet)->at(sfAccount)};
5654 auto const precheck =
5660 (*sle1)[sfMPTAmount] -= 10;
5661 (*sle2)[sfMPTAmount] += 10;
5669 auto const preclose = [&](
Account const& a1,
Account const& a2,
Env& env) ->
bool {
5670 auto [sid, vid] = setupVault(a1, a2, env);
5672 env(
trust(gw, gw[
"IOU"](0),
Account{
"vaultPseudo", vid}, tfSetFreeze));
5679 {{
"invalid MPToken transfer between holders"}},
5689 auto const preclose = [&](
Account const& a1,
Account const& a2,
Env& env) ->
bool {
5690 auto [sid, vid] = setupVault(a1, a2, env);
5692 env(
trust(gw, gw[
"IOU"](0), a2, tfSetFreeze));
5699 {{
"invalid MPToken transfer between holders"}},
6307 testcase <<
"ValidConfidentialMPToken";
6313 auto const precloseConfidential =
6315 MPTTester mpt(env, a1, {.holders = {a2}, .fund =
false});
6316 mpt.
create({.flags = tfMPTCanTransfer | tfMPTCanHoldConfidentialBalance});
6317 mptID = mpt.issuanceID();
6319 mpt.authorize({.account = a2});
6320 mpt.pay(a1, a2, 100);
6322 mpt.generateKeyPair(a1);
6323 mpt.set({.account = a1, .issuerPubKey = mpt.getPubKey(a1)});
6325 mpt.generateKeyPair(a2);
6329 .holderPubKey = mpt.getPubKey(a2),
6336 {
"MPToken deleted with encrypted fields while COA > 0"},
6342 ac.view().erase(sleToken);
6348 precloseConfidential);
6352 {
"MPToken encrypted field existence inconsistency"},
6358 sleToken->makeFieldAbsent(sfIssuerEncryptedBalance);
6359 ac.view().update(sleToken);
6365 precloseConfidential);
6368 {
"MPToken encrypted field existence inconsistency"},
6373 sleToken->makeFieldAbsent(sfIssuerEncryptedBalance);
6374 sleToken->makeFieldAbsent(sfConfidentialBalanceInbox);
6375 sleToken->makeFieldAbsent(sfConfidentialBalanceSpending);
6376 sleToken->setFieldVL(sfAuditorEncryptedBalance,
Blob{0x00});
6377 ac.view().update(sleToken);
6383 precloseConfidential);
6386 auto const precloseNoPrivacy = [&mptID](
6388 MPTTester mpt(env, a1, {.holders = {a2}, .fund =
false});
6390 mpt.
create({.flags = tfMPTCanTransfer});
6391 mptID = mpt.issuanceID();
6392 mpt.authorize({.account = a2});
6393 mpt.pay(a1, a2, 100);
6398 {
"MPToken has encrypted fields but Issuance does not have "
6399 "lsfMPTCanHoldConfidentialBalance "
6407 sleToken->setFieldVL(sfConfidentialBalanceInbox,
Blob{0x00});
6408 sleToken->setFieldVL(sfConfidentialBalanceSpending,
Blob{0x00});
6409 sleToken->setFieldVL(sfIssuerEncryptedBalance,
Blob{0x00});
6410 ac.view().update(sleToken);
6420 {
"Confidential outstanding amount exceeds total outstanding amount"},
6426 sleIssuance->setFieldU64(sfConfidentialOutstandingAmount, 200);
6427 ac.view().update(sleIssuance);
6433 precloseConfidential);
6437 {
"Token conservation violation for MPT"},
6443 sleIssuance->setFieldU64(
6444 sfConfidentialOutstandingAmount,
6445 sleIssuance->getFieldU64(sfConfidentialOutstandingAmount) - 10);
6446 ac.view().update(sleIssuance);
6453 precloseConfidential);
6457 {
"Invariant failed: OutstandingAmount changed "
6458 "by confidential transaction that should not "
6459 "modify it for MPT"},
6464 sleIssuance->setFieldU64(
6465 sfOutstandingAmount, sleIssuance->getFieldU64(sfOutstandingAmount) + 1);
6466 ac.view().update(sleIssuance);
6472 precloseConfidential);
6477 {
"Invariant failed: MPTAmount changed by confidential "
6478 "transaction that should not modify this field."},
6483 sleToken->setFieldU64(sfMPTAmount, sleToken->getFieldU64(sfMPTAmount) + 1);
6484 ac.view().update(sleToken);
6490 precloseConfidential);
6494 {
"MPToken sfConfidentialBalanceVersion not updated when sfConfidentialBalanceSpending "
6497 Blob const kChangedConfidentialSpending = {0xBA, 0xDD};
6501 sleToken->setFieldVL(sfConfidentialBalanceSpending, kChangedConfidentialSpending);
6504 ac.view().update(sleToken);
6510 precloseConfidential);
6513 auto const precloseOrphan = [&mptID](
6515 MPTTester mpt(env, a1, {.holders = {a2}, .fund =
false});
6516 mpt.
create({.flags = tfMPTCanTransfer | tfMPTCanHoldConfidentialBalance});
6517 mptID = mpt.issuanceID();
6518 mpt.authorize({.account = a2});
6521 mpt.generateKeyPair(a1);
6522 mpt.set({.account = a1, .issuerPubKey = mpt.getPubKey(a1)});
6523 mpt.generateKeyPair(a2);
6527 .holderPubKey = mpt.getPubKey(a2),
6542 ac.view().erase(sleToken);