Compare commits

...

10 Commits

Author SHA1 Message Date
Ed Hennis
c5988233d0 Set version to 3.1.1 (#6410) 2026-02-23 15:47:09 -05:00
Valentin Balaschenko
61481ff61d Set version to 3.1.1-rc1 2026-02-20 19:22:03 -05:00
Valentin Balaschenko
a6d1e2cc7c ci: Update prepare-runner action to fix macOS build environment
Updates XRPLF/actions prepare-runner to version 2cbf48101 which fixes
pip upgrade failures on macOS runners with Homebrew-managed Python.
2026-02-20 20:04:47 +00:00
Valentin Balaschenko
6a5f269020 Disable featureBatch and fixBatchInnerSigs amendments (#6402) 2026-02-20 20:03:42 +00:00
Ed Hennis
d325f20c76 Set version to 3.1.0 (#6284) 2026-01-27 20:13:06 -05:00
Ed Hennis
69dda6b34c Set version to 3.1.0-rc2 2026-01-22 18:13:41 -05:00
Ed Hennis
e3644f265c fix: Remove DEFAULT fields that change to the default in associateAsset (#6259)
- Add Vault creation tests for showing valid range for AssetsMaximum
2026-01-22 16:52:57 -05:00
Ayaz Salikhov
8c573cd0bb Fix dependencies so clio can use libxrpl (#6251)
- Include <functional> header in Number.h
2026-01-22 11:30:54 -05:00
Ed Hennis
c894cd2b5f Revert "Fix dependencies so clio can use libxrpl (#6251)"
This reverts commit ecfe43ece7.
2026-01-22 11:30:39 -05:00
Ayaz Salikhov
ecfe43ece7 Fix dependencies so clio can use libxrpl (#6251)
- Include <functional> header in Number.h
2026-01-21 14:11:21 -05:00
10 changed files with 295 additions and 7 deletions

View File

@@ -77,9 +77,9 @@ jobs:
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Prepare runner
uses: XRPLF/actions/.github/actions/prepare-runner@99685816bb60a95a66852f212f382580e180df3a
uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d
with:
disable_ccache: false
enable_ccache: ${{ inputs.ccache_enabled }}
- name: Print build environment
uses: ./.github/actions/print-env

View File

@@ -70,9 +70,9 @@ jobs:
uses: actions/checkout@08eba0b27e820071cde6df949e0beb9ba4906955 # v4.3.0
- name: Prepare runner
uses: XRPLF/actions/.github/actions/prepare-runner@99685816bb60a95a66852f212f382580e180df3a
uses: XRPLF/actions/prepare-runner@2cbf481018d930656e9276fcc20dc0e3a0be5b6d
with:
disable_ccache: false
enable_ccache: false
- name: Print build environment
uses: ./.github/actions/print-env

View File

@@ -23,6 +23,7 @@
#include <xrpl/beast/utility/instrumentation.h>
#include <cstdint>
#include <functional>
#include <limits>
#include <optional>
#include <ostream>

View File

@@ -416,6 +416,9 @@ public:
void
delField(int index);
SOEStyle
getStyle(SField const& field) const;
bool
hasMatchingEntry(STBase const&);

View File

@@ -32,7 +32,7 @@
// Add new amendments to the top of this list.
// Keep it sorted in reverse chronological order.
XRPL_FIX (BatchInnerSigs, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (BatchInnerSigs, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(LendingProtocol, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (DirectoryLimit, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (IncludeKeyletFields, Supported::yes, VoteBehavior::DefaultNo)
@@ -46,7 +46,7 @@ XRPL_FEATURE(TokenEscrow, Supported::yes, VoteBehavior::DefaultNo
XRPL_FIX (EnforceNFTokenTrustlineV2, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FIX (AMMv1_3, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionedDEX, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(Batch, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(SingleAssetVault, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionDelegation, Supported::no, VoteBehavior::DefaultNo)
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)

View File

@@ -36,7 +36,7 @@ namespace BuildInfo {
// and follow the format described at http://semver.org/
//------------------------------------------------------------------------------
// clang-format off
char const* const versionString = "3.1.0-rc1"
char const* const versionString = "3.1.1"
// clang-format on
#if defined(DEBUG) || defined(SANITIZER)

View File

@@ -142,6 +142,8 @@ areComparable(STAmount const& v1, STAmount const& v2)
return false;
}
static_assert(INITIAL_XRP.drops() == STAmount::cMaxNativeN);
STAmount::STAmount(SerialIter& sit, SField const& name) : STBase(name)
{
std::uint64_t value = sit.get64();

View File

@@ -599,6 +599,12 @@ STObject::delField(int index)
v_.erase(v_.begin() + index);
}
SOEStyle
STObject::getStyle(SField const& field) const
{
return mType ? mType->style(field) : soeINVALID;
}
unsigned char
STObject::getFieldU8(SField const& field) const
{

View File

@@ -18,10 +18,28 @@ associateAsset(SLE& sle, Asset const& asset)
// If the field is not set or present, skip it.
if (type == STI_NOTPRESENT)
continue;
// If the type doesn't downcast, then the flag shouldn't be on the
// SField
auto& ta = entry.downcast<STTakesAsset>();
auto const style = sle.getStyle(ta.getFName());
XRPL_ASSERT_PARTS(
style != soeINVALID,
"ripple::associateAsset",
"valid template element style");
XRPL_ASSERT_PARTS(
style != soeDEFAULT || !ta.isDefault(),
"ripple::associateAsset",
"non-default value");
ta.associateAsset(asset);
// associateAsset in derived classes may change the underlying
// value, but it won't know anything about how the value relates to
// the SLE. If the template element is soeDEFAULT, and the value
// changed to the default value, remove the field.
if (style == soeDEFAULT && ta.isDefault())
sle.makeFieldAbsent(field);
}
}
}

View File

@@ -5801,6 +5801,263 @@ class Vault_test : public beast::unit_test::suite
testCase(MPT, "MPT", owner, depositor, issuer);
}
void
testAssetsMaximum()
{
testcase("Assets Maximum");
using namespace test::jtx;
Env env{*this, testable_amendments() | featureSingleAssetVault};
Account const owner{"owner"};
Account const issuer{"issuer"};
Vault vault{env};
env.fund(XRP(1'000'000), issuer, owner);
env.close();
auto const maxInt64 =
std::to_string(std::numeric_limits<std::int64_t>::max());
BEAST_EXPECT(maxInt64 == "9223372036854775807");
// Naming things is hard
auto const maxInt64Plus1 = std::to_string(
static_cast<std::uint64_t>(
std::numeric_limits<std::int64_t>::max()) +
1);
BEAST_EXPECT(maxInt64Plus1 == "9223372036854775808");
auto const initialXRP = to_string(INITIAL_XRP);
BEAST_EXPECT(initialXRP == "100000000000000000");
auto const initialXRPPlus1 = to_string(INITIAL_XRP + 1);
BEAST_EXPECT(initialXRPPlus1 == "100000000000000001");
{
testcase("Assets Maximum: XRP");
PrettyAsset const xrpAsset = xrpIssue();
auto [tx, keylet] =
vault.create({.owner = owner, .asset = xrpAsset});
tx[sfData] = "4D65746144617461";
tx[sfAssetsMaximum] = maxInt64;
env(tx, ter(tefEXCEPTION), THISLINE);
env.close();
tx[sfAssetsMaximum] = initialXRPPlus1;
env(tx, ter(tefEXCEPTION), THISLINE);
env.close();
tx[sfAssetsMaximum] = initialXRP;
env(tx, THISLINE);
env.close();
tx[sfAssetsMaximum] = maxInt64Plus1;
env(tx, ter(tefEXCEPTION), THISLINE);
env.close();
// This value will be rounded
auto const insertAt = maxInt64Plus1.size() - 3;
auto const decimalTest = maxInt64Plus1.substr(0, insertAt) + "." +
maxInt64Plus1.substr(insertAt); // (max int64+1) / 1000
BEAST_EXPECT(decimalTest == "9223372036854775.808");
tx[sfAssetsMaximum] = decimalTest;
auto const newKeylet = keylet::vault(owner.id(), env.seq(owner));
env(tx, THISLINE);
env.close();
auto const vaultSle = env.le(newKeylet);
if (!BEAST_EXPECT(vaultSle))
return;
BEAST_EXPECT(vaultSle->at(sfAssetsMaximum) == 9223372036854776);
}
{
testcase("Assets Maximum: MPT");
PrettyAsset const mptAsset = [&]() {
MPTTester mptt{env, issuer, mptInitNoFund};
mptt.create(
{.flags =
tfMPTCanClawback | tfMPTCanTransfer | tfMPTCanLock});
env.close();
PrettyAsset const mptAsset = mptt["MPT"];
mptt.authorize({.account = owner});
env.close();
return mptAsset;
}();
env(pay(issuer, owner, mptAsset(100'000)), THISLINE);
env.close();
auto [tx, keylet] =
vault.create({.owner = owner, .asset = mptAsset});
tx[sfData] = "4D65746144617461";
tx[sfAssetsMaximum] = maxInt64;
env(tx, THISLINE);
env.close();
tx[sfAssetsMaximum] = initialXRPPlus1;
env(tx, THISLINE);
env.close();
tx[sfAssetsMaximum] = initialXRP;
env(tx, THISLINE);
env.close();
tx[sfAssetsMaximum] = maxInt64Plus1;
env(tx, ter(tefEXCEPTION), THISLINE);
env.close();
// This value will be rounded
auto const insertAt = maxInt64Plus1.size() - 1;
auto const decimalTest = maxInt64Plus1.substr(0, insertAt) + "." +
maxInt64Plus1.substr(insertAt); // (max int64+1) / 10
BEAST_EXPECT(decimalTest == "922337203685477580.8");
tx[sfAssetsMaximum] = decimalTest;
auto const newKeylet = keylet::vault(owner.id(), env.seq(owner));
env(tx, THISLINE);
env.close();
auto const vaultSle = env.le(newKeylet);
if (!BEAST_EXPECT(vaultSle))
return;
BEAST_EXPECT(vaultSle->at(sfAssetsMaximum) == 922337203685477581);
}
{
testcase("Assets Maximum: IOU");
// Almost anything goes with IOUs
PrettyAsset iouAsset = issuer["IOU"];
env.trust(iouAsset(1000), owner);
env(pay(issuer, owner, iouAsset(200)));
env.close();
auto [tx, keylet] =
vault.create({.owner = owner, .asset = iouAsset});
tx[sfData] = "4D65746144617461";
tx[sfAssetsMaximum] = maxInt64;
env(tx, THISLINE);
env.close();
tx[sfAssetsMaximum] = initialXRPPlus1;
env(tx, THISLINE);
env.close();
tx[sfAssetsMaximum] = initialXRP;
env(tx, THISLINE);
env.close();
tx[sfAssetsMaximum] = maxInt64Plus1;
env(tx, THISLINE);
env.close();
tx[sfAssetsMaximum] = "1000000000000000e80";
env.close();
tx[sfAssetsMaximum] = "1000000000000000e-96";
env.close();
// These values will be rounded to 15 significant digits
{
auto const insertAt = maxInt64Plus1.size() - 1;
auto const decimalTest = maxInt64Plus1.substr(0, insertAt) +
"." + maxInt64Plus1.substr(insertAt); // (max int64+1) / 10
BEAST_EXPECT(decimalTest == "922337203685477580.8");
tx[sfAssetsMaximum] = decimalTest;
auto const newKeylet =
keylet::vault(owner.id(), env.seq(owner));
env(tx, THISLINE);
env.close();
auto const vaultSle = env.le(newKeylet);
if (!BEAST_EXPECT(vaultSle))
return;
BEAST_EXPECT(
(vaultSle->at(sfAssetsMaximum) ==
Number{9223372036854776, 2, Number::normalized{}}));
}
{
tx[sfAssetsMaximum] =
"9223372036854775807e40"; // max int64 * 10^40
auto const newKeylet =
keylet::vault(owner.id(), env.seq(owner));
env(tx, THISLINE);
env.close();
auto const vaultSle = env.le(newKeylet);
if (!BEAST_EXPECT(vaultSle))
return;
BEAST_EXPECT(
(vaultSle->at(sfAssetsMaximum) ==
Number{9223372036854776, 43, Number::normalized{}}));
}
{
tx[sfAssetsMaximum] =
"9223372036854775807e-40"; // max int64 * 10^-40
auto const newKeylet =
keylet::vault(owner.id(), env.seq(owner));
env(tx, THISLINE);
env.close();
auto const vaultSle = env.le(newKeylet);
if (!BEAST_EXPECT(vaultSle))
return;
BEAST_EXPECT(
(vaultSle->at(sfAssetsMaximum) ==
Number{9223372036854776, -37, Number::normalized{}}));
}
{
tx[sfAssetsMaximum] =
"9223372036854775807e-100"; // max int64 * 10^-100
auto const newKeylet =
keylet::vault(owner.id(), env.seq(owner));
env(tx, THISLINE);
env.close();
// Field 'AssetsMaximum' may not be explicitly set to default.
auto const vaultSle = env.le(newKeylet);
if (!BEAST_EXPECT(vaultSle))
return;
BEAST_EXPECT(vaultSle->at(sfAssetsMaximum) == numZero);
}
// What _can't_ IOUs do?
// 1. Exceed maximum exponent / offset
tx[sfAssetsMaximum] = "1000000000000000e81";
env(tx, ter(tefEXCEPTION), THISLINE);
env.close();
// 2. Mantissa larger than uint64 max
try
{
tx[sfAssetsMaximum] =
"18446744073709551617e5"; // uint64 max + 1
env(tx, THISLINE);
BEAST_EXPECT(false);
}
catch (parse_error const& e)
{
using namespace std::string_literals;
BEAST_EXPECT(
e.what() ==
"invalidParamsField 'tx_json.AssetsMaximum' has invalid "
"data."s);
}
}
}
public:
void
run() override
@@ -5821,6 +6078,7 @@ public:
testDelegate();
testVaultClawbackBurnShares();
testVaultClawbackAssets();
testAssetsMaximum();
}
};