This commit is contained in:
John Freeman
2024-11-13 15:13:22 -06:00
committed by Bronek Kozicki
parent 286612cf19
commit 6046fa239c
8 changed files with 205 additions and 35 deletions

View File

@@ -236,6 +236,9 @@ public:
STAmount& STAmount&
operator=(XRPAmount const& amount); operator=(XRPAmount const& amount);
STAmount&
operator=(Number const&);
//-------------------------------------------------------------------------- //--------------------------------------------------------------------------
// //
// Modification // Modification
@@ -547,6 +550,15 @@ STAmount::operator=(XRPAmount const& amount)
return *this; return *this;
} }
inline STAmount&
STAmount::operator=(Number const& number)
{
mIsNegative = number.mantissa() < 0;
mValue = mIsNegative ? -number.mantissa() : number.mantissa();
mOffset = number.exponent();
return *this;
}
inline void inline void
STAmount::negate() STAmount::negate()
{ {

View File

@@ -239,6 +239,7 @@ class Vault_test : public beast::unit_test::suite
env.close(); env.close();
{ {
// Deposit non-zero amount.
auto tx = vault.deposit( auto tx = vault.deposit(
{.depositor = depositor, {.depositor = depositor,
.id = keylet.key, .id = keylet.key,
@@ -274,11 +275,103 @@ class Vault_test : public beast::unit_test::suite
BEAST_EXPECT(true); BEAST_EXPECT(true);
} }
TEST_CASE(Sequence)
{
using namespace test::jtx;
Env env{*this};
Account issuer{"issuer"};
Account owner{"owner"};
Account depositor{"depositor"};
env.fund(XRP(1000), issuer, owner, depositor);
env.close();
auto vault = env.vault();
SUBCASE("IOU")
{
// Construct asset.
Asset asset = issuer["IOU"];
// Fund depositor with asset.
env.trust(asset(1000), depositor);
env(pay(issuer, depositor, asset(1000)));
// Create vault.
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
env(tx);
env.close();
{
// Deposit non-zero amount.
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(123)});
env(tx);
env.close();
}
{
// Fail to set maximum lower than current amount.
auto tx = vault.set({.owner = owner, .id = keylet.key});
tx[sfAssetMaximum] = 100;
env(tx, ter(tecLIMIT_EXCEEDED));
env.close();
}
{
// Set maximum higher than current amount.
auto tx = vault.set({.owner = owner, .id = keylet.key});
tx[sfAssetMaximum] = 200;
env(tx);
env.close();
}
{
// Fail to deposit more than maximum.
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(100)});
env(tx, ter(tecLIMIT_EXCEEDED));
env.close();
}
{
// Fail to delete non-empty vault.
auto tx = vault.del({.owner = owner, .id = keylet.key});
env(tx, ter(tecHAS_OBLIGATIONS));
env.close();
}
{
// Fail to deposit more than assets held.
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(1000)});
env(tx, ter(tecINSUFFICIENT_FUNDS));
env.close();
}
{
// Fail to withdraw more than assets held.
auto tx = vault.withdraw(
{.depositor = depositor,
.id = keylet.key,
.amount = asset(1000)});
env(tx, ter(tecINSUFFICIENT_FUNDS));
env.close();
}
}
}
public: public:
void void
run() override run() override
{ {
EXECUTE(CreateUpdateDelete); // EXECUTE(CreateUpdateDelete);
EXECUTE(Sequence);
} }
}; };

View File

@@ -55,6 +55,9 @@ Subcase::~Subcase()
// because only now do we know which subcase was the leaf, // because only now do we know which subcase was the leaf,
// and we only want to print one name line for each subcase. // and we only want to print one name line for each subcase.
_.suite.testcase(_.name()); _.suite.testcase(_.name());
// Let the runner know that a test executed,
// even if `BEAST_EXPECT` was never called.
_.suite.pass();
} }
if (_.skipped == 0) if (_.skipped == 0)
{ {

View File

@@ -58,9 +58,13 @@ VaultDeposit::doApply()
if (!vault) if (!vault)
return tecOBJECT_NOT_FOUND; return tecOBJECT_NOT_FOUND;
auto amount = ctx_.tx[sfAmount]; // TODO: Check credentials.
if (vault->getFlags() & lsfVaultPrivate)
return tecNO_AUTH;
auto assets = ctx_.tx[sfAmount];
Asset const& asset = vault->at(sfAsset); Asset const& asset = vault->at(sfAsset);
if (amount.asset() != asset) if (assets.asset() != asset)
return tecWRONG_ASSET; return tecWRONG_ASSET;
if (accountHolds( if (accountHolds(
@@ -69,33 +73,30 @@ VaultDeposit::doApply()
asset, asset,
FreezeHandling::fhZERO_IF_FROZEN, FreezeHandling::fhZERO_IF_FROZEN,
AuthHandling::ahZERO_IF_UNAUTHORIZED, AuthHandling::ahZERO_IF_UNAUTHORIZED,
j_) < amount) j_) < assets)
{ {
return tecINSUFFICIENT_FUNDS; return tecINSUFFICIENT_FUNDS;
} }
vault->at(sfAssetTotal) += amount; vault->at(sfAssetTotal) += assets;
vault->at(sfAssetAvailable) += amount; vault->at(sfAssetAvailable) += assets;
// A deposit must not push the vault over its limit.
auto maximum = *vault->at(sfAssetMaximum); auto maximum = *vault->at(sfAssetMaximum);
if (maximum != 0 && *vault->at(sfAssetTotal) > maximum) if (maximum != 0 && *vault->at(sfAssetTotal) > maximum)
return tecLIMIT_EXCEEDED; return tecLIMIT_EXCEEDED;
// TODO: Check credentials.
if (vault->getFlags() & lsfVaultPrivate)
;
auto const& vaultAccount = vault->at(sfAccount); auto const& vaultAccount = vault->at(sfAccount);
// Transfer amount from sender to vault. // Transfer assets from depositor to vault.
if (auto ter = accountSend(view(), account_, vaultAccount, amount, j_)) if (auto ter = accountSend(view(), account_, vaultAccount, assets, j_))
return ter; return ter;
auto shares = assetsToSharesDeposit(view(), vault, amount); // Transfer shares from vault to depositor.
if (!shares) auto shares = assetsToSharesDeposit(view(), vault, assets);
return shares.error(); if (auto ter = accountSend(view(), vaultAccount, account_, shares, j_))
if (auto ter = accountSend(view(), vaultAccount, account_, *shares, j_))
return ter; return ter;
// TODO: copy mptIssuance.OutstandingAmount to vault.ShareTotal?
view().update(vault);
return tesSUCCESS; return tesSUCCESS;
} }

View File

@@ -73,7 +73,11 @@ VaultSet::doApply()
if (tx.isFieldPresent(sfData)) if (tx.isFieldPresent(sfData))
vault->at(sfData) = tx[sfData]; vault->at(sfData) = tx[sfData];
if (tx.isFieldPresent(sfAssetMaximum)) if (tx.isFieldPresent(sfAssetMaximum))
{
if (tx[sfAssetMaximum] < *vault->at(sfAssetTotal))
return tecLIMIT_EXCEEDED;
vault->at(sfAssetMaximum) = tx[sfAssetMaximum]; vault->at(sfAssetMaximum) = tx[sfAssetMaximum];
}
view().update(vault); view().update(vault);

View File

@@ -19,6 +19,7 @@
#include <xrpld/app/tx/detail/VaultWithdraw.h> #include <xrpld/app/tx/detail/VaultWithdraw.h>
#include <xrpld/ledger/View.h>
#include <xrpl/protocol/Feature.h> #include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/STNumber.h> #include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/TxFlags.h> #include <xrpl/protocol/TxFlags.h>
@@ -56,6 +57,65 @@ VaultWithdraw::doApply()
if (!vault) if (!vault)
return tecOBJECT_NOT_FOUND; return tecOBJECT_NOT_FOUND;
// TODO: Check credentials.
if (vault->getFlags() & lsfVaultPrivate)
return tecNO_AUTH;
auto amount = ctx_.tx[sfAmount];
STAmount shares, assets;
if (amount.asset() == vault->at(sfAsset))
{
// Fixed assets, variable shares.
assets = amount;
shares = assetsToSharesWithdraw(view(), vault, assets);
}
else if (amount.asset() == vault->at(sfMPTokenIssuanceID))
{
// Fixed shares, variable assets.
shares = amount;
assets = sharesToAssetsWithdraw(view(), vault, shares);
}
else
{
return tecWRONG_ASSET;
}
// The depositor must have enough shares.
if (accountHolds(
view(),
account_,
shares.asset(),
FreezeHandling::fhZERO_IF_FROZEN,
AuthHandling::ahZERO_IF_UNAUTHORIZED,
j_) < shares)
{
return tecINSUFFICIENT_FUNDS;
}
// The vault must have enough assets on hand.
// The vault may hold assets that it has already pledged.
// That is why we look at AssetAvailable instead of the account balance.
// TODO: Invariant: vault.AssetAvailable <= vault.Account.balance(vault.Asset)
if (*vault->at(sfAssetAvailable) < assets)
return tecINSUFFICIENT_FUNDS;
std::cerr << "total before: " << *vault->at(sfAssetTotal) << std::endl;
vault->at(sfAssetTotal) -= assets;
std::cerr << "total after: " << *vault->at(sfAssetTotal) << std::endl;
vault->at(sfAssetAvailable) -= assets;
auto const& vaultAccount = vault->at(sfAccount);
// Transfer shares from depositor to vault.
if (auto ter = accountSend(view(), account_, vaultAccount, shares, j_))
return ter;
// Transfer assets from vault to depositor.
if (auto ter = accountSend(view(), vaultAccount, account_, assets, j_))
return ter;
view().update(vault);
return tesSUCCESS; return tesSUCCESS;
} }

