Skip to content

Commit

Permalink
MPT permissioned domain checks
Browse files Browse the repository at this point in the history
  • Loading branch information
Bronek committed Jan 31, 2025
1 parent 3eebdae commit ad68074
Show file tree
Hide file tree
Showing 8 changed files with 152 additions and 121 deletions.
2 changes: 1 addition & 1 deletion include/xrpl/protocol/TER.h
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ enum TECcodes : TERUnderlyingType {
tecBAD_CREDENTIALS = 193,
tecWRONG_ASSET = 194,
tecLIMIT_EXCEEDED = 195,
tecREMOVING_PERMISSIONS = 196,
tecINVALID_DOMAIN = 196,
};

//------------------------------------------------------------------------------
Expand Down
2 changes: 1 addition & 1 deletion src/libxrpl/protocol/TER.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,7 @@ transResults()
MAKE_ERROR(tecBAD_CREDENTIALS, "Bad credentials."),
MAKE_ERROR(tecWRONG_ASSET, "Wrong asset given."),
MAKE_ERROR(tecLIMIT_EXCEEDED, "Limit exceeded."),
MAKE_ERROR(tecREMOVING_PERMISSIONS, "Would remove permissions previously granted."),
MAKE_ERROR(tecINVALID_DOMAIN, "Invalid permissioned domain."),

MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
Expand Down
34 changes: 26 additions & 8 deletions src/xrpld/app/misc/CredentialHelpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -188,12 +188,17 @@ valid(PreclaimContext const& ctx, AccountID const& src)
}

TER
valid(ReadView const& view, uint256 domainID, AccountID const& subject)
validDomain(ReadView const& view, uint256 domainID, AccountID const& subject)
{
// Note, permissioned domain objects can be deleted at any time
auto const slePD = view.read(keylet::permissionedDomain(domainID));
if (!slePD || !slePD->isFieldPresent(sfAcceptedCredentials))
if (!slePD)
return tecINVALID_DOMAIN;
else if (!slePD->isFieldPresent(sfAcceptedCredentials))
return tefINTERNAL;

auto const closeTime = view.info().parentCloseTime;
bool foundExpired = false;
for (auto const& h : slePD->getFieldArray(sfAcceptedCredentials))
{
if (!h.isFieldPresent(sfIssuer) || !h.isFieldPresent(sfCredentialType))
Expand All @@ -204,13 +209,26 @@ valid(ReadView const& view, uint256 domainID, AccountID const& subject)
auto const sleCredential =
view.read(keylet::credential(subject, issuer, type));

// Do not check for expired credentials here, we need ApplyView& for
// that, to allow us to delete them (see verifyDomain below)
if (sleCredential && (sleCredential->getFlags() & lsfAccepted))
return tesSUCCESS;
// We cannot delete expired credentials, that would require ApplyView&
// However we can check if credentials are expired. Expected transaction
// flow is to use `validDomain` in preclaim, converting tecEXPIRED to
// tesSUCCESS, then proceed to call `verifyValidDomain` in doApply. This
// allows expired credentials to be deleted by any transaction.
if (sleCredential)
{
if (checkExpired(sleCredential, closeTime))
{
foundExpired = true;
continue;
}
else if (sleCredential->getFlags() & lsfAccepted)
return tesSUCCESS;
else
continue;
}
}

return tecNO_PERMISSION;
return foundExpired ? tecEXPIRED : tecNO_PERMISSION;
}

TER
Expand Down Expand Up @@ -301,7 +319,7 @@ checkArray(STArray const& credentials, unsigned maxSize, beast::Journal j)
} // namespace credentials

