Skip to content

[DO NOT MERGE] adds PoC model for vault share price valuation#6434

Open
Tapanito wants to merge 3 commits intotapanito/lending-fix-amendmentfrom
tapanito/poc-vault-valuation
Open

[DO NOT MERGE] adds PoC model for vault share price valuation#6434
Tapanito wants to merge 3 commits intotapanito/lending-fix-amendmentfrom
tapanito/poc-vault-valuation

Conversation

@Tapanito
Copy link
Collaborator

Spec: XRPLF/XRPL-Standards#485

High Level Overview of Change

Context of Change

Type of Change

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to not work as expected)
  • Refactor (non-breaking change that only restructures code)
  • Performance (increase or change in throughput and/or latency)
  • Tests (you added tests for code that already exists, or your new feature included in this PR)
  • Documentation update
  • Chore (no impact to binary, e.g. .gitignore, formatting, dropping support for older tooling)
  • Release

API Impact

  • Public API: New feature (new methods and/or new fields)
  • Public API: Breaking change (in general, breaking changes should only impact the next api_version)
  • libxrpl change (any change that may affect libxrpl or dependents of libxrpl)
  • Peer protocol change (must be backward compatible or bump the peer protocol version)

@codecov
Copy link

codecov bot commented Feb 26, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 79.8%. Comparing base (ba53026) to head (2d66c06).

Additional details and impacted files

Impacted file tree graph

@@                      Coverage Diff                       @@
##           tapanito/lending-fix-amendment   #6434   +/-   ##
==============================================================
  Coverage                            79.8%   79.8%           
==============================================================
  Files                                 848     848           
  Lines                               67761   67767    +6     
  Branches                             7571    7561   -10     
==============================================================
+ Hits                                54056   54069   +13     
+ Misses                              13705   13698    -7     

see 15 files with indirect coverage changes

Impacted file tree graph

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

Copy link
Collaborator

@ximinez ximinez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Partial review

Copy link
Collaborator

@ximinez ximinez left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In addition to the comments below, I noticed there are a lot of places where you have comments describing the current state of the vault. e.g.

// Vault: assetsTotal=1000, omega=50 (loan outstanding), iota=100, sharesTotal=1000

I'd like to see those have actual BEAST_EXPECT checks. Either added, or replacing the comments, up to your judgement.

Partial review. Note to self: left off at testPaperLossThenActualDefault()


void
setInterestUnrealized(Number omega)
borrow(Number const& principal, Number const& interest)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!!!

Comment on lines +135 to +140
XRPL_ASSERT(
principal > 0 && interest >= 0,
"xrpl::Vault::borrow requires positive principal and non-negative interest");
XRPL_ASSERT(
principal <= assetsAvailable_,
"xrpl::Vault::borrow principal exceeds assets available");
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Since you're in a test, instead of XRPL_ASSERT, use BEAST_EXPECT, here and in the other tests. And return early if it fails.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good idea!

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So I'm not sure I can do that, since the Vault has nothing to do with the test suite, and I cannot inherit from it, the best I can do is:

    if (!suite_.expect(
                principal > 0 && interest >= 0 && (!extraInterest || *extraInterest >= 0),
                __FILE__,
                __LINE__))
            return;

I could move the vault class logic into the test itself, if we really want to replace assertions with beast expect.

// Deposit 95 assets: shares = floor(95 * 1000 / 950) = floor(100) = 100
auto const [shares, assets] = vault.deposit(STAmount{usd, 95});
// Deposit 95 assets: shares = floor(95 * 950 / 950) = floor(95) = 95
// actualAssets = 95 * 950 / 950 = 95
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have found that writing these kinds of equations out with units really helps as both error checking, and for comprehension. Especially over the long run when this may not be top of mind, and I'm looking at 950 / 950 and wondering "why is this not 1?". So, for this case:

        // Deposit 95 assets: shares = floor(95 assets * 950 shares / 950 assets) = floor(95 shares) = 95 shares
        // actualAssets = 95 shares * 950 assets / 950 shares = 95 assets

Or to take it a step further, which is probably overkill:

        // Deposit 95 assets: shares = floor(95 assets deposited * 950 shares total / 950 net assets) = floor(95 shares deposited) = 95 shares deposited
        // actualAssets = 95 shares deposited * 950 net assets / 950 total shares = 95 assets deposited

What do you think?

Comment on lines +718 to +721
auto const out = vault.redeem(STAmount{vault.shareAsset(), 1000}).value();
// Approximate bounds: should be within 10% of 0.001
BEAST_EXPECT(Number(out) > Number(9, -4)); // > 0.0009
BEAST_EXPECT(Number(out) < Number(11, -4)); // < 0.0011
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you compute this as an exact value? In fact, I would expect this to be the exact amount that was deposited.

auto const expected = ( Number(1, 12) + 1 ) / ( Number(1, 15) + 1000 );
BEAST_EXPECT(expected == Number(1, -3));
BEAST_EXPECT(out == STAmount(usd, expected));

// withdrawalNAV = 1100 - 100 - 500 = 500

// Hard default confirmed (hasPaperLoss=true):
// assetsTotal -= 500 → 600; omega -= 100 → 0; lossUnrealized -= 500 → 0
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// assetsTotal -= 500 → 600; omega -= 100 → 0; lossUnrealized -= 500 → 0
// assetsTotal -= 1100 → 600; omega -= 100 → 0; lossUnrealized -= 500 → 0

Comment on lines +1023 to +1025
// Withdraw 0.0001 assets: shares = truncate(0.0001 * 1 / 1) = 0 → tecPRECISION_LOSS
BEAST_EXPECT(!vault.withdraw(STAmount{usd, 1, -4}));
BEAST_EXPECT(vault.withdraw(STAmount{usd, 1, -4}).error() == tecPRECISION_LOSS);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Withdraw 0.0001 assets: shares = truncate(0.0001 * 1 / 1) = 0 → tecPRECISION_LOSS
BEAST_EXPECT(!vault.withdraw(STAmount{usd, 1, -4}));
BEAST_EXPECT(vault.withdraw(STAmount{usd, 1, -4}).error() == tecPRECISION_LOSS);
// Withdraw 0.0001 assets: shares = truncate(0.0001 * 1 / 1) = 0 → tecPRECISION_LOSS
auto const withdrawResult = vault.withdraw(STAmount{usd, 1, -4});
BEAST_EXPECT(!withdrawResult);
BEAST_EXPECT(withdrawResult.error() == tecPRECISION_LOSS);

Comment on lines +1082 to +1085
auto const ok = vault.redeem(STAmount{vault.shareAsset(), 200});
BEAST_EXPECT(ok.has_value());
BEAST_EXPECT(Number(ok.value()) == Number(200));
}
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At this point assetsAvailable is 0, correct? Is it fair to allow one holder to remove all the available assets, and force everyone else to wait for the loan to be paid back (and hope it doesn't default)?

{
return STAmount{shareAsset_, sharesTotal_};
}

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be worth it to add STAmount depositNAV() const and STAmount withdrawNAV() const function. The functions could be used by depost() and withdraw(), but they could also be used in the tests where right now you have comments saying what it should be.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants