Merge branch 'ximinez/lending-XLS-66-ongoing' into vlntb/RIPD-4257-fix-tecFROZEN

This commit is contained in:
Valentin Balaschenko
2025-12-24 09:13:26 +00:00
committed by GitHub
11 changed files with 69 additions and 46 deletions

View File

@@ -4,9 +4,6 @@ description: "Install Conan dependencies, optionally forcing a rebuild of all de
# Note that actions do not support 'type' and all inputs are strings, see
# https://docs.github.com/en/actions/reference/workflows-and-actions/metadata-syntax#inputs.
inputs:
build_dir:
description: "The directory where to build."
required: true
build_type:
description: 'The build type to use ("Debug", "Release").'
required: true
@@ -28,17 +25,13 @@ runs:
- name: Install Conan dependencies
shell: bash
env:
BUILD_DIR: ${{ inputs.build_dir }}
BUILD_NPROC: ${{ inputs.build_nproc }}
BUILD_OPTION: ${{ inputs.force_build == 'true' && '*' || 'missing' }}
BUILD_TYPE: ${{ inputs.build_type }}
LOG_VERBOSITY: ${{ inputs.log_verbosity }}
run: |
echo 'Installing dependencies.'
mkdir -p "${BUILD_DIR}"
cd "${BUILD_DIR}"
conan install \
--output-folder . \
--build="${BUILD_OPTION}" \
--options:host='&:tests=True' \
--options:host='&:xrpld=True' \
@@ -46,4 +39,4 @@ runs:
--conf:all tools.build:jobs=${BUILD_NPROC} \
--conf:all tools.build:verbosity="${LOG_VERBOSITY}" \
--conf:all tools.compilation:verbosity="${LOG_VERBOSITY}" \
..
.

View File

@@ -22,7 +22,7 @@ defaults:
shell: bash
env:
BUILD_DIR: .build
BUILD_DIR: build
NPROC_SUBTRACT: 2
jobs:

View File

