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&
operator=(XRPAmount const& amount);
STAmount&
operator=(Number const&);
//--------------------------------------------------------------------------
//
// Modification
@@ -547,6 +550,15 @@ STAmount::operator=(XRPAmount const& amount)
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
STAmount::negate()
{

View File

@@ -239,6 +239,7 @@ class Vault_test : public beast::unit_test::suite
env.close();
{
// Deposit non-zero amount.
auto tx = vault.deposit(
{.depositor = depositor,
.id = keylet.key,
@@ -255,7 +256,7 @@ class Vault_test : public beast::unit_test::suite
mptt.create({.flags = tfMPTCanTransfer | tfMPTCanLock});
Asset asset = mptt.issuanceID();
// Fund depositor with asset.
mptt.authorize({ .account = depositor });
mptt.authorize({.account = depositor});
env(pay(issuer, depositor, asset(1000)));
// Create vault.
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
@@ -274,11 +275,103 @@ class Vault_test : public beast::unit_test::suite
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:
void
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,
// and we only want to print one name line for each subcase.
_.suite.testcase(_.name());
// Let the runner know that a test executed,
// even if `BEAST_EXPECT` was never called.
_.suite.pass();
}
if (_.skipped == 0)
{

View File

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

View File

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

View File

@@ -19,6 +19,7 @@
#include <xrpld/app/tx/detail/VaultWithdraw.h>
#include <xrpld/ledger/View.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/STNumber.h>
#include <xrpl/protocol/TxFlags.h>
@@ -56,6 +57,65 @@ VaultWithdraw::doApply()
if (!vault)
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;
}

View File

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