Compare commits

...

3 Commits

Author SHA1 Message Date
tequ
2ecfbfc8ff Support new STIs for sto_* HookAPI (#657) 2026-02-18 11:48:13 +10:00
tequ
3c49f80013 Add new keylets to util_keylet (#533) 2026-02-18 11:18:59 +10:00
Niq Dudfield
090e4ad25e Merge dev (309e517e7) into sync-2.4.0: docs + guard checker CI (#683)
* Hook API Refactor2: Amendment Guards (#621)

* Hook API Refactor3: Consolidate the Hook API definitions from Enum.h and ApplyHook.h into a single file. (#622)

* Hook API Refactoring / Unit Testing (#581)

* fix `Xahau Ledger` to `Xahau Network` (#651)

* Add GitHub Actions workflow for Guard Checker Build (#658)

* fix `Xahau Ledger` to `Xahau Network` (#651)

* Add GitHub Actions workflow for Guard Checker Build (#658)

* fix: update guard checker build path for directory restructure

* fix: update stale ripple include paths in hook headers

* fix(test): avoid deleted PublicKey default ctor in HookAPI test

* chore(levelization): update ordering after hook/protocol dependency change

---------

Co-authored-by: tequ <git@tequ.dev>
2026-02-18 11:17:27 +10:00
15 changed files with 1711 additions and 763 deletions

View File

@@ -0,0 +1,24 @@
name: Guard Checker Build
on:
push:
pull_request:
jobs:
guard-checker-build:
strategy:
fail-fast: false
matrix:
include:
- run-on: ubuntu-latest
- run-on: macos-latest
runs-on: ${{ matrix.run-on }}
name: Guard Checker Build - ${{ matrix.run-on }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
- name: Build Guard Checker
run: |
cd include/xrpl/hook
make guard_checker

View File

@@ -140,6 +140,7 @@ test.toplevel > test.csf
test.toplevel > xrpl.json
test.unit_test > xrpl.basics
xrpl.hook > xrpl.basics
xrpl.hook > xrpl.protocol
xrpl.json > xrpl.basics
xrpl.protocol > xrpl.basics
xrpl.protocol > xrpl.json

View File

@@ -60,7 +60,7 @@ git-subtree. See those directories' README files for more details.
- [Xrpl Documentation](https://xrpl.org)
- [Xahau Documentation](https://xahau.network/)
- [Hooks Technical Documentation](https://xrpl-hooks.readme.io/)
- **Explorers**: Explore the Xahau ledger using various explorers:
- **Explorers**: Explore the Xahau Network using various explorers:
- [xahauexplorer.com](https://xahauexplorer.com)
- [xahscan.com](https://xahscan.com)
- [xahau.xrpl.org](https://xahau.xrpl.org)

View File

@@ -62,11 +62,11 @@ For these complaints or reports, please [contact our support team](mailto:bugs@x
### The following type of security problems are excluded
1. **In scope**. Only bugs in software under the scope of the program qualify. Currently, that means `xahaud` and `xahau-lib`.
2. **Relevant**. A security issue, posing a danger to user funds, privacy or the operation of the Xahau Ledger.
2. **Relevant**. A security issue, posing a danger to user funds, privacy or the operation of the Xahau Network.
3. **Original and previously unknown**. Bugs that are already known and discussed in public do not qualify. Previously reported bugs, even if publicly unknown, are not eligible.
4. **Specific**. We welcome general security advice or recommendations, but we cannot pay bounties for that.
5. **Fixable**. There has to be something we can do to permanently fix the problem. Note that bugs in other peoples software may still qualify in some cases. For example, if you find a bug in a library that we use which can compromise the security of software that is in scope and we can get it fixed, you may qualify for a bounty.
6. **Unused**. If you use the exploit to attack the Xahau Ledger, you do not qualify for a bounty. If you report a vulnerability used in an ongoing or past attack and there is specific, concrete evidence that suggests you are the attacker we reserve the right not to pay a bounty.
6. **Unused**. If you use the exploit to attack the Xahau Network, you do not qualify for a bounty. If you report a vulnerability used in an ongoing or past attack and there is specific, concrete evidence that suggests you are the attacker we reserve the right not to pay a bounty.
Please note: Reports that are lacking any proof (such as screenshots or other data), detailed information or details on how to reproduce any unexpected result will be investigated but will not be eligible for any reward.

View File

@@ -7,9 +7,9 @@
#define HOOKENUM_INCLUDED 1
#ifndef GUARD_CHECKER_BUILD
#include <ripple/basics/base_uint.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Rules.h>
#include <xrpl/basics/base_uint.h>
#include <xrpl/protocol/Feature.h>
#include <xrpl/protocol/Rules.h>
#else
// Override uint256, Feature and Rules for guard checker build
#define uint256 std::string
@@ -300,7 +300,17 @@ enum keylet_code : uint32_t {
NFT_OFFER = 23,
HOOK_DEFINITION = 24,
HOOK_STATE_DIR = 25,
CRON = 26
CRON = 26,
AMM = 27,
BRIDGE = 28,
XCHAIN_OWNED_CLAIM_ID = 29,
XCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID = 30,
DID = 31,
ORACLE = 32,
MPTOKEN_ISSUANCE = 33,
MPTOKEN = 34,
CREDENTIAL = 35,
PERMISSIONED_DOMAIN = 36,
};
}

View File

@@ -329,14 +329,15 @@ private:
int32_t,
parse_error>
get_stobject_length(
unsigned char* start, // in - begin iterator
unsigned char* maxptr, // in - end iterator
int& type, // out - populated by serialized type code
int& field, // out - populated by serialized field code
int& payload_start, // out - the start of actual payload data for
// this type
int& payload_length, // out - the length of actual payload data for
// this type
unsigned char* start, // in - begin iterator
unsigned char* maxptr, // in - end iterator
int& type, // out - populated by serialized type code
int& field, // out - populated by serialized field code
int& payload_start, // out - the start of actual payload data for
// this type
int& payload_length, // out - the length of actual payload data for
// this type
Rules const& rules,
int recursion_depth = 0) // used internally
const;
};

View File

@@ -80,7 +80,7 @@ namespace detail {
// Feature.cpp. Because it's only used to reserve storage, and determine how
// large to make the FeatureBitset, it MAY be larger. It MUST NOT be less than
// the actual number of amendments. A LogicError on startup will verify this.
static constexpr std::size_t numFeatures = 110;
static constexpr std::size_t numFeatures = 111;
/** Amendments that this server supports and the default voting behavior.
Whether they are enabled depends on the Rules defined in the validated

View File

@@ -29,6 +29,7 @@
// If you add an amendment here, then do not forget to increment `numFeatures`
// in include/xrpl/protocol/Feature.h.
XRPL_FEATURE(HookAPISerializedType240, Supported::yes, VoteBehavior::DefaultNo)
XRPL_FEATURE(PermissionedDomains, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(DynamicNFT, Supported::no, VoteBehavior::DefaultNo)
XRPL_FEATURE(Credentials, Supported::no, VoteBehavior::DefaultNo)

View File

@@ -487,7 +487,7 @@ isMemoOkay(STObject const& st, std::string& reason)
if (!paramObj->isFieldPresent(sfHookParameterValue) ||
paramObj->getFieldVL(sfHookParameterValue).size() > maxVal)
{
reason = "HookParameterValue cannot exceed 128 bytes.";
reason = "HookParameterValue cannot exceed 256 bytes.";
return false;
}
}

View File

@@ -94,7 +94,7 @@ public:
STTx const emitInvokeTx = STTx(ttINVOKE, [&](STObject& obj) {
obj[sfAccount] = alice.id();
obj[sfSequence] = 0;
obj[sfSigningPubKey] = PublicKey();
obj[sfSigningPubKey] = Slice{};
obj[sfFirstLedgerSequence] = env.closed()->seq() + 1;
obj[sfLastLedgerSequence] = env.closed()->seq() + 5;
obj[sfFee] = env.closed()->fees().base;
@@ -110,7 +110,7 @@ public:
STTx const emitSetHookTx = STTx(ttHOOK_SET, [&](STObject& obj) {
obj[sfAccount] = alice.id();
obj[sfSequence] = 0;
obj[sfSigningPubKey] = PublicKey();
obj[sfSigningPubKey] = Slice{};
obj[sfFirstLedgerSequence] = env.closed()->seq() + 1;
obj[sfLastLedgerSequence] = env.closed()->seq() + 5;
obj[sfFee] = env.closed()->fees().base;

View File

@@ -10951,6 +10951,186 @@ public:
// invoke the hook
env(pay(bob, alice, XRP(1)), M("test sto_validate"), fee(XRP(1)));
{
// test STIs
TestHook hook = wasm[R"[test.hook](
#include <stdint.h>
extern int32_t _g (uint32_t id, uint32_t maxiter);
#define GUARD(maxiter) _g((1ULL << 31U) + __LINE__, (maxiter)+1)
extern int64_t accept (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t rollback (uint32_t read_ptr, uint32_t read_len, int64_t error_code);
extern int64_t sto_validate(uint32_t, uint32_t);
extern int64_t otxn_param(uint32_t, uint32_t, uint32_t, uint32_t);
#define ASSERT(x)\
if (!(x))\
rollback((uint32_t)#x, sizeof(#x), __LINE__);
#define SBUF(x) (uint32_t)(x), sizeof(x)
uint8_t buf[1000];
int64_t hook(uint32_t reserved)
{
_g(1,1);
int64_t size = otxn_param(SBUF(buf), "V", 1);
int64_t result = sto_validate(buf, size);
accept(0,0,result);
}
)[test.hook]"];
for (auto feature : {
features - featureHookAPISerializedType240,
features | featureHookAPISerializedType240,
})
{
Env env{*this, feature};
env.fund(XRP(10000), alice, bob);
env.close();
auto hasEnabled = env.current()->rules().enabled(
featureHookAPISerializedType240);
// install the hook on alice
env(ripple::test::jtx::hook(
alice, {{hso(hook, overrideFlag)}}, 0),
M("set sto_validate"),
HSFEE);
env.close();
// invoke the hook
auto buildTx = [&](std::string value) {
auto payJv = pay(bob, alice, XRP(1));
Json::Value params{Json::arrayValue};
auto& param = params[0U][jss::HookParameter];
param[jss::HookParameterName] = strHex(std::string("V"));
param[jss::HookParameterValue] = value;
payJv[jss::HookParameters] = params;
return payJv;
};
auto testSTI = [&](std::string value, bool expectedResult) {
auto tx = buildTx(value);
env(tx, M("test STI"), fee(XRP(1)));
env.close();
auto const result = env.meta()
->getFieldArray(sfHookExecutions)[0]
.getFieldU64(sfHookReturnCode);
if (expectedResult)
BEAST_EXPECTS(result == 1, value);
else
BEAST_EXPECTS(result == 0, value);
};
// STI_UINT32
testSTI("2200000001", true);
// STI_UINT64
testSTI("301100000000000003E8", true);
// STI_UINT128
testSTI("4100000000000000000000000000000000", true);
// STI_UINT256
testSTI(
"5060000000000000000000000000000000000000000000000000000000"
"0000000000",
true);
// STI_AMOUNT
testSTI("614000000000000064", true);
testSTI(
"61D5038D7EA4C680000000000000000000000000005553440000000000"
"AE123A8556F3CF91154711376AFB0F894F832B3D",
true);
// STI_VL
testSTI("7504DEADBEEF", true);
// STI_ACCOUNT
testSTI("8114AE123A8556F3CF91154711376AFB0F894F832B3D", true);
// STI_NUMBER
// testSTI("000400000000000000000000000000000001", true);
// STI_OBJECT
testSTI("E05C22000000017504DEADBEEFE1", true);
// STI_ARRAY
testSTI(
"F05CE05B614000000000000064E1E05B61D5038D7EA4C6800000000000"
"00000000000000005553440000000000AE123A8556F3CF91154711376A"
"FB0F894F832B3DE1F1",
true);
// STI_UINT8
testSTI("00101003", true);
// STI_UINT160
testSTI("01110000000000000000000000000000000000000000", true);
// STI_PATHSET
testSTI(
"0112300000000000000000000000005553440000000000AE123A8556F3"
"CF91154711376AFB0F894F832B3D00",
hasEnabled);
testSTI(
"0112310A20B3C85F482532A9578DBB3950B85CA06594D1000000000000"
"00000000000042544300000000000A20B3C85F482532A9578DBB3950B8"
"5CA06594D13000000000000000000000000055534400000000000A20B3"
"C85F482532A9578DBB3950B85CA06594D1FF3157180C769B66D942EE69"
"E6DCC940CA48D82337AD00000000000000000000000042544300000000"
"0057180C769B66D942EE69E6DCC940CA48D82337AD1000000000000000"
"0000000000000000000000000030000000000000000000000000555344"
"00000000000A20B3C85F482532A9578DBB3950B85CA06594D100",
hasEnabled);
// STI_VECTOR256
testSTI(
"0013634000000000000000000000000000000000000000000000000000"
"0000000000000000000000000000000000000000000000000000000000"
"00000000000000000000",
true);
// STI_UINT96
// testSTI("000400000000000000000000000000000001", true);
// STI_UINT192
// testSTI("000400000000000000000000000000000001", true);
// STI_UINT384
// testSTI("000400000000000000000000000000000001", true);
// STI_UINT512
// testSTI("000400000000000000000000000000000001", true);
// STI_ISSUE
testSTI(
"03180000000000000000000000005553440000000000AE123A8556F3CF"
"91154711376AFB0F894F832B3D",
hasEnabled);
testSTI(
"03180000000000000000000000000000000000000000", hasEnabled);
// STI_XCHAIN_BRIDGE
/// Native-Native
testSTI(
"011914AE123A8556F3CF91154711376AFB0F894F832B3D000000000000"
"000000000000000000000000000014AE123A8556F3CF91154711376AFB"
"0F894F832B3D0000000000000000000000000000000000000000",
hasEnabled);
/// IOU-Native
testSTI(
"011914AE123A8556F3CF91154711376AFB0F894F832B3D000000000000"
"0000000000005553440000000000AE123A8556F3CF91154711376AFB0F"
"894F832B3D14AE123A8556F3CF91154711376AFB0F894F832B3D000000"
"0000"
"000000000000000000000000000000",
hasEnabled);
/// Native-IOU
testSTI(
"011914AE123A8556F3CF91154711376AFB0F894F832B3D000000000000"
"0000000000005553440000000000AE123A8556F3CF91154711376AFB0F"
"894F832B3D14AE123A8556F3CF91154711376AFB0F894F832B3D000000"
"0000000000000000000000000000000000",
hasEnabled);
/// IOU-IOU
testSTI(
"011914AE123A8556F3CF91154711376AFB0F894F832B3D000000000000"
"0000000000005553440000000000AE123A8556F3CF91154711376AFB0F"
"894F832B3D14AE123A8556F3CF91154711376AFB0F894F832B3D000000"
"0000000000000000005553440000000000AE123A8556F3CF9115471137"
"6AFB0F894F832B3D",
hasEnabled);
// STI_CURRENCY
testSTI(
"011A0000000000000000000000005553440000000000", hasEnabled);
}
}
}
void
@@ -11414,7 +11594,19 @@ public:
#define KEYLET_PAYCHAN 21
#define KEYLET_EMITTED_TXN 22
#define KEYLET_NFT_OFFER 23
#define KEYLET_HOOK_DEFINITION 24
#define KEYLET_HOOK_STATE_DIR 25
#define KEYLET_CRON 26
#define KEYLET_AMM 27
#define KEYLET_BRIDGE 28
#define KEYLET_XCHAIN_OWNED_CLAIM_ID 29
#define KEYLET_XCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID 30
#define KEYLET_DID 31
#define KEYLET_ORACLE 32
#define KEYLET_MPTOKEN_ISSUANCE 33
#define KEYLET_MPTOKEN 34
#define KEYLET_CREDENTIAL 35
#define KEYLET_PERMISSIONED_DOMAIN 36
#define ASSERT(x)\
if (!(x))\
rollback((uint32_t)#x, sizeof(#x), __LINE__);
@@ -11462,6 +11654,22 @@ public:
0x3AU,0x51U,0x8AU,0x22U,0x53U,0x81U,0x60U,0x84U,0x1CU,0x14U,0x32U,0xFEU,
0x6FU,0x3EU,0x6DU,0x6EU,0x76U,0x29U,0xFBU,0xBAU
};
uint8_t asset1[] = // USD.rB6v18pQ765Z9DH5RQsTFevoQPFmRtBqhT
{
0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,
0x00U,0x00U,0x55U,0x53U,0x44U,0x00U,0x00U,0x00U,0x00U,0x00U,
0x75U,0x6EU,0xDEU,0x88U,0xA9U,0x07U,0xD4U,0xCCU,0xF3U,0x8DU,0x6AU,0xDBU,
0x9FU,0xC7U,0x94U,0x64U,0x19U,0xF0U,0xC4U,0x1DU
};
uint8_t asset2[] = // EUR.raKM1bZkGmASBqN5v2swrf2uAPJ32Cd8GV
{
0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,0x00U,
0x00U,0x00U,0x45U,0x48U,0x52U,0x00U,0x00U,0x00U,0x00U,0x00U,
0x3AU,0x51U,0x8AU,0x22U,0x53U,0x81U,0x60U,0x84U,0x1CU,0x14U,0x32U,0xFEU,
0x6FU,0x3EU,0x6DU,0x6EU,0x76U,0x29U,0xFBU,0xBAU
};
int64_t hook(uint32_t reserved )
{
@@ -11916,11 +12124,76 @@ public:
0,0
)));
ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_NFT_OFFER,
SBUF(a), SBUF(ns),
0,0
)));
ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_HOOK_DEFINITION,
SBUF(ns),
0,0,
0,0
)));
ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_HOOK_STATE_DIR,
SBUF(a), SBUF(ns),
0,0
)));
ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_AMM,
SBUF(asset1), SBUF(asset2),
0,0
)));
ASSERT(INVALID_ARGUMENT == (e=util_keylet(buf, 34, KEYLET_BRIDGE,
SBUF(a), SBUF(b),
0,0
)));
ASSERT(INVALID_ARGUMENT == (e=util_keylet(buf, 34, KEYLET_XCHAIN_OWNED_CLAIM_ID,
SBUF(a), SBUF(b),
0,0
)));
ASSERT(INVALID_ARGUMENT == (e=util_keylet(buf, 34, KEYLET_XCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID,
SBUF(a), SBUF(b),
0,0
)));
ASSERT(INVALID_ARGUMENT == (e=util_keylet(buf, 34, KEYLET_DID,
SBUF(a),
0,0,
0,0
)));
ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_ORACLE,
SBUF(a), 3,
0,
0,0
)));
ASSERT(INVALID_ARGUMENT == (e=util_keylet(buf, 34, KEYLET_MPTOKEN_ISSUANCE,
SBUF(a),
0,0,
0,0
)));
ASSERT(INVALID_ARGUMENT == (e=util_keylet(buf, 34, KEYLET_MPTOKEN,
SBUF(a),
0,0,
0,0
)));
ASSERT(INVALID_ARGUMENT == (e=util_keylet(buf, 34, KEYLET_CREDENTIAL,
SBUF(a), SBUF(b),
0,0
)));
ASSERT(INVALID_ARGUMENT == (e=util_keylet(buf, 34, KEYLET_PERMISSIONED_DOMAIN,
SBUF(a),
0,0,
0,0
)));
ASSERT(34 == (e=util_keylet(buf, 34, KEYLET_CRON, SBUF(a), 1, 0, 0, 0)));
{

File diff suppressed because it is too large Load Diff

View File

@@ -70,7 +70,7 @@ namespace hook_api {
DECLARE_HOOK_FUNCTION( \
RETURN_TYPE, FUNCTION_NAME, HOOK_WRAP_PARAMS PARAMS_TUPLE);
#include <ripple/app/hook/hook_api.macro>
#include <xrpl/hook/hook_api.macro>
#undef HOOK_API_DEFINITION
#undef HOOK_WRAP_PARAMS
@@ -468,7 +468,7 @@ public:
#define HOOK_API_DEFINITION(RETURN_TYPE, FUNCTION_NAME, PARAMS_TUPLE, ...) \
ADD_HOOK_FUNCTION(FUNCTION_NAME, ctx);
#include <ripple/app/hook/hook_api.macro>
#include <xrpl/hook/hook_api.macro>
#undef HOOK_API_DEFINITION
#undef HOOK_WRAP_PARAMS

View File

@@ -355,7 +355,14 @@ HookAPI::sto_validate(Bytes const& data) const
{
int type = -1, field = -1, payload_start = -1, payload_length = -1;
auto const length = get_stobject_length(
upto, end, type, field, payload_start, payload_length, 0);
upto,
end,
type,
field,
payload_start,
payload_length,
hookCtx.applyCtx.view().rules(),
0);
if (!length)
return 0;
upto += length.value();
@@ -389,7 +396,14 @@ HookAPI::sto_subfield(Bytes const& data, uint32_t field_id) const
{
int type = -1, field = -1, payload_start = -1, payload_length = -1;
auto const length = get_stobject_length(
upto, end, type, field, payload_start, payload_length, 0);
upto,
end,
type,
field,
payload_start,
payload_length,
hookCtx.applyCtx.view().rules(),
0);
if (!length)
return Unexpected(PARSE_ERROR);
if ((type << 16) + field == field_id)
@@ -464,7 +478,14 @@ HookAPI::sto_subarray(Bytes const& data, uint32_t index_id) const
{
int type = -1, field = -1, payload_start = -1, payload_length = -1;
auto const length = get_stobject_length(
upto, end, type, field, payload_start, payload_length, 0);
upto,
end,
type,
field,
payload_start,
payload_length,
hookCtx.applyCtx.view().rules(),
0);
if (!length)
return Unexpected(PARSE_ERROR);
@@ -530,6 +551,7 @@ HookAPI::sto_emplace(
field,
payload_start,
payload_length,
hookCtx.applyCtx.view().rules(),
0);
if (!length)
return Unexpected(PARSE_ERROR);
@@ -565,7 +587,14 @@ HookAPI::sto_emplace(
{
int type = -1, field = -1, payload_start = -1, payload_length = -1;
auto const length = get_stobject_length(
upto, end, type, field, payload_start, payload_length, 0);
upto,
end,
type,
field,
payload_start,
payload_length,
hookCtx.applyCtx.view().rules(),
0);
if (!length)
return Unexpected(PARSE_ERROR);
if ((type << 16) + field == field_id)
@@ -2998,12 +3027,20 @@ HookAPI::get_stobject_length(
int& payload_start, // out - the start of actual payload data for this type
int& payload_length, // out - the length of actual payload data for this
// type
Rules const& rules,
int recursion_depth) // used internally
const
{
if (recursion_depth > 10)
return Unexpected(pe_excessive_nesting);
uint16_t max_sti_type = rules.enabled(featureHookAPISerializedType240)
? STI_CURRENCY
: STI_VECTOR256;
if (type > max_sti_type)
return pe_unknown_type_early;
unsigned char* end = maxptr;
unsigned char* upto = start;
int high = *upto >> 4;
@@ -3055,14 +3092,20 @@ HookAPI::get_stobject_length(
auto const& fieldObj = ripple::SField::getField;
*/
if (type < 1 || type > 19 || (type >= 9 && type <= 13))
// type 10~13 are reserved
if (type < 1 || max_sti_type < type || (10 <= type && type <= 13))
return Unexpected(pe_unknown_type_early);
// not supported types
if (type == STI_NUMBER || type == STI_UINT96 || type == STI_UINT192 ||
type == STI_UINT384 || type == STI_UINT512)
return pe_unknown_type_early;
bool is_vl =
(type == SerializedTypeID::STI_ACCOUNT ||
type == SerializedTypeID::STI_VL ||
type == SerializedTypeID::STI_PATHSET ||
type == SerializedTypeID::STI_VECTOR256);
(type == STI_ACCOUNT || type == STI_VL ||
(type == STI_PATHSET &&
!rules.enabled(featureHookAPISerializedType240)) ||
type == STI_VECTOR256);
int length = -1;
if (is_vl)
@@ -3095,42 +3138,116 @@ HookAPI::get_stobject_length(
return Unexpected(pe_unexpected_end);
}
}
else if ((type >= 1 && type <= 5) || type == 16 || type == 17)
else if (
(type >= STI_UINT16 && type <= STI_UINT256) || type == STI_UINT8 ||
type == STI_UINT160 || type == STI_CURRENCY)
{
switch (type)
{
case SerializedTypeID::STI_UINT16:
case STI_UINT16:
length = 2;
break;
case SerializedTypeID::STI_UINT32:
case STI_UINT32:
length = 4;
break;
case SerializedTypeID::STI_UINT64:
case STI_UINT64:
length = 8;
break;
case SerializedTypeID::STI_UINT128:
case STI_UINT128:
length = 16;
break;
case SerializedTypeID::STI_UINT256:
case STI_UINT256:
length = 32;
break;
case SerializedTypeID::STI_UINT8:
case STI_UINT8:
length = 1;
break;
case SerializedTypeID::STI_UINT160:
case STI_UINT160:
length = 20;
break;
case STI_CURRENCY:
length = 20;
break;
default:
length = -1;
break;
return -1;
}
}
else if (type == SerializedTypeID::STI_AMOUNT)
else if (type == STI_AMOUNT) /* AMOUNT */
{
length = (*upto >> 6 == 1) ? 8 : 48;
if (upto >= end)
return Unexpected(pe_unexpected_end);
}
else if (
type == STI_PATHSET && rules.enabled(featureHookAPISerializedType240))
{
length = 0;
while (upto + length < end)
{
// iterate Path step
while (*(upto + length) & 0x01 || *(upto + length) & 0x10 ||
*(upto + length) & 0x20)
{
int flag = *(upto + length++);
// flag shoud be 0x01 or 0x10 or 0x20 or those union
if (flag == 0 || flag & ~(0x01 | 0x10 | 0x20))
return pe_unexpected_end;
if (flag & 0x01) // account
length += 20;
if (flag & 0x10) // currency
length += 20;
if (flag & 0x20) // issuer
length += 20;
int next_flag = *(upto + length);
if (next_flag == 0x00 || next_flag == 0xff)
// end of Path step
break;
}
// continue or end of Paths
int lastflag = *(upto + length++);
if (lastflag == 0xff)
continue; // continue byte
else if (lastflag == 0x00)
break; // end byte
else
return pe_unexpected_end;
}
if (upto >= end)
return pe_unexpected_end;
}
else if (type == STI_ISSUE)
{
auto zero20 = std::array<char, 20>{0};
// if first 20 byte is all zeros return 20
// else return 40
if (memcmp(upto, zero20.data(), 20) == 0)
length = 20;
else
length = 40;
}
else if (type == STI_XCHAIN_BRIDGE)
{
auto zero20 = std::array<char, 20>{0};
// Lock Chain
length = 1; // Door Account1 prefix length
length += 20; // Door Account1 length
// Door Issue1
if (memcmp(upto + length, zero20.data(), 20) == 0)
length += 20; // only Currency
else
length += 40; // Currency and Issue
// Issuing Chain
length += 1; // Door Account2 prefix length
length += 20; // Door Account2 length
// Door Issue2
if (memcmp(upto + length, zero20.data(), 20) == 0)
length += 20; // only Currency
else
length += 40; // Currency and Issue
}
if (length > -1)
{
@@ -3149,8 +3266,7 @@ HookAPI::get_stobject_length(
return length + (upto - start);
}
if (type == SerializedTypeID::STI_OBJECT ||
type == SerializedTypeID::STI_ARRAY)
if (type == STI_OBJECT || type == STI_ARRAY)
{
payload_start = upto - start;
@@ -3165,6 +3281,7 @@ HookAPI::get_stobject_length(
subfield,
payload_start_,
payload_length_,
hookCtx.applyCtx.view().rules(),
recursion_depth + 1);
DBG_PRINTF(
"%d get_stobject_length i %d %d-%d, upto %d sublength %d\n",
@@ -3180,8 +3297,8 @@ HookAPI::get_stobject_length(
if (upto >= end)
return Unexpected(pe_unexpected_end);
if ((*upto == 0xE1U && type == 0xEU) ||
(*upto == 0xF1U && type == 0xFU))
if ((*upto == 0xE1U && type == 0xEU) || // STI_OBJECT Maker
(*upto == 0xF1U && type == 0xFU)) // STI_ARRAY Maker
{
payload_length = upto - start - payload_start;
upto++;

View File

@@ -2327,7 +2327,13 @@ DEFINE_HOOK_FUNCTION(
case keylet_code::OWNER_DIR:
case keylet_code::SIGNERS:
case keylet_code::ACCOUNT:
case keylet_code::HOOK: {
case keylet_code::HOOK:
case keylet_code::DID: {
if (keylet_type == keylet_code::DID)
{
if (!applyCtx.view().rules().enabled(featureDID))
return INVALID_ARGUMENT;
}
if (a == 0 || b == 0)
return INVALID_ARGUMENT;
@@ -2350,13 +2356,43 @@ DEFINE_HOOK_FUNCTION(
? ripple::keylet::signers(id)
: keylet_type == keylet_code::OWNER_DIR
? ripple::keylet::ownerDir(id)
: keylet_type == keylet_code::DID
? ripple::keylet::did(id)
: ripple::keylet::account(id);
return serialize_keylet(kl, memory, write_ptr, write_len);
}
// keylets that take 20 byte account id, and (4 byte uint for 32
// byte hash)
// keylets that take 20 byte account id, and (4 byte uint for 32
// byte hash)
case keylet_code::ORACLE: {
if (!applyCtx.view().rules().enabled(featurePriceOracle))
return INVALID_ARGUMENT;
if (a == 0 || b == 0)
return INVALID_ARGUMENT;
if (d != 0 || e != 0 || f != 0)
return INVALID_ARGUMENT;
uint32_t read_ptr = a, read_len = b;
if (NOT_IN_BOUNDS(read_ptr, read_len, memory_length))
return OUT_OF_BOUNDS;
if (read_len != 20)
return INVALID_ARGUMENT;
ripple::AccountID id = AccountID::fromVoid(memory + read_ptr);
uint32_t seqId = c;
ripple::Keylet kl = ripple::keylet::oracle(id, seqId);
return serialize_keylet(kl, memory, write_ptr, write_len);
}
// keylets that take 20 byte account id, and UInt32or256 (4 byte
// uint or 32 byte hash)
case keylet_code::OFFER:
case keylet_code::CHECK:
case keylet_code::ESCROW:
@@ -2639,6 +2675,62 @@ DEFINE_HOOK_FUNCTION(
return serialize_keylet(kl, memory, write_ptr, write_len);
}
// keylets that take two 40 byte assets
case keylet_code::AMM: {
if (!applyCtx.view().rules().enabled(featureAMM))
return INVALID_ARGUMENT;
if (a == 0 || b == 0 || c == 0 || d == 0)
return INVALID_ARGUMENT;
if (e != 0 || f != 0)
return INVALID_ARGUMENT;
uint32_t aread_ptr = a, aread_len = b;
uint32_t bread_ptr = c, bread_len = d;
if (NOT_IN_BOUNDS(aread_ptr, aread_len, memory_length) ||
NOT_IN_BOUNDS(bread_ptr, bread_len, memory_length))
return OUT_OF_BOUNDS;
if (aread_len != 40 || bread_len != 40)
return INVALID_ARGUMENT;
Currency aCur = Currency::fromVoid(memory + aread_ptr);
Currency bCur = Currency::fromVoid(memory + bread_ptr);
AccountID aAcc = AccountID::fromVoid(memory + aread_ptr + 20);
AccountID bAcc = AccountID::fromVoid(memory + bread_ptr + 20);
Issue aIss = Issue{aCur, aAcc};
Issue bIss = Issue{bCur, bAcc};
ripple::Keylet kl =
ripple::keylet::amm(Asset{aIss}, Asset{bIss});
return serialize_keylet(kl, memory, write_ptr, write_len);
}
case keylet_code::BRIDGE:
case keylet_code::XCHAIN_OWNED_CLAIM_ID:
case keylet_code::XCHAIN_OWNED_CREATE_ACCOUNT_CLAIM_ID: {
if (!applyCtx.view().rules().enabled(featureXChainBridge))
return INVALID_ARGUMENT;
}
case keylet_code::MPTOKEN_ISSUANCE:
case keylet_code::MPTOKEN: {
if (!applyCtx.view().rules().enabled(featureMPTokensV1))
return INVALID_ARGUMENT;
}
case keylet_code::CREDENTIAL: {
if (!applyCtx.view().rules().enabled(featureCredentials))
return INVALID_ARGUMENT;
}
case keylet_code::PERMISSIONED_DOMAIN: {
if (!applyCtx.view().rules().enabled(
featurePermissionedDomains))
return INVALID_ARGUMENT;
}
}
}
catch (std::exception& e)