TER
verifyDomain(
verifyValidDomain(
ApplyView& view,
AccountID const& account,
uint256 domainID,
Expand Down
10 changes: 5 additions & 5 deletions src/xrpld/app/misc/CredentialHelpers.h
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,11 @@ checkFields(PreflightContext const& ctx);
TER
valid(PreclaimContext const& ctx, AccountID const& src);

// Check if subject has any credential maching the given domain. Do not use in
// doApply (only in preclaim) since it does not remove expired credentials. If
// you call it in prelaim, you also must call verifyDomain in doApply
// Check if subject has any credential maching the given domain. If you call it
// in preclaim and it returns tecEXPIRED, you should call verifyValidDomain in
// doApply. This will ensure that expired credentials are deleted.
TER
valid(ReadView const& view, uint256 domainID, AccountID const& subject);
validDomain(ReadView const& view, uint256 domainID, AccountID const& subject);

// This function is only called when we about to return tecNO_PERMISSION
// because all the checks for the DepositPreauth authorization failed.
Expand All @@ -83,7 +83,7 @@ checkArray(STArray const& credentials, unsigned maxSize, beast::Journal j);
// Check expired credentials and for credentials maching DomainID of the ledger
// object
TER
verifyDomain(
verifyValidDomain(
ApplyView& view,
AccountID const& account,
uint256 domainID,
Expand Down
37 changes: 31 additions & 6 deletions src/xrpld/app/tx/detail/VaultDeposit.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -97,11 +97,37 @@ VaultDeposit::doApply()
}

// Make sure the depositor can hold shares.
MPTIssue const mptIssue((*vault)[sfMPTokenIssuanceID]);
if (auto const err =
verifyAuth(ctx_.view(), mptIssue, account_, mPriorBalance, j_);
!isTesSuccess(err))
return err;
auto const mptIssuanceID = (*vault)[sfMPTokenIssuanceID];
auto const sleIssuance = view().read(keylet::mptIssuance(mptIssuanceID));
if (!sleIssuance)
return tefINTERNAL;

auto const& vaultAccount = vault->at(sfAccount);

MPTIssue const mptIssue(mptIssuanceID);
if (vault->getFlags() == tfVaultPrivate)
{
if (auto const err =
verifyAuth(ctx_.view(), mptIssue, account_, mPriorBalance, j_);
!isTesSuccess(err))
return err;
}
else
{
// No authorization needed, but must ensure there is MPToken
auto sleMpt = view().read(keylet::mptoken(mptIssuanceID, account_));
if (!sleMpt && account_ != vaultAccount)
{
if (auto const err = MPTokenAuthorize::authorize(
view(),
ctx_.journal,
{.priorBalance = mPriorBalance,
.mptIssuanceID = mptIssuanceID,
.accountID = account_});
!isTesSuccess(err))
return err;
}
}

// Compute exchange before transferring any amounts.
auto const shares = assetsToSharesDeposit(view(), vault, assets);
Expand All @@ -118,7 +144,6 @@ VaultDeposit::doApply()
if (maximum != 0 && *vault->at(sfAssetTotal) > maximum)
return tecLIMIT_EXCEEDED;

auto const& vaultAccount = vault->at(sfAccount);
// Transfer assets from depositor to vault.
if (auto ter = accountSend(view(), account_, vaultAccount, assets, j_))
return ter;
Expand Down
9 changes: 1 addition & 8 deletions src/xrpld/app/tx/detail/VaultSet.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,14 +67,7 @@ VaultSet::preclaim(PreclaimContext const& ctx)
if (auto const domain = ctx.tx[~sfDomainID])
{
if ((sle->getFlags() & tfVaultPrivate) == 0)
return tecREMOVING_PERMISSIONS;
if (auto const oldDomain = sle->at(~sfDomainID))
{
if (*oldDomain != *domain)
return tecREMOVING_PERMISSIONS;
// else no change
}
// else domain wasn't set previously, we allow setting it now
return tecINVALID_DOMAIN;
}

return tesSUCCESS;
Expand Down
6 changes: 4 additions & 2 deletions src/xrpld/ledger/View.h
Original file line number Diff line number Diff line change
Expand Up @@ -586,8 +586,10 @@ requireAuth(ReadView const& view, Issue const& issue, AccountID const& account);

/** Check if the account lacks required authorization.
*
* Does not check for expired credentials, hence must be followed by
* verifyAuth from doApply
* This will also check for expired credentials. If it is called directly
* from preclaim, the user should convert result tecEXPIRED to tesSUCCESS and
* proceed to also check permissions with verifyValidDomain inside doApply.
* This will ensure that any expired credentials are deleted.
*/
[[nodiscard]] TER
requireAuth(
Expand Down
Loading

0 comments on commit ad68074

Please sign in to comment.