mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-19 02:25:52 +00:00
Start vault implementation
This commit is contained in:
committed by
Bronek Kozicki
parent
1a032f04e3
commit
ff8c6491d7
@@ -364,6 +364,8 @@ public:
|
||||
*/
|
||||
Value&
|
||||
operator[](const StaticString& key);
|
||||
Value const&
|
||||
operator[](const StaticString& key) const;
|
||||
|
||||
/// Return the member named key if it exist, defaultValue otherwise.
|
||||
Value
|
||||
|
||||
@@ -48,14 +48,6 @@ class STObject;
|
||||
class STAmount;
|
||||
class Rules;
|
||||
|
||||
/** Calculate AMM account ID.
|
||||
*/
|
||||
AccountID
|
||||
ammAccountID(
|
||||
std::uint16_t prefix,
|
||||
uint256 const& parentHash,
|
||||
uint256 const& ammID);
|
||||
|
||||
/** Calculate Liquidity Provider Token (LPT) Currency.
|
||||
*/
|
||||
Currency
|
||||
|
||||
@@ -114,6 +114,14 @@ public:
|
||||
equalTokens(Asset const& lhs, Asset const& rhs);
|
||||
};
|
||||
|
||||
inline Json::Value
|
||||
to_json(Asset const& asset)
|
||||
{
|
||||
Json::Value jv;
|
||||
asset.setJson(jv);
|
||||
return jv;
|
||||
}
|
||||
|
||||
template <ValidIssueType TIss>
|
||||
constexpr bool
|
||||
Asset::holds() const
|
||||
@@ -219,9 +227,6 @@ validJSONAsset(Json::Value const& jv);
|
||||
Asset
|
||||
assetFromJson(Json::Value const& jv);
|
||||
|
||||
Json::Value
|
||||
to_json(Asset const& asset);
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif // RIPPLE_PROTOCOL_ASSET_H_INCLUDED
|
||||
|
||||
@@ -80,7 +80,7 @@ namespace detail {
|
||||
// Feature.cpp. Because it's only used to reserve storage, and determine how
|
||||
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
|
||||
// the actual number of amendments. A LogicError on startup will verify this.
|
||||
static constexpr std::size_t numFeatures = 83;
|
||||
static constexpr std::size_t numFeatures = 84;
|
||||
|
||||
/** Amendments that this server supports and the default voting behavior.
|
||||
Whether they are enabled depends on the Rules defined in the validated
|
||||
|
||||
@@ -330,6 +330,15 @@ mptoken(uint256 const& mptokenKey)
|
||||
Keylet
|
||||
mptoken(uint256 const& issuanceKey, AccountID const& holder) noexcept;
|
||||
|
||||
Keylet
|
||||
vault(AccountID const& owner, std::uint32_t seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
vault(uint256 const& vaultKey)
|
||||
{
|
||||
return {ltVAULT, vaultKey};
|
||||
}
|
||||
|
||||
} // namespace keylet
|
||||
|
||||
// Everything below is deprecated and should be removed in favor of keylets:
|
||||
|
||||
@@ -189,6 +189,9 @@ enum LedgerSpecificFlags {
|
||||
|
||||
// ltCREDENTIAL
|
||||
lsfAccepted = 0x00010000,
|
||||
|
||||
// ltVAULT
|
||||
lsfVaultPrivate = 0x00010000,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -42,8 +42,11 @@ public:
|
||||
AccountID const&
|
||||
getIssuer() const;
|
||||
|
||||
MPTID const&
|
||||
getMptID() const;
|
||||
constexpr MPTID const&
|
||||
getMptID() const
|
||||
{
|
||||
return mptID_;
|
||||
}
|
||||
|
||||
std::string
|
||||
getText() const;
|
||||
|
||||
@@ -111,6 +111,9 @@ std::size_t constexpr maxMPTokenMetadataLength = 1024;
|
||||
/** The maximum amount of MPTokenIssuance */
|
||||
std::uint64_t constexpr maxMPTokenAmount = 0x7FFF'FFFF'FFFF'FFFFull;
|
||||
|
||||
/** The maximum length of MPTokenMetadata */
|
||||
std::size_t constexpr maxVaultDataLength = 256;
|
||||
|
||||
/** A ledger index. */
|
||||
using LedgerIndex = std::uint32_t;
|
||||
|
||||
|
||||
@@ -268,7 +268,7 @@ public:
|
||||
std::string
|
||||
getText() const override;
|
||||
|
||||
Json::Value getJson(JsonOptions) const override;
|
||||
Json::Value getJson(JsonOptions = JsonOptions::none) const override;
|
||||
|
||||
void
|
||||
add(Serializer& s) const override;
|
||||
|
||||
@@ -90,6 +90,14 @@ struct JsonOptions
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T>
|
||||
requires requires(T const& t) { t.getJson(JsonOptions::none); }
|
||||
Json::Value
|
||||
to_json(T const& t)
|
||||
{
|
||||
return t.getJson(JsonOptions::none);
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
class STVar;
|
||||
}
|
||||
@@ -155,7 +163,7 @@ public:
|
||||
virtual std::string
|
||||
getText() const;
|
||||
|
||||
virtual Json::Value getJson(JsonOptions /*options*/) const;
|
||||
virtual Json::Value getJson(JsonOptions = JsonOptions::none) const;
|
||||
|
||||
virtual void
|
||||
add(Serializer& s) const;
|
||||
|
||||
@@ -45,6 +45,15 @@ public:
|
||||
|
||||
explicit STIssue(SField const& name);
|
||||
|
||||
STIssue&
|
||||
operator=(STIssue const& rhs) = default;
|
||||
STIssue&
|
||||
operator=(Asset const& rhs)
|
||||
{
|
||||
asset_ = rhs;
|
||||
return *this;
|
||||
}
|
||||
|
||||
template <ValidIssueType TIss>
|
||||
TIss const&
|
||||
get() const;
|
||||
|
||||
@@ -63,6 +63,13 @@ public:
|
||||
void
|
||||
setValue(Number const& v);
|
||||
|
||||
STNumber&
|
||||
operator=(Number const& rhs)
|
||||
{
|
||||
setValue(rhs);
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool
|
||||
isEquivalent(STBase const& t) const override;
|
||||
bool
|
||||
|
||||
@@ -152,8 +152,7 @@ public:
|
||||
getText() const override;
|
||||
|
||||
// TODO(tom): options should be an enum.
|
||||
Json::Value
|
||||
getJson(JsonOptions options) const override;
|
||||
Json::Value getJson(JsonOptions = JsonOptions::none) const override;
|
||||
|
||||
void
|
||||
addWithoutSigningFields(Serializer& s) const;
|
||||
@@ -482,9 +481,19 @@ private:
|
||||
template <class T>
|
||||
class STObject::Proxy
|
||||
{
|
||||
protected:
|
||||
public:
|
||||
using value_type = typename T::value_type;
|
||||
|
||||
value_type
|
||||
value() const;
|
||||
|
||||
value_type
|
||||
operator*() const;
|
||||
|
||||
T const*
|
||||
operator->() const;
|
||||
|
||||
protected:
|
||||
STObject* st_;
|
||||
SOEStyle style_;
|
||||
TypedField<T> const* f_;
|
||||
@@ -493,9 +502,6 @@ protected:
|
||||
|
||||
Proxy(STObject* st, TypedField<T> const* f);
|
||||
|
||||
value_type
|
||||
value() const;
|
||||
|
||||
T const*
|
||||
find() const;
|
||||
|
||||
@@ -510,7 +516,7 @@ template <typename U>
|
||||
concept IsArithmetic = std::is_arithmetic_v<U> || std::is_same_v<U, STAmount>;
|
||||
|
||||
template <class T>
|
||||
class STObject::ValueProxy : private Proxy<T>
|
||||
class STObject::ValueProxy : public Proxy<T>
|
||||
{
|
||||
private:
|
||||
using value_type = typename T::value_type;
|
||||
@@ -536,6 +542,20 @@ public:
|
||||
|
||||
operator value_type() const;
|
||||
|
||||
template <typename U>
|
||||
friend bool
|
||||
operator==(U const& lhs, STObject::ValueProxy<T> const& rhs)
|
||||
{
|
||||
return rhs.value() == lhs;
|
||||
}
|
||||
|
||||
template <typename U>
|
||||
friend bool
|
||||
operator!=(U const& lhs, STObject::ValueProxy<T> const& rhs)
|
||||
{
|
||||
return !(lhs == rhs);
|
||||
}
|
||||
|
||||
private:
|
||||
friend class STObject;
|
||||
|
||||
@@ -543,7 +563,7 @@ private:
|
||||
};
|
||||
|
||||
template <class T>
|
||||
class STObject::OptionalProxy : private Proxy<T>
|
||||
class STObject::OptionalProxy : public Proxy<T>
|
||||
{
|
||||
private:
|
||||
using value_type = typename T::value_type;
|
||||
@@ -563,15 +583,6 @@ public:
|
||||
explicit
|
||||
operator bool() const noexcept;
|
||||
|
||||
/** Return the contained value
|
||||
|
||||
Throws:
|
||||
|
||||
STObject::FieldErr if !engaged()
|
||||
*/
|
||||
value_type
|
||||
operator*() const;
|
||||
|
||||
operator optional_type() const;
|
||||
|
||||
/** Explicit conversion to std::optional */
|
||||
@@ -715,6 +726,20 @@ STObject::Proxy<T>::value() const -> value_type
|
||||
return value_type{};
|
||||
}
|
||||
|
||||
template <class T>
|
||||
auto
|
||||
STObject::Proxy<T>::operator*() const -> value_type
|
||||
{
|
||||
return this->value();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
T const*
|
||||
STObject::Proxy<T>::operator->() const
|
||||
{
|
||||
return this->find();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
inline T const*
|
||||
STObject::Proxy<T>::find() const
|
||||
@@ -790,13 +815,6 @@ STObject::OptionalProxy<T>::operator bool() const noexcept
|
||||
return engaged();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
auto
|
||||
STObject::OptionalProxy<T>::operator*() const -> value_type
|
||||
{
|
||||
return this->value();
|
||||
}
|
||||
|
||||
template <class T>
|
||||
STObject::OptionalProxy<T>::operator typename STObject::OptionalProxy<
|
||||
T>::optional_type() const
|
||||
|
||||
@@ -101,6 +101,10 @@ public:
|
||||
SeqProxy
|
||||
getSeqProxy() const;
|
||||
|
||||
/** Returns the first non-zero value of (Sequence, TicketSequence). */
|
||||
std::uint32_t
|
||||
getSequence() const;
|
||||
|
||||
boost::container::flat_set<AccountID>
|
||||
getMentionedAccounts() const;
|
||||
|
||||
|
||||
@@ -141,6 +141,7 @@ enum TEMcodes : TERUnderlyingType {
|
||||
temARRAY_TOO_LARGE,
|
||||
|
||||
temBAD_TRANSFER_FEE,
|
||||
temSTRING_TOO_LARGE,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -344,6 +345,7 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecARRAY_TOO_LARGE = 191,
|
||||
tecLOCKED = 192,
|
||||
tecBAD_CREDENTIALS = 193,
|
||||
tecWRONG_ASSET = 194,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -214,6 +214,12 @@ constexpr std::uint32_t tfAMMClawbackMask = ~(tfUniversal | tfClawTwoAssets);
|
||||
// BridgeModify flags:
|
||||
constexpr std::uint32_t tfClearAccountCreateAmount = 0x00010000;
|
||||
constexpr std::uint32_t tfBridgeModifyMask = ~(tfUniversal | tfClearAccountCreateAmount);
|
||||
|
||||
// VaultCreate flags:
|
||||
constexpr std::uint32_t const tfVaultPrivate = 0x00010000;
|
||||
static_assert(tfVaultPrivate == lsfVaultPrivate);
|
||||
constexpr std::uint32_t const tfVaultShareNonTransferable = 0x00020000;
|
||||
constexpr std::uint32_t const tfVaultCreateMask = ~(tfUniversal | tfVaultPrivate | tfVaultShareNonTransferable);
|
||||
// clang-format on
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -29,6 +29,7 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FEATURE(SingleAssetVault, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(Credentials, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(AMMClawback, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (AMMv1_2, Supported::yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -379,6 +379,37 @@ LEDGER_ENTRY(ltAMM, 0x0079, AMM, ({
|
||||
{sfPreviousTxnLgrSeq, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** A ledger object representing an individual MPToken asset type, but not
|
||||
* any balances of that asset itself.
|
||||
|
||||
\sa keylet::mptIssuance
|
||||
*/
|
||||
LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, ({
|
||||
{sfIssuer, soeREQUIRED},
|
||||
{sfSequence, soeREQUIRED},
|
||||
{sfTransferFee, soeDEFAULT},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
{sfAssetScale, soeDEFAULT},
|
||||
{sfMaximumAmount, soeOPTIONAL},
|
||||
{sfOutstandingAmount, soeREQUIRED},
|
||||
{sfMPTokenMetadata, soeOPTIONAL},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** A ledger object representing an individual MPToken balance.
|
||||
|
||||
\sa keylet::mptoken
|
||||
*/
|
||||
LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, ({
|
||||
{sfAccount, soeREQUIRED},
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfMPTAmount, soeDEFAULT},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** A ledger object which tracks Oracle
|
||||
\sa keylet::oracle
|
||||
*/
|
||||
@@ -394,34 +425,6 @@ LEDGER_ENTRY(ltORACLE, 0x0080, Oracle, ({
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** A ledger object which tracks MPTokenIssuance
|
||||
\sa keylet::mptIssuance
|
||||
*/
|
||||
LEDGER_ENTRY(ltMPTOKEN_ISSUANCE, 0x007e, MPTokenIssuance, ({
|
||||
{sfIssuer, soeREQUIRED},
|
||||
{sfSequence, soeREQUIRED},
|
||||
{sfTransferFee, soeDEFAULT},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
{sfAssetScale, soeDEFAULT},
|
||||
{sfMaximumAmount, soeOPTIONAL},
|
||||
{sfOutstandingAmount, soeREQUIRED},
|
||||
{sfMPTokenMetadata, soeOPTIONAL},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** A ledger object which tracks MPToken
|
||||
\sa keylet::mptoken
|
||||
*/
|
||||
LEDGER_ENTRY(ltMPTOKEN, 0x007f, MPToken, ({
|
||||
{sfAccount, soeREQUIRED},
|
||||
{sfMPTokenIssuanceID, soeREQUIRED},
|
||||
{sfMPTAmount, soeDEFAULT},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** A ledger object which tracks Credential
|
||||
\sa keylet::credential
|
||||
*/
|
||||
@@ -436,3 +439,26 @@ LEDGER_ENTRY(ltCREDENTIAL, 0x0081, Credential, ({
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** A ledger object representing a single asset vault.
|
||||
|
||||
\sa keylet::mptoken
|
||||
*/
|
||||
LEDGER_ENTRY(ltVAULT, 0x0082, Vault, ({
|
||||
{sfPreviousTxnID, soeREQUIRED},
|
||||
{sfPreviousTxnLgrSeq, soeREQUIRED},
|
||||
{sfSequence, soeREQUIRED},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
{sfOwner, soeREQUIRED},
|
||||
{sfAccount, soeREQUIRED},
|
||||
{sfData, soeDEFAULT},
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAssetTotal, soeDEFAULT},
|
||||
{sfAssetAvailable, soeDEFAULT},
|
||||
{sfAssetMaximum, soeDEFAULT},
|
||||
{sfLossUnrealized, soeDEFAULT},
|
||||
{sfMPTokenIssuanceID, soeREQUIRED}, // sfShare
|
||||
// no ShareTotal ever (use MPTIssuance.sfOutstandingAmount)
|
||||
// no WithdrawalPolicy yet
|
||||
// no PermissionedDomainID yet
|
||||
}))
|
||||
|
||||
@@ -190,9 +190,14 @@ TYPED_SFIELD(sfHookStateKey, UINT256, 30)
|
||||
TYPED_SFIELD(sfHookHash, UINT256, 31)
|
||||
TYPED_SFIELD(sfHookNamespace, UINT256, 32)
|
||||
TYPED_SFIELD(sfHookSetTxnID, UINT256, 33)
|
||||
TYPED_SFIELD(sfVaultID, UINT256, 34)
|
||||
|
||||
// number (common)
|
||||
TYPED_SFIELD(sfNumber, NUMBER, 1)
|
||||
TYPED_SFIELD(sfAssetAvailable, NUMBER, 2)
|
||||
TYPED_SFIELD(sfAssetMaximum, NUMBER, 3)
|
||||
TYPED_SFIELD(sfAssetTotal, NUMBER, 4)
|
||||
TYPED_SFIELD(sfLossUnrealized, NUMBER, 5)
|
||||
|
||||
// currency amount (common)
|
||||
TYPED_SFIELD(sfAmount, AMOUNT, 1)
|
||||
|
||||
@@ -447,6 +447,49 @@ TRANSACTION(ttCREDENTIAL_DELETE, 60, CredentialDelete, ({
|
||||
{sfCredentialType, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction creates a single asset vault. */
|
||||
TRANSACTION(ttVAULT_CREATE, 61, VaultCreate, ({
|
||||
{sfAsset, soeREQUIRED},
|
||||
{sfAssetMaximum, soeOPTIONAL},
|
||||
{sfMPTokenMetadata, soeOPTIONAL},
|
||||
// no PermissionedDomainID yet
|
||||
// no WithdrawalPolicy yet
|
||||
{sfData, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction updates a single asset vault. */
|
||||
TRANSACTION(ttVAULT_SET, 62, VaultSet, ({
|
||||
{sfVaultID, soeREQUIRED},
|
||||
{sfAssetMaximum, soeOPTIONAL},
|
||||
// no PermissionedDomainID yet
|
||||
// no WithdrawalPolicy yet
|
||||
{sfData, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction deletes a single asset vault. */
|
||||
TRANSACTION(ttVAULT_DELETE, 63, VaultDelete, ({
|
||||
{sfVaultID, soeREQUIRED},
|
||||
}))
|
||||
|
||||
/** This transaction trades assets for shares with a vault. */
|
||||
TRANSACTION(ttVAULT_DEPOSIT, 64, VaultDeposit, ({
|
||||
{sfVaultID, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
}))
|
||||
|
||||
/** This transaction trades shares for assets with a vault. */
|
||||
TRANSACTION(ttVAULT_WITHDRAW, 65, VaultWithdraw, ({
|
||||
{sfVaultID, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
{sfDestination, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction trades shares for assets with a vault. */
|
||||
TRANSACTION(ttVAULT_CLAWBACK, 66, VaultClawback, ({
|
||||
{sfVaultID, soeREQUIRED},
|
||||
{sfHolder, soeREQUIRED},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
}))
|
||||
|
||||
/** This system-generated transaction type is used to update the status of the various amendments.
|
||||
|
||||
|
||||
@@ -113,14 +113,17 @@ JSS(Subject); // in: Credential transactions
|
||||
JSS(TakerGets); // field.
|
||||
JSS(TakerPays); // field.
|
||||
JSS(Ticket); // ledger type.
|
||||
JSS(TxnSignature); // field.
|
||||
JSS(TradingFee); // in/out: AMM trading fee
|
||||
JSS(TransactionType); // in: TransactionSign.
|
||||
JSS(TransferRate); // in: TransferRate.
|
||||
JSS(TxnSignature); // field.
|
||||
JSS(URI); // field.
|
||||
JSS(Vault); // ledger type.
|
||||
JSS(VaultID); // field.
|
||||
JSS(VoteSlots); // out: AMM Vote
|
||||
JSS(XChainOwnedClaimID); // ledger type.
|
||||
JSS(XChainOwnedCreateAccountClaimID); // ledger type.
|
||||
//
|
||||
JSS(aborted); // out: InboundLedger
|
||||
JSS(accepted); // out: LedgerToJson, OwnerInfo, SubmitTransaction
|
||||
JSS(account); // in/out: many
|
||||
@@ -749,6 +752,8 @@ JSS(NegativeUNL); // out: ValidatorList; ledger type
|
||||
|
||||
#undef JSS
|
||||
|
||||
// clang-format on
|
||||
|
||||
} // namespace jss
|
||||
} // namespace ripple
|
||||
|
||||
|
||||
@@ -887,6 +887,12 @@ Value::operator[](const StaticString& key)
|
||||
return resolveReference(key, true);
|
||||
}
|
||||
|
||||
Value const&
|
||||
Value::operator[](const StaticString& key) const
|
||||
{
|
||||
return (*this)[key.c_str()];
|
||||
}
|
||||
|
||||
Value&
|
||||
Value::append(const Value& value)
|
||||
{
|
||||
|
||||
@@ -26,18 +26,6 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
AccountID
|
||||
ammAccountID(
|
||||
std::uint16_t prefix,
|
||||
uint256 const& parentHash,
|
||||
uint256 const& ammID)
|
||||
{
|
||||
ripesha_hasher rsh;
|
||||
auto const hash = sha512Half(prefix, parentHash, ammID);
|
||||
rsh(hash.data(), hash.size());
|
||||
return AccountID{static_cast<ripesha_hasher::result_type>(rsh)};
|
||||
}
|
||||
|
||||
Currency
|
||||
ammLPTCurrency(Currency const& cur1, Currency const& cur2)
|
||||
{
|
||||
|
||||
@@ -70,11 +70,4 @@ assetFromJson(Json::Value const& v)
|
||||
return mptIssueFromJson(v);
|
||||
}
|
||||
|
||||
Json::Value
|
||||
to_json(Asset const& asset)
|
||||
{
|
||||
return std::visit(
|
||||
[&](auto const& issue) { return to_json(issue); }, asset.value());
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -78,6 +78,7 @@ enum class LedgerNameSpace : std::uint16_t {
|
||||
MPTOKEN_ISSUANCE = '~',
|
||||
MPTOKEN = 't',
|
||||
CREDENTIAL = 'D',
|
||||
VAULT = 'V',
|
||||
|
||||
// No longer used or supported. Left here to reserve the space
|
||||
// to avoid accidental reuse.
|
||||
@@ -527,6 +528,12 @@ credential(
|
||||
indexHash(LedgerNameSpace::CREDENTIAL, subject, issuer, credType)};
|
||||
}
|
||||
|
||||
Keylet
|
||||
vault(AccountID const& owner, std::uint32_t seq) noexcept
|
||||
{
|
||||
return vault(indexHash(LedgerNameSpace::VAULT, owner, seq));
|
||||
}
|
||||
|
||||
} // namespace keylet
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -35,7 +35,7 @@ Keylet::check(STLedgerEntry const& sle) const
|
||||
if (type == ltCHILD)
|
||||
return sle.getType() != ltDIR_NODE;
|
||||
|
||||
return sle.getType() == type;
|
||||
return sle.getType() == type && sle.key() == key;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -39,12 +39,6 @@ MPTIssue::getIssuer() const
|
||||
return *account;
|
||||
}
|
||||
|
||||
MPTID const&
|
||||
MPTIssue::getMptID() const
|
||||
{
|
||||
return mptID_;
|
||||
}
|
||||
|
||||
std::string
|
||||
MPTIssue::getText() const
|
||||
{
|
||||
|
||||
@@ -197,6 +197,12 @@ STTx::getSeqProxy() const
|
||||
return SeqProxy{SeqProxy::ticket, *ticketSeq};
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
STTx::getSequence() const
|
||||
{
|
||||
return getSeqProxy().value();
|
||||
}
|
||||
|
||||
void
|
||||
STTx::sign(PublicKey const& publicKey, SecretKey const& secretKey)
|
||||
{
|
||||
|
||||
@@ -117,6 +117,7 @@ transResults()
|
||||
MAKE_ERROR(tecARRAY_TOO_LARGE, "Array is too large."),
|
||||
MAKE_ERROR(tecLOCKED, "Fund is locked."),
|
||||
MAKE_ERROR(tecBAD_CREDENTIALS, "Bad credentials."),
|
||||
MAKE_ERROR(tecWRONG_ASSET, "Wrong asset given."),
|
||||
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
|
||||
@@ -3535,7 +3535,7 @@ private:
|
||||
if (!BEAST_EXPECT(checkArraySize(affected, 4u)))
|
||||
return;
|
||||
auto ff =
|
||||
affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
|
||||
affected[2u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
|
||||
BEAST_EXPECT(
|
||||
ff[sfHighLimit.fieldName] ==
|
||||
bob["USD"](100).value().getJson(JsonOptions::none));
|
||||
@@ -3809,10 +3809,10 @@ private:
|
||||
auto ff =
|
||||
affected[1u][sfModifiedNode.fieldName][sfFinalFields.fieldName];
|
||||
BEAST_EXPECT(
|
||||
ff[sfHighLimit.fieldName] ==
|
||||
ff[sfLowLimit.fieldName] ==
|
||||
G1["USD"](0).value().getJson(JsonOptions::none));
|
||||
BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfLowFreeze));
|
||||
BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfHighFreeze);
|
||||
BEAST_EXPECT(ff[jss::Flags].asUInt() & lsfLowFreeze);
|
||||
BEAST_EXPECT(!(ff[jss::Flags].asUInt() & lsfHighFreeze));
|
||||
env.close();
|
||||
|
||||
// test: Can make a payment via the new offer
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
#include <test/jtx/xchain_bridge.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
#include <format>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
228
src/test/app/Vault_test.cpp
Normal file
228
src/test/app/Vault_test.cpp
Normal file
@@ -0,0 +1,228 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/fee.h>
|
||||
#include <test/jtx/mpt.h>
|
||||
#include <test/jtx/utility.h>
|
||||
#include <test/jtx/vault.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class Vault_test : public beast::unit_test::suite
|
||||
{
|
||||
TEST_CASE(CreateUpdateDelete)
|
||||
{
|
||||
using namespace test::jtx;
|
||||
Env env{*this};
|
||||
|
||||
Account issuer{"issuer"};
|
||||
Account owner{"owner"};
|
||||
env.fund(XRP(1000), issuer, owner);
|
||||
env.close();
|
||||
auto vault = env.vault();
|
||||
|
||||
SUBCASE("IOU")
|
||||
{
|
||||
Asset asset = issuer["IOU"];
|
||||
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||
|
||||
SUBCASE("nothing to delete")
|
||||
{
|
||||
tx = vault.del({.owner = issuer, .id = keylet.key});
|
||||
env(tx, ter(tecOBJECT_NOT_FOUND));
|
||||
env.close();
|
||||
}
|
||||
|
||||
SUBCASE("create")
|
||||
{
|
||||
env(tx);
|
||||
{
|
||||
auto meta = env.meta()->getJson();
|
||||
// JLOG(env.journal.error()) << meta;
|
||||
auto n = 0;
|
||||
for (auto const& affected : meta[sfAffectedNodes])
|
||||
{
|
||||
if (!affected[sfCreatedNode])
|
||||
continue;
|
||||
++n;
|
||||
}
|
||||
}
|
||||
BEAST_EXPECT(env.le(keylet));
|
||||
|
||||
tx = vault.del({.owner = issuer, .id = keylet.key});
|
||||
env(tx, ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
tx = vault.del({.owner = owner, .id = keylet.key});
|
||||
env(tx);
|
||||
{
|
||||
auto n = 0;
|
||||
auto meta = env.meta()->getJson();
|
||||
for (auto const& affected : meta[sfAffectedNodes])
|
||||
{
|
||||
if (!affected[sfDeletedNode])
|
||||
continue;
|
||||
// JLOG(env.journal.error())
|
||||
// << affected[sfDeletedNode][sfLedgerEntryType];
|
||||
++n;
|
||||
}
|
||||
}
|
||||
BEAST_EXPECT(!env.le(keylet));
|
||||
// TODO: Assert all the entries created earlier are deleted.
|
||||
}
|
||||
|
||||
// The vault owner is the transaction submitter.
|
||||
// If that account is missing,
|
||||
// then `preclaim` throws an exception.
|
||||
|
||||
SUBCASE("insufficient fee")
|
||||
{
|
||||
env(tx, fee(env.current()->fees().base), ter(telINSUF_FEE_P));
|
||||
env.close();
|
||||
}
|
||||
|
||||
SUBCASE("insufficient reserve")
|
||||
{
|
||||
// It is possible to construct a complicated mathematical
|
||||
// expression for this amount, but it is sadly not easy.
|
||||
env(pay(owner, issuer, XRP(775)));
|
||||
env.close();
|
||||
env(tx, ter(tecINSUFFICIENT_RESERVE));
|
||||
env.close();
|
||||
}
|
||||
|
||||
SUBCASE("global freeze")
|
||||
{
|
||||
env(fset(issuer, asfGlobalFreeze));
|
||||
env.close();
|
||||
env(tx, ter(tecFROZEN));
|
||||
env.close();
|
||||
}
|
||||
|
||||
SUBCASE("data too large")
|
||||
{
|
||||
tx[sfData] = blob257;
|
||||
env(tx, ter(temSTRING_TOO_LARGE));
|
||||
env.close();
|
||||
}
|
||||
|
||||
SUBCASE("metadata too large")
|
||||
{
|
||||
// This metadata is for the share token.
|
||||
tx[sfMPTokenMetadata] = blob1025;
|
||||
env(tx, ter(temSTRING_TOO_LARGE));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("MPT")
|
||||
{
|
||||
MPTTester mptt{env, issuer, {.fund = false}};
|
||||
mptt.create({.flags = tfMPTCanTransfer | tfMPTCanLock});
|
||||
Asset asset = mptt.issuanceID();
|
||||
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||
|
||||
SUBCASE("create")
|
||||
{
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
SUBCASE("update")
|
||||
{
|
||||
auto tx = vault.set({.owner = owner, .id = keylet.key});
|
||||
|
||||
SUBCASE("happy path")
|
||||
{
|
||||
tx[sfData] = "ABCD";
|
||||
tx[sfAssetMaximum] = 123;
|
||||
env(tx);
|
||||
env.close();
|
||||
}
|
||||
|
||||
SUBCASE("not the owner")
|
||||
{
|
||||
tx[sfAccount] = issuer.human();
|
||||
env(tx, ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
}
|
||||
|
||||
SUBCASE("data too large")
|
||||
{
|
||||
tx[sfData] = blob257;
|
||||
env(tx, ter(temSTRING_TOO_LARGE));
|
||||
env.close();
|
||||
}
|
||||
|
||||
SUBCASE("shrinking assets")
|
||||
{
|
||||
// TODO: VaultSet (update) fail: AssetMaximum <
|
||||
// AssetTotal
|
||||
}
|
||||
|
||||
SUBCASE("immutable flags")
|
||||
{
|
||||
tx[sfFlags] = tfVaultPrivate;
|
||||
env(tx, ter(temINVALID_FLAG));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("global lock")
|
||||
{
|
||||
mptt.set({.account = issuer, .flags = tfMPTLock});
|
||||
env(tx, ter(tecLOCKED));
|
||||
}
|
||||
}
|
||||
|
||||
SUBCASE("MPT cannot transfer")
|
||||
{
|
||||
MPTTester mptt{env, issuer, {.fund = false}};
|
||||
mptt.create();
|
||||
Asset asset = mptt.issuanceID();
|
||||
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||
env(tx, ter(tecLOCKED));
|
||||
}
|
||||
|
||||
// TODO: VaultSet (update) succeed
|
||||
// TODO: VaultSet (update) fail: wrong owner
|
||||
// TODO: VaultSet (update) fail: Data too large
|
||||
// TODO: VaultSet (update) fail: tfPrivate flag
|
||||
// TODO: VaultSet (update) fail: tfShareNonTransferable flag
|
||||
// TODO: Payment to VaultSet.PA fail
|
||||
// TODO: VaultSet (update) fail: missing vault
|
||||
|
||||
BEAST_EXPECT(true);
|
||||
}
|
||||
|
||||
public:
|
||||
void
|
||||
run() override
|
||||
{
|
||||
EXECUTE(CreateUpdateDelete);
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE_PRIO(Vault, tx, ripple, 1);
|
||||
|
||||
} // namespace ripple
|
||||
@@ -44,6 +44,11 @@ public:
|
||||
|
||||
IOUAmount const zz(beast::zero);
|
||||
BEAST_EXPECT(z == zz);
|
||||
|
||||
// https://github.com/XRPLF/rippled/issues/5170
|
||||
IOUAmount const zzz{};
|
||||
BEAST_EXPECT(zzz == beast::zero);
|
||||
// BEAST_EXPECT(zzz == zz);
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -60,6 +60,7 @@
|
||||
#include <test/jtx/sendmax.h>
|
||||
#include <test/jtx/seq.h>
|
||||
#include <test/jtx/sig.h>
|
||||
#include <test/jtx/subcases.h>
|
||||
#include <test/jtx/tag.h>
|
||||
#include <test/jtx/tags.h>
|
||||
#include <test/jtx/ter.h>
|
||||
|
||||
@@ -28,6 +28,7 @@
|
||||
#include <test/jtx/envconfig.h>
|
||||
#include <test/jtx/require.h>
|
||||
#include <test/jtx/tags.h>
|
||||
#include <test/jtx/vault.h>
|
||||
#include <test/unit_test/SuiteJournal.h>
|
||||
#include <xrpld/app/ledger/Ledger.h>
|
||||
#include <xrpld/app/ledger/OpenLedger.h>
|
||||
@@ -452,6 +453,12 @@ public:
|
||||
std::uint32_t
|
||||
ownerCount(Account const& account) const;
|
||||
|
||||
Vault
|
||||
vault()
|
||||
{
|
||||
return Vault{*this};
|
||||
}
|
||||
|
||||
/** Return an account root.
|
||||
@return empty if the account does not exist.
|
||||
*/
|
||||
@@ -785,6 +792,9 @@ Env::rpc(std::string const& cmd, Args&&... args)
|
||||
std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
extern std::string blob257;
|
||||
extern std::string blob1025;
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
@@ -586,7 +586,10 @@ Env::disableFeature(uint256 const feature)
|
||||
app().config().features.erase(feature);
|
||||
}
|
||||
|
||||
} // namespace jtx
|
||||
// The strings are hexadecimal. 1 byte = 2 hexadecimal characters.
|
||||
std::string blob257(514, 'A');
|
||||
std::string blob1025(2050, 'B');
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
79
src/test/jtx/impl/subcases.cpp
Normal file
79
src/test/jtx/impl/subcases.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx/subcases.h>
|
||||
|
||||
#include <stdexcept>
|
||||
|
||||
namespace subcases {
|
||||
|
||||
Subcase::Subcase(Context& context, char const* name)
|
||||
: context_(context), name_(name)
|
||||
{
|
||||
}
|
||||
|
||||
Subcase::operator bool() const
|
||||
{
|
||||
auto& _ = context_;
|
||||
++_.level;
|
||||
if (_.level >= MAXIMUM_SUBCASE_DEPTH)
|
||||
throw std::logic_error("maximum subcase depth exceeded");
|
||||
if (_.entered < _.level && _.skip[_.level] == _.skipped)
|
||||
{
|
||||
_.entered = _.level;
|
||||
_.names[_.level] = name_;
|
||||
_.skipped = 0;
|
||||
return true;
|
||||
}
|
||||
++_.skipped;
|
||||
return false;
|
||||
}
|
||||
|
||||
Subcase::~Subcase()
|
||||
{
|
||||
auto& _ = context_;
|
||||
if (_.level == _.entered && _.skipped == 0)
|
||||
{
|
||||
// We are destroying the leaf subcase that executed on this pass.
|
||||
// We call `suite::testcase()` here, after the subcase is finished,
|
||||
// because only now do we know which subcase was the leaf,
|
||||
// and we only want to print one name line for each subcase.
|
||||
_.suite.testcase(_.name());
|
||||
}
|
||||
if (_.skipped == 0)
|
||||
{
|
||||
++_.skip[_.level];
|
||||
_.skip[_.level + 1] = 0;
|
||||
}
|
||||
--_.level;
|
||||
}
|
||||
|
||||
void
|
||||
execute(beast::unit_test::suite* suite, char const* name, Supercase supercase)
|
||||
{
|
||||
Context context{*suite};
|
||||
context.names[0] = name;
|
||||
do
|
||||
{
|
||||
context.lap();
|
||||
supercase(context);
|
||||
} while (context.skipped != 0);
|
||||
}
|
||||
|
||||
} // namespace subcases
|
||||
69
src/test/jtx/impl/vault.cpp
Normal file
69
src/test/jtx/impl/vault.cpp
Normal file
@@ -0,0 +1,69 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/jtx/Env.h>
|
||||
#include <test/jtx/vault.h>
|
||||
|
||||
#include <xrpl/json/json_value.h>
|
||||
#include <xrpl/protocol/STAmount.h>
|
||||
#include <xrpl/protocol/jss.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
std::tuple<Json::Value, Keylet>
|
||||
Vault::create(CreateArgs const& args)
|
||||
{
|
||||
auto keylet = keylet::vault(args.owner.id(), env.seq(args.owner));
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::VaultCreate;
|
||||
jv[jss::Account] = args.owner.human();
|
||||
jv[jss::Asset] = to_json(args.asset);
|
||||
jv[jss::Fee] = STAmount(env.current()->fees().increment).getJson();
|
||||
if (args.flags)
|
||||
jv[jss::Flags] = *args.flags;
|
||||
return {jv, keylet};
|
||||
}
|
||||
|
||||
Json::Value
|
||||
Vault::set(SetArgs const& args)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::VaultSet;
|
||||
jv[jss::Account] = args.owner.human();
|
||||
jv[jss::VaultID] = to_string(args.id);
|
||||
return jv;
|
||||
}
|
||||
|
||||
Json::Value
|
||||
Vault::del(DeleteArgs const& args)
|
||||
{
|
||||
Json::Value jv;
|
||||
jv[jss::TransactionType] = jss::VaultDelete;
|
||||
jv[jss::Account] = args.owner.human();
|
||||
jv[jss::VaultID] = to_string(args.id);
|
||||
return jv;
|
||||
}
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
131
src/test/jtx/subcases.h
Normal file
131
src/test/jtx/subcases.h
Normal file
@@ -0,0 +1,131 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TEST_JTX_SUBCASES_H_INCLUDED
|
||||
#define RIPPLE_TEST_JTX_SUBCASES_H_INCLUDED
|
||||
|
||||
#include <xrpl/beast/unit_test/suite.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <functional>
|
||||
|
||||
namespace subcases {
|
||||
|
||||
constexpr std::size_t MAXIMUM_SUBCASE_DEPTH = 10;
|
||||
|
||||
/**
|
||||
* This short library implements a pattern found in doctest and Catch:
|
||||
*
|
||||
* TEST_CASE(testName) {
|
||||
* // setup
|
||||
* SUBCASE("one") {
|
||||
* // actions and assertions
|
||||
* }
|
||||
* SUBCASE("two") {
|
||||
* // actions and assertions
|
||||
* }
|
||||
* SUBCASE("three") {
|
||||
* // actions and assertions
|
||||
* }
|
||||
* // assertions before teardown
|
||||
* }
|
||||
*
|
||||
* EXECUTE(testName);
|
||||
*
|
||||
* In short:
|
||||
*
|
||||
* - Top-level test cases are declared with `TEST_CASE(name)`.
|
||||
* The name must be a legal identifier.
|
||||
* It will become the name of a function.
|
||||
* - Subcases are declared with `SUBCASE("description")`.
|
||||
* Descriptions do not need to be unique.
|
||||
* - Test cases are executed with `EXECUTE(name)`,
|
||||
* where `name` is the one that was passed to `TEST_CASE`.
|
||||
* When executing a test case, it will loop,
|
||||
* executing exactly one leaf subcase in each pass,
|
||||
* until all subcases have executed.
|
||||
* The top-level test case is considered a subcase too.
|
||||
*
|
||||
* This lets test authors easily share common setup among multiple subcases.
|
||||
* Subcases can be nested up to `MAXIMUM_SUBCASE_DEPTH`.
|
||||
*/
|
||||
|
||||
struct Context
|
||||
{
|
||||
beast::unit_test::suite& suite;
|
||||
// The number of subcases to skip at each level to reach the next subcase.
|
||||
std::uint8_t skip[MAXIMUM_SUBCASE_DEPTH] = {0};
|
||||
// The subcase names at each level.
|
||||
char const* names[MAXIMUM_SUBCASE_DEPTH] = {""};
|
||||
// The current level.
|
||||
std::uint8_t level = 0;
|
||||
// The maximum depth at which we entered a subcase.
|
||||
std::uint8_t entered = 0;
|
||||
// The number of subcases we skipped on this or deeper levels
|
||||
// since entering a subcase.
|
||||
std::uint8_t skipped = 0;
|
||||
|
||||
std::string
|
||||
name() const
|
||||
{
|
||||
std::string n;
|
||||
for (auto i = 0; i <= level; ++i)
|
||||
{
|
||||
if (i != 0)
|
||||
{
|
||||
n += " > ";
|
||||
}
|
||||
n += names[i];
|
||||
}
|
||||
return n;
|
||||
}
|
||||
|
||||
void
|
||||
lap()
|
||||
{
|
||||
level = 0;
|
||||
entered = 0;
|
||||
skipped = 0;
|
||||
}
|
||||
};
|
||||
|
||||
struct Subcase
|
||||
{
|
||||
Context& context_;
|
||||
char const* name_;
|
||||
Subcase(Context& context, char const* name);
|
||||
~Subcase();
|
||||
/** Return true if we should enter this subcase. */
|
||||
operator bool() const;
|
||||
};
|
||||
|
||||
using Supercase = std::function<void(Context&)>;
|
||||
|
||||
void
|
||||
execute(beast::unit_test::suite* suite, char const* name, Supercase supercase);
|
||||
|
||||
} // namespace subcases
|
||||
|
||||
#define TEST_CASE(name) void name(subcases::Context& _09876)
|
||||
#define SUBCASE(name) if (subcases::Subcase _54321{_09876, name})
|
||||
#define SKIP(name) if (false)
|
||||
#define EXECUTE(name) \
|
||||
subcases::execute(this, #name, [&](auto& ctx) { name(ctx); })
|
||||
|
||||
#endif
|
||||
77
src/test/jtx/vault.h
Normal file
77
src/test/jtx/vault.h
Normal file
@@ -0,0 +1,77 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TEST_JTX_VAULT_H_INCLUDED
|
||||
#define RIPPLE_TEST_JTX_VAULT_H_INCLUDED
|
||||
|
||||
#include <test/jtx/Account.h>
|
||||
#include <xrpl/json/json_value.h>
|
||||
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
#include <xrpl/protocol/Asset.h>
|
||||
#include <xrpl/protocol/Keylet.h>
|
||||
|
||||
#include <optional>
|
||||
#include <tuple>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
namespace jtx {
|
||||
|
||||
class Env;
|
||||
|
||||
struct Vault
|
||||
{
|
||||
Env& env;
|
||||
|
||||
struct CreateArgs
|
||||
{
|
||||
Account owner;
|
||||
Asset asset;
|
||||
std::optional<std::uint32_t> flags{};
|
||||
};
|
||||
|
||||
/** Return a VaultCreate transaction and the Vault's expected keylet. */
|
||||
std::tuple<Json::Value, Keylet>
|
||||
create(CreateArgs const& args);
|
||||
|
||||
struct SetArgs
|
||||
{
|
||||
Account owner;
|
||||
uint256 id;
|
||||
};
|
||||
|
||||
Json::Value
|
||||
set(SetArgs const& args);
|
||||
|
||||
struct DeleteArgs
|
||||
{
|
||||
Account owner;
|
||||
uint256 id;
|
||||
};
|
||||
|
||||
Json::Value
|
||||
del(DeleteArgs const& args);
|
||||
};
|
||||
|
||||
} // namespace jtx
|
||||
} // namespace test
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -115,12 +115,14 @@ class Invariants_test : public beast::unit_test::suite
|
||||
sink.messages().str().starts_with("Invariant failed:") ||
|
||||
sink.messages().str().starts_with(
|
||||
"Transaction caused an exception"));
|
||||
// uncomment if you want to log the invariant failure message
|
||||
// log << " --> " << sink.messages().str() << std::endl;
|
||||
for (auto const& m : expect_logs)
|
||||
{
|
||||
BEAST_EXPECT(
|
||||
sink.messages().str().find(m) != std::string::npos);
|
||||
if (sink.messages().str().find(m) == std::string::npos)
|
||||
{
|
||||
// uncomment if you want to log the invariant failure
|
||||
// message log << " --> " << m << std::endl;
|
||||
fail();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -606,7 +608,7 @@ class Invariants_test : public beast::unit_test::suite
|
||||
testcase << "valid new account root";
|
||||
|
||||
doInvariantCheck(
|
||||
{{"account root created by a non-Payment"}},
|
||||
{{"account root created illegally"}},
|
||||
[](Account const&, Account const&, ApplyContext& ac) {
|
||||
// Insert a new account root created by a non-payment into
|
||||
// the view.
|
||||
|
||||
@@ -336,22 +336,22 @@ DirectIPaymentStep::quality(ReadView const& sb, QualityDirection qDir) const
|
||||
if (!sle)
|
||||
return QUALITY_ONE;
|
||||
|
||||
auto const& field = [this, qDir]() -> SF_UINT32 const& {
|
||||
auto const& field = *[this, qDir]() {
|
||||
if (qDir == QualityDirection::in)
|
||||
{
|
||||
// compute dst quality in
|
||||
if (this->dst_ < this->src_)
|
||||
return sfLowQualityIn;
|
||||
return &sfLowQualityIn;
|
||||
else
|
||||
return sfHighQualityIn;
|
||||
return &sfHighQualityIn;
|
||||
}
|
||||
else
|
||||
{
|
||||
// compute src quality out
|
||||
if (this->src_ < this->dst_)
|
||||
return sfLowQualityOut;
|
||||
return &sfLowQualityOut;
|
||||
else
|
||||
return sfHighQualityOut;
|
||||
return &sfHighQualityOut;
|
||||
}
|
||||
}();
|
||||
|
||||
|
||||
@@ -220,64 +220,39 @@ applyCreate(
|
||||
auto const ammKeylet = keylet::amm(amount.issue(), amount2.issue());
|
||||
|
||||
// Mitigate same account exists possibility
|
||||
auto const ammAccount = [&]() -> Expected<AccountID, TER> {
|
||||
std::uint16_t constexpr maxAccountAttempts = 256;
|
||||
for (auto p = 0; p < maxAccountAttempts; ++p)
|
||||
{
|
||||
auto const ammAccount =
|
||||
ammAccountID(p, sb.info().parentHash, ammKeylet.key);
|
||||
if (!sb.read(keylet::account(ammAccount)))
|
||||
return ammAccount;
|
||||
}
|
||||
return Unexpected(tecDUPLICATE);
|
||||
}();
|
||||
|
||||
auto const maybeAccount = createPseudoAccount(sb, ammKeylet.key);
|
||||
// AMM account already exists (should not happen)
|
||||
if (!ammAccount)
|
||||
if (!maybeAccount)
|
||||
{
|
||||
JLOG(j_.error()) << "AMM Instance: AMM already exists.";
|
||||
return {ammAccount.error(), false};
|
||||
return {maybeAccount.error(), false};
|
||||
}
|
||||
auto& account = *maybeAccount;
|
||||
auto const accountId = (*account)[sfAccount];
|
||||
|
||||
// LP Token already exists. (should not happen)
|
||||
auto const lptIss = ammLPTIssue(
|
||||
amount.issue().currency, amount2.issue().currency, *ammAccount);
|
||||
if (sb.read(keylet::line(*ammAccount, lptIss)))
|
||||
amount.issue().currency, amount2.issue().currency, accountId);
|
||||
if (sb.read(keylet::line(accountId, lptIss)))
|
||||
{
|
||||
JLOG(j_.error()) << "AMM Instance: LP Token already exists.";
|
||||
return {tecDUPLICATE, false};
|
||||
}
|
||||
|
||||
// Create AMM Root Account.
|
||||
auto sleAMMRoot = std::make_shared<SLE>(keylet::account(*ammAccount));
|
||||
sleAMMRoot->setAccountID(sfAccount, *ammAccount);
|
||||
sleAMMRoot->setFieldAmount(sfBalance, STAmount{});
|
||||
std::uint32_t const seqno{
|
||||
ctx_.view().rules().enabled(featureDeletableAccounts)
|
||||
? ctx_.view().seq()
|
||||
: 1};
|
||||
sleAMMRoot->setFieldU32(sfSequence, seqno);
|
||||
// Ignore reserves requirement, disable the master key, allow default
|
||||
// rippling (AMM LPToken can be used in payments and offer crossing but
|
||||
// not as a token in another AMM), and enable deposit authorization to
|
||||
// prevent payments into AMM.
|
||||
// Note, that the trustlines created by AMM have 0 credit limit.
|
||||
// This prevents shifting the balance between accounts via AMM,
|
||||
// or sending unsolicited LPTokens. This is a desired behavior.
|
||||
// A user can only receive LPTokens through affirmative action -
|
||||
// either an AMMDeposit, TrustSet, crossing an offer, etc.
|
||||
sleAMMRoot->setFieldU32(
|
||||
sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
|
||||
// Link the root account and AMM object
|
||||
sleAMMRoot->setFieldH256(sfAMMID, ammKeylet.key);
|
||||
sb.insert(sleAMMRoot);
|
||||
account->setFieldH256(sfAMMID, ammKeylet.key);
|
||||
|
||||
// Calculate initial LPT balance.
|
||||
auto const lpTokens = ammLPTokens(amount, amount2, lptIss);
|
||||
|
||||
// Create ltAMM
|
||||
auto ammSle = std::make_shared<SLE>(ammKeylet);
|
||||
ammSle->setAccountID(sfAccount, *ammAccount);
|
||||
ammSle->setAccountID(sfAccount, accountId);
|
||||
ammSle->setFieldAmount(sfLPTokenBalance, lpTokens);
|
||||
auto const& [issue1, issue2] = std::minmax(amount.issue(), amount2.issue());
|
||||
ammSle->setFieldIssue(sfAsset, STIssue{sfAsset, issue1});
|
||||
@@ -287,14 +262,7 @@ applyCreate(
|
||||
ctx_.view(), ammSle, account_, lptIss, ctx_.tx[sfTradingFee]);
|
||||
|
||||
// Add owner directory to link the root account and AMM object.
|
||||
if (auto const page = sb.dirInsert(
|
||||
keylet::ownerDir(*ammAccount),
|
||||
ammSle->key(),
|
||||
describeOwnerDir(*ammAccount)))
|
||||
{
|
||||
ammSle->setFieldU64(sfOwnerNode, *page);
|
||||
}
|
||||
else
|
||||
if (auto ter = dirLink(sb, accountId, ammSle); ter)
|
||||
{
|
||||
JLOG(j_.debug()) << "AMM Instance: failed to insert owner dir";
|
||||
return {tecDIR_FULL, false};
|
||||
@@ -302,7 +270,7 @@ applyCreate(
|
||||
sb.insert(ammSle);
|
||||
|
||||
// Send LPT to LP.
|
||||
auto res = accountSend(sb, *ammAccount, account_, lpTokens, ctx_.journal);
|
||||
auto res = accountSend(sb, accountId, account_, lpTokens, ctx_.journal);
|
||||
if (res != tesSUCCESS)
|
||||
{
|
||||
JLOG(j_.debug()) << "AMM Instance: failed to send LPT " << lpTokens;
|
||||
@@ -313,7 +281,7 @@ applyCreate(
|
||||
if (auto const res = accountSend(
|
||||
sb,
|
||||
account_,
|
||||
*ammAccount,
|
||||
accountId,
|
||||
amount,
|
||||
ctx_.journal,
|
||||
WaiveTransferFee::Yes))
|
||||
@@ -322,7 +290,7 @@ applyCreate(
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (SLE::pointer sleRippleState =
|
||||
sb.peek(keylet::line(*ammAccount, amount.issue()));
|
||||
sb.peek(keylet::line(accountId, amount.issue()));
|
||||
!sleRippleState)
|
||||
return tecINTERNAL;
|
||||
else
|
||||
@@ -351,7 +319,7 @@ applyCreate(
|
||||
return {res, false};
|
||||
}
|
||||
|
||||
JLOG(j_.debug()) << "AMM Instance: success " << *ammAccount << " "
|
||||
JLOG(j_.debug()) << "AMM Instance: success " << accountId << " "
|
||||
<< ammKeylet.key << " " << lpTokens << " " << amount << " "
|
||||
<< amount2;
|
||||
auto addOrderBook =
|
||||
|
||||
@@ -327,7 +327,8 @@ AccountRootsNotDeleted::finalize(
|
||||
// A successful AccountDelete or AMMDelete MUST delete exactly
|
||||
// one account root.
|
||||
if ((tx.getTxnType() == ttACCOUNT_DELETE ||
|
||||
tx.getTxnType() == ttAMM_DELETE) &&
|
||||
tx.getTxnType() == ttAMM_DELETE ||
|
||||
tx.getTxnType() == ttVAULT_DELETE) &&
|
||||
result == tesSUCCESS)
|
||||
{
|
||||
if (accountsDeleted_ == 1)
|
||||
@@ -485,6 +486,7 @@ LedgerEntryTypesMatch::visitEntry(
|
||||
case ltMPTOKEN_ISSUANCE:
|
||||
case ltMPTOKEN:
|
||||
case ltCREDENTIAL:
|
||||
case ltVAULT:
|
||||
break;
|
||||
default:
|
||||
invalidTypeAdded_ = true;
|
||||
@@ -586,6 +588,7 @@ ValidNewAccountRoot::finalize(
|
||||
|
||||
// From this point on we know exactly one account was created.
|
||||
if ((tx.getTxnType() == ttPAYMENT || tx.getTxnType() == ttAMM_CREATE ||
|
||||
tx.getTxnType() == ttVAULT_CREATE ||
|
||||
tx.getTxnType() == ttXCHAIN_ADD_CLAIM_ATTESTATION ||
|
||||
tx.getTxnType() == ttXCHAIN_ADD_ACCOUNT_CREATE_ATTESTATION) &&
|
||||
result == tesSUCCESS)
|
||||
@@ -602,9 +605,7 @@ ValidNewAccountRoot::finalize(
|
||||
return true;
|
||||
}
|
||||
|
||||
JLOG(j.fatal()) << "Invariant failed: account root created "
|
||||
"by a non-Payment, by an unsuccessful transaction, "
|
||||
"or by AMM";
|
||||
JLOG(j.fatal()) << "Invariant failed: account root created illegally";
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -992,28 +993,30 @@ ValidMPTIssuance::finalize(
|
||||
{
|
||||
if (result == tesSUCCESS)
|
||||
{
|
||||
if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_CREATE)
|
||||
if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_CREATE ||
|
||||
tx.getTxnType() == ttVAULT_CREATE)
|
||||
{
|
||||
if (mptIssuancesCreated_ == 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT issuance creation "
|
||||
JLOG(j.fatal()) << "Invariant failed: transaction "
|
||||
"succeeded without creating a MPT issuance";
|
||||
}
|
||||
else if (mptIssuancesDeleted_ != 0)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT issuance creation "
|
||||
JLOG(j.fatal()) << "Invariant failed: transaction "
|
||||
"succeeded while removing MPT issuances";
|
||||
}
|
||||
else if (mptIssuancesCreated_ > 1)
|
||||
{
|
||||
JLOG(j.fatal()) << "Invariant failed: MPT issuance creation "
|
||||
JLOG(j.fatal()) << "Invariant failed: transaction "
|
||||
"succeeded but created multiple issuances";
|
||||
}
|
||||
|
||||
return mptIssuancesCreated_ == 1 && mptIssuancesDeleted_ == 0;
|
||||
}
|
||||
|
||||
if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_DESTROY)
|
||||
if (tx.getTxnType() == ttMPTOKEN_ISSUANCE_DESTROY ||
|
||||
tx.getTxnType() == ttVAULT_DELETE)
|
||||
{
|
||||
if (mptIssuancesDeleted_ == 0)
|
||||
{
|
||||
|
||||
@@ -141,7 +141,7 @@ MPTokenAuthorize::authorize(
|
||||
beast::Journal journal,
|
||||
MPTAuthorizeArgs const& args)
|
||||
{
|
||||
auto const sleAcct = view.peek(keylet::account(args.account));
|
||||
auto const sleAcct = view.peek(keylet::account(args.accountID));
|
||||
if (!sleAcct)
|
||||
return tecINTERNAL;
|
||||
|
||||
@@ -150,19 +150,22 @@ MPTokenAuthorize::authorize(
|
||||
// `holderID` is NOT used
|
||||
if (!args.holderID)
|
||||
{
|
||||
auto const mptokenKey =
|
||||
keylet::mptoken(args.mptIssuanceID, args.accountID);
|
||||
auto sleMpt = view.peek(mptokenKey);
|
||||
|
||||
// When a holder wants to unauthorize/delete a MPT, the ledger must
|
||||
// - delete mptokenKey from owner directory
|
||||
// - delete the MPToken
|
||||
if (args.flags & tfMPTUnauthorize)
|
||||
{
|
||||
auto const mptokenKey =
|
||||
keylet::mptoken(args.mptIssuanceID, args.account);
|
||||
auto const sleMpt = view.peek(mptokenKey);
|
||||
if (!sleMpt || (*sleMpt)[sfMPTAmount] != 0)
|
||||
return tecINTERNAL;
|
||||
if (!sleMpt)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
if ((*sleMpt)[sfMPTAmount] != 0)
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
if (!view.dirRemove(
|
||||
keylet::ownerDir(args.account),
|
||||
keylet::ownerDir(args.accountID),
|
||||
(*sleMpt)[sfOwnerNode],
|
||||
sleMpt->key(),
|
||||
false))
|
||||
@@ -191,27 +194,19 @@ MPTokenAuthorize::authorize(
|
||||
if (args.priorBalance < reserveCreate)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
auto const mptokenKey =
|
||||
keylet::mptoken(args.mptIssuanceID, args.account);
|
||||
if (sleMpt)
|
||||
return tecDUPLICATE;
|
||||
sleMpt = std::make_shared<SLE>(mptokenKey);
|
||||
|
||||
auto const ownerNode = view.dirInsert(
|
||||
keylet::ownerDir(args.account),
|
||||
mptokenKey,
|
||||
describeOwnerDir(args.account));
|
||||
|
||||
if (!ownerNode)
|
||||
return tecDIR_FULL;
|
||||
|
||||
auto mptoken = std::make_shared<SLE>(mptokenKey);
|
||||
(*mptoken)[sfAccount] = args.account;
|
||||
(*mptoken)[sfMPTokenIssuanceID] = args.mptIssuanceID;
|
||||
(*mptoken)[sfFlags] = 0;
|
||||
(*mptoken)[sfOwnerNode] = *ownerNode;
|
||||
view.insert(mptoken);
|
||||
|
||||
// Update owner count.
|
||||
if (auto ter = dirLink(view, args.accountID, sleMpt))
|
||||
return ter;
|
||||
adjustOwnerCount(view, sleAcct, 1, journal);
|
||||
|
||||
(*sleMpt)[sfAccount] = args.accountID;
|
||||
(*sleMpt)[sfMPTokenIssuanceID] = args.mptIssuanceID;
|
||||
(*sleMpt)[sfFlags] = 0;
|
||||
view.insert(sleMpt);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
@@ -223,7 +218,7 @@ MPTokenAuthorize::authorize(
|
||||
// If the account that submitted this tx is the issuer of the MPT
|
||||
// Note: `account_` is issuer's account
|
||||
// `holderID` is holder's account
|
||||
if (args.account != (*sleMptIssuance)[sfIssuer])
|
||||
if (args.accountID != (*sleMptIssuance)[sfIssuer])
|
||||
return tecINTERNAL;
|
||||
|
||||
auto const sleMpt =
|
||||
@@ -259,7 +254,7 @@ MPTokenAuthorize::doApply()
|
||||
ctx_.journal,
|
||||
{.priorBalance = mPriorBalance,
|
||||
.mptIssuanceID = tx[sfMPTokenIssuanceID],
|
||||
.account = account_,
|
||||
.accountID = account_,
|
||||
.flags = tx.getFlags(),
|
||||
.holderID = tx[~sfHolder]});
|
||||
}
|
||||
|
||||
@@ -26,11 +26,11 @@ namespace ripple {
|
||||
|
||||
struct MPTAuthorizeArgs
|
||||
{
|
||||
XRPAmount const& priorBalance;
|
||||
XRPAmount const& priorBalance{};
|
||||
uint192 const& mptIssuanceID;
|
||||
AccountID const& account;
|
||||
std::uint32_t flags;
|
||||
std::optional<AccountID> holderID;
|
||||
AccountID const& accountID;
|
||||
std::uint32_t flags{};
|
||||
std::optional<AccountID> holderID{};
|
||||
};
|
||||
|
||||
class MPTokenAuthorize : public Transactor
|
||||
|
||||
@@ -67,7 +67,7 @@ MPTokenIssuanceCreate::preflight(PreflightContext const& ctx)
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
Expected<MPTID, TER>
|
||||
MPTokenIssuanceCreate::create(
|
||||
ApplyView& view,
|
||||
beast::Journal journal,
|
||||
@@ -75,14 +75,10 @@ MPTokenIssuanceCreate::create(
|
||||
{
|
||||
auto const acct = view.peek(keylet::account(args.account));
|
||||
if (!acct)
|
||||
return tecINTERNAL;
|
||||
return Unexpected(tecINTERNAL);
|
||||
|
||||
if (args.priorBalance <
|
||||
view.fees().accountReserve((*acct)[sfOwnerCount] + 1))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
auto const mptIssuanceKeylet =
|
||||
keylet::mptIssuance(args.sequence, args.account);
|
||||
auto mptId = makeMptID(args.sequence, args.account);
|
||||
auto const mptIssuanceKeylet = keylet::mptIssuance(mptId);
|
||||
|
||||
// create the MPTokenIssuance
|
||||
{
|
||||
@@ -92,7 +88,7 @@ MPTokenIssuanceCreate::create(
|
||||
describeOwnerDir(args.account));
|
||||
|
||||
if (!ownerNode)
|
||||
return tecDIR_FULL;
|
||||
return Unexpected(tecDIR_FULL);
|
||||
|
||||
auto mptIssuance = std::make_shared<SLE>(mptIssuanceKeylet);
|
||||
(*mptIssuance)[sfFlags] = args.flags & ~tfUniversal;
|
||||
@@ -119,24 +115,29 @@ MPTokenIssuanceCreate::create(
|
||||
// Update owner count.
|
||||
adjustOwnerCount(view, acct, 1, journal);
|
||||
|
||||
return tesSUCCESS;
|
||||
return mptId;
|
||||
}
|
||||
|
||||
TER
|
||||
MPTokenIssuanceCreate::doApply()
|
||||
{
|
||||
auto const& tx = ctx_.tx;
|
||||
return create(
|
||||
ctx_.view(),
|
||||
ctx_.journal,
|
||||
{.priorBalance = mPriorBalance,
|
||||
.account = account_,
|
||||
|
||||
auto const acct = view().peek(keylet::account(account_));
|
||||
if (mPriorBalance < view().fees().accountReserve((*acct)[sfOwnerCount] + 1))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
auto result = create(
|
||||
view(),
|
||||
j_,
|
||||
{.account = account_,
|
||||
.sequence = tx.getSeqProxy().value(),
|
||||
.flags = tx.getFlags(),
|
||||
.maxAmount = tx[~sfMaximumAmount],
|
||||
.assetScale = tx[~sfAssetScale],
|
||||
.transferFee = tx[~sfTransferFee],
|
||||
.metadata = tx[~sfMPTokenMetadata]});
|
||||
return result ? tesSUCCESS : result.error();
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -21,19 +21,20 @@
|
||||
#define RIPPLE_TX_MPTOKENISSUANCECREATE_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
#include <xrpl/basics/Expected.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
struct MPTCreateArgs
|
||||
{
|
||||
XRPAmount const& priorBalance;
|
||||
AccountID const& account;
|
||||
std::uint32_t sequence;
|
||||
std::uint32_t flags;
|
||||
std::optional<std::uint64_t> maxAmount;
|
||||
std::optional<std::uint8_t> assetScale;
|
||||
std::optional<std::uint16_t> transferFee;
|
||||
std::optional<Slice> const& metadata;
|
||||
std::uint32_t flags = 0;
|
||||
std::optional<std::uint64_t> maxAmount{};
|
||||
std::optional<std::uint8_t> assetScale{};
|
||||
std::optional<std::uint16_t> transferFee{};
|
||||
std::optional<Slice> const& metadata{};
|
||||
};
|
||||
|
||||
class MPTokenIssuanceCreate : public Transactor
|
||||
@@ -51,7 +52,7 @@ public:
|
||||
TER
|
||||
doApply() override;
|
||||
|
||||
static TER
|
||||
static Expected<MPTID, TER>
|
||||
create(ApplyView& view, beast::Journal journal, MPTCreateArgs const& args);
|
||||
};
|
||||
|
||||
|
||||
@@ -63,22 +63,39 @@ MPTokenIssuanceDestroy::preclaim(PreclaimContext const& ctx)
|
||||
}
|
||||
|
||||
TER
|
||||
MPTokenIssuanceDestroy::doApply()
|
||||
MPTokenIssuanceDestroy::destroy(
|
||||
ApplyView& view,
|
||||
beast::Journal journal,
|
||||
MPTDestroyArgs const& args)
|
||||
{
|
||||
auto const mpt =
|
||||
view().peek(keylet::mptIssuance(ctx_.tx[sfMPTokenIssuanceID]));
|
||||
if (account_ != mpt->getAccountID(sfIssuer))
|
||||
return tecINTERNAL;
|
||||
auto const mpt = view.peek(keylet::mptIssuance(args.issuanceID));
|
||||
if (!mpt)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
if (!view().dirRemove(
|
||||
keylet::ownerDir(account_), (*mpt)[sfOwnerNode], mpt->key(), false))
|
||||
if ((*mpt)[sfIssuer] != args.account)
|
||||
return tecNO_PERMISSION;
|
||||
auto const& issuer = args.account;
|
||||
|
||||
if ((*mpt)[~sfOutstandingAmount] != 0)
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
if (!view.dirRemove(
|
||||
keylet::ownerDir(issuer), (*mpt)[sfOwnerNode], mpt->key(), false))
|
||||
return tefBAD_LEDGER;
|
||||
|
||||
view().erase(mpt);
|
||||
|
||||
adjustOwnerCount(view(), view().peek(keylet::account(account_)), -1, j_);
|
||||
|
||||
view.erase(mpt);
|
||||
adjustOwnerCount(view, view.peek(keylet::account(issuer)), -1, journal);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
MPTokenIssuanceDestroy::doApply()
|
||||
{
|
||||
return destroy(
|
||||
view(),
|
||||
j_,
|
||||
{.account = ctx_.tx[sfAccount],
|
||||
.issuanceID = ctx_.tx[sfMPTokenIssuanceID]});
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -24,6 +24,12 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
struct MPTDestroyArgs
|
||||
{
|
||||
AccountID const& account;
|
||||
MPTID issuanceID;
|
||||
};
|
||||
|
||||
class MPTokenIssuanceDestroy : public Transactor
|
||||
{
|
||||
public:
|
||||
@@ -39,6 +45,12 @@ public:
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
static TER
|
||||
destroy(
|
||||
ApplyView& view,
|
||||
beast::Journal journal,
|
||||
MPTDestroyArgs const& args);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
62
src/xrpld/app/tx/detail/VaultClawback.cpp
Normal file
62
src/xrpld/app/tx/detail/VaultClawback.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2022 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/tx/detail/VaultClawback.h>
|
||||
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
NotTEC
|
||||
VaultClawback::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureSingleAssetVault))
|
||||
return temDISABLED;
|
||||
|
||||
if (auto const ter = preflight1(ctx))
|
||||
return ter;
|
||||
|
||||
if (ctx.tx.getFlags() & tfUniversalMask)
|
||||
return temINVALID_FLAG;
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
VaultClawback::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
|
||||
if (!vault)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
VaultClawback::doApply()
|
||||
{
|
||||
auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
|
||||
if (!vault)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
48
src/xrpld/app/tx/detail/VaultClawback.h
Normal file
48
src/xrpld/app/tx/detail/VaultClawback.h
Normal file
@@ -0,0 +1,48 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_VAULTCLAWBACK_H_INCLUDED
|
||||
#define RIPPLE_TX_VAULTCLAWBACK_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class VaultClawback : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit VaultClawback(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
151
src/xrpld/app/tx/detail/VaultCreate.cpp
Normal file
151
src/xrpld/app/tx/detail/VaultCreate.cpp
Normal file
@@ -0,0 +1,151 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2023 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/tx/detail/MPTokenIssuanceCreate.h>
|
||||
#include <xrpld/app/tx/detail/VaultCreate.h>
|
||||
#include <xrpld/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
NotTEC
|
||||
VaultCreate::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureSingleAssetVault))
|
||||
return temDISABLED;
|
||||
|
||||
if (auto const ter = preflight1(ctx))
|
||||
return ter;
|
||||
|
||||
if (ctx.tx.getFlags() & tfVaultCreateMask)
|
||||
return temINVALID_FLAG;
|
||||
|
||||
if (auto const data = ctx.tx[~sfData])
|
||||
{
|
||||
if (data->length() > maxVaultDataLength)
|
||||
return temSTRING_TOO_LARGE;
|
||||
}
|
||||
|
||||
// This block is copied from `MPTokenIssuanceCreate::preflight`.
|
||||
if (auto const metadata = ctx.tx[~sfMPTokenMetadata])
|
||||
{
|
||||
if (metadata->length() == 0 ||
|
||||
metadata->length() > maxMPTokenMetadataLength)
|
||||
return temSTRING_TOO_LARGE;
|
||||
}
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
VaultCreate::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
// One reserve increment is typically much greater than one base fee.
|
||||
return view.fees().increment;
|
||||
}
|
||||
|
||||
TER
|
||||
VaultCreate::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto asset = ctx.tx[sfAsset];
|
||||
if (asset.holds<MPTIssue>())
|
||||
{
|
||||
auto mptID = asset.get<MPTIssue>().getMptID();
|
||||
auto issuance = ctx.view.read(keylet::mptIssuance(mptID));
|
||||
if (issuance->getFlags() & lsfMPTLocked)
|
||||
return tecLOCKED;
|
||||
if ((issuance->getFlags() & lsfMPTCanTransfer) == 0)
|
||||
return tecLOCKED;
|
||||
}
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
VaultCreate::doApply()
|
||||
{
|
||||
// All return codes in `doApply` must be `tec`, `ter`, or `tes`.
|
||||
// As we move checks into `preflight` and `preclaim`,
|
||||
// we can consider downgrading them to `tef` or `tem`.
|
||||
|
||||
auto const& tx = ctx_.tx;
|
||||
auto const& ownerId = account_;
|
||||
auto sequence = tx.getSequence();
|
||||
|
||||
auto owner = view().peek(keylet::account(ownerId));
|
||||
auto vault = std::make_shared<SLE>(keylet::vault(ownerId, sequence));
|
||||
|
||||
if (auto ter = dirLink(view(), ownerId, vault))
|
||||
return ter;
|
||||
// Should the next 3 lines be folded into `dirLink`?
|
||||
adjustOwnerCount(view(), owner, 1, j_);
|
||||
auto ownerCount = owner->at(sfOwnerCount);
|
||||
if (mPriorBalance < view().fees().accountReserve(ownerCount))
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
|
||||
auto maybePseudo = createPseudoAccount(view(), vault->key());
|
||||
if (!maybePseudo)
|
||||
return maybePseudo.error();
|
||||
auto& pseudo = *maybePseudo;
|
||||
auto pseudoId = pseudo->at(sfAccount);
|
||||
|
||||
if (auto ter =
|
||||
addEmptyHolding(view(), pseudoId, mPriorBalance, tx[sfAsset], j_))
|
||||
return ter;
|
||||
|
||||
auto txFlags = tx.getFlags();
|
||||
std::uint32_t mptFlags = 0;
|
||||
if (!(txFlags & tfVaultShareNonTransferable))
|
||||
mptFlags |= (lsfMPTCanEscrow | lsfMPTCanTrade | lsfMPTCanTransfer);
|
||||
if (txFlags & tfVaultPrivate)
|
||||
mptFlags |= lsfMPTRequireAuth;
|
||||
|
||||
auto maybeShare = MPTokenIssuanceCreate::create(
|
||||
view(),
|
||||
j_,
|
||||
{
|
||||
.account = pseudoId,
|
||||
.sequence = 1,
|
||||
.flags = mptFlags,
|
||||
.metadata = tx[~sfMPTokenMetadata],
|
||||
});
|
||||
if (!maybeShare)
|
||||
return maybeShare.error();
|
||||
auto& share = *maybeShare;
|
||||
|
||||
vault->at(sfFlags) = txFlags & tfVaultPrivate;
|
||||
vault->at(sfSequence) = sequence;
|
||||
vault->at(sfOwner) = ownerId;
|
||||
vault->at(sfAccount) = pseudoId;
|
||||
vault->at(sfAsset) = tx[sfAsset];
|
||||
// Leave default values for AssetTotal and AssetAvailable, both zero.
|
||||
if (auto value = tx[~sfAssetMaximum])
|
||||
vault->at(sfAssetMaximum) = *value;
|
||||
vault->at(sfMPTokenIssuanceID) = share;
|
||||
if (auto value = tx[~sfData])
|
||||
vault->at(sfData) = *value;
|
||||
// No `LossUnrealized`.
|
||||
view().insert(vault);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
51
src/xrpld/app/tx/detail/VaultCreate.h
Normal file
51
src/xrpld/app/tx/detail/VaultCreate.h
Normal file
@@ -0,0 +1,51 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_VAULTCREATE_H_INCLUDED
|
||||
#define RIPPLE_TX_VAULTCREATE_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class VaultCreate : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit VaultCreate(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static XRPAmount
|
||||
calculateBaseFee(ReadView const& view, STTx const& tx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
105
src/xrpld/app/tx/detail/VaultDelete.cpp
Normal file
105
src/xrpld/app/tx/detail/VaultDelete.cpp
Normal file
@@ -0,0 +1,105 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2022 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/tx/detail/VaultDelete.h>
|
||||
|
||||
#include <xrpld/app/tx/detail/MPTokenIssuanceDestroy.h>
|
||||
#include <xrpld/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
NotTEC
|
||||
VaultDelete::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureSingleAssetVault))
|
||||
return temDISABLED;
|
||||
|
||||
if (auto const ter = preflight1(ctx))
|
||||
return ter;
|
||||
|
||||
if (ctx.tx.getFlags() & tfUniversalMask)
|
||||
return temINVALID_FLAG;
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
VaultDelete::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
|
||||
if (!vault)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
if (vault->at(sfOwner) != ctx.tx[sfAccount])
|
||||
return tecNO_PERMISSION;
|
||||
if (vault->at(sfAssetAvailable) != 0)
|
||||
return tecHAS_OBLIGATIONS;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
VaultDelete::doApply()
|
||||
{
|
||||
auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
|
||||
if (!vault)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// Destroy the asset holding.
|
||||
if (auto ter = removeEmptyHolding(
|
||||
view(), vault->at(sfAccount), vault->at(sfAsset), j_))
|
||||
return ter;
|
||||
|
||||
// Destroy the share issuance.
|
||||
if (auto ter = MPTokenIssuanceDestroy::destroy(
|
||||
view(),
|
||||
j_,
|
||||
{.account = vault->at(sfAccount),
|
||||
.issuanceID = vault->at(sfMPTokenIssuanceID)}))
|
||||
return ter;
|
||||
|
||||
// The psuedo-account's directory should have been deleted already.
|
||||
auto const& pseudoID = vault->at(sfAccount);
|
||||
if (view().peek(keylet::ownerDir(pseudoID)))
|
||||
return tecHAS_OBLIGATIONS;
|
||||
|
||||
// Destroy the pseudo-account.
|
||||
view().erase(view().peek(keylet::account(pseudoID)));
|
||||
|
||||
// Remove the vault from its owner's directory.
|
||||
auto const ownerID = vault->at(sfOwner);
|
||||
if (!view().dirRemove(
|
||||
keylet::ownerDir(ownerID),
|
||||
vault->at(sfOwnerNode),
|
||||
vault->key(),
|
||||
false))
|
||||
return tefBAD_LEDGER;
|
||||
auto const owner = view().peek(keylet::account(ownerID));
|
||||
if (!owner)
|
||||
return tefBAD_LEDGER;
|
||||
adjustOwnerCount(view(), owner, -1, j_);
|
||||
|
||||
// Destroy the vault.
|
||||
view().erase(vault);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
48
src/xrpld/app/tx/detail/VaultDelete.h
Normal file
48
src/xrpld/app/tx/detail/VaultDelete.h
Normal file
@@ -0,0 +1,48 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_VAULTDELETE_H_INCLUDED
|
||||
#define RIPPLE_TX_VAULTDELETE_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class VaultDelete : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit VaultDelete(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
79
src/xrpld/app/tx/detail/VaultDeposit.cpp
Normal file
79
src/xrpld/app/tx/detail/VaultDeposit.cpp
Normal file
@@ -0,0 +1,79 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2022 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/tx/detail/VaultDeposit.h>
|
||||
|
||||
#include <xrpld/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
NotTEC
|
||||
VaultDeposit::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureSingleAssetVault))
|
||||
return temDISABLED;
|
||||
|
||||
if (auto const ter = preflight1(ctx))
|
||||
return ter;
|
||||
|
||||
if (ctx.tx.getFlags() & tfUniversalMask)
|
||||
return temINVALID_FLAG;
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
VaultDeposit::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
|
||||
if (!vault)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
VaultDeposit::doApply()
|
||||
{
|
||||
auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
|
||||
if (!vault)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
auto amount = ctx_.tx[sfAmount];
|
||||
Asset const& asset = vault->at(sfAsset);
|
||||
if (amount.asset() != asset)
|
||||
return tecWRONG_ASSET;
|
||||
|
||||
if (accountHolds(view(), account_, asset,
|
||||
FreezeHandling::fhZERO_IF_FROZEN,
|
||||
AuthHandling::ahZERO_IF_UNAUTHORIZED, j_) < amount)
|
||||
{
|
||||
return tecINSUFFICIENT_FUNDS;
|
||||
}
|
||||
|
||||
// STAmount assetTotalNew{sfAssetTotal};
|
||||
// assetTotalNew = vault->at(sfAssetTotal) + amount;
|
||||
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
48
src/xrpld/app/tx/detail/VaultDeposit.h
Normal file
48
src/xrpld/app/tx/detail/VaultDeposit.h
Normal file
@@ -0,0 +1,48 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_VAULTDEPOSIT_H_INCLUDED
|
||||
#define RIPPLE_TX_VAULTDEPOSIT_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class VaultDeposit : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit VaultDeposit(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
83
src/xrpld/app/tx/detail/VaultSet.cpp
Normal file
83
src/xrpld/app/tx/detail/VaultSet.cpp
Normal file
@@ -0,0 +1,83 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2023 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/tx/detail/VaultSet.h>
|
||||
#include <xrpld/ledger/View.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
NotTEC
|
||||
VaultSet::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureSingleAssetVault))
|
||||
return temDISABLED;
|
||||
|
||||
if (auto const ter = preflight1(ctx))
|
||||
return ter;
|
||||
if (ctx.tx.getFlags() & tfUniversalMask)
|
||||
return temINVALID_FLAG;
|
||||
if (auto const data = ctx.tx[~sfData])
|
||||
{
|
||||
if (data->length() > maxVaultDataLength)
|
||||
return temSTRING_TOO_LARGE;
|
||||
}
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
VaultSet::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
VaultSet::doApply()
|
||||
{
|
||||
// All return codes in `doApply` must be `tec`, `ter`, or `tes`.
|
||||
// As we move checks into `preflight` and `preclaim`,
|
||||
// we can consider downgrading them to `tef` or `tem`.
|
||||
|
||||
auto const& tx = ctx_.tx;
|
||||
auto const& owner = account_;
|
||||
|
||||
// Update existing object.
|
||||
auto vault = view().peek({ltVAULT, tx[sfVaultID]});
|
||||
if (!vault)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
// Assert that submitter is the Owner.
|
||||
if (owner != vault->at(sfOwner))
|
||||
return tecNO_PERMISSION;
|
||||
|
||||
// Update mutable flags and fields if given.
|
||||
if (tx.isFieldPresent(sfData))
|
||||
vault->at(sfData) = tx[sfData];
|
||||
if (tx.isFieldPresent(sfAssetMaximum))
|
||||
vault->at(sfAssetMaximum) = tx[sfAssetMaximum];
|
||||
|
||||
view().update(vault);
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
48
src/xrpld/app/tx/detail/VaultSet.h
Normal file
48
src/xrpld/app/tx/detail/VaultSet.h
Normal file
@@ -0,0 +1,48 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_VAULTSET_H_INCLUDED
|
||||
#define RIPPLE_TX_VAULTSET_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class VaultSet : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit VaultSet(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
62
src/xrpld/app/tx/detail/VaultWithdraw.cpp
Normal file
62
src/xrpld/app/tx/detail/VaultWithdraw.cpp
Normal file
@@ -0,0 +1,62 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2022 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/app/tx/detail/VaultWithdraw.h>
|
||||
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/STNumber.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
NotTEC
|
||||
VaultWithdraw::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (!ctx.rules.enabled(featureSingleAssetVault))
|
||||
return temDISABLED;
|
||||
|
||||
if (auto const ter = preflight1(ctx))
|
||||
return ter;
|
||||
|
||||
if (ctx.tx.getFlags() & tfUniversalMask)
|
||||
return temINVALID_FLAG;
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
TER
|
||||
VaultWithdraw::preclaim(PreclaimContext const& ctx)
|
||||
{
|
||||
auto const vault = ctx.view.read(keylet::vault(ctx.tx[sfVaultID]));
|
||||
if (!vault)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
TER
|
||||
VaultWithdraw::doApply()
|
||||
{
|
||||
auto const vault = view().peek(keylet::vault(ctx_.tx[sfVaultID]));
|
||||
if (!vault)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
48
src/xrpld/app/tx/detail/VaultWithdraw.h
Normal file
48
src/xrpld/app/tx/detail/VaultWithdraw.h
Normal file
@@ -0,0 +1,48 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2024 Ripple Labs Inc.
|
||||
|
||||
Permission to use, copy, modify, and/or distribute this software for any
|
||||
purpose with or without fee is hereby granted, provided that the above
|
||||
copyright notice and this permission notice appear in all copies.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
||||
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
||||
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
||||
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
||||
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
||||
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
||||
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#ifndef RIPPLE_TX_VAULTWITHDRAW_H_INCLUDED
|
||||
#define RIPPLE_TX_VAULTWITHDRAW_H_INCLUDED
|
||||
|
||||
#include <xrpld/app/tx/detail/Transactor.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
class VaultWithdraw : public Transactor
|
||||
{
|
||||
public:
|
||||
static constexpr ConsequencesFactoryType ConsequencesFactory{Normal};
|
||||
|
||||
explicit VaultWithdraw(ApplyContext& ctx) : Transactor(ctx)
|
||||
{
|
||||
}
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
static TER
|
||||
preclaim(PreclaimContext const& ctx);
|
||||
|
||||
TER
|
||||
doApply() override;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
@@ -57,6 +57,12 @@
|
||||
#include <xrpld/app/tx/detail/SetRegularKey.h>
|
||||
#include <xrpld/app/tx/detail/SetSignerList.h>
|
||||
#include <xrpld/app/tx/detail/SetTrust.h>
|
||||
#include <xrpld/app/tx/detail/VaultClawback.h>
|
||||
#include <xrpld/app/tx/detail/VaultCreate.h>
|
||||
#include <xrpld/app/tx/detail/VaultDelete.h>
|
||||
#include <xrpld/app/tx/detail/VaultDeposit.h>
|
||||
#include <xrpld/app/tx/detail/VaultSet.h>
|
||||
#include <xrpld/app/tx/detail/VaultWithdraw.h>
|
||||
#include <xrpld/app/tx/detail/XChainBridge.h>
|
||||
#include <xrpl/protocol/TxFormats.h>
|
||||
|
||||
|
||||
@@ -39,8 +39,6 @@
|
||||
#include <memory>
|
||||
#include <utility>
|
||||
|
||||
#include <vector>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
enum class WaiveTransferFee : bool { No = false, Yes };
|
||||
@@ -182,6 +180,15 @@ accountHolds(
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j);
|
||||
|
||||
[[nodiscard]] STAmount
|
||||
accountHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Asset const& asset,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j);
|
||||
|
||||
// Returns the amount an account can spend of the currency type saDefault, or
|
||||
// returns saDefault if this account is the issuer of the currency in
|
||||
// question. Should be used in favor of accountHolds when questioning how much
|
||||
@@ -420,6 +427,12 @@ dirNext(
|
||||
[[nodiscard]] std::function<void(SLE::ref)>
|
||||
describeOwnerDir(AccountID const& account);
|
||||
|
||||
[[nodiscard]] TER
|
||||
dirLink(ApplyView& view, AccountID const& owner, std::shared_ptr<SLE>& object);
|
||||
|
||||
[[nodiscard]] Expected<std::shared_ptr<SLE>, TER>
|
||||
createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey);
|
||||
|
||||
// VFALCO NOTE Both STAmount parameters should just
|
||||
// be "Amount", a unit-less number.
|
||||
//
|
||||
@@ -454,6 +467,30 @@ trustDelete(
|
||||
AccountID const& uHighAccountID,
|
||||
beast::Journal j);
|
||||
|
||||
/** Create the structures necessary for an account to hold an asset.
|
||||
*
|
||||
* If the asset is:
|
||||
* - XRP: Do nothing.
|
||||
* - IOU: Check that the asset is not globally frozen,
|
||||
* and create a trust line (with limit 0).
|
||||
* - MPT: Check that the asset is not globally locked,
|
||||
* and create an MPToken.
|
||||
*/
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
XRPAmount priorBalance,
|
||||
Asset const& asset,
|
||||
beast::Journal journal);
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& account,
|
||||
Asset const& asset,
|
||||
beast::Journal journal);
|
||||
|
||||
/** Delete an offer.
|
||||
|
||||
Requirements:
|
||||
@@ -581,6 +618,33 @@ deleteAMMTrustLine(
|
||||
std::optional<AccountID> const& ammAccountID,
|
||||
beast::Journal j);
|
||||
|
||||
// From the perspective of a vault,
|
||||
// return the number of shares to give the depositor
|
||||
// when they deposit a fixed amount of assets.
|
||||
[[nodiscard]] Expected<Number, TER>
|
||||
assetsToSharesDeposit(
|
||||
ReadView const& view,
|
||||
std::shared_ptr<SLE> const& vault,
|
||||
STAmount const& assets);
|
||||
|
||||
// From the perspective of a vault,
|
||||
// return the number of shares to demand from the depositor
|
||||
// when they ask to withdraw a fixed amount of assets.
|
||||
[[nodiscard]] Expected<Number, TER>
|
||||
assetsToSharesWithdraw(
|
||||
ReadView const& view,
|
||||
std::shared_ptr<SLE> const& vault,
|
||||
STAmount const& assets);
|
||||
|
||||
// From the perspective of a vault,
|
||||
// return the number of assets to give the depositor
|
||||
// when they redeem a fixed amount of shares.
|
||||
[[nodiscard]] Expected<Number, TER>
|
||||
sharesToAssetsWithdraw(
|
||||
ReadView const& view,
|
||||
std::shared_ptr<SLE> const& vault,
|
||||
STAmount const& shares);
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -18,6 +18,8 @@
|
||||
//==============================================================================
|
||||
|
||||
#include <xrpld/ledger/ReadView.h>
|
||||
// TODO: Move the helper out of the `app` module.
|
||||
#include <xrpld/app/tx/detail/MPTokenAuthorize.h>
|
||||
#include <xrpld/ledger/View.h>
|
||||
#include <xrpl/basics/Log.h>
|
||||
#include <xrpl/basics/chrono.h>
|
||||
@@ -26,8 +28,12 @@
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/digest.h>
|
||||
#include <xrpl/protocol/st.h>
|
||||
#include <optional>
|
||||
#include <type_traits>
|
||||
#include <variant>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
@@ -362,6 +368,24 @@ accountHolds(
|
||||
return amount;
|
||||
}
|
||||
|
||||
[[nodiscard]] STAmount
|
||||
accountHolds(
|
||||
ReadView const& view,
|
||||
AccountID const& account,
|
||||
Asset const& asset,
|
||||
FreezeHandling zeroIfFrozen,
|
||||
AuthHandling zeroIfUnauthorized,
|
||||
beast::Journal j)
|
||||
{
|
||||
return std::visit([&] (auto const& value) {
|
||||
if constexpr (std::is_same_v<std::remove_cvref_t<decltype(value)>, Issue>)
|
||||
{
|
||||
return accountHolds(view, account, value, zeroIfFrozen, j);
|
||||
}
|
||||
return accountHolds(view, account, value, zeroIfFrozen, zeroIfUnauthorized, j);
|
||||
}, asset.value());
|
||||
}
|
||||
|
||||
STAmount
|
||||
accountFunds(
|
||||
ReadView const& view,
|
||||
@@ -852,6 +876,52 @@ describeOwnerDir(AccountID const& account)
|
||||
};
|
||||
}
|
||||
|
||||
TER
|
||||
dirLink(ApplyView& view, AccountID const& owner, std::shared_ptr<SLE>& object)
|
||||
{
|
||||
auto const page = view.dirInsert(
|
||||
keylet::ownerDir(owner), object->key(), describeOwnerDir(owner));
|
||||
if (!page)
|
||||
return tecDIR_FULL;
|
||||
object->setFieldU64(sfOwnerNode, *page);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
Expected<std::shared_ptr<SLE>, TER>
|
||||
createPseudoAccount(ApplyView& view, uint256 const& pseudoOwnerKey)
|
||||
{
|
||||
AccountID accountId;
|
||||
for (auto i = 0;; ++i)
|
||||
{
|
||||
if (i >= 256)
|
||||
return Unexpected(tecDUPLICATE);
|
||||
ripesha_hasher rsh;
|
||||
auto const hash = sha512Half(i, view.info().parentHash, pseudoOwnerKey);
|
||||
rsh(hash.data(), hash.size());
|
||||
accountId = static_cast<ripesha_hasher::result_type>(rsh);
|
||||
if (!view.read(keylet::account(accountId)))
|
||||
break;
|
||||
}
|
||||
|
||||
// Create pseudo-account.
|
||||
auto account = std::make_shared<SLE>(keylet::account(accountId));
|
||||
account->setAccountID(sfAccount, accountId);
|
||||
account->setFieldAmount(sfBalance, STAmount{});
|
||||
std::uint32_t const seqno{
|
||||
view.rules().enabled(featureDeletableAccounts) ? view.seq() : 1};
|
||||
account->setFieldU32(sfSequence, seqno);
|
||||
// Ignore reserves requirement, disable the master key, allow default
|
||||
// rippling, and enable deposit authorization to prevent payments into
|
||||
// pseudo-account.
|
||||
account->setFieldU32(
|
||||
sfFlags, lsfDisableMaster | lsfDefaultRipple | lsfDepositAuth);
|
||||
// Link the pseudo-account with its owner object.
|
||||
// account->setFieldH256(sfPseudoOwner, pseudoOwnerKey);
|
||||
view.insert(account);
|
||||
|
||||
return account;
|
||||
}
|
||||
|
||||
TER
|
||||
trustCreate(
|
||||
ApplyView& view,
|
||||
@@ -1006,6 +1076,119 @@ trustDelete(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
addEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
XRPAmount priorBalance,
|
||||
Asset const& asset,
|
||||
beast::Journal journal)
|
||||
{
|
||||
if (asset.holds<Issue>())
|
||||
{
|
||||
auto const& issue = asset.get<Issue>();
|
||||
// Every account can hold XRP.
|
||||
if (issue.native())
|
||||
return tesSUCCESS;
|
||||
|
||||
auto const& issuerId = issue.getIssuer();
|
||||
auto const& currency = issue.currency;
|
||||
if (isGlobalFrozen(view, issuerId))
|
||||
return tecFROZEN;
|
||||
|
||||
auto const& srcId = issuerId;
|
||||
auto const& dstId = accountID;
|
||||
auto const high = srcId > dstId;
|
||||
auto const index = keylet::line(srcId, dstId, currency);
|
||||
auto const sle = view.peek(keylet::account(accountID));
|
||||
return trustCreate(
|
||||
view,
|
||||
high,
|
||||
srcId,
|
||||
dstId,
|
||||
index.key,
|
||||
sle,
|
||||
/*auth=*/false,
|
||||
/*noRipple=*/true,
|
||||
/*freeze=*/false,
|
||||
/*balance=*/STAmount{Issue{currency, noAccount()}},
|
||||
/*limit=*/STAmount{Issue{currency, dstId}},
|
||||
/*qualityIn=*/0,
|
||||
/*qualityOut=*/0,
|
||||
journal);
|
||||
}
|
||||
|
||||
if (asset.holds<MPTIssue>())
|
||||
{
|
||||
auto const& mptIssue = asset.get<MPTIssue>();
|
||||
auto const& mptID = mptIssue.getMptID();
|
||||
auto const mpt = view.peek(keylet::mptIssuance(mptID));
|
||||
if (mpt->getFlags() & lsfMPTLocked)
|
||||
return tecLOCKED;
|
||||
return MPTokenAuthorize::authorize(
|
||||
view,
|
||||
journal,
|
||||
{.priorBalance = priorBalance,
|
||||
.mptIssuanceID = mptID,
|
||||
.accountID = accountID});
|
||||
}
|
||||
|
||||
// Should be unreachable.
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
[[nodiscard]] TER
|
||||
removeEmptyHolding(
|
||||
ApplyView& view,
|
||||
AccountID const& accountID,
|
||||
Asset const& asset,
|
||||
beast::Journal journal)
|
||||
{
|
||||
if (asset.holds<Issue>())
|
||||
{
|
||||
auto const& issue = asset.get<Issue>();
|
||||
if (issue.native())
|
||||
{
|
||||
auto const sle = view.read(keylet::account(accountID));
|
||||
if (!sle)
|
||||
return tecINTERNAL;
|
||||
auto const balance = sle->getFieldAmount(sfBalance);
|
||||
if (balance.xrp() != 0)
|
||||
return tecHAS_OBLIGATIONS;
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
// `asset` is an IOU.
|
||||
auto const line = view.peek(keylet::line(accountID, issue));
|
||||
if (!line)
|
||||
return tecOBJECT_NOT_FOUND;
|
||||
if (line->at(sfBalance)->iou() != beast::zero)
|
||||
return tecHAS_OBLIGATIONS;
|
||||
return trustDelete(
|
||||
view,
|
||||
line,
|
||||
line->at(sfLowLimit)->getIssuer(),
|
||||
line->at(sfHighLimit)->getIssuer(),
|
||||
journal);
|
||||
}
|
||||
|
||||
if (asset.holds<MPTIssue>())
|
||||
{
|
||||
auto const& mptIssue = asset.get<MPTIssue>();
|
||||
auto const& mptID = mptIssue.getMptID();
|
||||
// `MPTokenAuthorize::authorize` asserts that the balance is 0.
|
||||
return MPTokenAuthorize::authorize(
|
||||
view,
|
||||
journal,
|
||||
{.mptIssuanceID = mptID,
|
||||
.accountID = accountID,
|
||||
.flags = tfMPTUnauthorize});
|
||||
}
|
||||
|
||||
// Should be unreachable.
|
||||
return tecINTERNAL;
|
||||
}
|
||||
|
||||
TER
|
||||
offerDelete(ApplyView& view, std::shared_ptr<SLE> const& sle, beast::Journal j)
|
||||
{
|
||||
@@ -2039,4 +2222,66 @@ rippleCredit(
|
||||
saAmount.asset().value());
|
||||
}
|
||||
|
||||
Number
|
||||
getShareTotal(ReadView const& view, std::shared_ptr<SLE> const& vault)
|
||||
{
|
||||
auto issuance =
|
||||
view.read(keylet::mptIssuance(vault->at(sfMPTokenIssuanceID)));
|
||||
return issuance->at(sfOutstandingAmount);
|
||||
}
|
||||
|
||||
[[nodiscard]] Expected<Number, TER>
|
||||
assetsToSharesDeposit(
|
||||
ReadView const& view,
|
||||
std::shared_ptr<SLE> const& vault,
|
||||
STAmount const& assets)
|
||||
{
|
||||
assert(assets.asset() == vault->at(sfAsset));
|
||||
Number assetTotal = *vault->at(sfAssetTotal);
|
||||
if (assetTotal == 0)
|
||||
return assets;
|
||||
Number shareTotal = getShareTotal(view, vault);
|
||||
auto shares = shareTotal * (assets / assetTotal);
|
||||
return shares;
|
||||
}
|
||||
|
||||
[[nodiscard]] Expected<Number, TER>
|
||||
assetsToSharesWithdraw(
|
||||
ReadView const& view,
|
||||
std::shared_ptr<SLE> const& vault,
|
||||
STAmount const& assets)
|
||||
{
|
||||
assert(assets.asset() == vault->at(sfAsset));
|
||||
Number assetTotal = vault->at(sfAssetTotal);
|
||||
assetTotal -= vault->at(sfLossUnrealized);
|
||||
// TODO: What error here?
|
||||
if (assets > assetTotal)
|
||||
return Unexpected{tecINTERNAL};
|
||||
if (assetTotal == 0)
|
||||
return 0;
|
||||
Number shareTotal = getShareTotal(view, vault);
|
||||
auto shares = shareTotal * (assets / assetTotal);
|
||||
// TODO: Limit by withdrawal policy?
|
||||
return shares;
|
||||
}
|
||||
|
||||
[[nodiscard]] Expected<Number, TER>
|
||||
sharesToAssetsWithdraw(
|
||||
ReadView const& view,
|
||||
std::shared_ptr<SLE> const& vault,
|
||||
STAmount const& shares)
|
||||
{
|
||||
assert(shares.asset() == vault->at(sfMPTokenIssuanceID));
|
||||
Number assetTotal = vault->at(sfAssetTotal);
|
||||
assetTotal -= vault->at(sfLossUnrealized);
|
||||
if (assetTotal == 0)
|
||||
return 0;
|
||||
Number shareTotal = getShareTotal(view, vault);
|
||||
if (shares > shareTotal)
|
||||
return Unexpected{tecINTERNAL};
|
||||
auto assets = assetTotal * (shares / shareTotal);
|
||||
// TODO: Limit by withdrawal policy?
|
||||
return assets;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
Reference in New Issue
Block a user