View File

@@ -621,7 +621,7 @@ deleteAMMTrustLine(
// From the perspective of a vault, // From the perspective of a vault,
// return the number of shares to give the depositor // return the number of shares to give the depositor
// when they deposit a fixed amount of assets. // when they deposit a fixed amount of assets.
[[nodiscard]] Expected<STAmount, TER> [[nodiscard]] STAmount
assetsToSharesDeposit( assetsToSharesDeposit(
ReadView const& view, ReadView const& view,
std::shared_ptr<SLE> const& vault, std::shared_ptr<SLE> const& vault,
@@ -630,7 +630,7 @@ assetsToSharesDeposit(
// From the perspective of a vault, // From the perspective of a vault,
// return the number of shares to demand from the depositor // return the number of shares to demand from the depositor
// when they ask to withdraw a fixed amount of assets. // when they ask to withdraw a fixed amount of assets.
[[nodiscard]] Expected<Number, TER> [[nodiscard]] STAmount
assetsToSharesWithdraw( assetsToSharesWithdraw(
ReadView const& view, ReadView const& view,
std::shared_ptr<SLE> const& vault, std::shared_ptr<SLE> const& vault,
@@ -639,7 +639,7 @@ assetsToSharesWithdraw(
// From the perspective of a vault, // From the perspective of a vault,
// return the number of assets to give the depositor // return the number of assets to give the depositor
// when they redeem a fixed amount of shares. // when they redeem a fixed amount of shares.
[[nodiscard]] Expected<Number, TER> [[nodiscard]] STAmount
sharesToAssetsWithdraw( sharesToAssetsWithdraw(
ReadView const& view, ReadView const& view,
std::shared_ptr<SLE> const& vault, std::shared_ptr<SLE> const& vault,

View File

@@ -2235,7 +2235,7 @@ getShareTotal(ReadView const& view, std::shared_ptr<SLE> const& vault)
return issuance->at(sfOutstandingAmount); return issuance->at(sfOutstandingAmount);
} }
[[nodiscard]] Expected<STAmount, TER> [[nodiscard]] STAmount
assetsToSharesDeposit( assetsToSharesDeposit(
ReadView const& view, ReadView const& view,
std::shared_ptr<SLE> const& vault, std::shared_ptr<SLE> const& vault,
@@ -2251,7 +2251,7 @@ assetsToSharesDeposit(
return amount; return amount;
} }
[[nodiscard]] Expected<Number, TER> [[nodiscard]] STAmount
assetsToSharesWithdraw( assetsToSharesWithdraw(
ReadView const& view, ReadView const& view,
std::shared_ptr<SLE> const& vault, std::shared_ptr<SLE> const& vault,
@@ -2260,18 +2260,16 @@ assetsToSharesWithdraw(
assert(assets.asset() == vault->at(sfAsset)); assert(assets.asset() == vault->at(sfAsset));
Number assetTotal = vault->at(sfAssetTotal); Number assetTotal = vault->at(sfAssetTotal);
assetTotal -= vault->at(sfLossUnrealized); assetTotal -= vault->at(sfLossUnrealized);
// TODO: What error here? STAmount amount{vault->at(sfMPTokenIssuanceID)};
if (assets > assetTotal)
return Unexpected{tecINTERNAL};
if (assetTotal == 0) if (assetTotal == 0)
return 0; return amount;
Number shareTotal = getShareTotal(view, vault); Number shareTotal = getShareTotal(view, vault);
auto shares = shareTotal * (assets / assetTotal); amount = shareTotal * (assets / assetTotal);
// TODO: Limit by withdrawal policy? // TODO: Limit by withdrawal policy?
return shares; return amount;
} }
[[nodiscard]] Expected<Number, TER> [[nodiscard]] STAmount
sharesToAssetsWithdraw( sharesToAssetsWithdraw(
ReadView const& view, ReadView const& view,
std::shared_ptr<SLE> const& vault, std::shared_ptr<SLE> const& vault,
@@ -2280,14 +2278,13 @@ sharesToAssetsWithdraw(
assert(shares.asset() == vault->at(sfMPTokenIssuanceID)); assert(shares.asset() == vault->at(sfMPTokenIssuanceID));
Number assetTotal = vault->at(sfAssetTotal); Number assetTotal = vault->at(sfAssetTotal);
assetTotal -= vault->at(sfLossUnrealized); assetTotal -= vault->at(sfLossUnrealized);
STAmount amount{vault->at(sfAsset)};
if (assetTotal == 0) if (assetTotal == 0)
return 0; return amount;
Number shareTotal = getShareTotal(view, vault); Number shareTotal = getShareTotal(view, vault);
if (shares > shareTotal) amount = assetTotal * (shares / shareTotal);
return Unexpected{tecINTERNAL};
auto assets = assetTotal * (shares / shareTotal);
// TODO: Limit by withdrawal policy? // TODO: Limit by withdrawal policy?
return assets; return amount;
} }
} // namespace ripple } // namespace ripple