@@ -3,11 +3,6 @@ name: Build and test configuration
on:
workflow_call:
inputs:
build_dir:
description: "The directory where to build."
required: true
type: string
build_only:
description: 'Whether to only build or to build and test the code ("true", "false").'
required: true
@@ -59,6 +54,11 @@ defaults:
run:
shell: bash
env:
# Conan installs the generators in the build/generators directory, see the
# layout() method in conanfile.py. We then run CMake from the build directory.
BUILD_DIR: build
jobs:
build-and-test:
name: ${{ inputs.config_name }}
@@ -96,7 +96,6 @@ jobs:
- name: Build dependencies
uses: ./.github/actions/build-deps
with:
build_dir: ${{ inputs.build_dir }}
build_nproc: ${{ steps.nproc.outputs.nproc }}
build_type: ${{ inputs.build_type }}
# Set the verbosity to "quiet" for Windows to avoid an excessive
@@ -104,7 +103,7 @@ jobs:
log_verbosity: ${{ runner.os == 'Windows' && 'quiet' || 'verbose' }}
- name: Configure CMake
working-directory: ${{ inputs.build_dir }}
working-directory: ${{ env.BUILD_DIR }}
env:
BUILD_TYPE: ${{ inputs.build_type }}
CMAKE_ARGS: ${{ inputs.cmake_args }}
@@ -117,7 +116,7 @@ jobs:
..
- name: Build the binary
working-directory: ${{ inputs.build_dir }}
working-directory: ${{ env.BUILD_DIR }}
env:
BUILD_NPROC: ${{ steps.nproc.outputs.nproc }}
BUILD_TYPE: ${{ inputs.build_type }}
@@ -132,8 +131,6 @@ jobs:
- name: Upload the binary (Linux)
if: ${{ github.repository_owner == 'XRPLF' && runner.os == 'Linux' }}
uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2
env:
BUILD_DIR: ${{ inputs.build_dir }}
with:
name: xrpld-${{ inputs.config_name }}
path: ${{ env.BUILD_DIR }}/xrpld
@@ -142,7 +139,7 @@ jobs:
- name: Check linking (Linux)
if: ${{ runner.os == 'Linux' }}
working-directory: ${{ inputs.build_dir }}
working-directory: ${{ env.BUILD_DIR }}
run: |
ldd ./xrpld
if [ "$(ldd ./xrpld | grep -E '(libstdc\+\+|libgcc)' | wc -l)" -eq 0 ]; then
@@ -154,13 +151,13 @@ jobs:
- name: Verify presence of instrumentation (Linux)
if: ${{ runner.os == 'Linux' && env.ENABLED_VOIDSTAR == 'true' }}
working-directory: ${{ inputs.build_dir }}
working-directory: ${{ env.BUILD_DIR }}
run: |
./xrpld --version | grep libvoidstar
- name: Run the separate tests
if: ${{ !inputs.build_only }}
working-directory: ${{ inputs.build_dir }}
working-directory: ${{ env.BUILD_DIR }}
# Windows locks some of the build files while running tests, and parallel jobs can collide
env:
BUILD_TYPE: ${{ inputs.build_type }}
@@ -173,7 +170,7 @@ jobs:
- name: Run the embedded tests
if: ${{ !inputs.build_only }}
working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', inputs.build_dir, inputs.build_type) || inputs.build_dir }}
working-directory: ${{ runner.os == 'Windows' && format('{0}/{1}', env.BUILD_DIR, inputs.build_type) || env.BUILD_DIR }}
env:
BUILD_NPROC: ${{ steps.nproc.outputs.nproc }}
run: |
@@ -189,7 +186,7 @@ jobs:
- name: Prepare coverage report
if: ${{ !inputs.build_only && env.ENABLED_COVERAGE == 'true' }}
working-directory: ${{ inputs.build_dir }}
working-directory: ${{ env.BUILD_DIR }}
env:
BUILD_NPROC: ${{ steps.nproc.outputs.nproc }}
BUILD_TYPE: ${{ inputs.build_type }}
@@ -207,7 +204,7 @@ jobs:
disable_search: true
disable_telem: true
fail_ci_if_error: true
files: ${{ inputs.build_dir }}/coverage.xml
files: ${{ env.BUILD_DIR }}/coverage.xml
plugins: noop
token: ${{ secrets.CODECOV_TOKEN }}
verbose: true

View File

@@ -8,11 +8,6 @@ name: Build and test
on:
workflow_call:
inputs:
build_dir:
description: "The directory where to build."
required: false
type: string
default: ".build"
os:
description: 'The operating system to use for the build ("linux", "macos", "windows").'
required: true
@@ -46,7 +41,6 @@ jobs:
matrix: ${{ fromJson(needs.generate-matrix.outputs.matrix) }}
max-parallel: 10
with:
build_dir: ${{ inputs.build_dir }}
build_only: ${{ matrix.build_only }}
build_type: ${{ matrix.build_type }}
cmake_args: ${{ matrix.cmake_args }}

View File

@@ -92,7 +92,6 @@ jobs:
- name: Build dependencies
uses: ./.github/actions/build-deps
with:
build_dir: .build
build_nproc: ${{ steps.nproc.outputs.nproc }}
build_type: ${{ matrix.build_type }}
force_build: ${{ github.event_name == 'schedule' || github.event.inputs.force_source_build == 'true' }}

View File

