mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-02 17:06:00 +00:00
Merge branch 'develop' into Bronek/upgrades_rolled_up
This commit is contained in:
2
.github/actions/dependencies/action.yml
vendored
2
.github/actions/dependencies/action.yml
vendored
@@ -23,7 +23,7 @@ runs:
|
|||||||
conan remote add --index 0 ripple "${CONAN_URL}"
|
conan remote add --index 0 ripple "${CONAN_URL}"
|
||||||
echo "Added conan remote ripple at ${CONAN_URL}"
|
echo "Added conan remote ripple at ${CONAN_URL}"
|
||||||
- name: try to authenticate to Ripple Conan remote
|
- name: try to authenticate to Ripple Conan remote
|
||||||
if: env.CONAN_URL != '' && env.CONAN_LOGIN_USERNAME_RIPPLE != '' && env.CONAN_PASSWORD_RIPPLE != ''
|
if: env.CONAN_LOGIN_USERNAME_RIPPLE != '' && env.CONAN_PASSWORD_RIPPLE != ''
|
||||||
id: remote
|
id: remote
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
2
.github/workflows/macos.yml
vendored
2
.github/workflows/macos.yml
vendored
@@ -18,7 +18,7 @@ concurrency:
|
|||||||
# This part of Conan configuration is specific to this workflow only; we do not want
|
# This part of Conan configuration is specific to this workflow only; we do not want
|
||||||
# to pollute conan/profiles directory with settings which might not work for others
|
# to pollute conan/profiles directory with settings which might not work for others
|
||||||
env:
|
env:
|
||||||
#CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/dev
|
CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/dev
|
||||||
CONAN_LOGIN_USERNAME_RIPPLE: ${{ secrets.CONAN_USERNAME }}
|
CONAN_LOGIN_USERNAME_RIPPLE: ${{ secrets.CONAN_USERNAME }}
|
||||||
CONAN_PASSWORD_RIPPLE: ${{ secrets.CONAN_TOKEN }}
|
CONAN_PASSWORD_RIPPLE: ${{ secrets.CONAN_TOKEN }}
|
||||||
CONAN_GLOBAL_CONF: |
|
CONAN_GLOBAL_CONF: |
|
||||||
|
|||||||
10
.github/workflows/nix.yml
vendored
10
.github/workflows/nix.yml
vendored
@@ -19,7 +19,7 @@ concurrency:
|
|||||||
# This part of Conan configuration is specific to this workflow only; we do not want
|
# This part of Conan configuration is specific to this workflow only; we do not want
|
||||||
# to pollute conan/profiles directory with settings which might not work for others
|
# to pollute conan/profiles directory with settings which might not work for others
|
||||||
env:
|
env:
|
||||||
#CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/dev
|
CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/dev
|
||||||
CONAN_LOGIN_USERNAME_RIPPLE: ${{ secrets.CONAN_USERNAME }}
|
CONAN_LOGIN_USERNAME_RIPPLE: ${{ secrets.CONAN_USERNAME }}
|
||||||
CONAN_PASSWORD_RIPPLE: ${{ secrets.CONAN_TOKEN }}
|
CONAN_PASSWORD_RIPPLE: ${{ secrets.CONAN_TOKEN }}
|
||||||
CONAN_GLOBAL_CONF: |
|
CONAN_GLOBAL_CONF: |
|
||||||
@@ -367,10 +367,12 @@ jobs:
|
|||||||
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
uses: actions/download-artifact@d3f86a106a0bac45b974a628896c90dbdf5c8093
|
||||||
with:
|
with:
|
||||||
name: linux-clang-Debug
|
name: linux-clang-Debug
|
||||||
|
|
||||||
- name: extract cache
|
- name: extract cache
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ${CONAN_HOME}
|
mkdir -p ${CONAN_HOME}
|
||||||
tar -xzf conan.tar.gz -C ${CONAN_HOME}
|
tar -xzf conan.tar.gz -C ${CONAN_HOME}
|
||||||
|
|
||||||
- name: check environment
|
- name: check environment
|
||||||
run: |
|
run: |
|
||||||
echo ${PATH} | tr ':' '\n'
|
echo ${PATH} | tr ':' '\n'
|
||||||
@@ -378,17 +380,21 @@ jobs:
|
|||||||
cmake --version
|
cmake --version
|
||||||
env | sort
|
env | sort
|
||||||
ls ${CONAN_HOME}
|
ls ${CONAN_HOME}
|
||||||
|
|
||||||
- name: checkout
|
- name: checkout
|
||||||
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683
|
||||||
|
|
||||||
- name: dependencies
|
- name: dependencies
|
||||||
uses: ./.github/actions/dependencies
|
uses: ./.github/actions/dependencies
|
||||||
with:
|
with:
|
||||||
configuration: Debug
|
configuration: Debug
|
||||||
|
|
||||||
- name: prepare environment
|
- name: prepare environment
|
||||||
run: |
|
run: |
|
||||||
mkdir -p ${build_dir}
|
mkdir -p ${build_dir}
|
||||||
echo "SOURCE_DIR=$(pwd)" >> $GITHUB_ENV
|
echo "SOURCE_DIR=$(pwd)" >> $GITHUB_ENV
|
||||||
echo "BUILD_DIR=$(pwd)/${build_dir}" >> $GITHUB_ENV
|
echo "BUILD_DIR=$(pwd)/${build_dir}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: build with instrumentation
|
- name: build with instrumentation
|
||||||
run: |
|
run: |
|
||||||
cd ${BUILD_DIR}
|
cd ${BUILD_DIR}
|
||||||
@@ -402,10 +408,12 @@ jobs:
|
|||||||
-DSECP256K1_BUILD_EXHAUSTIVE_TESTS=OFF \
|
-DSECP256K1_BUILD_EXHAUSTIVE_TESTS=OFF \
|
||||||
-DCMAKE_TOOLCHAIN_FILE=${BUILD_DIR}/build/generators/conan_toolchain.cmake
|
-DCMAKE_TOOLCHAIN_FILE=${BUILD_DIR}/build/generators/conan_toolchain.cmake
|
||||||
cmake --build . --parallel $(nproc)
|
cmake --build . --parallel $(nproc)
|
||||||
|
|
||||||
- name: verify instrumentation enabled
|
- name: verify instrumentation enabled
|
||||||
run: |
|
run: |
|
||||||
cd ${BUILD_DIR}
|
cd ${BUILD_DIR}
|
||||||
./rippled --version | grep libvoidstar
|
./rippled --version | grep libvoidstar
|
||||||
|
|
||||||
- name: run unit tests
|
- name: run unit tests
|
||||||
run: |
|
run: |
|
||||||
cd ${BUILD_DIR}
|
cd ${BUILD_DIR}
|
||||||
|
|||||||
2
.github/workflows/windows.yml
vendored
2
.github/workflows/windows.yml
vendored
@@ -21,7 +21,7 @@ concurrency:
|
|||||||
# This part of Conan configuration is specific to this workflow only; we do not want
|
# This part of Conan configuration is specific to this workflow only; we do not want
|
||||||
# to pollute conan/profiles directory with settings which might not work for others
|
# to pollute conan/profiles directory with settings which might not work for others
|
||||||
env:
|
env:
|
||||||
#CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/dev
|
CONAN_URL: http://18.143.149.228:8081/artifactory/api/conan/dev
|
||||||
CONAN_LOGIN_USERNAME_RIPPLE: ${{ secrets.CONAN_USERNAME }}
|
CONAN_LOGIN_USERNAME_RIPPLE: ${{ secrets.CONAN_USERNAME }}
|
||||||
CONAN_PASSWORD_RIPPLE: ${{ secrets.CONAN_TOKEN }}
|
CONAN_PASSWORD_RIPPLE: ${{ secrets.CONAN_TOKEN }}
|
||||||
CONAN_GLOBAL_CONF: |
|
CONAN_GLOBAL_CONF: |
|
||||||
|
|||||||
57
BUILD.md
57
BUILD.md
@@ -378,18 +378,13 @@ and can be helpful for detecting `#include` omissions.
|
|||||||
|
|
||||||
## Troubleshooting
|
## Troubleshooting
|
||||||
|
|
||||||
|
|
||||||
### Conan
|
### Conan
|
||||||
|
|
||||||
After any updates or changes to dependencies, you may need to do the following:
|
After any updates or changes to dependencies, you may need to do the following:
|
||||||
|
|
||||||
1. Remove your build directory.
|
1. Remove your build directory.
|
||||||
2. Remove the Conan cache:
|
2. Remove the Conan cache: `conan remove "*" -c`
|
||||||
```
|
3. Re-run [conan install](#build-and-test).
|
||||||
rm -rf ~/.conan/data
|
|
||||||
```
|
|
||||||
4. Re-run [conan install](#build-and-test).
|
|
||||||
|
|
||||||
|
|
||||||
### 'protobuf/port_def.inc' file not found
|
### 'protobuf/port_def.inc' file not found
|
||||||
|
|
||||||
@@ -407,54 +402,6 @@ For example, if you want to build Debug:
|
|||||||
1. For conan install, pass `--settings build_type=Debug`
|
1. For conan install, pass `--settings build_type=Debug`
|
||||||
2. For cmake, pass `-DCMAKE_BUILD_TYPE=Debug`
|
2. For cmake, pass `-DCMAKE_BUILD_TYPE=Debug`
|
||||||
|
|
||||||
|
|
||||||
### no std::result_of
|
|
||||||
|
|
||||||
If your compiler version is recent enough to have removed `std::result_of` as
|
|
||||||
part of C++20, e.g. Apple Clang 15.0, then you might need to add a preprocessor
|
|
||||||
definition to your build.
|
|
||||||
|
|
||||||
```
|
|
||||||
conan profile update 'options.boost:extra_b2_flags="define=BOOST_ASIO_HAS_STD_INVOKE_RESULT"' default
|
|
||||||
conan profile update 'env.CFLAGS="-DBOOST_ASIO_HAS_STD_INVOKE_RESULT"' default
|
|
||||||
conan profile update 'env.CXXFLAGS="-DBOOST_ASIO_HAS_STD_INVOKE_RESULT"' default
|
|
||||||
conan profile update 'conf.tools.build:cflags+=["-DBOOST_ASIO_HAS_STD_INVOKE_RESULT"]' default
|
|
||||||
conan profile update 'conf.tools.build:cxxflags+=["-DBOOST_ASIO_HAS_STD_INVOKE_RESULT"]' default
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### call to 'async_teardown' is ambiguous
|
|
||||||
|
|
||||||
If you are compiling with an early version of Clang 16, then you might hit
|
|
||||||
a [regression][6] when compiling C++20 that manifests as an [error in a Boost
|
|
||||||
header][7]. You can workaround it by adding this preprocessor definition:
|
|
||||||
|
|
||||||
```
|
|
||||||
conan profile update 'env.CXXFLAGS="-DBOOST_ASIO_DISABLE_CONCEPTS"' default
|
|
||||||
conan profile update 'conf.tools.build:cxxflags+=["-DBOOST_ASIO_DISABLE_CONCEPTS"]' default
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
### recompile with -fPIC
|
|
||||||
|
|
||||||
If you get a linker error suggesting that you recompile Boost with
|
|
||||||
position-independent code, such as:
|
|
||||||
|
|
||||||
```
|
|
||||||
/usr/bin/ld.gold: error: /home/username/.conan/data/boost/1.77.0/_/_/package/.../lib/libboost_container.a(alloc_lib.o):
|
|
||||||
requires unsupported dynamic reloc 11; recompile with -fPIC
|
|
||||||
```
|
|
||||||
|
|
||||||
Conan most likely downloaded a bad binary distribution of the dependency.
|
|
||||||
This seems to be a [bug][1] in Conan just for Boost 1.77.0 compiled with GCC
|
|
||||||
for Linux. The solution is to build the dependency locally by passing
|
|
||||||
`--build boost` when calling `conan install`.
|
|
||||||
|
|
||||||
```
|
|
||||||
conan install --build boost ...
|
|
||||||
```
|
|
||||||
|
|
||||||
|
|
||||||
## Add a Dependency
|
## Add a Dependency
|
||||||
|
|
||||||
If you want to experiment with a new package, follow these steps:
|
If you want to experiment with a new package, follow these steps:
|
||||||
|
|||||||
@@ -509,6 +509,7 @@ TRANSACTION(ttVAULT_WITHDRAW, 69, VaultWithdraw, Delegation::delegatable, ({
|
|||||||
{sfVaultID, soeREQUIRED},
|
{sfVaultID, soeREQUIRED},
|
||||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||||
{sfDestination, soeOPTIONAL},
|
{sfDestination, soeOPTIONAL},
|
||||||
|
{sfDestinationTag, soeOPTIONAL},
|
||||||
}))
|
}))
|
||||||
|
|
||||||
/** This transaction claws back tokens from a vault. */
|
/** This transaction claws back tokens from a vault. */
|
||||||
|
|||||||
@@ -234,6 +234,28 @@ class Vault_test : public beast::unit_test::suite
|
|||||||
env(tx, ter{tecNO_PERMISSION});
|
env(tx, ter{tecNO_PERMISSION});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
testcase(prefix + " fail to withdraw to zero destination");
|
||||||
|
auto tx = vault.withdraw(
|
||||||
|
{.depositor = depositor,
|
||||||
|
.id = keylet.key,
|
||||||
|
.amount = asset(1000)});
|
||||||
|
tx[sfDestination] = "0";
|
||||||
|
env(tx, ter(temMALFORMED));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
testcase(
|
||||||
|
prefix +
|
||||||
|
" fail to withdraw with tag but without destination");
|
||||||
|
auto tx = vault.withdraw(
|
||||||
|
{.depositor = depositor,
|
||||||
|
.id = keylet.key,
|
||||||
|
.amount = asset(1000)});
|
||||||
|
tx[sfDestinationTag] = "0";
|
||||||
|
env(tx, ter(temMALFORMED));
|
||||||
|
}
|
||||||
|
|
||||||
if (!asset.raw().native())
|
if (!asset.raw().native())
|
||||||
{
|
{
|
||||||
testcase(
|
testcase(
|
||||||
@@ -1335,6 +1357,7 @@ class Vault_test : public beast::unit_test::suite
|
|||||||
struct CaseArgs
|
struct CaseArgs
|
||||||
{
|
{
|
||||||
bool enableClawback = true;
|
bool enableClawback = true;
|
||||||
|
bool requireAuth = true;
|
||||||
};
|
};
|
||||||
|
|
||||||
auto testCase = [this](
|
auto testCase = [this](
|
||||||
@@ -1356,16 +1379,20 @@ class Vault_test : public beast::unit_test::suite
|
|||||||
Vault vault{env};
|
Vault vault{env};
|
||||||
|
|
||||||
MPTTester mptt{env, issuer, mptInitNoFund};
|
MPTTester mptt{env, issuer, mptInitNoFund};
|
||||||
|
auto const none = LedgerSpecificFlags(0);
|
||||||
mptt.create(
|
mptt.create(
|
||||||
{.flags = tfMPTCanTransfer | tfMPTCanLock |
|
{.flags = tfMPTCanTransfer | tfMPTCanLock |
|
||||||
(args.enableClawback ? lsfMPTCanClawback
|
(args.enableClawback ? tfMPTCanClawback : none) |
|
||||||
: LedgerSpecificFlags(0)) |
|
(args.requireAuth ? tfMPTRequireAuth : none)});
|
||||||
tfMPTRequireAuth});
|
|
||||||
PrettyAsset asset = mptt.issuanceID();
|
PrettyAsset asset = mptt.issuanceID();
|
||||||
mptt.authorize({.account = owner});
|
mptt.authorize({.account = owner});
|
||||||
mptt.authorize({.account = issuer, .holder = owner});
|
|
||||||
mptt.authorize({.account = depositor});
|
mptt.authorize({.account = depositor});
|
||||||
mptt.authorize({.account = issuer, .holder = depositor});
|
if (args.requireAuth)
|
||||||
|
{
|
||||||
|
mptt.authorize({.account = issuer, .holder = owner});
|
||||||
|
mptt.authorize({.account = issuer, .holder = depositor});
|
||||||
|
}
|
||||||
|
|
||||||
env(pay(issuer, depositor, asset(1000)));
|
env(pay(issuer, depositor, asset(1000)));
|
||||||
env.close();
|
env.close();
|
||||||
|
|
||||||
@@ -1514,6 +1541,100 @@ class Vault_test : public beast::unit_test::suite
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testCase(
|
||||||
|
[this](
|
||||||
|
Env& env,
|
||||||
|
Account const& issuer,
|
||||||
|
Account const& owner,
|
||||||
|
Account const& depositor,
|
||||||
|
PrettyAsset const& asset,
|
||||||
|
Vault& vault,
|
||||||
|
MPTTester& mptt) {
|
||||||
|
testcase(
|
||||||
|
"MPT 3rd party without MPToken cannot be withdrawal "
|
||||||
|
"destination");
|
||||||
|
|
||||||
|
auto [tx, keylet] =
|
||||||
|
vault.create({.owner = owner, .asset = asset});
|
||||||
|
env(tx);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
tx = vault.deposit(
|
||||||
|
{.depositor = depositor,
|
||||||
|
.id = keylet.key,
|
||||||
|
.amount = asset(100)});
|
||||||
|
env(tx);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
{
|
||||||
|
// Set destination to 3rd party without MPToken
|
||||||
|
Account charlie{"charlie"};
|
||||||
|
env.fund(XRP(1000), charlie);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
tx = vault.withdraw(
|
||||||
|
{.depositor = depositor,
|
||||||
|
.id = keylet.key,
|
||||||
|
.amount = asset(100)});
|
||||||
|
tx[sfDestination] = charlie.human();
|
||||||
|
env(tx, ter(tecNO_AUTH));
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{.requireAuth = false});
|
||||||
|
|
||||||
|
testCase(
|
||||||
|
[this](
|
||||||
|
Env& env,
|
||||||
|
Account const& issuer,
|
||||||
|
Account const& owner,
|
||||||
|
Account const& depositor,
|
||||||
|
PrettyAsset const& asset,
|
||||||
|
Vault& vault,
|
||||||
|
MPTTester& mptt) {
|
||||||
|
testcase("MPT depositor without MPToken cannot withdraw");
|
||||||
|
|
||||||
|
auto [tx, keylet] =
|
||||||
|
vault.create({.owner = owner, .asset = asset});
|
||||||
|
env(tx);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
tx = vault.deposit(
|
||||||
|
{.depositor = depositor,
|
||||||
|
.id = keylet.key,
|
||||||
|
.amount = asset(1000)});
|
||||||
|
env(tx);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
{
|
||||||
|
// Remove depositor's MPToken and withdraw will fail
|
||||||
|
mptt.authorize(
|
||||||
|
{.account = depositor, .flags = tfMPTUnauthorize});
|
||||||
|
env.close();
|
||||||
|
auto const mptoken =
|
||||||
|
env.le(keylet::mptoken(mptt.issuanceID(), depositor));
|
||||||
|
BEAST_EXPECT(mptoken == nullptr);
|
||||||
|
|
||||||
|
tx = vault.withdraw(
|
||||||
|
{.depositor = depositor,
|
||||||
|
.id = keylet.key,
|
||||||
|
.amount = asset(100)});
|
||||||
|
env(tx, ter(tecNO_AUTH));
|
||||||
|
}
|
||||||
|
|
||||||
|
{
|
||||||
|
// Restore depositor's MPToken and withdraw will succeed
|
||||||
|
mptt.authorize({.account = depositor});
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
tx = vault.withdraw(
|
||||||
|
{.depositor = depositor,
|
||||||
|
.id = keylet.key,
|
||||||
|
.amount = asset(100)});
|
||||||
|
env(tx);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{.requireAuth = false});
|
||||||
|
|
||||||
testCase([this](
|
testCase([this](
|
||||||
Env& env,
|
Env& env,
|
||||||
Account const& issuer,
|
Account const& issuer,
|
||||||
@@ -1803,6 +1924,7 @@ class Vault_test : public beast::unit_test::suite
|
|||||||
|
|
||||||
PrettyAsset const asset = issuer["IOU"];
|
PrettyAsset const asset = issuer["IOU"];
|
||||||
env.trust(asset(1000), owner);
|
env.trust(asset(1000), owner);
|
||||||
|
env.trust(asset(1000), charlie);
|
||||||
env(pay(issuer, owner, asset(200)));
|
env(pay(issuer, owner, asset(200)));
|
||||||
env(rate(issuer, 1.25));
|
env(rate(issuer, 1.25));
|
||||||
env.close();
|
env.close();
|
||||||
@@ -2118,6 +2240,79 @@ class Vault_test : public beast::unit_test::suite
|
|||||||
env.close();
|
env.close();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testCase([&, this](
|
||||||
|
Env& env,
|
||||||
|
Account const& owner,
|
||||||
|
Account const& issuer,
|
||||||
|
Account const& charlie,
|
||||||
|
auto,
|
||||||
|
Vault& vault,
|
||||||
|
PrettyAsset const& asset,
|
||||||
|
auto&&...) {
|
||||||
|
testcase("IOU no trust line to 3rd party");
|
||||||
|
|
||||||
|
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||||
|
env(tx);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
env(vault.deposit(
|
||||||
|
{.depositor = owner, .id = keylet.key, .amount = asset(100)}));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
Account const erin{"erin"};
|
||||||
|
env.fund(XRP(1000), erin);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// Withdraw to 3rd party without trust line
|
||||||
|
auto const tx1 = [&](ripple::Keylet keylet) {
|
||||||
|
auto tx = vault.withdraw(
|
||||||
|
{.depositor = owner,
|
||||||
|
.id = keylet.key,
|
||||||
|
.amount = asset(10)});
|
||||||
|
tx[sfDestination] = erin.human();
|
||||||
|
return tx;
|
||||||
|
}(keylet);
|
||||||
|
env(tx1, ter{tecNO_LINE});
|
||||||
|
});
|
||||||
|
|
||||||
|
testCase([&, this](
|
||||||
|
Env& env,
|
||||||
|
Account const& owner,
|
||||||
|
Account const& issuer,
|
||||||
|
Account const& charlie,
|
||||||
|
auto,
|
||||||
|
Vault& vault,
|
||||||
|
PrettyAsset const& asset,
|
||||||
|
auto&&...) {
|
||||||
|
testcase("IOU no trust line to depositor");
|
||||||
|
|
||||||
|
auto [tx, keylet] = vault.create({.owner = owner, .asset = asset});
|
||||||
|
env(tx);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// reset limit, so deposit of all funds will delete the trust line
|
||||||
|
env.trust(asset(0), owner);
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
env(vault.deposit(
|
||||||
|
{.depositor = owner, .id = keylet.key, .amount = asset(200)}));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
auto trustline =
|
||||||
|
env.le(keylet::line(owner, asset.raw().get<Issue>()));
|
||||||
|
BEAST_EXPECT(trustline == nullptr);
|
||||||
|
|
||||||
|
// Withdraw without trust line, will succeed
|
||||||
|
auto const tx1 = [&](ripple::Keylet keylet) {
|
||||||
|
auto tx = vault.withdraw(
|
||||||
|
{.depositor = owner,
|
||||||
|
.id = keylet.key,
|
||||||
|
.amount = asset(10)});
|
||||||
|
return tx;
|
||||||
|
}(keylet);
|
||||||
|
env(tx1);
|
||||||
|
});
|
||||||
|
|
||||||
testCase([&, this](
|
testCase([&, this](
|
||||||
Env& env,
|
Env& env,
|
||||||
Account const& owner,
|
Account const& owner,
|
||||||
|
|||||||
@@ -315,14 +315,14 @@ escrowCreatePreclaimHelper<MPTIssue>(
|
|||||||
// authorized
|
// authorized
|
||||||
auto const& mptIssue = amount.get<MPTIssue>();
|
auto const& mptIssue = amount.get<MPTIssue>();
|
||||||
if (auto const ter =
|
if (auto const ter =
|
||||||
requireAuth(ctx.view, mptIssue, account, MPTAuthType::WeakAuth);
|
requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth);
|
||||||
ter != tesSUCCESS)
|
ter != tesSUCCESS)
|
||||||
return ter;
|
return ter;
|
||||||
|
|
||||||
// If the issuer has requireAuth set, check if the destination is
|
// If the issuer has requireAuth set, check if the destination is
|
||||||
// authorized
|
// authorized
|
||||||
if (auto const ter =
|
if (auto const ter =
|
||||||
requireAuth(ctx.view, mptIssue, dest, MPTAuthType::WeakAuth);
|
requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth);
|
||||||
ter != tesSUCCESS)
|
ter != tesSUCCESS)
|
||||||
return ter;
|
return ter;
|
||||||
|
|
||||||
@@ -746,7 +746,7 @@ escrowFinishPreclaimHelper<MPTIssue>(
|
|||||||
// authorized
|
// authorized
|
||||||
auto const& mptIssue = amount.get<MPTIssue>();
|
auto const& mptIssue = amount.get<MPTIssue>();
|
||||||
if (auto const ter =
|
if (auto const ter =
|
||||||
requireAuth(ctx.view, mptIssue, dest, MPTAuthType::WeakAuth);
|
requireAuth(ctx.view, mptIssue, dest, AuthType::WeakAuth);
|
||||||
ter != tesSUCCESS)
|
ter != tesSUCCESS)
|
||||||
return ter;
|
return ter;
|
||||||
|
|
||||||
@@ -1259,7 +1259,7 @@ escrowCancelPreclaimHelper<MPTIssue>(
|
|||||||
// authorized
|
// authorized
|
||||||
auto const& mptIssue = amount.get<MPTIssue>();
|
auto const& mptIssue = amount.get<MPTIssue>();
|
||||||
if (auto const ter =
|
if (auto const ter =
|
||||||
requireAuth(ctx.view, mptIssue, account, MPTAuthType::WeakAuth);
|
requireAuth(ctx.view, mptIssue, account, AuthType::WeakAuth);
|
||||||
ter != tesSUCCESS)
|
ter != tesSUCCESS)
|
||||||
return ter;
|
return ter;
|
||||||
|
|
||||||
|
|||||||
@@ -52,9 +52,19 @@ VaultWithdraw::preflight(PreflightContext const& ctx)
|
|||||||
return temBAD_AMOUNT;
|
return temBAD_AMOUNT;
|
||||||
|
|
||||||
if (auto const destination = ctx.tx[~sfDestination];
|
if (auto const destination = ctx.tx[~sfDestination];
|
||||||
destination && *destination == beast::zero)
|
destination.has_value())
|
||||||
{
|
{
|
||||||
JLOG(ctx.j.debug()) << "VaultWithdraw: zero/empty destination account.";
|
if (*destination == beast::zero)
|
||||||
|
{
|
||||||
|
JLOG(ctx.j.debug())
|
||||||
|
<< "VaultWithdraw: zero/empty destination account.";
|
||||||
|
return temMALFORMED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
else if (ctx.tx.isFieldPresent(sfDestinationTag))
|
||||||
|
{
|
||||||
|
JLOG(ctx.j.debug()) << "VaultWithdraw: sfDestinationTag is set but "
|
||||||
|
"sfDestination is not";
|
||||||
return temMALFORMED;
|
return temMALFORMED;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -123,33 +133,39 @@ VaultWithdraw::preclaim(PreclaimContext const& ctx)
|
|||||||
|
|
||||||
// Withdrawal to a 3rd party destination account is essentially a transfer,
|
// Withdrawal to a 3rd party destination account is essentially a transfer,
|
||||||
// via shares in the vault. Enforce all the usual asset transfer checks.
|
// via shares in the vault. Enforce all the usual asset transfer checks.
|
||||||
|
AuthType authType = AuthType::Legacy;
|
||||||
if (account != dstAcct)
|
if (account != dstAcct)
|
||||||
{
|
{
|
||||||
auto const sleDst = ctx.view.read(keylet::account(dstAcct));
|
auto const sleDst = ctx.view.read(keylet::account(dstAcct));
|
||||||
if (sleDst == nullptr)
|
if (sleDst == nullptr)
|
||||||
return tecNO_DST;
|
return tecNO_DST;
|
||||||
|
|
||||||
if (sleDst->getFlags() & lsfRequireDestTag)
|
if (sleDst->isFlag(lsfRequireDestTag) &&
|
||||||
|
!ctx.tx.isFieldPresent(sfDestinationTag))
|
||||||
return tecDST_TAG_NEEDED; // Cannot send without a tag
|
return tecDST_TAG_NEEDED; // Cannot send without a tag
|
||||||
|
|
||||||
if (sleDst->getFlags() & lsfDepositAuth)
|
if (sleDst->isFlag(lsfDepositAuth))
|
||||||
{
|
{
|
||||||
if (!ctx.view.exists(keylet::depositPreauth(dstAcct, account)))
|
if (!ctx.view.exists(keylet::depositPreauth(dstAcct, account)))
|
||||||
return tecNO_PERMISSION;
|
return tecNO_PERMISSION;
|
||||||
}
|
}
|
||||||
|
// The destination account must have consented to receive the asset by
|
||||||
|
// creating a RippleState or MPToken
|
||||||
|
authType = AuthType::StrongAuth;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Destination MPToken must exist (if asset is an MPT)
|
// Destination MPToken (for an MPT) or trust line (for an IOU) must exist
|
||||||
if (auto const ter = requireAuth(ctx.view, vaultAsset, dstAcct);
|
// if not sending to Account.
|
||||||
|
if (auto const ter = requireAuth(ctx.view, vaultAsset, dstAcct, authType);
|
||||||
!isTesSuccess(ter))
|
!isTesSuccess(ter))
|
||||||
return ter;
|
return ter;
|
||||||
|
|
||||||
// Cannot withdraw from a Vault an Asset frozen for the destination account
|
// Cannot withdraw from a Vault an Asset frozen for the destination account
|
||||||
if (isFrozen(ctx.view, dstAcct, vaultAsset))
|
if (auto const ret = checkFrozen(ctx.view, dstAcct, vaultAsset))
|
||||||
return vaultAsset.holds<Issue>() ? tecFROZEN : tecLOCKED;
|
return ret;
|
||||||
|
|
||||||
if (isFrozen(ctx.view, account, vaultShare))
|
if (auto const ret = checkFrozen(ctx.view, account, vaultShare))
|
||||||
return tecLOCKED;
|
return ret;
|
||||||
|
|
||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -175,6 +175,29 @@ isFrozen(
|
|||||||
asset.value());
|
asset.value());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline TER
|
||||||
|
checkFrozen(ReadView const& view, AccountID const& account, Issue const& issue)
|
||||||
|
{
|
||||||
|
return isFrozen(view, account, issue) ? (TER)tecFROZEN : (TER)tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline TER
|
||||||
|
checkFrozen(
|
||||||
|
ReadView const& view,
|
||||||
|
AccountID const& account,
|
||||||
|
MPTIssue const& mptIssue)
|
||||||
|
{
|
||||||
|
return isFrozen(view, account, mptIssue) ? (TER)tecLOCKED : (TER)tesSUCCESS;
|
||||||
|
}
|
||||||
|
|
||||||
|
[[nodiscard]] inline TER
|
||||||
|
checkFrozen(ReadView const& view, AccountID const& account, Asset const& asset)
|
||||||
|
{
|
||||||
|
return std::visit(
|
||||||
|
[&](auto const& issue) { return checkFrozen(view, account, issue); },
|
||||||
|
asset.value());
|
||||||
|
}
|
||||||
|
|
||||||
[[nodiscard]] bool
|
[[nodiscard]] bool
|
||||||
isAnyFrozen(
|
isAnyFrozen(
|
||||||
ReadView const& view,
|
ReadView const& view,
|
||||||
@@ -725,19 +748,40 @@ transferXRP(
|
|||||||
STAmount const& amount,
|
STAmount const& amount,
|
||||||
beast::Journal j);
|
beast::Journal j);
|
||||||
|
|
||||||
/* Check if MPToken exists:
|
/* Check if MPToken (for MPT) or trust line (for IOU) exists:
|
||||||
* - StrongAuth - before checking lsfMPTRequireAuth is set
|
* - StrongAuth - before checking if authorization is required
|
||||||
* - WeakAuth - after checking if lsfMPTRequireAuth is set
|
* - WeakAuth
|
||||||
|
* for MPT - after checking lsfMPTRequireAuth flag
|
||||||
|
* for IOU - do not check if trust line exists
|
||||||
|
* - Legacy
|
||||||
|
* for MPT - before checking lsfMPTRequireAuth flag i.e. same as StrongAuth
|
||||||
|
* for IOU - do not check if trust line exists i.e. same as WeakAuth
|
||||||
*/
|
*/
|
||||||
enum class MPTAuthType : bool { StrongAuth = true, WeakAuth = false };
|
enum class AuthType { StrongAuth, WeakAuth, Legacy };
|
||||||
|
|
||||||
/** Check if the account lacks required authorization.
|
/** Check if the account lacks required authorization.
|
||||||
*
|
*
|
||||||
* Return tecNO_AUTH or tecNO_LINE if it does
|
* Return tecNO_AUTH or tecNO_LINE if it does
|
||||||
* and tesSUCCESS otherwise.
|
* and tesSUCCESS otherwise.
|
||||||
|
*
|
||||||
|
* If StrongAuth then return tecNO_LINE if the RippleState doesn't exist. Return
|
||||||
|
* tecNO_AUTH if lsfRequireAuth is set on the issuer's AccountRoot, and the
|
||||||
|
* RippleState does exist, and the RippleState is not authorized.
|
||||||
|
*
|
||||||
|
* If WeakAuth then return tecNO_AUTH if lsfRequireAuth is set, and the
|
||||||
|
* RippleState exists, and is not authorized. Return tecNO_LINE if
|
||||||
|
* lsfRequireAuth is set and the RippleState doesn't exist. Consequently, if
|
||||||
|
* WeakAuth and lsfRequireAuth is *not* set, this function will return
|
||||||
|
* tesSUCCESS even if RippleState does *not* exist.
|
||||||
|
*
|
||||||
|
* The default "Legacy" auth type is equivalent to WeakAuth.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] TER
|
[[nodiscard]] TER
|
||||||
requireAuth(ReadView const& view, Issue const& issue, AccountID const& account);
|
requireAuth(
|
||||||
|
ReadView const& view,
|
||||||
|
Issue const& issue,
|
||||||
|
AccountID const& account,
|
||||||
|
AuthType authType = AuthType::Legacy);
|
||||||
|
|
||||||
/** Check if the account lacks required authorization.
|
/** Check if the account lacks required authorization.
|
||||||
*
|
*
|
||||||
@@ -751,32 +795,33 @@ requireAuth(ReadView const& view, Issue const& issue, AccountID const& account);
|
|||||||
* purely defensive, as we currently do not allow such vaults to be created.
|
* purely defensive, as we currently do not allow such vaults to be created.
|
||||||
*
|
*
|
||||||
* If StrongAuth then return tecNO_AUTH if MPToken doesn't exist or
|
* If StrongAuth then return tecNO_AUTH if MPToken doesn't exist or
|
||||||
* lsfMPTRequireAuth is set and MPToken is not authorized. If WeakAuth then
|
* lsfMPTRequireAuth is set and MPToken is not authorized.
|
||||||
* return tecNO_AUTH if lsfMPTRequireAuth is set and MPToken doesn't exist or is
|
*
|
||||||
* not authorized (explicitly or via credentials, if DomainID is set in
|
* If WeakAuth then return tecNO_AUTH if lsfMPTRequireAuth is set and MPToken
|
||||||
* MPTokenIssuance). Consequently, if WeakAuth and lsfMPTRequireAuth is *not*
|
* doesn't exist or is not authorized (explicitly or via credentials, if
|
||||||
* set, this function will return true even if MPToken does *not* exist.
|
* DomainID is set in MPTokenIssuance). Consequently, if WeakAuth and
|
||||||
|
* lsfMPTRequireAuth is *not* set, this function will return true even if
|
||||||
|
* MPToken does *not* exist.
|
||||||
|
*
|
||||||
|
* The default "Legacy" auth type is equivalent to StrongAuth.
|
||||||
*/
|
*/
|
||||||
[[nodiscard]] TER
|
[[nodiscard]] TER
|
||||||
requireAuth(
|
requireAuth(
|
||||||
ReadView const& view,
|
ReadView const& view,
|
||||||
MPTIssue const& mptIssue,
|
MPTIssue const& mptIssue,
|
||||||
AccountID const& account,
|
AccountID const& account,
|
||||||
MPTAuthType authType = MPTAuthType::StrongAuth,
|
AuthType authType = AuthType::Legacy,
|
||||||
int depth = 0);
|
int depth = 0);
|
||||||
|
|
||||||
[[nodiscard]] TER inline requireAuth(
|
[[nodiscard]] TER inline requireAuth(
|
||||||
ReadView const& view,
|
ReadView const& view,
|
||||||
Asset const& asset,
|
Asset const& asset,
|
||||||
AccountID const& account,
|
AccountID const& account,
|
||||||
MPTAuthType authType = MPTAuthType::StrongAuth)
|
AuthType authType = AuthType::Legacy)
|
||||||
{
|
{
|
||||||
return std::visit(
|
return std::visit(
|
||||||
[&]<ValidIssueType TIss>(TIss const& issue_) {
|
[&]<ValidIssueType TIss>(TIss const& issue_) {
|
||||||
if constexpr (std::is_same_v<TIss, Issue>)
|
return requireAuth(view, issue_, account, authType);
|
||||||
return requireAuth(view, issue_, account);
|
|
||||||
else
|
|
||||||
return requireAuth(view, issue_, account, authType);
|
|
||||||
},
|
},
|
||||||
asset.value());
|
asset.value());
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -505,8 +505,8 @@ accountHolds(
|
|||||||
if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED &&
|
if (zeroIfUnauthorized == ahZERO_IF_UNAUTHORIZED &&
|
||||||
view.rules().enabled(featureSingleAssetVault))
|
view.rules().enabled(featureSingleAssetVault))
|
||||||
{
|
{
|
||||||
if (auto const err = requireAuth(
|
if (auto const err =
|
||||||
view, mptIssue, account, MPTAuthType::StrongAuth);
|
requireAuth(view, mptIssue, account, AuthType::StrongAuth);
|
||||||
!isTesSuccess(err))
|
!isTesSuccess(err))
|
||||||
amount.clear(mptIssue);
|
amount.clear(mptIssue);
|
||||||
}
|
}
|
||||||
@@ -2298,15 +2298,27 @@ transferXRP(
|
|||||||
}
|
}
|
||||||
|
|
||||||
TER
|
TER
|
||||||
requireAuth(ReadView const& view, Issue const& issue, AccountID const& account)
|
requireAuth(
|
||||||
|
ReadView const& view,
|
||||||
|
Issue const& issue,
|
||||||
|
AccountID const& account,
|
||||||
|
AuthType authType)
|
||||||
{
|
{
|
||||||
if (isXRP(issue) || issue.account == account)
|
if (isXRP(issue) || issue.account == account)
|
||||||
return tesSUCCESS;
|
return tesSUCCESS;
|
||||||
|
|
||||||
|
auto const trustLine =
|
||||||
|
view.read(keylet::line(account, issue.account, issue.currency));
|
||||||
|
// If account has no line, and this is a strong check, fail
|
||||||
|
if (!trustLine && authType == AuthType::StrongAuth)
|
||||||
|
return tecNO_LINE;
|
||||||
|
|
||||||
|
// If this is a weak or legacy check, or if the account has a line, fail if
|
||||||
|
// auth is required and not set on the line
|
||||||
if (auto const issuerAccount = view.read(keylet::account(issue.account));
|
if (auto const issuerAccount = view.read(keylet::account(issue.account));
|
||||||
issuerAccount && (*issuerAccount)[sfFlags] & lsfRequireAuth)
|
issuerAccount && (*issuerAccount)[sfFlags] & lsfRequireAuth)
|
||||||
{
|
{
|
||||||
if (auto const trustLine =
|
if (trustLine)
|
||||||
view.read(keylet::line(account, issue.account, issue.currency)))
|
|
||||||
return ((*trustLine)[sfFlags] &
|
return ((*trustLine)[sfFlags] &
|
||||||
((account > issue.account) ? lsfLowAuth : lsfHighAuth))
|
((account > issue.account) ? lsfLowAuth : lsfHighAuth))
|
||||||
? tesSUCCESS
|
? tesSUCCESS
|
||||||
@@ -2322,7 +2334,7 @@ requireAuth(
|
|||||||
ReadView const& view,
|
ReadView const& view,
|
||||||
MPTIssue const& mptIssue,
|
MPTIssue const& mptIssue,
|
||||||
AccountID const& account,
|
AccountID const& account,
|
||||||
MPTAuthType authType,
|
AuthType authType,
|
||||||
int depth)
|
int depth)
|
||||||
{
|
{
|
||||||
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
|
auto const mptID = keylet::mptIssuance(mptIssue.getMptID());
|
||||||
@@ -2357,7 +2369,7 @@ requireAuth(
|
|||||||
if (auto const err = std::visit(
|
if (auto const err = std::visit(
|
||||||
[&]<ValidIssueType TIss>(TIss const& issue) {
|
[&]<ValidIssueType TIss>(TIss const& issue) {
|
||||||
if constexpr (std::is_same_v<TIss, Issue>)
|
if constexpr (std::is_same_v<TIss, Issue>)
|
||||||
return requireAuth(view, issue, account);
|
return requireAuth(view, issue, account, authType);
|
||||||
else
|
else
|
||||||
return requireAuth(
|
return requireAuth(
|
||||||
view, issue, account, authType, depth + 1);
|
view, issue, account, authType, depth + 1);
|
||||||
@@ -2372,7 +2384,8 @@ requireAuth(
|
|||||||
auto const sleToken = view.read(mptokenID);
|
auto const sleToken = view.read(mptokenID);
|
||||||
|
|
||||||
// if account has no MPToken, fail
|
// if account has no MPToken, fail
|
||||||
if (!sleToken && authType == MPTAuthType::StrongAuth)
|
if (!sleToken &&
|
||||||
|
(authType == AuthType::StrongAuth || authType == AuthType::Legacy))
|
||||||
return tecNO_AUTH;
|
return tecNO_AUTH;
|
||||||
|
|
||||||
// Note, this check is not amendment-gated because DomainID will be always
|
// Note, this check is not amendment-gated because DomainID will be always
|
||||||
|
|||||||
Reference in New Issue
Block a user