1407 bool const fixEnabled = features[fixCleanup3_1_3];
1414 Env(*
this, features),
1415 {{
"permissioned domain with no rules."}},
1421 fixEnabled ? failTers : badTers);
1423 testcase <<
"PermissionedDomain 2";
1427 Env(*
this, features),
1428 {{
"permissioned domain bad credentials size " +
std::to_string(kTooBig)}},
1434 fixEnabled ? failTers : badTers);
1436 testcase <<
"PermissionedDomain 3";
1438 Env(*
this, features),
1439 {{
"permissioned domain credentials aren't sorted"}},
1447 cred.setAccountID(sfIssuer, a2);
1449 cred.setFieldVL(sfCredentialType,
Slice(credType.c_str(), credType.size()));
1452 slePd->setFieldArray(sfAcceptedCredentials,
credentials);
1453 ac.view().update(slePd);
1458 fixEnabled ? failTers : badTers);
1460 testcase <<
"PermissionedDomain 4";
1462 Env(*
this, features),
1463 {{
"permissioned domain credentials aren't unique"}},
1471 cred.setAccountID(sfIssuer, a2);
1472 cred.setFieldVL(sfCredentialType,
Slice(
"cred_type", 9));
1475 slePd->setFieldArray(sfAcceptedCredentials,
credentials);
1476 ac.view().update(slePd);
1481 fixEnabled ? failTers : badTers);
1483 testcase <<
"PermissionedDomain Set 1";
1485 Env(*
this, features),
1486 {{
"permissioned domain with no rules."}},
1494 slePd->setFieldArray(sfAcceptedCredentials,
credentials);
1495 ac.view().update(slePd);
1502 fixEnabled ? failTers : badTers);
1504 testcase <<
"PermissionedDomain Set 2";
1506 Env(*
this, features),
1507 {{
"permissioned domain bad credentials size " +
std::to_string(kTooBig)}},
1519 cred.setAccountID(sfIssuer, a2);
1521 cred.setFieldVL(sfCredentialType,
Slice(credType.c_str(), credType.size()));
1525 slePd->setFieldArray(sfAcceptedCredentials,
credentials);
1526 ac.view().update(slePd);
1533 fixEnabled ? failTers : badTers);
1535 testcase <<
"PermissionedDomain Set 3";
1537 Env(*
this, features),
1538 {{
"permissioned domain credentials aren't sorted"}},
1549 cred.setAccountID(sfIssuer, a2);
1551 cred.setFieldVL(sfCredentialType,
Slice(credType.c_str(), credType.size()));
1555 slePd->setFieldArray(sfAcceptedCredentials,
credentials);
1556 ac.view().update(slePd);
1563 fixEnabled ? failTers : badTers);
1565 testcase <<
"PermissionedDomain Set 4";
1567 Env(*
this, features),
1568 {{
"permissioned domain credentials aren't unique"}},
1579 cred.setAccountID(sfIssuer, a2);
1580 cred.setFieldVL(sfCredentialType,
Slice(
"cred_type", 9));
1583 slePd->setFieldArray(sfAcceptedCredentials,
credentials);
1584 ac.view().update(slePd);
1591 fixEnabled ? failTers : badTers);
1596 {
"transaction affected more than 1 permissioned domain entry."}};
1600 {
"domain object modified, but not deleted by "}};
1603 {
"domain object(s) affected by an unauthorized transaction."}};
1606 testcase <<
"PermissionedDomain set 2 domains ";
1608 Env(*
this, features),
1609 fixEnabled ? badMoreThan1 : emptyV,
1617 fixEnabled ? failTers : goodTers);
1621 testcase <<
"PermissionedDomain del 2 domains";
1623 Env env1(*
this, features);
1638 fixEnabled ? badMoreThan1 : emptyV,
1640 auto sle1 = ac.
view().
peek({ltPERMISSIONED_DOMAIN, pd1});
1641 auto sle2 = ac.
view().
peek({ltPERMISSIONED_DOMAIN, pd2});
1648 fixEnabled ? failTers : goodTers);
1652 testcase <<
"PermissionedDomain set 0 domains ";
1654 Env(*
this, features),
1655 fixEnabled ? badNoDomains : emptyV,
1659 fixEnabled ? badTers : goodTers);
1663 testcase <<
"PermissionedDomain del 0 domains";
1665 Env env1(*
this, features);
1677 Env(*
this, features),
1680 fixEnabled ? badNoDomains : emptyV,
1684 fixEnabled ? badTers : goodTers);
1688 testcase <<
"PermissionedDomain set, delete domain";
1690 Env env1(*
this, features);
1704 fixEnabled ? badDeleted : emptyV,
1706 auto sle1 = ac.
view().
peek({ltPERMISSIONED_DOMAIN, pd1});
1712 fixEnabled ? failTers : goodTers);
1716 testcase <<
"PermissionedDomain del, create domain ";
1718 Env(*
this, features),
1719 fixEnabled ? badNotDeleted : emptyV,
1726 fixEnabled ? failTers : goodTers);
1730 testcase <<
"PermissionedDomain invalid tx";
1733 fixEnabled ? badTx : emptyV,
1893 bool const fixEnabled = features[fixCleanup3_1_3];
1898 Env(*
this, features),
1899 {{
"domain doesn't exist"}},
1903 sleOffer->setAccountID(sfAccount, a1);
1904 sleOffer->setFieldAmount(sfTakerPays, a1[
"USD"](10));
1905 sleOffer->setFieldAmount(sfTakerGets,
XRP(1));
1906 ac.view().insert(sleOffer);
1915 uint256{
"F10D0CC9A0F9A3CBF585B80BE09A186483668FDBDD39AA7E33"
1918 tx.setFieldAmount(sfTakerPays, a1[
"USD"](10));
1919 tx.setFieldAmount(sfTakerGets,
XRP(1));
1925 Env(*
this, features),
1926 {{
"hybrid offer is malformed"}},
1930 sleOffer->setAccountID(sfAccount, a2);
1931 sleOffer->setFieldAmount(sfTakerPays, a1[
"USD"](10));
1932 sleOffer->setFieldAmount(sfTakerGets,
XRP(1));
1933 sleOffer->setFlag(lsfHybrid);
1937 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
1938 ac.view().insert(sleOffer);
1947 Env env1(*
this, features);
1961 {{
"hybrid offer is malformed"}},
1965 sleOffer->setAccountID(sfAccount, a2);
1966 sleOffer->setFieldAmount(sfTakerPays, a1[
"USD"](10));
1967 sleOffer->setFieldAmount(sfTakerGets,
XRP(1));
1968 sleOffer->setFlag(lsfHybrid);
1969 sleOffer->setFieldH256(sfDomainID, pd1);
1974 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
1975 ac.view().insert(sleOffer);
1985 Env env1(*
this, features);
2004 sleOffer->setAccountID(sfAccount, a2);
2005 sleOffer->setFieldAmount(sfTakerPays, a1[
"USD"](10));
2006 sleOffer->setFieldAmount(sfTakerGets,
XRP(1));
2007 sleOffer->setFlag(lsfHybrid);
2008 sleOffer->setFieldH256(sfDomainID, pd1);
2011 sleOffer->setFieldArray(sfAdditionalBooks, bookArr);
2012 ac.view().insert(sleOffer);
2023 Env env1(*
this, features);
2037 {{
"hybrid offer is malformed"}},
2041 sleOffer->setAccountID(sfAccount, a2);
2042 sleOffer->setFieldAmount(sfTakerPays, a1[
"USD"](10));
2043 sleOffer->setFieldAmount(sfTakerGets,
XRP(1));
2044 sleOffer->setFlag(lsfHybrid);
2045 sleOffer->setFieldH256(sfDomainID, pd1);
2046 ac.view().insert(sleOffer);
2055 Env env1(*
this, features);
2070 {{
"transaction consumed wrong domains"}},
2074 sleOffer->setAccountID(sfAccount, a2);
2075 sleOffer->setFieldAmount(sfTakerPays, a1[
"USD"](10));
2076 sleOffer->setFieldAmount(sfTakerGets,
XRP(1));
2077 sleOffer->setFieldH256(sfDomainID, pd1);
2078 ac.view().insert(sleOffer);
2085 tx.setFieldH256(sfDomainID, pd2);
2086 tx.setFieldAmount(sfTakerPays, a1[
"USD"](10));
2087 tx.setFieldAmount(sfTakerGets,
XRP(1));
2093 Env env1(*
this, features);
2107 {{
"domain transaction affected regular offers"}},
2111 sleOffer->setAccountID(sfAccount, a2);
2112 sleOffer->setFieldAmount(sfTakerPays, a1[
"USD"](10));
2113 sleOffer->setFieldAmount(sfTakerGets,
XRP(1));
2114 ac.view().insert(sleOffer);
2122 tx.setFieldH256(sfDomainID, pd1);
2123 tx.setFieldAmount(sfTakerPays, a1[
"USD"](10));
2124 tx.setFieldAmount(sfTakerGets,
XRP(1));
2665 struct AccountAmount
2688 auto const mptIssuanceID = (*sleVault)[sfShareMPTID];
2694 if (args.lossUnrealized)
2695 (*sleVault)[sfLossUnrealized] = *args.lossUnrealized;
2696 if (args.assetsMaximum)
2697 (*sleVault)[sfAssetsMaximum] = *args.assetsMaximum;
2700 if (args.assetsTotal)
2701 (*sleVault)[sfAssetsTotal] = *(*sleVault)[sfAssetsTotal] + *args.assetsTotal;
2702 if (args.assetsAvailable)
2704 (*sleVault)[sfAssetsAvailable] =
2705 *(*sleVault)[sfAssetsAvailable] + *args.assetsAvailable;
2709 if (args.sharesTotal)
2711 (*sleShares)[sfOutstandingAmount] =
2712 *(*sleShares)[sfOutstandingAmount] + *args.sharesTotal;
2716 auto const assets = *(*sleVault)[sfAsset];
2717 auto const pseudoId = *(*sleVault)[sfAccount];
2718 if (args.vaultAssets)
2720 if (assets.native())
2723 if (!slePseudoAccount)
2725 (*slePseudoAccount)[sfBalance] =
2726 *(*slePseudoAccount)[sfBalance] + *args.vaultAssets;
2727 ac.
update(slePseudoAccount);
2731 auto const mptId = assets.get<
MPTIssue>().getMptID();
2735 (*sleMPToken)[sfMPTAmount] = *(*sleMPToken)[sfMPTAmount] + *args.vaultAssets;
2744 if (args.accountAssets)
2746 auto const& pair = *args.accountAssets;
2747 if (assets.native())
2752 (*sleAccount)[sfBalance] = *(*sleAccount)[sfBalance] + pair.amount;
2757 auto const mptID = assets.get<
MPTIssue>().getMptID();
2761 (*sleMPToken)[sfMPTAmount] = *(*sleMPToken)[sfMPTAmount] + pair.amount;
2770 if (args.accountShares)
2772 auto const& pair = *args.accountShares;
2776 (*sleMPToken)[sfMPTAmount] = *(*sleMPToken)[sfMPTAmount] + pair.amount;
2782 static constexpr auto kArgs = [](
AccountID id,
int adjustment,
auto fn) -> Adjustments {
2783 Adjustments sample = {
2784 .assetsTotal = adjustment,
2785 .assetsAvailable = adjustment,
2786 .lossUnrealized = 0,
2787 .sharesTotal = adjustment,
2788 .vaultAssets = adjustment,
2790 AccountAmount{.account = id, .amount = -adjustment},
2792 AccountAmount{.account = id, .amount = adjustment}};
2799 auto const precloseXrp = [&](
Account const& a1,
Account const& a2,
Env& env) ->
bool {
2800 env.fund(
XRP(1000), a3, a4);
2801 Vault const vault{env};
2802 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
2804 env(vault.deposit({.depositor = a1, .id = keylet.key, .amount = XRP(10)}));
2805 env(vault.deposit({.depositor = a2, .id = keylet.key, .amount = XRP(10)}));
2806 env(vault.deposit({.depositor = a3, .id = keylet.key, .amount = XRP(10)}));
2810 testcase <<
"Vault general checks";
2812 {
"vault deletion succeeded without deleting a vault"},
2818 ac.view().
update(sleVault);
2825 Vault const vault{env};
2826 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
2832 {
"vault updated by a wrong transaction type"},
2838 ac.view().
erase(sleVault);
2845 Vault const vault{env};
2846 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
2852 {
"vault updated by a wrong transaction type"},
2858 ac.view().
update(sleVault);
2865 Vault const vault{env};
2866 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
2872 {
"vault updated by a wrong transaction type"},
2874 auto const sequence = ac.view().
seq();
2877 auto const vaultPage = ac.view().
dirInsert(
2879 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
2880 ac.view().
insert(sleVault);
2888 {
"vault deleted by a wrong transaction type"},
2894 ac.view().
erase(sleVault);
2901 Vault const vault{env};
2902 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
2908 {
"vault operation updated more than single vault"},
2915 ac.view().
erase(sleVault);
2922 ac.view().
erase(sleVault);
2930 Vault const vault{env};
2932 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
2936 auto [tx, _] = vault.create({.owner = a2, .asset =
xrpIssue()});
2943 {
"vault operation updated more than single vault"},
2945 auto const sequence = ac.view().
seq();
2946 auto const insertVault = [&](
Account const a) {
2949 auto const vaultPage = ac.view().
dirInsert(
2951 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
2952 ac.view().
insert(sleVault);
2963 {
"deleted vault must also delete shares"},
2969 ac.view().
erase(sleVault);
2976 Vault const vault{env};
2977 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
2983 {
"deleted vault must have no shares outstanding",
2984 "deleted vault must have no assets outstanding",
2985 "deleted vault must have no assets available"},
2994 ac.view().
erase(sleVault);
2995 ac.view().
erase(sleShares);
3002 Vault const vault{env};
3003 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
3005 env(vault.deposit({.depositor = a1, .id = keylet.key, .amount = XRP(10)}));
3010 {
"vault operation succeeded without modifying a vault"},
3021 sleShares->setFieldH256(sfDomainID,
uint256(13));
3022 ac.view().
update(sleShares);
3032 {
"vault operation succeeded without modifying a vault"},
3038 Vault const vault{env};
3039 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
3045 {
"vault operation succeeded without modifying a vault"},
3051 Vault const vault{env};
3052 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
3058 {
"vault operation succeeded without modifying a vault"},
3064 Vault const vault{env};
3065 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
3071 {
"vault operation succeeded without modifying a vault"},
3077 Vault const vault{env};
3078 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
3084 {
"vault operation succeeded without modifying a vault"},
3090 Vault const vault{env};
3091 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
3097 {
"updated vault must have shares"},
3103 (*sleVault)[sfAssetsMaximum] = 200;
3104 ac.view().
update(sleVault);
3109 ac.view().
erase(sleShares);
3116 Vault const vault{env};
3117 auto [tx, _] = vault.create({.owner = a1, .asset =
xrpIssue()});
3123 {
"vault operation succeeded without updating shares",
3124 "assets available must not be greater than assets outstanding"},
3130 (*sleVault)[sfAssetsTotal] = 9;
3131 ac.view().
update(sleVault);
3138 Vault const vault{env};
3139 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
3141 env(vault.deposit({.depositor = a1, .id = keylet.key, .amount = XRP(10)}));
3146 {
"set must not change assets outstanding",
3147 "set must not change assets available",
3148 "set must not change shares outstanding",
3149 "set must not change vault balance",
3150 "assets available must be positive",
3151 "assets available must not be greater than assets outstanding",
3152 "assets outstanding must be positive"},
3159 if (!slePseudoAccount)
3161 (*slePseudoAccount)[sfBalance] = *(*slePseudoAccount)[sfBalance] - 10;
3162 ac.view().
update(slePseudoAccount);
3168 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
3171 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {
3172 sample.assetsAvailable = (kDropsPerXrp * -100).value();
3173 sample.assetsTotal = (kDropsPerXrp * -200).value();
3174 sample.sharesTotal = -1;
3184 {
"violation of vault immutable data"},
3191 ac.view().
update(sleVault);
3200 {
"violation of vault immutable data"},
3206 sleVault->setAccountID(sfAccount, a2.id());
3207 ac.view().
update(sleVault);
3216 {
"violation of vault immutable data"},
3222 (*sleVault)[sfShareMPTID] =
MPTID(42);
3223 ac.view().
update(sleVault);
3232 {
"vault transaction must not change loss unrealized",
3233 "set must not change assets outstanding"},
3236 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {
3237 sample.lossUnrealized = 13;
3238 sample.assetsTotal = 20;
3248 {
"loss unrealized must not exceed the difference "
3249 "between assets outstanding and available",
3250 "vault transaction must not change loss unrealized"},
3253 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 100, [&](Adjustments& sample) {
3254 sample.lossUnrealized = 13;
3259 ttVAULT_DEPOSIT, [](
STObject& tx) { tx.setFieldAmount(sfAmount,
XRPAmount(200)); }},
3265 {
"set assets outstanding must not exceed assets maximum"},
3268 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {
3269 sample.assetsMaximum = 1;
3279 {
"assets maximum must be positive"},
3282 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {
3283 sample.assetsMaximum = -1;
3293 {
"set must not change shares outstanding",
3294 "updated zero sized vault must have no assets outstanding",
3295 "updated zero sized vault must have no assets available"},
3301 ac.view().
update(sleVault);
3305 (*sleShares)[sfOutstandingAmount] = 0;
3306 ac.view().
update(sleShares);
3316 {
"updated shares must not exceed maximum"},
3325 (*sleShares)[sfMaximumAmount] = 10;
3326 ac.view().
update(sleShares);
3328 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [](Adjustments&) {}));
3337 {
"updated shares must not exceed maximum"},
3340 kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [](Adjustments&) {}));
3349 ac.view().
update(sleShares);
3361 "created vault must be empty",
3362 "updated zero sized vault must have no assets outstanding",
3363 "create operation must not have updated a vault",
3370 (*sleVault)[sfAssetsTotal] = 9;
3371 ac.view().
update(sleVault);
3378 Vault const vault{env};
3379 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
3386 "created vault must be empty",
3387 "updated zero sized vault must have no assets available",
3388 "assets available must not be greater than assets outstanding",
3389 "create operation must not have updated a vault",
3396 (*sleVault)[sfAssetsAvailable] = 9;
3397 ac.view().
update(sleVault);
3404 Vault const vault{env};
3405 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
3412 "created vault must be empty",
3413 "loss unrealized must not exceed the difference between assets "
3414 "outstanding and available",
3415 "vault transaction must not change loss unrealized",
3416 "create operation must not have updated a vault",
3423 (*sleVault)[sfLossUnrealized] = 1;
3424 ac.view().
update(sleVault);
3431 Vault const vault{env};
3432 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
3439 "created vault must be empty",
3440 "create operation must not have updated a vault",
3450 ac.view().
update(sleVault);
3451 (*sleShares)[sfOutstandingAmount] = 9;
3452 ac.view().
update(sleShares);
3459 Vault const vault{env};
3460 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
3467 "assets maximum must be positive",
3468 "create operation must not have updated a vault",
3475 (*sleVault)[sfAssetsMaximum] =
Number(-1);
3476 ac.view().
update(sleVault);
3483 Vault const vault{env};
3484 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
3490 {
"create operation must not have updated a vault",
3491 "shares issuer and vault pseudo-account must be the same",
3492 "shares issuer must be a pseudo-account",
3493 "shares issuer pseudo-account must point back to the vault"},
3502 ac.view().
update(sleVault);
3503 (*sleShares)[sfIssuer] = a1.
id();
3504 ac.view().
update(sleShares);
3511 Vault const vault{env};
3512 auto [tx,
keylet] = vault.create({.owner = a1, .asset =
xrpIssue()});
3518 {
"vault created by a wrong transaction type",
"account root created illegally"},
3523 auto const sequence = ac.view().
seq();
3526 auto const vaultPage = ac.view().
dirInsert(
3528 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
3533 sleAccount->setAccountID(sfAccount, pseudoId);
3534 sleAccount->setFieldAmount(sfBalance,
STAmount{});
3539 sleAccount->setFieldU32(sfSequence, seqno);
3540 sleAccount->setFieldU32(
3541 sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
3542 sleAccount->setFieldH256(sfVaultID, vaultKeylet.key);
3543 ac.view().
insert(sleAccount);
3545 auto const sharesMptId =
makeMptID(sequence, pseudoId);
3548 auto const sharesPage = ac.view().
dirInsert(
3550 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
3552 sleShares->at(sfFlags) = 0;
3553 sleShares->at(sfIssuer) = pseudoId;
3554 sleShares->at(sfOutstandingAmount) = 0;
3555 sleShares->at(sfSequence) = sequence;
3557 sleVault->at(sfAccount) = pseudoId;
3558 sleVault->at(sfFlags) = 0;
3559 sleVault->at(sfSequence) = sequence;
3560 sleVault->at(sfOwner) = a1.
id();
3561 sleVault->at(sfAssetsTotal) =
Number(0);
3562 sleVault->at(sfAssetsAvailable) =
Number(0);
3563 sleVault->at(sfLossUnrealized) =
Number(0);
3564 sleVault->at(sfShareMPTID) = sharesMptId;
3567 ac.view().
insert(sleVault);
3568 ac.view().
insert(sleShares);
3576 {
"shares issuer and vault pseudo-account must be the same",
3577 "shares issuer pseudo-account must point back to the vault"},
3579 auto const sequence = ac.view().
seq();
3582 auto const vaultPage = ac.view().
dirInsert(
3584 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
3589 sleAccount->setAccountID(sfAccount, pseudoId);
3590 sleAccount->setFieldAmount(sfBalance,
STAmount{});
3595 sleAccount->setFieldU32(sfSequence, seqno);
3596 sleAccount->setFieldU32(
3597 sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
3600 sleAccount->setFieldH256(sfVaultID,
uint256(42));
3601 ac.view().
insert(sleAccount);
3603 auto const sharesMptId =
makeMptID(sequence, pseudoId);
3606 auto const sharesPage = ac.view().
dirInsert(
3608 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
3610 sleShares->at(sfFlags) = 0;
3611 sleShares->at(sfIssuer) = pseudoId;
3612 sleShares->at(sfOutstandingAmount) = 0;
3613 sleShares->at(sfSequence) = sequence;
3617 sleVault->at(sfAccount) = a2.id();
3618 sleVault->at(sfFlags) = 0;
3619 sleVault->at(sfSequence) = sequence;
3620 sleVault->at(sfOwner) = a1.
id();
3621 sleVault->at(sfAssetsTotal) =
Number(0);
3622 sleVault->at(sfAssetsAvailable) =
Number(0);
3623 sleVault->at(sfLossUnrealized) =
Number(0);
3624 sleVault->at(sfShareMPTID) = sharesMptId;
3627 ac.view().
insert(sleVault);
3628 ac.view().
insert(sleShares);
3636 {
"shares issuer and vault pseudo-account must be the same",
"shares issuer must exist"},
3638 auto const sequence = ac.view().
seq();
3641 auto const vaultPage = ac.view().
dirInsert(
3643 sleVault->setFieldU64(sfOwnerNode, *vaultPage);
3645 auto const sharesMptId =
makeMptID(sequence, a2.id());
3648 auto const sharesPage = ac.view().
dirInsert(
3650 sleShares->setFieldU64(sfOwnerNode, *sharesPage);
3652 sleShares->at(sfFlags) = 0;
3654 sleShares->at(sfIssuer) =
AccountID(42);
3655 sleShares->at(sfOutstandingAmount) = 0;
3656 sleShares->at(sfSequence) = sequence;
3658 sleVault->at(sfAccount) = a2.id();
3659 sleVault->at(sfFlags) = 0;
3660 sleVault->at(sfSequence) = sequence;
3661 sleVault->at(sfOwner) = a1.
id();
3662 sleVault->at(sfAssetsTotal) =
Number(0);
3663 sleVault->at(sfAssetsAvailable) =
Number(0);
3664 sleVault->at(sfLossUnrealized) =
Number(0);
3665 sleVault->at(sfShareMPTID) = sharesMptId;
3668 ac.view().
insert(sleVault);
3669 ac.view().
insert(sleShares);
3678 {
"deposit must change vault balance"},
3681 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [](Adjustments& sample) {
3682 sample.vaultAssets.reset();
3691 {
"deposit assets outstanding must not exceed assets maximum"},
3694 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 200, [&](Adjustments& sample) {
3695 sample.assetsMaximum = 1;
3700 ttVAULT_DEPOSIT, [](
STObject& tx) { tx.setFieldAmount(sfAmount,
XRPAmount(200)); }},
3710 {
"deposit must increase vault balance",
"deposit must change depositor balance"},
3718 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
3721 return kAdjust(ac.view(),
keylet, kArgs(a3.
id(), -10, [&](Adjustments& sample) {
3722 sample.accountAssets->amount = -100;
3730 tx[sfAccount] = a3.
id();
3736 {
"deposit must increase vault balance",
3737 "deposit must decrease depositor balance",
3738 "deposit must change vault and depositor balance by equal amount",
3739 "deposit and assets outstanding must add up",
3740 "deposit and assets available must add up"},
3748 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 10;
3751 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [&](Adjustments& sample) {
3752 sample.vaultAssets = -20;
3753 sample.accountAssets->amount = 10;
3763 {
"deposit must change depositor balance"},
3771 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 10;
3774 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [&](Adjustments& sample) {
3775 sample.accountAssets->amount = 0;
3785 {
"deposit must change depositor shares"},
3788 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [&](Adjustments& sample) {
3789 sample.accountShares.reset();
3799 {
"deposit must change vault shares"},
3803 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [](Adjustments& sample) {
3804 sample.sharesTotal = 0;
3814 {
"deposit must increase depositor shares",
3815 "deposit must change depositor and vault shares by equal amount",
3816 "deposit must not change vault balance by more than deposited "
3820 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [&](Adjustments& sample) {
3821 sample.accountShares->amount = -5;
3822 sample.sharesTotal = -10;
3832 {
"deposit and assets outstanding must add up"},
3835 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 2000;
3839 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [&](Adjustments& sample) {
3840 sample.assetsTotal = 11;
3848 tx[sfDelegate] = a3.
id();
3856 {
"deposit and assets outstanding must add up",
3857 "deposit and assets available must add up"},
3860 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 10, [&](Adjustments& sample) {
3861 sample.assetsTotal = 7;
3862 sample.assetsAvailable = 7;
3873 {
"withdrawal must change vault balance"},
3876 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [](Adjustments& sample) {
3877 sample.vaultAssets.reset();
3890 {
"withdrawal must change one destination balance"},
3898 (*sleA4)[sfBalance] = *(*sleA4)[sfBalance] + 10;
3901 return kAdjust(ac.view(),
keylet, kArgs(a3.
id(), -10, [&](Adjustments& sample) {
3902 sample.accountAssets->amount = -100;
3910 tx[sfAccount] = a3.
id();
3919 "withdrawal must change vault and destination balance by equal amount",
3920 "withdrawal must decrease vault balance",
3921 "withdrawal must increase destination balance",
3922 "withdrawal and assets outstanding must add up",
3923 "withdrawal and assets available must add up",
3932 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 10;
3935 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
3936 sample.vaultAssets = 10;
3937 sample.accountAssets->amount = -20;
3947 {
"withdrawal must change one destination balance"},
3950 if (!kAdjust(ac.view(),
keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
3951 *sample.vaultAssets -= 5;
3957 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] + 5;
3962 STTx{ttVAULT_WITHDRAW, [&](
STObject& tx) { tx.setAccountID(sfDestination, a3.
id()); }},
3968 {
"withdrawal must change depositor shares"},
3971 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
3972 sample.accountShares.reset();
3982 {
"withdrawal must change vault shares"},
3985 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), -10, [](Adjustments& sample) {
3986 sample.sharesTotal = 0;
3996 {
"withdrawal must decrease depositor shares",
3997 "withdrawal must change depositor and vault shares by equal "
4001 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
4002 sample.accountShares->amount = 5;
4003 sample.sharesTotal = 10;
4013 {
"withdrawal and assets outstanding must add up",
4014 "withdrawal and assets available must add up"},
4017 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
4018 sample.assetsTotal = -15;
4019 sample.assetsAvailable = -15;
4029 {
"withdrawal and assets outstanding must add up"},
4032 (*sleA3)[sfBalance] = *(*sleA3)[sfBalance] - 2000;
4036 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
4037 sample.assetsTotal = -7;
4045 tx[sfDelegate] = a3.
id();
4052 auto const precloseMpt = [&](
Account const& a1,
Account const& a2,
Env& env) ->
bool {
4053 env.fund(
XRP(1000), a3, a4);
4058 jv[sfAccount] = a3.
human();
4059 jv[sfTransactionType] = jss::MPTokenIssuanceCreate;
4060 jv[sfFlags] = tfMPTCanTransfer;
4065 auto const mptID =
makeMptID(env.seq(a3) - 1, a3);
4070 jv[sfAccount] = a1.
human();
4071 jv[sfTransactionType] = jss::MPTokenAuthorize;
4072 jv[sfMPTokenIssuanceID] =
to_string(mptID);
4074 jv[sfAccount] = a2.human();
4076 jv[sfAccount] = a4.
human();
4083 env(
pay(a3, a1, asset(1000)));
4084 env(
pay(a3, a2, asset(1000)));
4085 env(
pay(a3, a4, asset(1000)));
4089 Vault const vault{env};
4090 auto [tx,
keylet] = vault.create({.owner = a1, .asset = asset});
4092 env(vault.deposit({.depositor = a1, .id = keylet.key, .amount = asset(10)}));
4093 env(vault.deposit({.depositor = a2, .id = keylet.key, .amount = asset(10)}));
4094 env(vault.deposit({.depositor = a4, .id = keylet.key, .amount = asset(10)}));
4099 {
"withdrawal must decrease depositor shares",
4100 "withdrawal must change depositor and vault shares by equal "
4104 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), -10, [&](Adjustments& sample) {
4105 sample.accountShares->amount = 5;
4109 STTx{ttVAULT_WITHDRAW, [&](
STObject& tx) { tx[sfAccount] = a3.
id(); }},
4116 {
"clawback must change vault balance"},
4119 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), -1, [&](Adjustments& sample) {
4120 sample.vaultAssets.reset();
4124 STTx{ttVAULT_CLAWBACK, [&](
STObject& tx) { tx[sfAccount] = a3.
id(); }},
4130 {
"clawback may only be performed by the asset issuer"},
4133 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {}));
4142 {
"clawback may only be performed by the asset issuer"},
4145 return kAdjust(ac.view(),
keylet, kArgs(a2.id(), 0, [&](Adjustments& sample) {}));
4148 STTx{ttVAULT_CLAWBACK, [&](
STObject& tx) { tx[sfAccount] = a4.
id(); }},
4153 {
"clawback must decrease vault balance",
4154 "clawback must decrease holder shares",
4155 "clawback must change vault shares"},
4158 return kAdjust(ac.view(),
keylet, kArgs(a4.
id(), 10, [&](Adjustments& sample) {
4159 sample.sharesTotal = 0;
4166 tx[sfAccount] = a3.
id();
4167 tx[sfHolder] = a4.
id();
4173 {
"clawback must change holder shares"},
4176 return kAdjust(ac.view(),
keylet, kArgs(a4.
id(), -10, [&](Adjustments& sample) {
4177 sample.accountShares.reset();
4184 tx[sfAccount] = a3.
id();
4185 tx[sfHolder] = a4.
id();
4191 {
"clawback must change holder and vault shares by equal amount",
4192 "clawback and assets outstanding must add up",
4193 "clawback and assets available must add up"},
4196 return kAdjust(ac.view(),
keylet, kArgs(a4.
id(), -10, [&](Adjustments& sample) {
4197 sample.accountShares->amount = -8;
4198 sample.assetsTotal = -7;
4199 sample.assetsAvailable = -7;
4206 tx[sfAccount] = a3.
id();
4207 tx[sfHolder] = a4.
id();
4220 auto const nonCanonicalMPTAmount = [&](
SField const& field) {
4223 nonCanonicalMPTIssue,
4229 auto const negativeMPTAmount = [&](
SField const& field) {
4232 auto const nonCanonicalMPTPayment = [&]() {
4234 tx.setFieldAmount(sfAmount, nonCanonicalMPTAmount(sfAmount));
4243 nonCanonicalMPTPayment(),
4247 {{
"ledger entry contains non-canonical MPT or XRP amount"}},
4254 sleNew->setAccountID(sfAccount, a1.
id());
4255 sleNew->setAccountID(sfDestination, a2.id());
4256 sleNew->setFieldAmount(sfSendMax, nonCanonicalMPTAmount(sfSendMax));
4257 ac.view().insert(sleNew);
4262 {{
"ledger entry contains non-canonical MPT or XRP amount"}},
4269 sleNew->setAccountID(sfAccount, a1.
id());
4270 sleNew->setAccountID(sfDestination, a2.id());
4271 sleNew->setFieldAmount(sfSendMax, negativeMPTAmount(sfSendMax));
4272 ac.view().insert(sleNew);
4278 {{
"OutstandingAmount overflow"}},
4287 sleNew->setFieldU64(sfOutstandingAmount, 110);
4288 sleNew->setFieldU64(sfMaximumAmount, 100);
4289 ac.view().insert(sleNew);
4295 {{
"invalid OutstandingAmount balance"}},
4304 sleNew->setFieldU64(sfOutstandingAmount, 100);
4305 sleNew->setFieldU64(sfMaximumAmount, 100);
4306 ac.view().insert(sleNew);
4309 sleNew->setFieldU64(sfMPTAmount, 90);
4310 ac.view().insert(sleNew);
4321 return update(
id, ac, a1);
4328 env.fund(
XRP(1'000), gw);
4330 {.env = env, .issuer = gw, .holders = {a1}, .pay = 100, .maxAmt = 100});
4336 "invalid OutstandingAmount balance",
4341 sle->setFieldU64(sfMPTAmount, 101);
4350 sle->setFieldU64(sfOutstandingAmount, 101);
4361 for (
auto const& [tx, nTokens] :
tests)
4364 {{
std::string(
"MPToken created for the MPT issuer")}},
4370 auto seq = sle->getFieldU32(sfSequence);
4371 for (
int i = 0; i < nTokens; ++i)
4390 for (
auto const& tx : {ttAMM_WITHDRAW, ttAMM_CLAWBACK})
4395 {{
"MPT authorize succeeded but created/deleted bad number of mptokens"}},
4397 for (
auto const& a : {a1, a2, a3})
4411 env.fund(
XRP(1'000), gw, a3);
4412 MPTTester const mpt({.env = env, .issuer = gw, .holders = {a1, a2, a3}});
4422 {{
"sfReferenceHolding set on a new MPTokenIssuance by a "
4423 "non-VaultCreate transaction"}},
4430 sleNew->setFieldH256(sfReferenceHolding,
uint256{1});
4445 {{
"sfReferenceHolding was modified on an existing "
4446 "MPTokenIssuance"}},
4455 sleIssuance->setFieldH256(sfReferenceHolding,
uint256{2});
4463 Account const issuer{
"issuer"};
4464 env.fund(
XRP(10'000), issuer);
4467 mptt.
create({.flags = tfMPTCanTransfer | tfMPTCanLock});
4472 Vault const vault{env};
4473 auto [tx,
keylet] = vault.create({.owner = a1, .asset = asset});
4487 {{
"vault pseudo-account holding deleted by a "
4488 "non-VaultDelete transaction"}},
4493 auto const sleIssuance =
4495 if (!sleIssuance || !sleIssuance->isFieldPresent(sfReferenceHolding))
4508 Account const issuer{
"issuer"};
4509 env.fund(
XRP(10'000), issuer);
4512 mptt.
create({.flags = tfMPTCanTransfer | tfMPTCanLock});
4517 Vault const vault{env};
4518 auto [tx,
keylet] = vault.create({.owner = a1, .asset = asset});
4531 for (
auto const enabled : {
true,
false})
4533 for (
auto const& [tx, crossCurrencyPayment] : invalidTransferTests)
4535 for (
auto const flag :
4542 auto const isSuccess = !enabled || flag == 0 ||
4543 (tx == ttPAYMENT && !crossCurrencyPayment && (flag == ~lsfMPTCanTrade)) ||
4544 (tx == ttAMM_WITHDRAW &&
4545 (flag == ~lsfMPTCanTrade || flag == ~lsfMPTCanTransfer));
4550 {{isSuccess ?
"" :
"invalid MPToken transfer between holders"}},
4556 sle->at(sfMPTAmount) = v;
4563 auto const flags = issuanceSle->at(sfFlags);
4564 if (flag == lsfMPTLocked)
4566 issuanceSle->at(sfFlags) = flags | lsfMPTLocked;
4568 else if (flag != 0u)
4570 issuanceSle->at(sfFlags) = flags & flag;
4572 issuanceSle->at(sfOutstandingAmount) = 200;
4574 return update(a1, 101) && update(a2, 99);
4580 if (crossCurrencyPayment)
4589 env.fund(
XRP(1'000), gw);
4591 {.env = env, .issuer = gw, .holders = {a1, a2}, .pay = 100});
4595 env.disableFeature(featureMPTokensV2);
4611 auto const preclose = [&](
Account const& a1,
Account const& a2,
Env& env) ->
bool {
4612 env.fund(
XRP(1'000), gw);
4613 env.trust(gw[
"IOU"](10'000), a1);
4614 env.trust(gw[
"IOU"](10'000), a2);
4616 env(
pay(gw, a1, gw[
"IOU"](500)));
4617 env(
pay(gw, a2, gw[
"IOU"](500)));
4621 Vault const vault{env};
4622 auto [
createTx, vaultKeylet] = vault.create({.owner = a1, .asset = iou});
4626 env(vault.deposit({.depositor = a1, .id = vaultKeylet.key, .amount = iou(100)}));
4627 env(vault.deposit({.depositor = a2, .id = vaultKeylet.key, .amount = iou(100)}));
4630 shareID = env.le(vaultKeylet)->at(sfShareMPTID);
4636 env(
trust(gw, gw[
"IOU"](0), a2, tfSetFreeze));
4642 auto const precheck =
4648 (*sle1)[sfMPTAmount] -= 10;
4649 (*sle2)[sfMPTAmount] += 10;
4660 {{
"invalid MPToken transfer between holders"}},
4693 auto const precheck2 =
4697 if (!sleAMM || !sle2)
4699 (*sleAMM)[sfMPTAmount] -= 10;
4700 (*sle2)[sfMPTAmount] += 10;
4709 auto const setupVaultAMM = [&](
Account const& a1,
Account const& a2,
Env& env) ->
bool {
4710 env.fund(
XRP(1'000), gw);
4711 env(
fset(gw, asfDefaultRipple));
4714 env.trust(gw[
"IOU"](10'000), a1);
4715 env.trust(gw[
"IOU"](10'000), a2);
4717 env(
pay(gw, a1, gw[
"IOU"](1'000)));
4718 env(
pay(gw, a2, gw[
"IOU"](500)));
4721 Vault const vault{env};
4722 auto [
createTx, vaultKeylet] = vault.create({.owner = a1, .asset = gw[
"IOU"]});
4727 {.depositor = a1, .id = vaultKeylet.key, .amount = gw[
"IOU"](500)}));
4729 {.depositor = a2, .id = vaultKeylet.key, .amount = gw[
"IOU"](200)}));
4732 shareID = env.le(vaultKeylet)->at(sfShareMPTID);
4733 vaultPseudoID = env.le(vaultKeylet)->at(sfAccount);
4738 ammAcctID =
amm.ammAccount();
4748 auto const preclose3 = [&](
Account const& a1,
Account const& a2,
Env& env) ->
bool {
4749 if (!setupVaultAMM(a1, a2, env))
4751 env(
trust(gw, gw[
"IOU"](0),
Account{
"vaultPseudo", vaultPseudoID}, tfSetFreeze));
4758 {{
"invalid MPToken transfer between holders"}},
4767 {{
"invalid MPToken transfer between holders"}},
4780 auto const preclose4 = [&](
Account const& a1,
Account const& a2,
Env& env) ->
bool {
4781 if (!setupVaultAMM(a1, a2, env))
4783 env(
trust(gw, gw[
"IOU"](0), a2, tfSetFreeze));
4790 {{
"invalid MPToken transfer between holders"}},
5183 testcase <<
"ValidConfidentialMPToken";
5189 auto const precloseConfidential =
5191 MPTTester mpt(env, a1, {.holders = {a2}, .fund =
false});
5192 mpt.
create({.flags = tfMPTCanTransfer | tfMPTCanHoldConfidentialBalance});
5193 mptID = mpt.issuanceID();
5195 mpt.authorize({.account = a2});
5196 mpt.pay(a1, a2, 100);
5198 mpt.generateKeyPair(a1);
5199 mpt.set({.account = a1, .issuerPubKey = mpt.getPubKey(a1)});
5201 mpt.generateKeyPair(a2);
5205 .holderPubKey = mpt.getPubKey(a2),
5212 {
"MPToken deleted with encrypted fields while COA > 0"},
5218 ac.view().erase(sleToken);
5224 precloseConfidential);
5228 {
"MPToken encrypted field existence inconsistency"},
5234 sleToken->makeFieldAbsent(sfIssuerEncryptedBalance);
5235 ac.view().update(sleToken);
5241 precloseConfidential);
5244 {
"MPToken encrypted field existence inconsistency"},
5249 sleToken->makeFieldAbsent(sfIssuerEncryptedBalance);
5250 sleToken->makeFieldAbsent(sfConfidentialBalanceInbox);
5251 sleToken->makeFieldAbsent(sfConfidentialBalanceSpending);
5252 sleToken->setFieldVL(sfAuditorEncryptedBalance,
Blob{0x00});
5253 ac.view().update(sleToken);
5259 precloseConfidential);
5262 auto const precloseNoPrivacy = [&mptID](
5264 MPTTester mpt(env, a1, {.holders = {a2}, .fund =
false});
5266 mpt.
create({.flags = tfMPTCanTransfer});
5267 mptID = mpt.issuanceID();
5268 mpt.authorize({.account = a2});
5269 mpt.pay(a1, a2, 100);
5274 {
"MPToken has encrypted fields but Issuance does not have "
5275 "lsfMPTCanHoldConfidentialBalance "
5283 sleToken->setFieldVL(sfConfidentialBalanceInbox,
Blob{0x00});
5284 sleToken->setFieldVL(sfConfidentialBalanceSpending,
Blob{0x00});
5285 sleToken->setFieldVL(sfIssuerEncryptedBalance,
Blob{0x00});
5286 ac.view().update(sleToken);
5296 {
"Confidential outstanding amount exceeds total outstanding amount"},
5302 sleIssuance->setFieldU64(sfConfidentialOutstandingAmount, 200);
5303 ac.view().update(sleIssuance);
5309 precloseConfidential);
5313 {
"Token conservation violation for MPT"},
5319 sleIssuance->setFieldU64(
5320 sfConfidentialOutstandingAmount,
5321 sleIssuance->getFieldU64(sfConfidentialOutstandingAmount) - 10);
5322 ac.view().update(sleIssuance);
5329 precloseConfidential);
5333 {
"Invariant failed: OutstandingAmount changed "
5334 "by confidential transaction that should not "
5335 "modify it for MPT"},
5340 sleIssuance->setFieldU64(
5341 sfOutstandingAmount, sleIssuance->getFieldU64(sfOutstandingAmount) + 1);
5342 ac.view().update(sleIssuance);
5348 precloseConfidential);
5353 {
"Invariant failed: MPTAmount changed by confidential "
5354 "transaction that should not modify this field."},
5359 sleToken->setFieldU64(sfMPTAmount, sleToken->getFieldU64(sfMPTAmount) + 1);
5360 ac.view().update(sleToken);
5366 precloseConfidential);
5370 {
"MPToken sfConfidentialBalanceVersion not updated when sfConfidentialBalanceSpending "
5373 Blob const kChangedConfidentialSpending = {0xBA, 0xDD};
5377 sleToken->setFieldVL(sfConfidentialBalanceSpending, kChangedConfidentialSpending);
5380 ac.view().update(sleToken);
5386 precloseConfidential);
5389 auto const precloseOrphan = [&mptID](
5391 MPTTester mpt(env, a1, {.holders = {a2}, .fund =
false});
5392 mpt.
create({.flags = tfMPTCanTransfer | tfMPTCanHoldConfidentialBalance});
5393 mptID = mpt.issuanceID();
5394 mpt.authorize({.account = a2});
5397 mpt.generateKeyPair(a1);
5398 mpt.set({.account = a1, .issuerPubKey = mpt.getPubKey(a1)});
5399 mpt.generateKeyPair(a2);
5403 .holderPubKey = mpt.getPubKey(a2),
5418 ac.view().erase(sleToken);