@@ -541,7 +541,7 @@ LEDGER_ENTRY(ltLOAN, 0x0089, Loan, loan, ({
{sfStartDate, soeREQUIRED},
{sfPaymentInterval, soeREQUIRED},
{sfGracePeriod, soeDEFAULT},
{sfPreviousPaymentDate, soeDEFAULT},
{sfPreviousPaymentDueDate, soeDEFAULT},
{sfNextPaymentDueDate, soeDEFAULT},
// The loan object tracks these values:
//

View File

@@ -102,7 +102,7 @@ TYPED_SFIELD(sfMutableFlags, UINT32, 53)
TYPED_SFIELD(sfStartDate, UINT32, 54)
TYPED_SFIELD(sfPaymentInterval, UINT32, 55)
TYPED_SFIELD(sfGracePeriod, UINT32, 56)
TYPED_SFIELD(sfPreviousPaymentDate, UINT32, 57)
TYPED_SFIELD(sfPreviousPaymentDueDate, UINT32, 57)
TYPED_SFIELD(sfNextPaymentDueDate, UINT32, 58)
TYPED_SFIELD(sfPaymentRemaining, UINT32, 59)
TYPED_SFIELD(sfPaymentTotal, UINT32, 60)

View File

@@ -372,7 +372,7 @@ protected:
if (auto loan = env.le(loanKeylet); env.test.BEAST_EXPECT(loan))
{
env.test.BEAST_EXPECT(
loan->at(sfPreviousPaymentDate) == previousPaymentDate);
loan->at(sfPreviousPaymentDueDate) == previousPaymentDate);
env.test.BEAST_EXPECT(
loan->at(sfPaymentRemaining) == paymentRemaining);
env.test.BEAST_EXPECT(
@@ -507,7 +507,7 @@ protected:
if (auto loan = env.le(loanKeylet); BEAST_EXPECT(loan))
{
return LoanState{
.previousPaymentDate = loan->at(sfPreviousPaymentDate),
.previousPaymentDate = loan->at(sfPreviousPaymentDueDate),
.startDate = tp{d{loan->at(sfStartDate)}},
.nextPaymentDate = loan->at(sfNextPaymentDueDate),
.paymentRemaining = loan->at(sfPaymentRemaining),
@@ -1454,7 +1454,7 @@ protected:
BEAST_EXPECT(
loan->at(sfPaymentInterval) == *loanParams.payInterval);
BEAST_EXPECT(loan->at(sfGracePeriod) == *loanParams.gracePd);
BEAST_EXPECT(loan->at(sfPreviousPaymentDate) == 0);
BEAST_EXPECT(loan->at(sfPreviousPaymentDueDate) == 0);
BEAST_EXPECT(
loan->at(sfNextPaymentDueDate) ==
startDate + *loanParams.payInterval);
@@ -3597,13 +3597,39 @@ protected:
env(tx);
env.close();
testcase("Vault maximum value exceeded");
testcase("Vault at maximum value");
env(set(issuer, broker.brokerID, principalRequest),
counterparty(lender),
interestRate(TenthBips32(10'000)),
sig(sfCounterpartySignature, lender),
fee(env.current()->fees().base * 5),
ter(tecLIMIT_EXCEEDED));
ter(tecLIMIT_EXCEEDED),
THISLINE);
},
nullptr);
testCase(
[&, this](Env& env, BrokerInfo const& broker, auto&) {
using namespace loan;
Number const principalRequest = broker.asset(1'000).value();
Vault vault{env};
auto tx = vault.set({.owner = lender, .id = broker.vaultID});
tx[sfAssetsMaximum] =
BrokerParameters::defaults().vaultDeposit +
broker.asset(1).number();
env(tx);
env.close();
testcase("Vault maximum value exceeded");
env(set(issuer, broker.brokerID, principalRequest),
counterparty(lender),
interestRate(TenthBips32(100'000)),
sig(sfCounterpartySignature, lender),
fee(env.current()->fees().base * 5),
paymentTotal(2),
paymentInterval(3600 * 24),
ter(tecLIMIT_EXCEEDED),
THISLINE);
},
nullptr);
}
@@ -3841,7 +3867,7 @@ protected:
BEAST_EXPECT(loan[sfPaymentInterval] == 60);
BEAST_EXPECT(loan[sfPeriodicPayment] == "1000000000");
BEAST_EXPECT(loan[sfPaymentRemaining] == 1);
BEAST_EXPECT(!loan.isMember(sfPreviousPaymentDate));
BEAST_EXPECT(!loan.isMember(sfPreviousPaymentDueDate));
BEAST_EXPECT(loan[sfPrincipalOutstanding] == "1000000000");
BEAST_EXPECT(loan[sfTotalValueOutstanding] == "1000000000");
BEAST_EXPECT(!loan.isMember(sfLoanScale));
@@ -6048,7 +6074,7 @@ protected:
{
// --- PoC Summary ----------------------------------------------------
// Scenario: Borrower makes one periodic payment early (before next due)
// so doPayment sets sfPreviousPaymentDate to the (future)
// so doPayment sets sfPreviousPaymentDueDate to the (future)
// sfNextPaymentDueDate and advances sfNextPaymentDueDate by one
// interval. Borrower then immediately performs a full-payment
// (tfLoanFullPayment). Why it matters: Full-payment interest accrual

View File

@@ -1669,7 +1669,7 @@ loanMakePayment(
Number const periodicPayment = loan->at(sfPeriodicPayment);
auto prevPaymentDateProxy = loan->at(sfPreviousPaymentDate);
auto prevPaymentDateProxy = loan->at(sfPreviousPaymentDueDate);
std::uint32_t const startDate = loan->at(sfStartDate);
std::uint32_t const paymentInterval = loan->at(sfPaymentInterval);

View File

@@ -374,7 +374,8 @@ LoanManage::unimpairLoan(
loanSle->clearFlag(lsfLoanImpaired);
auto const paymentInterval = loanSle->at(sfPaymentInterval);
auto const normalPaymentDueDate =
std::max(loanSle->at(sfPreviousPaymentDate), loanSle->at(sfStartDate)) +
std::max(
loanSle->at(sfPreviousPaymentDueDate), loanSle->at(sfStartDate)) +
paymentInterval;
if (!hasExpired(view, normalPaymentDueDate))
{

View File

@@ -282,6 +282,15 @@ LoanSet::preclaim(PreclaimContext const& ctx)
if (!vault)
// Should be impossible
return tefBAD_LEDGER; // LCOV_EXCL_LINE
if (vault->at(sfAssetsMaximum) != 0 &&
vault->at(sfAssetsTotal) >= vault->at(sfAssetsMaximum))
{
JLOG(ctx.j.warn())
<< "Vault at maximum assets limit. Can't add another loan.";
return tecLIMIT_EXCEEDED;
}
Asset const asset = vault->at(sfAsset);
auto const vaultPseudo = vault->at(sfAccount);
@@ -411,8 +420,12 @@ LoanSet::doApply()
principalRequested,
properties.loanState.managementFeeDue);
if (vaultSle->at(sfAssetsMaximum) != 0 &&
vaultTotalProxy + state.interestDue > vaultSle->at(sfAssetsMaximum))
auto const vaultMaximum = *vaultSle->at(sfAssetsMaximum);
XRPL_ASSERT_PARTS(
vaultMaximum == 0 || vaultMaximum > *vaultTotalProxy,
"ripple::LoanSet::doApply",
"Vault is below maximum limit");
if (vaultMaximum != 0 && state.interestDue > vaultMaximum - vaultTotalProxy)
{
JLOG(j_.warn()) << "Loan would exceed the maximum assets of the vault";
return tecLIMIT_EXCEEDED;
@@ -600,7 +613,7 @@ LoanSet::doApply()
loan->at(sfTotalValueOutstanding) = properties.loanState.valueOutstanding;
loan->at(sfManagementFeeOutstanding) =
properties.loanState.managementFeeDue;
loan->at(sfPreviousPaymentDate) = 0;
loan->at(sfPreviousPaymentDueDate) = 0;
loan->at(sfNextPaymentDueDate) = startDate + paymentInterval;
loan->at(sfPaymentRemaining) = paymentTotal;
view.insert(loan);