mirror of
https://github.com/XRPLF/rippled.git
synced 2026-06-22 01:56:59 +00:00
Compare commits
12 Commits
bthomee/no
...
tapanito/u
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
3ebc4b5a00 | ||
|
|
577d7457f1 | ||
|
|
a389f922dd | ||
|
|
79f4ddc4a6 | ||
|
|
8811e6c091 | ||
|
|
8645272b1a | ||
|
|
b3978ae302 | ||
|
|
0e3807040d | ||
|
|
aa5b02cde3 | ||
|
|
99747f6b04 | ||
|
|
fc620bf29a | ||
|
|
b01ba1fce2 |
6
.github/scripts/strategy-matrix/linux.json
vendored
6
.github/scripts/strategy-matrix/linux.json
vendored
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"image_tag": "sha-8abe82e",
|
||||
"image_tag": "sha-63ffdc3",
|
||||
"configs": {
|
||||
"ubuntu": [
|
||||
{
|
||||
@@ -67,7 +67,7 @@
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"image": "debian:bookworm"
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-debian:sha-63ffdc3"
|
||||
}
|
||||
],
|
||||
|
||||
@@ -76,7 +76,7 @@
|
||||
"compiler": ["gcc"],
|
||||
"build_type": ["Release"],
|
||||
"arch": ["amd64"],
|
||||
"image": "registry.access.redhat.com/ubi9/ubi:latest"
|
||||
"image": "ghcr.io/xrplf/xrpld/packaging-rhel:sha-63ffdc3"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
10
.github/workflows/build-nix-images.yml
vendored
10
.github/workflows/build-nix-images.yml
vendored
@@ -6,23 +6,20 @@ on:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-nix-images.yml"
|
||||
- ".github/workflows/reusable-build-docker-image.yml"
|
||||
- ".github/workflows/reusable-build-merge-docker-images.yml"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "nix/**"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/build-nix-images.yml"
|
||||
- ".github/workflows/reusable-build-docker-image.yml"
|
||||
- ".github/workflows/reusable-build-merge-docker-images.yml"
|
||||
- "flake.nix"
|
||||
- "flake.lock"
|
||||
- "nix/**"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
# Read `on-trigger.yml` for the rationale behind this concurrency group name.
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' && github.sha || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
@@ -49,8 +46,9 @@ jobs:
|
||||
base_image: debian:bookworm
|
||||
- name: rhel
|
||||
base_image: registry.access.redhat.com/ubi9/ubi:latest
|
||||
uses: ./.github/workflows/reusable-build-merge-docker-images.yml
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@c1b480188519e0cad040e6aa70db1cbc5a797e07
|
||||
with:
|
||||
image_name: ghcr.io/xrplf/xrpld/nix-${{ matrix.distro.name }}
|
||||
dockerfile: nix/docker/Dockerfile
|
||||
base_image: ${{ matrix.distro.base_image }}
|
||||
push: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }}
|
||||
|
||||
10
.github/workflows/build-packaging-images.yml
vendored
10
.github/workflows/build-packaging-images.yml
vendored
@@ -6,21 +6,18 @@ on:
|
||||
- develop
|
||||
paths:
|
||||
- ".github/workflows/build-packaging-images.yml"
|
||||
- ".github/workflows/reusable-build-docker-image.yml"
|
||||
- ".github/workflows/reusable-build-merge-docker-images.yml"
|
||||
- "package/Dockerfile"
|
||||
- "package/install-packaging-tools.sh"
|
||||
pull_request:
|
||||
paths:
|
||||
- ".github/workflows/build-packaging-images.yml"
|
||||
- ".github/workflows/reusable-build-docker-image.yml"
|
||||
- ".github/workflows/reusable-build-merge-docker-images.yml"
|
||||
- "package/Dockerfile"
|
||||
- "package/install-packaging-tools.sh"
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency:
|
||||
group: ${{ github.workflow }}-${{ github.ref }}
|
||||
# Read `on-trigger.yml` for the rationale behind this concurrency group name.
|
||||
group: ${{ github.workflow }}-${{ github.event_name == 'push' && github.ref == 'refs/heads/develop' && github.sha || github.ref }}
|
||||
cancel-in-progress: true
|
||||
|
||||
defaults:
|
||||
@@ -41,8 +38,9 @@ jobs:
|
||||
base_image: debian:bookworm
|
||||
- name: rhel
|
||||
base_image: registry.access.redhat.com/ubi9/ubi:latest
|
||||
uses: ./.github/workflows/reusable-build-merge-docker-images.yml
|
||||
uses: XRPLF/actions/.github/workflows/build-multiarch-image.yml@c1b480188519e0cad040e6aa70db1cbc5a797e07
|
||||
with:
|
||||
image_name: ghcr.io/xrplf/xrpld/packaging-${{ matrix.distro.name }}
|
||||
dockerfile: package/Dockerfile
|
||||
base_image: ${{ matrix.distro.base_image }}
|
||||
push: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }}
|
||||
|
||||
2
.github/workflows/pre-commit.yml
vendored
2
.github/workflows/pre-commit.yml
vendored
@@ -14,7 +14,7 @@ on:
|
||||
jobs:
|
||||
# Call the workflow in the XRPLF/actions repo that runs the pre-commit hooks.
|
||||
run-hooks:
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@cba1f0891650baf1a9c88624dc2d72573be2eb81
|
||||
uses: XRPLF/actions/.github/workflows/pre-commit.yml@312aaab296060ff89d7f798dcab59f019bea6e02
|
||||
with:
|
||||
runs_on: ubuntu-latest
|
||||
container: '{ "image": "ghcr.io/xrplf/ci/tools-rippled-pre-commit:sha-41ec7c1" }'
|
||||
|
||||
4
.github/workflows/publish-docs.yml
vendored
4
.github/workflows/publish-docs.yml
vendored
@@ -41,13 +41,13 @@ env:
|
||||
jobs:
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-8abe82e
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-63ffdc3
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
@@ -1,89 +0,0 @@
|
||||
# Build a single-platform Docker image. On push, the image is pushed to
|
||||
# GHCR with arch-suffixed tags (e.g. `:latest-amd64`, `:sha-abc-amd64`)
|
||||
# so the calling workflow can stitch per-arch builds into a multi-arch
|
||||
# manifest without needing to pass digests around.
|
||||
name: Reusable build Docker image (single platform)
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
image_name:
|
||||
description: "Full image name without tag (e.g. 'ghcr.io/xrplf/xrpld/nix-ubuntu')"
|
||||
required: true
|
||||
type: string
|
||||
dockerfile:
|
||||
description: "Path to the Dockerfile, relative to the repository root"
|
||||
required: true
|
||||
type: string
|
||||
base_image:
|
||||
description: "Value passed to the Dockerfile as the BASE_IMAGE build arg"
|
||||
required: true
|
||||
type: string
|
||||
platform:
|
||||
description: "Docker platform string, e.g. linux/amd64"
|
||||
required: true
|
||||
type: string
|
||||
runner:
|
||||
description: "GitHub Actions runner label to build on"
|
||||
required: true
|
||||
type: string
|
||||
push:
|
||||
description: "Whether to push the image to GHCR"
|
||||
required: true
|
||||
type: boolean
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ inputs.platform }}
|
||||
runs-on: ${{ inputs.runner }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Determine arch
|
||||
id: vars
|
||||
env:
|
||||
PLATFORM: ${{ inputs.platform }}
|
||||
run: |
|
||||
echo "arch=${PLATFORM##*/}" >>$GITHUB_OUTPUT
|
||||
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
if: inputs.push
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
||||
with:
|
||||
images: ${{ inputs.image_name }}
|
||||
tags: |
|
||||
type=sha,prefix=sha-,format=short
|
||||
type=raw,value=latest
|
||||
flavor: |
|
||||
suffix=-${{ steps.vars.outputs.arch }},onlatest=true
|
||||
|
||||
- name: Build and push
|
||||
uses: docker/build-push-action@f9f3042f7e2789586610d6e8b85c8f03e5195baf # v7.2.0
|
||||
with:
|
||||
context: .
|
||||
file: ${{ inputs.dockerfile }}
|
||||
platforms: ${{ inputs.platform }}
|
||||
push: ${{ inputs.push }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
labels: ${{ steps.meta.outputs.labels }}
|
||||
build-args: BASE_IMAGE=${{ inputs.base_image }}
|
||||
@@ -1,89 +0,0 @@
|
||||
name: Reusable build and merge Docker image (multi-arch)
|
||||
|
||||
on:
|
||||
workflow_call:
|
||||
inputs:
|
||||
image_name:
|
||||
description: "Full image name without tag (e.g. 'ghcr.io/xrplf/xrpld/nix-ubuntu')"
|
||||
required: true
|
||||
type: string
|
||||
dockerfile:
|
||||
description: "Path to the Dockerfile, relative to the repository root"
|
||||
required: true
|
||||
type: string
|
||||
base_image:
|
||||
description: "Value passed to the Dockerfile as the BASE_IMAGE build arg"
|
||||
required: true
|
||||
type: string
|
||||
|
||||
defaults:
|
||||
run:
|
||||
shell: bash
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build ${{ inputs.image_name }}
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
target:
|
||||
- platform: linux/amd64
|
||||
runner: ubuntu-latest
|
||||
- platform: linux/arm64
|
||||
runner: ubuntu-24.04-arm
|
||||
|
||||
uses: ./.github/workflows/reusable-build-docker-image.yml
|
||||
with:
|
||||
image_name: ${{ inputs.image_name }}
|
||||
dockerfile: ${{ inputs.dockerfile }}
|
||||
base_image: ${{ inputs.base_image }}
|
||||
platform: ${{ matrix.target.platform }}
|
||||
runner: ${{ matrix.target.runner }}
|
||||
push: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }}
|
||||
|
||||
merge:
|
||||
name: Merge ${{ inputs.image_name }}
|
||||
needs: build
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
packages: write
|
||||
|
||||
steps:
|
||||
- name: Set up Docker Buildx
|
||||
uses: docker/setup-buildx-action@d7f5e7f509e45cec5c76c4d5afdd7de93d0b3df5 # v4.1.0
|
||||
|
||||
- name: Docker metadata
|
||||
id: meta
|
||||
uses: docker/metadata-action@80c7e94dd9b9319bd5eb7a0e0fe9291e23a2a2e9 # v6.1.0
|
||||
with:
|
||||
images: ${{ inputs.image_name }}
|
||||
tags: |
|
||||
type=sha,prefix=sha-,format=short
|
||||
type=raw,value=latest
|
||||
|
||||
- name: Login to GitHub Container Registry
|
||||
uses: docker/login-action@650006c6eb7dba73a995cc03b0b2d7f5ca915bee # v4.2.0
|
||||
with:
|
||||
registry: ghcr.io
|
||||
username: ${{ github.repository_owner }}
|
||||
password: ${{ secrets.GITHUB_TOKEN }}
|
||||
|
||||
- name: Create multi-arch manifests
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }}
|
||||
run: |
|
||||
for tag in $(jq -cr '.tags[]' <<<"$DOCKER_METADATA_OUTPUT_JSON"); do
|
||||
docker buildx imagetools create -t "$tag" "${tag}-amd64" "${tag}-arm64"
|
||||
done
|
||||
|
||||
- name: Inspect image
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && github.event_name == 'push' }}
|
||||
env:
|
||||
IMAGE_NAME: ${{ inputs.image_name }}
|
||||
IMAGE_VERSION: ${{ steps.meta.outputs.version }}
|
||||
run: |
|
||||
docker buildx imagetools inspect "${IMAGE_NAME}:${IMAGE_VERSION}"
|
||||
@@ -113,7 +113,7 @@ jobs:
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8
|
||||
with:
|
||||
enable_ccache: ${{ inputs.ccache_enabled }}
|
||||
|
||||
@@ -370,7 +370,7 @@ jobs:
|
||||
|
||||
- name: Upload coverage report
|
||||
if: ${{ github.repository == 'XRPLF/rippled' && !inputs.build_only && env.COVERAGE_ENABLED == 'true' }}
|
||||
uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1
|
||||
uses: codecov/codecov-action@fb8b3582c8e4def4969c97caa2f19720cb33a72f # v7.0.0
|
||||
with:
|
||||
disable_search: true
|
||||
disable_telem: true
|
||||
|
||||
6
.github/workflows/reusable-clang-tidy.yml
vendored
6
.github/workflows/reusable-clang-tidy.yml
vendored
@@ -29,14 +29,14 @@ jobs:
|
||||
if: ${{ inputs.check_only_changed }}
|
||||
permissions:
|
||||
contents: read
|
||||
uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@224f3c48d3014d082a1129237b8291ff0b0a331f
|
||||
uses: XRPLF/actions/.github/workflows/determine-tidy-files.yml@312aaab296060ff89d7f798dcab59f019bea6e02
|
||||
|
||||
run-clang-tidy:
|
||||
name: Run clang tidy
|
||||
needs: [determine-files]
|
||||
if: ${{ always() && !cancelled() && (!inputs.check_only_changed || needs.determine-files.outputs.cpp_changed_files != '' || needs.determine-files.outputs.clang_tidy_config_changed == 'true') }}
|
||||
runs-on: ["self-hosted", "Linux", "X64", "heavy"]
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-8abe82e"
|
||||
container: "ghcr.io/xrplf/xrpld/nix-debian:sha-63ffdc3"
|
||||
permissions:
|
||||
contents: read
|
||||
issues: write
|
||||
@@ -45,7 +45,7 @@ jobs:
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
25
.github/workflows/reusable-package.yml
vendored
25
.github/workflows/reusable-package.yml
vendored
@@ -68,31 +68,6 @@ jobs:
|
||||
timeout-minutes: 30
|
||||
|
||||
steps:
|
||||
# Packaging runs in a vanilla distro image, so the tooling has to come
|
||||
# from the distro's archive: debhelper for deb, rpm-build (and the
|
||||
# systemd / find-debuginfo macros it depends on) for rpm. Run this
|
||||
# before actions/checkout so the latter can use git (real history) for
|
||||
# build_pkg.sh's SOURCE_DATE_EPOCH; otherwise it falls back to a tarball
|
||||
# download and the timestamp comes from wall-clock time.
|
||||
- name: Install packaging tooling (deb)
|
||||
if: ${{ matrix.distro == 'debian' }}
|
||||
run: |
|
||||
export DEBIAN_FRONTEND=noninteractive
|
||||
apt-get update
|
||||
apt-get install -y --no-install-recommends \
|
||||
ca-certificates \
|
||||
debhelper \
|
||||
git
|
||||
|
||||
- name: Install packaging tooling (rpm)
|
||||
if: ${{ matrix.distro == 'rhel' }}
|
||||
run: |
|
||||
dnf install -y --setopt=install_weak_deps=False \
|
||||
git \
|
||||
rpm-build \
|
||||
redhat-rpm-config \
|
||||
systemd-rpm-macros
|
||||
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
|
||||
2
.github/workflows/reusable-upload-recipe.yml
vendored
2
.github/workflows/reusable-upload-recipe.yml
vendored
@@ -40,7 +40,7 @@ defaults:
|
||||
jobs:
|
||||
upload:
|
||||
runs-on: ubuntu-latest
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-8abe82e
|
||||
container: ghcr.io/xrplf/xrpld/nix-ubuntu:sha-63ffdc3
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
2
.github/workflows/upload-conan-deps.yml
vendored
2
.github/workflows/upload-conan-deps.yml
vendored
@@ -67,7 +67,7 @@ jobs:
|
||||
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
|
||||
|
||||
- name: Prepare runner
|
||||
uses: XRPLF/actions/prepare-runner@90f11ee655d1687824fb8793db770477d52afbab
|
||||
uses: XRPLF/actions/prepare-runner@c47daebb2f9db64ffbac71b47d68a661498d5ce8
|
||||
with:
|
||||
enable_ccache: false
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@ gcc --version
|
||||
gcov --version
|
||||
gcovr --version
|
||||
git --version
|
||||
git-cliff --version
|
||||
gpg --version
|
||||
less --version
|
||||
make --version
|
||||
|
||||
@@ -15,6 +15,7 @@ in
|
||||
doxygen
|
||||
gcovr
|
||||
git
|
||||
git-cliff
|
||||
gnumake
|
||||
gnupg # needed for signing commits & codecov/codecov-action
|
||||
llvmPackages_22.clang-tools
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
#include <test/jtx/escrow.h>
|
||||
#include <test/jtx/fee.h>
|
||||
#include <test/jtx/flags.h>
|
||||
#include <test/jtx/mpt.h>
|
||||
#include <test/jtx/offer.h>
|
||||
#include <test/jtx/paths.h>
|
||||
#include <test/jtx/pay.h>
|
||||
@@ -19,6 +20,7 @@
|
||||
#include <test/jtx/ter.h>
|
||||
#include <test/jtx/trust.h>
|
||||
#include <test/jtx/txflags.h>
|
||||
#include <test/jtx/vault.h>
|
||||
|
||||
#include <xrpl/basics/Number.h>
|
||||
#include <xrpl/basics/base_uint.h>
|
||||
@@ -36,6 +38,7 @@
|
||||
#include <xrpl/protocol/Indexes.h>
|
||||
#include <xrpl/protocol/Issue.h>
|
||||
#include <xrpl/protocol/LedgerFormats.h>
|
||||
#include <xrpl/protocol/MPTIssue.h>
|
||||
#include <xrpl/protocol/Protocol.h>
|
||||
#include <xrpl/protocol/Quality.h>
|
||||
#include <xrpl/protocol/Rules.h>
|
||||
@@ -7072,6 +7075,194 @@ private:
|
||||
{all});
|
||||
}
|
||||
|
||||
// Create a single-asset vault, deposit assets so the depositor receives
|
||||
// shares (an MPT issued by the vault pseudo-account), then pair those
|
||||
// shares with XRP in an AMM. Finally do a single-asset deposit of more
|
||||
// shares into the AMM.
|
||||
void
|
||||
testVaultSharesAMM()
|
||||
{
|
||||
testcase("Vault Shares paired with XRP in AMM");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
// Vaults rely on featureSingleAssetVault (which the AMM_test class
|
||||
// strips by default). MPT-AMM pairs require featureMPTokensV2.
|
||||
FeatureBitset const features{
|
||||
jtx::testableAmendments() | featureSingleAssetVault | featureMPTokensV2};
|
||||
|
||||
Env env{*this, features};
|
||||
|
||||
Account const owner{"vaultOwner"};
|
||||
env.fund(XRP(1'000'000), owner);
|
||||
env.close();
|
||||
|
||||
// Use XRP as the vault asset for simplicity.
|
||||
PrettyAsset const asset{xrpIssue(), 1'000'000};
|
||||
|
||||
Vault const vault{env};
|
||||
auto [vaultTx, vaultKeylet] = vault.create({.owner = owner, .asset = asset});
|
||||
env(vaultTx);
|
||||
env.close();
|
||||
if (!BEAST_EXPECT(env.le(vaultKeylet)))
|
||||
return;
|
||||
|
||||
// Deposit 10,000 XRP into the vault. Owner receives shares (MPT)
|
||||
// issued by the vault's pseudo-account.
|
||||
env(vault.deposit(
|
||||
{.depositor = owner, .id = vaultKeylet.key, .amount = asset(10'000).value()}));
|
||||
env.close();
|
||||
|
||||
auto const vaultSle = env.le(vaultKeylet);
|
||||
if (!BEAST_EXPECT(vaultSle))
|
||||
return;
|
||||
MPTID const shareMptID = vaultSle->at(sfShareMPTID);
|
||||
MPTIssue const shareIssue{shareMptID};
|
||||
// The share MPT is issued by the vault's pseudo-account. Memoize so
|
||||
// env.balance() can format share amounts.
|
||||
env.memoize(Account{"vaultPseudo", vaultSle->at(sfAccount)});
|
||||
|
||||
// XRP vaults use scale=6, so a 10,000 XRP deposit yields
|
||||
// 10,000 * 1e6 = 10^10 share units (raw MPT amount).
|
||||
STAmount const sharesHeld = env.balance(owner, shareIssue);
|
||||
BEAST_EXPECT(sharesHeld.mantissa() == 10'000'000'000ull);
|
||||
BEAST_EXPECT(sharesHeld.asset() == shareIssue);
|
||||
|
||||
// Seed the AMM with half the shares + 5,000 XRP.
|
||||
STAmount const halfShares(shareIssue, std::uint64_t{5'000'000'000});
|
||||
AMM ammOwner(env, owner, halfShares, XRP(5'000));
|
||||
BEAST_EXPECT(ammOwner.ammExists());
|
||||
|
||||
// Single-asset deposit: add 2,500,000,000 more shares (a quarter of
|
||||
// the original holding) to the share side of the pool.
|
||||
STAmount const extraShares(shareIssue, std::uint64_t{2'500'000'000});
|
||||
ammOwner.deposit(owner, extraShares);
|
||||
|
||||
// The share-side pool should now equal halfShares + extraShares,
|
||||
// while the XRP-side balance is unchanged at 5,000 XRP.
|
||||
auto const [shareBalance, xrpBalance, lpt] = ammOwner.balances(shareIssue, xrpIssue());
|
||||
BEAST_EXPECT(shareBalance == halfShares + extraShares);
|
||||
BEAST_EXPECT(xrpBalance == XRP(5'000));
|
||||
// Owner now holds the original 10B shares minus what was put into the
|
||||
// AMM (5B seed + 2.5B single-asset deposit) = 2.5B.
|
||||
STAmount const expectedOwnerShares(shareIssue, std::uint64_t{2'500'000'000});
|
||||
BEAST_EXPECT(env.balance(owner, shareIssue) == expectedOwnerShares);
|
||||
}
|
||||
|
||||
// Create a Vault whose underlying asset is a lockable / clawback-able
|
||||
// MPT. Pair the vault shares with XRP in an AMM. Transfer half of the
|
||||
// owner's LP tokens to a second account, then issuer-lock the
|
||||
// underlying MPT, then try to transfer LP tokens / cash out again.
|
||||
//
|
||||
// Locking the underlying MPT cascades up via
|
||||
// `isVaultPseudoAccountFrozen`: the vault-share MPT is treated as
|
||||
// frozen because its underlying is locked. So:
|
||||
// - LP-token Payment after lock fails (`tecPATH_DRY`).
|
||||
// - AMM withdrawal of LP tokens fails (`tecFROZEN`).
|
||||
// The LP tokens are effectively stuck for as long as the underlying
|
||||
// MPT remains locked.
|
||||
void
|
||||
testLockedVaultMPTCashOut()
|
||||
{
|
||||
testcase("Cash out LP Tokens after vault MPT locked");
|
||||
|
||||
using namespace jtx;
|
||||
|
||||
FeatureBitset const features{
|
||||
jtx::testableAmendments() | featureSingleAssetVault | featureMPTokensV2};
|
||||
|
||||
Env env{*this, features};
|
||||
|
||||
Account const issuer{"issuer"};
|
||||
Account const owner{"vaultOwner"};
|
||||
Account const trader{"trader"};
|
||||
|
||||
env.fund(XRP(1'000'000), issuer, owner, trader);
|
||||
env.close();
|
||||
|
||||
// Underlying MPT supports lock + clawback. kMptDexFlags adds
|
||||
// CanTransfer + CanTrade so the vault and AMM can route it.
|
||||
MPTTester mpt(
|
||||
{.env = env,
|
||||
.issuer = issuer,
|
||||
.holders = {owner},
|
||||
.pay = 100'000,
|
||||
.flags = tfMPTCanLock | tfMPTCanClawback | kMptDexFlags});
|
||||
PrettyAsset const asset = MPT(mpt);
|
||||
|
||||
// Create the vault.
|
||||
Vault const vault{env};
|
||||
auto [vaultTx, vaultKeylet] = vault.create({.owner = owner, .asset = asset});
|
||||
env(vaultTx);
|
||||
env.close();
|
||||
if (!BEAST_EXPECT(env.le(vaultKeylet)))
|
||||
return;
|
||||
|
||||
// Deposit 50,000 of the underlying MPT.
|
||||
env(vault.deposit(
|
||||
{.depositor = owner, .id = vaultKeylet.key, .amount = asset(50'000).value()}));
|
||||
env.close();
|
||||
|
||||
auto const vaultSle = env.le(vaultKeylet);
|
||||
if (!BEAST_EXPECT(vaultSle))
|
||||
return;
|
||||
MPTID const shareMptID = vaultSle->at(sfShareMPTID);
|
||||
MPTIssue const shareIssue{shareMptID};
|
||||
env.memoize(Account{"vaultPseudo", vaultSle->at(sfAccount)});
|
||||
|
||||
// MPT vaults use scale=0, so 50,000 deposit -> 50,000 share units.
|
||||
STAmount const sharesHeld = env.balance(owner, shareIssue);
|
||||
BEAST_EXPECT(sharesHeld.mantissa() == 50'000);
|
||||
|
||||
// Create the AMM: 25,000 vault shares + 1,000 XRP.
|
||||
STAmount const seedShares(shareIssue, std::uint64_t{25'000});
|
||||
AMM ammOwner(env, owner, seedShares, XRP(1'000));
|
||||
BEAST_EXPECT(ammOwner.ammExists());
|
||||
|
||||
// The AMM pseudo-account issues the LP tokens; memoize so
|
||||
// env.balance() can format LP-token amounts.
|
||||
env.memoize(Account{"ammPseudo", ammOwner.ammAccount()});
|
||||
|
||||
// Owner's LP token balance after AMM creation.
|
||||
auto const lptIssue = ammOwner.lptIssue();
|
||||
STAmount const lptOwner0 = env.balance(owner, lptIssue);
|
||||
STAmount const lptZero(lptIssue, std::uint32_t{0});
|
||||
BEAST_EXPECT(lptOwner0 != lptZero);
|
||||
|
||||
// Trader needs a trust line to receive LP tokens.
|
||||
STAmount const lptTrustLimit(lptIssue, std::uint64_t{1'000'000'000});
|
||||
env(trust(trader, lptTrustLimit));
|
||||
env.close();
|
||||
|
||||
// Step 1: transfer half the LP tokens from owner -> trader.
|
||||
STAmount const halfLpt(lptIssue, lptOwner0.mantissa() / 2, lptOwner0.exponent());
|
||||
env(pay(owner, trader, halfLpt));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.balance(trader, lptIssue) == halfLpt);
|
||||
|
||||
// Step 2: issuer locks the underlying MPT.
|
||||
mpt.set({.flags = tfMPTLock});
|
||||
env.close();
|
||||
|
||||
// Step 3: transfer LP tokens again. The lock on the underlying MPT
|
||||
// cascades through the vault-share issuance via
|
||||
// isVaultPseudoAccountFrozen, so the AMM-routed Payment fails.
|
||||
STAmount const quarterLpt(lptIssue, lptOwner0.mantissa() / 4, lptOwner0.exponent());
|
||||
env(pay(owner, trader, quarterLpt), Ter(tecPATH_DRY));
|
||||
env.close();
|
||||
// Trader's balance is still just the half from before the lock.
|
||||
BEAST_EXPECT(env.balance(trader, lptIssue) == halfLpt);
|
||||
|
||||
// Step 4: try to cash out the LP tokens. The AMM withdrawal must
|
||||
// touch the vault-share side, which is now treated as frozen
|
||||
// because its underlying is locked, so the withdrawal fails.
|
||||
ammOwner.withdrawAll(trader, std::nullopt, Ter(tecFROZEN));
|
||||
env.close();
|
||||
// Trader still holds the LP tokens; nothing was redeemed.
|
||||
BEAST_EXPECT(env.balance(trader, lptIssue) == halfLpt);
|
||||
BEAST_EXPECT(env.balance(trader, shareIssue) == STAmount(shareIssue, std::uint64_t{0}));
|
||||
}
|
||||
|
||||
void
|
||||
testStaleAuthAccountsAfterReinit(FeatureBitset features)
|
||||
{
|
||||
@@ -7165,6 +7356,8 @@ private:
|
||||
run() override
|
||||
{
|
||||
FeatureBitset const all{testableAmendments()};
|
||||
testVaultSharesAMM();
|
||||
testLockedVaultMPTCashOut();
|
||||
testInvalidInstance();
|
||||
testInstanceCreate();
|
||||
testInvalidDeposit(all);
|
||||
|
||||
@@ -6459,6 +6459,140 @@ class Vault_test : public beast::unit_test::Suite
|
||||
runTest(amendments);
|
||||
}
|
||||
|
||||
// Issuer mutates the underlying MPT's lsfMPTCanTransfer / lsfMPTCanTrade
|
||||
// flags after holders have already deposited into a vault. Demonstrates:
|
||||
//
|
||||
// - VaultDeposit and VaultWithdraw both go through `canTransfer`,
|
||||
// so clearing lsfMPTCanTransfer causes (`tecNO_AUTH`).
|
||||
//
|
||||
// - The issuer is exempt: `canTransfer` short-circuits when either
|
||||
// side of the transfer is the issuer, so the issuer can still
|
||||
// deposit and withdraw.
|
||||
//
|
||||
// - lsfMPTCanTrade is *not* checked by VaultDeposit/VaultWithdraw at
|
||||
// all — clearing it has no effect on vault I/O. (It only gates
|
||||
// DEX/AMM operations via `canTrade`.)
|
||||
void
|
||||
testMutateCanTransferAfterDeposit()
|
||||
{
|
||||
using namespace test::jtx;
|
||||
testcase("MPT vault: clearing CanTransfer/CanTrade after deposit");
|
||||
|
||||
Env env{*this, testableAmendments() | featureSingleAssetVault};
|
||||
|
||||
Account const issuer{"issuer"};
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
|
||||
env.fund(XRP(1'000), issuer, alice, bob);
|
||||
env.close();
|
||||
|
||||
// MPT is transferable, tradable, lockable, and clawback-capable. Both
|
||||
// CanTransfer and CanTrade are mutable so the issuer can flip them
|
||||
// later via MPTokenIssuanceSet.
|
||||
MPTTester mptt{env, issuer, kMptInitNoFund};
|
||||
mptt.create(
|
||||
{.flags = tfMPTCanTransfer | tfMPTCanTrade | tfMPTCanLock | tfMPTCanClawback,
|
||||
.mutableFlags = tmfMPTCanMutateCanTransfer | tmfMPTCanMutateCanTrade});
|
||||
PrettyAsset const asset = mptt.issuanceID();
|
||||
|
||||
mptt.authorize({.account = alice});
|
||||
mptt.authorize({.account = bob});
|
||||
env(pay(issuer, alice, asset(100'000)));
|
||||
env(pay(issuer, bob, asset(100'000)));
|
||||
env.close();
|
||||
|
||||
Vault const vault{env};
|
||||
auto [createTx, vaultKeylet] = vault.create({.owner = alice, .asset = asset});
|
||||
env(createTx);
|
||||
env.close();
|
||||
BEAST_EXPECT(env.le(vaultKeylet));
|
||||
|
||||
// Both holders deposit. Issuer also deposits (issuer can be a
|
||||
// depositor too) so we can later confirm the issuer-exempt path.
|
||||
env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = asset(50'000)}));
|
||||
env(vault.deposit({.depositor = bob, .id = vaultKeylet.key, .amount = asset(30'000)}));
|
||||
env(vault.deposit({.depositor = issuer, .id = vaultKeylet.key, .amount = asset(20'000)}));
|
||||
env.close();
|
||||
|
||||
// -- 1. Issuer clears lsfMPTCanTransfer ---------------------------
|
||||
mptt.set({.mutableFlags = tmfMPTClearCanTransfer});
|
||||
env.close();
|
||||
|
||||
{
|
||||
auto const sle = env.le(keylet::mptIssuance(asset.raw().get<MPTIssue>().getMptID()));
|
||||
BEAST_EXPECT(sle && !sle->isFlag(lsfMPTCanTransfer));
|
||||
BEAST_EXPECT(sle && sle->isFlag(lsfMPTCanTrade));
|
||||
}
|
||||
|
||||
// 2. Holder deposits and withdrawals are blocked: vault pseudo-
|
||||
// account is neither sender nor receiver = issuer, so
|
||||
// canTransfer returns tecNO_AUTH.
|
||||
env(vault.deposit({.depositor = alice, .id = vaultKeylet.key, .amount = asset(1'000)}),
|
||||
Ter(tecNO_AUTH));
|
||||
env(vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = asset(1'000)}),
|
||||
Ter(tecNO_AUTH));
|
||||
env(vault.withdraw({.depositor = bob, .id = vaultKeylet.key, .amount = asset(1'000)}),
|
||||
Ter(tecNO_AUTH));
|
||||
env.close();
|
||||
|
||||
// 3. Issuer-as-depositor is exempt — `canTransfer` short-circuits
|
||||
// on the issuer side. Both deposit and withdraw succeed.
|
||||
env(vault.deposit({.depositor = issuer, .id = vaultKeylet.key, .amount = asset(5'000)}));
|
||||
env(vault.withdraw({.depositor = issuer, .id = vaultKeylet.key, .amount = asset(5'000)}));
|
||||
env.close();
|
||||
|
||||
// 3b. A holder can also escape by withdrawing *to the issuer* via
|
||||
// sfDestination. `canTransfer`'s issuer short-circuit fires on
|
||||
// `to == issuer`, so the withdrawal succeeds even though
|
||||
// CanTransfer is cleared. The holder's shares are burned and
|
||||
// the underlying MPT lands at the issuer (presumably part of
|
||||
// an off-ledger redemption arrangement).
|
||||
auto const aliceMptBefore = env.balance(alice, asset);
|
||||
auto withdrawToIssuer =
|
||||
vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = asset(2'000)});
|
||||
withdrawToIssuer[sfDestination] = issuer.human();
|
||||
env(withdrawToIssuer);
|
||||
env.close();
|
||||
// Alice's MPT balance is unchanged — the asset went to the issuer,
|
||||
// not back to her — but her share holding was burned.
|
||||
BEAST_EXPECT(env.balance(alice, asset) == aliceMptBefore);
|
||||
|
||||
// -- 4. Also clear lsfMPTCanTrade. Vault paths don't consult
|
||||
// CanTrade, so this changes nothing for vault I/O. ----------
|
||||
mptt.set({.mutableFlags = tmfMPTClearCanTrade});
|
||||
env.close();
|
||||
|
||||
{
|
||||
auto const sle = env.le(keylet::mptIssuance(asset.raw().get<MPTIssue>().getMptID()));
|
||||
BEAST_EXPECT(sle && !sle->isFlag(lsfMPTCanTrade));
|
||||
}
|
||||
|
||||
// Holder ops still fail the same way (CanTransfer-driven), and the
|
||||
// issuer is still exempt.
|
||||
env(vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = asset(1'000)}),
|
||||
Ter(tecNO_AUTH));
|
||||
env(vault.deposit({.depositor = issuer, .id = vaultKeylet.key, .amount = asset(1'000)}));
|
||||
env.close();
|
||||
|
||||
// -- 5. Re-enable CanTransfer; leave CanTrade cleared. ------------
|
||||
mptt.set({.mutableFlags = tmfMPTSetCanTransfer});
|
||||
env.close();
|
||||
|
||||
// Holders can now withdraw all their stake — confirms CanTrade is
|
||||
// not consulted by the vault transactors. Alice already redeemed
|
||||
// 2,000 to the issuer, so only 48,000 remains for her.
|
||||
env(vault.withdraw({.depositor = alice, .id = vaultKeylet.key, .amount = asset(48'000)}));
|
||||
env(vault.withdraw({.depositor = bob, .id = vaultKeylet.key, .amount = asset(30'000)}));
|
||||
env.close();
|
||||
|
||||
{
|
||||
auto const sle = env.le(keylet::mptIssuance(asset.raw().get<MPTIssue>().getMptID()));
|
||||
BEAST_EXPECT(sle && sle->isFlag(lsfMPTCanTransfer));
|
||||
BEAST_EXPECT(sle && !sle->isFlag(lsfMPTCanTrade));
|
||||
}
|
||||
}
|
||||
|
||||
// -----------------------------------------------------------------------
|
||||
// Helpers and tests: sole-shareholder / stuck-depositor (XLS-0065 +
|
||||
// fixCleanup3_2_0). The vault-level withdraw behavior is tested here;
|
||||
@@ -8057,6 +8191,7 @@ public:
|
||||
testAssetsMaximum();
|
||||
testBug6LimitBypassWithShares();
|
||||
testRemoveEmptyHoldingLockedAmount();
|
||||
testMutateCanTransferAfterDeposit();
|
||||
|
||||
testWithdrawSoleShareholderFixedAssetExit(all_ - fixCleanup3_2_0);
|
||||
testWithdrawSoleShareholderFixedAssetExit(all_);
|
||||
|
||||
@@ -53,4 +53,4 @@ foreach(module IN LISTS test_modules)
|
||||
)
|
||||
endforeach()
|
||||
|
||||
gtest_discover_tests(xrpl_tests)
|
||||
gtest_discover_tests(xrpl_tests DISCOVERY_TIMEOUT 60)
|
||||
|
||||
Reference in New Issue
Block a user