mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-26 21:45:50 +00:00
3462 lines
131 KiB
C++
3462 lines
131 KiB
C++
//------------------------------------------------------------------------------
|
|
/*
|
|
This file is part of rippled: https://github.com/ripple/rippled
|
|
Copyright (c) 2025 XRPL-Labs
|
|
|
|
Permission to use, copy, modify, and/or distribute this software for any
|
|
purpose with or without fee is hereby granted, provided that the above
|
|
copyright notice and this permission notice appear in all copies.
|
|
|
|
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
|
|
WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
|
|
MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
|
|
ANY SPECIAL , DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
|
|
WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
|
|
ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
|
|
OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
|
|
*/
|
|
//==============================================================================
|
|
#include <ripple/app/hook/HookAPI.h>
|
|
#include <ripple/protocol/STAccount.h>
|
|
#include <limits>
|
|
#include <test/jtx.h>
|
|
#include <tuple>
|
|
#include <vector>
|
|
|
|
namespace ripple {
|
|
|
|
namespace test {
|
|
|
|
class HookAPI_test : public beast::unit_test::suite
|
|
{
|
|
private:
|
|
ApplyContext
|
|
createApplyContext(jtx::Env& env, OpenView& ov, STTx const& tx)
|
|
{
|
|
ApplyContext applyCtx{
|
|
env.app(),
|
|
ov,
|
|
tx,
|
|
tesSUCCESS,
|
|
env.current()->fees().base,
|
|
tapNONE,
|
|
env.journal};
|
|
return applyCtx;
|
|
}
|
|
|
|
public:
|
|
void
|
|
test_accept(FeatureBitset features)
|
|
{
|
|
testcase("Test accept() hookapi");
|
|
|
|
// TODO
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_rollback(FeatureBitset features)
|
|
{
|
|
testcase("Test rollback() hookapi");
|
|
|
|
// TODO
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
testGuards(FeatureBitset features)
|
|
{
|
|
testcase("Test guards");
|
|
|
|
// TODO
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_emit(FeatureBitset features)
|
|
{
|
|
testcase("Test emit");
|
|
using namespace jtx;
|
|
|
|
auto const alice = Account{"alice"};
|
|
auto const bob = Account{"bob"};
|
|
|
|
using namespace hook_api;
|
|
Env env{*this, features};
|
|
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
|
|
STTx const emitInvokeTx = STTx(ttINVOKE, [&](STObject& obj) {
|
|
obj[sfAccount] = alice.id();
|
|
obj[sfSequence] = 0;
|
|
obj[sfSigningPubKey] = PublicKey();
|
|
obj[sfFirstLedgerSequence] = env.closed()->seq() + 1;
|
|
obj[sfLastLedgerSequence] = env.closed()->seq() + 5;
|
|
obj[sfFee] = env.closed()->fees().base;
|
|
|
|
auto& emitDetails = obj.peekFieldObject(sfEmitDetails);
|
|
emitDetails[sfEmitGeneration] = 1;
|
|
emitDetails[sfEmitBurden] = 1;
|
|
emitDetails[sfEmitParentTxnID] = invokeTx.getTransactionID();
|
|
emitDetails[sfEmitNonce] = uint256();
|
|
emitDetails[sfEmitHookHash] = uint256();
|
|
});
|
|
|
|
STTx const emitSetHookTx = STTx(ttHOOK_SET, [&](STObject& obj) {
|
|
obj[sfAccount] = alice.id();
|
|
obj[sfSequence] = 0;
|
|
obj[sfSigningPubKey] = PublicKey();
|
|
obj[sfFirstLedgerSequence] = env.closed()->seq() + 1;
|
|
obj[sfLastLedgerSequence] = env.closed()->seq() + 5;
|
|
obj[sfFee] = env.closed()->fees().base;
|
|
STObject hookobj(sfHook);
|
|
auto& hooks = obj.peekFieldArray(sfHooks);
|
|
hooks.emplace_back(std::move(hookobj));
|
|
|
|
auto& emitDetails = obj.peekFieldObject(sfEmitDetails);
|
|
emitDetails[sfEmitGeneration] = 1;
|
|
emitDetails[sfEmitBurden] = 1;
|
|
emitDetails[sfEmitParentTxnID] = invokeTx.getTransactionID();
|
|
emitDetails[sfEmitNonce] = uint256();
|
|
emitDetails[sfEmitHookHash] = uint256();
|
|
});
|
|
|
|
{
|
|
// PREREQUISITE_NOT_MET
|
|
auto hookCtx = makeStubHookContext(
|
|
applyCtx, alice.id(), alice.id(), {.expected_etxn_count = -1});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
auto const result = api.emit(emitInvokeTx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == PREREQUISITE_NOT_MET);
|
|
}
|
|
{
|
|
// TOO_MANY_EMITTED_TXN
|
|
std::string reason;
|
|
auto tx = std::make_shared<ripple::Transaction>(
|
|
std::make_shared<ripple::STTx const>(invokeTx),
|
|
reason,
|
|
env.app());
|
|
std::queue<std::shared_ptr<ripple::Transaction>> emittedTxn;
|
|
emittedTxn.push(tx);
|
|
auto hookCtx = makeStubHookContext(
|
|
applyCtx,
|
|
alice.id(),
|
|
alice.id(),
|
|
{
|
|
.expected_etxn_count = 1,
|
|
.result = {.emittedTxn = emittedTxn},
|
|
});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
auto const result = api.emit(emitInvokeTx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == TOO_MANY_EMITTED_TXN);
|
|
}
|
|
// EMISSION_FAILURE
|
|
{
|
|
// Invalid txn
|
|
auto hookCtx = makeStubHookContext(
|
|
applyCtx,
|
|
alice.id(),
|
|
alice.id(),
|
|
{
|
|
.expected_etxn_count = 1,
|
|
.nonce_used = {{uint256(0), true}},
|
|
});
|
|
hook::HookAPI api(hookCtx);
|
|
auto tx = emitInvokeTx;
|
|
Serializer s = tx.getSerializer();
|
|
s.add8(0); // invalid value
|
|
auto const result = api.emit(s.slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
// Pseudo txn
|
|
auto hookCtx = makeStubHookContext(
|
|
applyCtx,
|
|
alice.id(),
|
|
alice.id(),
|
|
{
|
|
.expected_etxn_count = 1,
|
|
.nonce_used = {{uint256(0), true}},
|
|
});
|
|
hook::HookAPI api(hookCtx);
|
|
auto tx = emitInvokeTx;
|
|
tx.setFieldU16(sfTransactionType, ttFEE);
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
// HookCanEmit (non-SetHook)
|
|
auto hookCtx = makeStubHookContext(
|
|
applyCtx,
|
|
alice.id(),
|
|
alice.id(),
|
|
{.expected_etxn_count = 1,
|
|
.nonce_used = {{uint256(0), true}},
|
|
.result = {
|
|
.hookCanEmit = UINT256_BIT[ttINVOKE],
|
|
}});
|
|
hook::HookAPI api(hookCtx);
|
|
auto tx = emitInvokeTx;
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
// HookCanEmit (SetHook) Error
|
|
auto hookCtx = makeStubHookContext(
|
|
applyCtx,
|
|
alice.id(),
|
|
alice.id(),
|
|
{
|
|
.expected_etxn_count = 1,
|
|
.nonce_used = {{uint256(0), true}},
|
|
.result = {.hookCanEmit = uint256()},
|
|
});
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.emit(emitSetHookTx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
// HookCanEmit (SetHook) Success
|
|
auto hookCtx = makeStubHookContext(
|
|
applyCtx,
|
|
alice.id(),
|
|
alice.id(),
|
|
{
|
|
.expected_etxn_count = 1,
|
|
.nonce_used = {{uint256(0), true}},
|
|
.result = {.hookCanEmit = UINT256_BIT[ttHOOK_SET]},
|
|
});
|
|
hook::HookAPI api(hookCtx);
|
|
auto tx = emitSetHookTx;
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.has_value());
|
|
}
|
|
|
|
auto hookCtx = makeStubHookContext(
|
|
applyCtx,
|
|
alice.id(),
|
|
alice.id(),
|
|
{
|
|
.expected_etxn_count = 1,
|
|
.nonce_used = {{uint256(0), true}},
|
|
.result = {.hookCanEmit = uint256()},
|
|
});
|
|
hook::HookAPI api(hookCtx);
|
|
{
|
|
// Invalid sfAccount
|
|
auto tx = emitInvokeTx;
|
|
{
|
|
// Missing sfAccount
|
|
tx.makeFieldAbsent(sfAccount);
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
// Invalid sfAccount (!= HookAccount)
|
|
tx.setAccountID(sfAccount, bob.id());
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
}
|
|
{
|
|
// Invalid sfSequence
|
|
auto tx = emitInvokeTx;
|
|
{
|
|
// Missing sfSequence
|
|
tx.makeFieldAbsent(sfSequence);
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
// Invalid sfSequence (non-zero)
|
|
tx.setFieldU32(sfSequence, 1);
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
}
|
|
{
|
|
// Invalid sfSigningPubKey
|
|
auto tx = emitInvokeTx;
|
|
{
|
|
// Missing sfSigningPubKey
|
|
tx.makeFieldAbsent(sfSigningPubKey);
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
// Invalid sfSigningPubKey (wrong size)
|
|
for (int i = 1; i < 33; ++i)
|
|
{
|
|
tx.setFieldVL(sfSigningPubKey, std::vector<uint8_t>(i, 0));
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
}
|
|
{
|
|
// Invalid sfSigningPubKey (non-zero)
|
|
for (int i = 0; i < 33; ++i)
|
|
{
|
|
auto vec = std::vector<uint8_t>(33, 0);
|
|
vec[i] = 1;
|
|
tx.setFieldVL(sfSigningPubKey, vec);
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
}
|
|
}
|
|
{
|
|
// Invalid sfSigners
|
|
auto tx = emitInvokeTx;
|
|
tx.setFieldArray(sfSigners, STArray(sfSigners, 1));
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
// Invalid sfTicketSequence
|
|
auto tx = emitInvokeTx;
|
|
tx.setFieldU32(sfTicketSequence, 1);
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
// Invalid sfAccountTxnID
|
|
auto tx = emitInvokeTx;
|
|
tx.setFieldH256(sfAccountTxnID, uint256(1));
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
; // Invalid sfEmitDetails
|
|
{
|
|
// Missing sfEmitDetails
|
|
auto tx = emitInvokeTx;
|
|
tx.makeFieldAbsent(sfEmitDetails);
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
std::vector<std::reference_wrapper<SField const>>
|
|
detail_fields = {
|
|
sfEmitGeneration,
|
|
sfEmitBurden,
|
|
sfEmitParentTxnID,
|
|
sfEmitNonce,
|
|
sfEmitHookHash,
|
|
};
|
|
// Missing fields in sfEmitDetails
|
|
for (auto const& rf : detail_fields)
|
|
{
|
|
SField const& field = rf.get();
|
|
auto tx = emitInvokeTx;
|
|
auto& details = tx.peekFieldObject(sfEmitDetails);
|
|
details.makeFieldAbsent(field);
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
}
|
|
// TODO: test callback
|
|
{
|
|
; // Invalid sfEmitGeneration
|
|
{
|
|
// Over Max sfEmitGeneration
|
|
auto tx = emitInvokeTx;
|
|
auto& details = tx.peekFieldObject(sfEmitDetails);
|
|
details.setFieldU32(sfEmitGeneration, 11);
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
// Invalid sfEmitGeneration
|
|
auto tx = emitInvokeTx;
|
|
auto& details = tx.peekFieldObject(sfEmitDetails);
|
|
details.setFieldU32(
|
|
sfEmitGeneration, hookCtx.generation + 2);
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
}
|
|
{
|
|
// Invalid sfEmitBurden
|
|
auto tx = emitInvokeTx;
|
|
auto& details = tx.peekFieldObject(sfEmitDetails);
|
|
BEAST_EXPECT(hookCtx.burden == 0);
|
|
details.setFieldU64(sfEmitBurden, 2);
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
// Invalid sfEmitParentTxnID
|
|
auto tx = emitInvokeTx;
|
|
auto& details = tx.peekFieldObject(sfEmitDetails);
|
|
BEAST_EXPECT(applyCtx.tx.getTransactionID() != uint256(1));
|
|
details.setFieldH256(sfEmitParentTxnID, uint256(1));
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
// Invalid sfEmitNonce
|
|
auto tx = emitInvokeTx;
|
|
auto& details = tx.peekFieldObject(sfEmitDetails);
|
|
BEAST_EXPECT(
|
|
hookCtx.nonce_used.find(uint256(1)) ==
|
|
hookCtx.nonce_used.end());
|
|
details.setFieldH256(sfEmitNonce, uint256(1));
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
// TODO: test Callback
|
|
{
|
|
// Invalid sfEmitHookHash
|
|
auto tx = emitInvokeTx;
|
|
auto& details = tx.peekFieldObject(sfEmitDetails);
|
|
BEAST_EXPECT(hookCtx.result.hookHash != uint256(1));
|
|
details.setFieldH256(sfEmitHookHash, uint256(1));
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
}
|
|
{
|
|
// Invalid sfTxnSignature
|
|
auto tx = emitInvokeTx;
|
|
tx.setFieldVL(sfTxnSignature, std::vector<uint8_t>(1, 0));
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
; // Invalid sfLastLedgerSequence
|
|
{
|
|
// Missing sfLastLedgerSequence
|
|
auto tx = emitInvokeTx;
|
|
tx.makeFieldAbsent(sfLastLedgerSequence);
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
// Invalid sfLastLedgerSequence
|
|
// (smaller than next ledger seq)
|
|
auto tx = emitInvokeTx;
|
|
auto const currentSeq = applyCtx.view().info().seq;
|
|
tx.setFieldU32(sfLastLedgerSequence, currentSeq);
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
// Invalid sfLastLedgerSequence
|
|
// (greater than current ledger seq + 5)
|
|
auto tx = emitInvokeTx;
|
|
auto const currentSeq = applyCtx.view().info().seq;
|
|
tx.setFieldU32(sfLastLedgerSequence, currentSeq + 6);
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
}
|
|
{
|
|
; // Invalid sfFirstLedgerSequence
|
|
{
|
|
// missing sfFirstLedgerSequence
|
|
auto tx = emitInvokeTx;
|
|
tx.makeFieldAbsent(sfFirstLedgerSequence);
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
// Invalid sfFirstLedgerSequence
|
|
auto tx = emitInvokeTx;
|
|
auto const lastLedgerSeq = tx.getFieldU32(sfLastLedgerSequence);
|
|
tx.setFieldU32(sfFirstLedgerSequence, lastLedgerSeq + 1);
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
}
|
|
{
|
|
; // Invalid sfFee
|
|
{
|
|
// Missing sfFee
|
|
auto tx = emitInvokeTx;
|
|
tx.makeFieldAbsent(sfFee);
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
// Invalid sfFee
|
|
auto tx = emitInvokeTx;
|
|
tx.setFieldAmount(sfFee, drops(1));
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
}
|
|
{
|
|
// Preflight failure
|
|
auto tx = emitInvokeTx;
|
|
tx.setFieldVL(sfBlob, std::vector<uint8_t>(128 * 1024 + 1, 1));
|
|
auto const result = api.emit(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == EMISSION_FAILURE);
|
|
}
|
|
{
|
|
// Success
|
|
auto tx = emitInvokeTx;
|
|
Serializer s;
|
|
tx.add(s);
|
|
auto const result = api.emit(s.slice());
|
|
BEAST_EXPECT(result.has_value());
|
|
}
|
|
}
|
|
|
|
void
|
|
test_etxn_details(FeatureBitset features)
|
|
{
|
|
testcase("Test etxn_details");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
|
|
{
|
|
// PREREQUISITE_NOT_MET
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
std::array<uint8_t, 256> buffer{};
|
|
auto const result = api.etxn_details(buffer.data());
|
|
BEAST_EXPECT(!result.has_value());
|
|
BEAST_EXPECT(result.error() == PREREQUISITE_NOT_MET);
|
|
}
|
|
|
|
{
|
|
// FEE_TOO_LARGE (via etxn_burden overflow)
|
|
StubHookContext stubCtx{
|
|
.expected_etxn_count = 2,
|
|
.burden = std::numeric_limits<uint64_t>::max(),
|
|
};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
std::array<uint8_t, 256> buffer{};
|
|
auto const result = api.etxn_details(buffer.data());
|
|
BEAST_EXPECT(!result.has_value());
|
|
BEAST_EXPECT(result.error() == FEE_TOO_LARGE);
|
|
}
|
|
|
|
{
|
|
// SUCCESS path length check and nonce increment (no-callback)
|
|
StubHookContext stubCtx{
|
|
.expected_etxn_count = 2,
|
|
.result = {.hookHash = uint256{3}},
|
|
};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
std::array<uint8_t, 256> buffer{};
|
|
auto const result = api.etxn_details(buffer.data());
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() == 116);
|
|
BEAST_EXPECT(hookCtx.emit_nonce_counter == 1);
|
|
}
|
|
|
|
{
|
|
// SUCCESS path length check and nonce increment (callback)
|
|
StubHookContext stubCtx{
|
|
.expected_etxn_count = 2,
|
|
.result = {.hookHash = uint256{3}, .hasCallback = true},
|
|
};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
std::array<uint8_t, 256> buffer{};
|
|
auto const result = api.etxn_details(buffer.data());
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() == 138);
|
|
BEAST_EXPECT(hookCtx.emit_nonce_counter == 1);
|
|
}
|
|
}
|
|
|
|
void
|
|
test_etxn_fee_base(FeatureBitset features)
|
|
{
|
|
testcase("Test etxn_fee_base");
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
hook::HookContext hookCtx = makeStubHookContext(
|
|
applyCtx,
|
|
alice.id(),
|
|
alice.id(),
|
|
{
|
|
.expected_etxn_count = -1,
|
|
.nonce_used = {{uint256(0), true}},
|
|
.result = {.hookCanEmit = uint256()},
|
|
});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
// PREREQUISITE_NOT_MET
|
|
{
|
|
auto const result =
|
|
api.etxn_fee_base(invokeTx.getSerializer().slice());
|
|
BEAST_EXPECT(result.error() == PREREQUISITE_NOT_MET);
|
|
}
|
|
|
|
hookCtx.expected_etxn_count = 1;
|
|
|
|
// INVALID_TXN
|
|
{
|
|
auto tx = invokeTx;
|
|
Serializer s = tx.getSerializer();
|
|
s.add8(0); // invalid value
|
|
auto const result = api.etxn_fee_base(s.slice());
|
|
BEAST_EXPECT(result.error() == INVALID_TXN);
|
|
}
|
|
{
|
|
// SUCCESS
|
|
auto const result =
|
|
api.etxn_fee_base(invokeTx.getSerializer().slice());
|
|
BEAST_EXPECT(result.has_value());
|
|
auto const baseFee = env.closed()->fees().base;
|
|
BEAST_EXPECT(result.value() == baseFee);
|
|
}
|
|
{
|
|
// Fee value
|
|
auto tx = invokeTx;
|
|
// add 100 bytes of blob
|
|
tx.setFieldVL(sfBlob, std::vector<uint8_t>(100, 1));
|
|
// add 100 bytes of memo
|
|
tx.setFieldArray(sfMemos, STArray(sfMemos, 1));
|
|
auto& memos = tx.peekFieldArray(sfMemos);
|
|
STObject memo = STObject(sfMemo);
|
|
memo.setFieldVL(sfMemoData, std::vector<uint8_t>(100, 1));
|
|
memos.emplace_back(memo);
|
|
auto const result = api.etxn_fee_base(tx.getSerializer().slice());
|
|
BEAST_EXPECT(result.has_value());
|
|
auto const baseFee = env.closed()->fees().base;
|
|
auto const blobSize = 100;
|
|
auto const memoSize = 100;
|
|
if (env.closed()->rules().enabled(fixEtxnFeeBase))
|
|
BEAST_EXPECT(result.value() == baseFee + blobSize + memoSize);
|
|
else
|
|
BEAST_EXPECT(result.value() == baseFee + memoSize);
|
|
}
|
|
}
|
|
|
|
void
|
|
test_etxn_burden(FeatureBitset features)
|
|
{
|
|
testcase("Test etxn_burden");
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
|
|
{
|
|
// PREREQUISITE_NOT_MET
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.etxn_burden();
|
|
BEAST_EXPECT(result.error() == PREREQUISITE_NOT_MET);
|
|
}
|
|
|
|
{
|
|
// FEE_TOO_LARGE (overflow)
|
|
StubHookContext stubCtx{
|
|
.expected_etxn_count = 2,
|
|
.burden = std::numeric_limits<uint64_t>::max(),
|
|
};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.etxn_burden();
|
|
BEAST_EXPECT(result.error() == FEE_TOO_LARGE);
|
|
}
|
|
|
|
{
|
|
// SUCCESS
|
|
StubHookContext stubCtx{.expected_etxn_count = 3, .burden = 5};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.etxn_burden();
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() == 15);
|
|
}
|
|
}
|
|
|
|
void
|
|
test_etxn_generation(FeatureBitset features)
|
|
{
|
|
testcase("Test etxn_generation");
|
|
using namespace jtx;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx baseTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
|
|
{
|
|
// Cached generation value
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, baseTx);
|
|
StubHookContext stubCtx{.generation = 4};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.etxn_generation() == 5);
|
|
}
|
|
|
|
{
|
|
// No emit details -> otxn_generation() == 0
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, baseTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.etxn_generation() == 1);
|
|
}
|
|
|
|
{
|
|
// Emit details supply generation
|
|
STTx emitTx = STTx(ttINVOKE, [&](STObject& obj) {
|
|
obj.peekFieldObject(sfEmitDetails)
|
|
.setFieldU32(sfEmitGeneration, 2);
|
|
});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, emitTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.etxn_generation() == 3);
|
|
}
|
|
}
|
|
|
|
void
|
|
test_etxn_nonce(FeatureBitset features)
|
|
{
|
|
testcase("Test etxn_nonce");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
hook::HookContext hookCtx = makeStubHookContext(
|
|
applyCtx,
|
|
alice.id(),
|
|
alice.id(),
|
|
{
|
|
.expected_etxn_count = -1,
|
|
.nonce_used = {{uint256(0), true}},
|
|
.result = {.hookCanEmit = uint256()},
|
|
});
|
|
|
|
// TOO_MANY_NONCES
|
|
{
|
|
hookCtx.emit_nonce_counter = hook_api::max_nonce + 1;
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.etxn_nonce();
|
|
BEAST_EXPECT(result.error() == TOO_MANY_NONCES);
|
|
}
|
|
|
|
// SUCCESS
|
|
{
|
|
hookCtx.emit_nonce_counter = hook_api::max_nonce;
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.etxn_nonce();
|
|
BEAST_EXPECT(result.has_value());
|
|
}
|
|
|
|
{
|
|
// Flags and cache tracking
|
|
StubHookContext stubCtx{
|
|
.nonce_used = {},
|
|
.result =
|
|
{.isCallback = true,
|
|
.isStrong = true,
|
|
.hookChainPosition = 2},
|
|
};
|
|
auto hookCtxNonce =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtxNonce);
|
|
auto const expectedFlags =
|
|
static_cast<uint32_t>(0b11U | (2U << 2U));
|
|
auto const expected = ripple::sha512Half(
|
|
ripple::HashPrefix::emitTxnNonce,
|
|
hookCtxNonce.applyCtx.tx.getTransactionID(),
|
|
hookCtxNonce.emit_nonce_counter,
|
|
hookCtxNonce.result.account,
|
|
hookCtxNonce.result.hookHash,
|
|
expectedFlags);
|
|
auto const result = api.etxn_nonce();
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() == expected);
|
|
BEAST_EXPECT(hookCtxNonce.emit_nonce_counter == 1);
|
|
BEAST_EXPECT(hookCtxNonce.nonce_used.count(expected) == 1);
|
|
}
|
|
}
|
|
|
|
void
|
|
test_etxn_reserve(FeatureBitset features)
|
|
{
|
|
testcase("Test etxn_reserve");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
|
|
{
|
|
// ALREADY_SET
|
|
StubHookContext stubCtx{.expected_etxn_count = 1};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.etxn_reserve(2);
|
|
BEAST_EXPECT(result.error() == ALREADY_SET);
|
|
}
|
|
|
|
{
|
|
// TOO_SMALL
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.etxn_reserve(0);
|
|
BEAST_EXPECT(result.error() == TOO_SMALL);
|
|
}
|
|
|
|
{
|
|
// TOO_BIG
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.etxn_reserve(hook_api::max_emit + 1);
|
|
BEAST_EXPECT(result.error() == TOO_BIG);
|
|
}
|
|
|
|
{
|
|
// SUCCESS
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.etxn_reserve(3);
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(hookCtx.expected_etxn_count == 3);
|
|
}
|
|
}
|
|
|
|
void
|
|
test_fee_base(FeatureBitset features)
|
|
{
|
|
testcase("Test fee_base");
|
|
|
|
using namespace jtx;
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
auto const expected = env.closed()->fees().base.drops();
|
|
BEAST_EXPECT(api.fee_base() == expected);
|
|
}
|
|
|
|
void
|
|
ASSERT_FLOAT_EQUAL(hook::HookAPI& api, uint64_t x, uint64_t y)
|
|
{
|
|
auto float_exponent = [](uint64_t f) -> int32_t {
|
|
return ((int32_t)(((f) >> 54U) & 0xFFU)) - 97;
|
|
};
|
|
int64_t px = (x);
|
|
int64_t py = (y);
|
|
int64_t mx = api.float_mantissa(px).value();
|
|
int64_t my = api.float_mantissa(py).value();
|
|
int32_t diffexp = float_exponent(px) - float_exponent(py);
|
|
if (diffexp == 1)
|
|
mx *= 10LL;
|
|
if (diffexp == -1)
|
|
my *= 10LL;
|
|
int64_t diffman = mx - my;
|
|
if (diffman < 0)
|
|
diffman *= -1LL;
|
|
if (diffexp < 0)
|
|
diffexp *= -1;
|
|
if (diffexp > 1 || diffman > 5000000 || mx < 0 || my < 0)
|
|
BEAST_EXPECT(false);
|
|
else
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_float_compare(FeatureBitset features)
|
|
{
|
|
testcase("Test float_compare");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
using namespace compare_mode;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
auto const testSuccess =
|
|
[&](uint64_t left, uint64_t right, uint32_t mode) {
|
|
auto const result = api.float_compare(left, right, mode);
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() == 1);
|
|
};
|
|
|
|
auto const testError = [&](uint64_t left,
|
|
uint64_t right,
|
|
uint32_t mode,
|
|
hook::HookReturnCode error) {
|
|
auto const result = api.float_compare(left, right, mode);
|
|
BEAST_EXPECT(!result.has_value());
|
|
BEAST_EXPECT(result.error() == error);
|
|
};
|
|
|
|
auto const one = api.float_one();
|
|
auto const two = api.float_set(0, 2).value();
|
|
auto const three = 6091866696204910592ULL; // encoded 3.0 from SetHook
|
|
|
|
testSuccess(one, one, EQUAL);
|
|
testSuccess(one, two, LESS);
|
|
testSuccess(two, one, GREATER);
|
|
testError(one, two, 0, INVALID_ARGUMENT);
|
|
|
|
// Invalid flags
|
|
testError(one, two, 0b1000U, INVALID_ARGUMENT);
|
|
testError(one, two, ~0b111U, INVALID_ARGUMENT);
|
|
testError(one, two, 0b111U, INVALID_ARGUMENT);
|
|
|
|
// Relative ordering samples
|
|
uint64_t largeNegative = 1622844335003378560ULL; // -154846915
|
|
uint64_t smallNegative = 1352229899321148800ULL; // -1.15001111e-7
|
|
uint64_t smallPositive =
|
|
5713898440837102138ULL; // 3.33411333131321e-21
|
|
uint64_t largePositive = 7749425685711506120ULL; // 3.234326634253e+92
|
|
|
|
testSuccess(largeNegative, smallNegative, LESS);
|
|
testSuccess(largeNegative, largePositive, LESS);
|
|
testSuccess(smallNegative, smallPositive, LESS);
|
|
testSuccess(smallPositive, largePositive, LESS);
|
|
testSuccess(smallNegative, 0, LESS);
|
|
testSuccess(largeNegative, 0, LESS);
|
|
testSuccess(smallPositive, 0, GREATER);
|
|
testSuccess(largePositive, 0, GREATER);
|
|
|
|
// Not-equal flag check
|
|
testSuccess(two, three, GREATER | LESS);
|
|
}
|
|
|
|
void
|
|
test_float_divide(FeatureBitset features)
|
|
{
|
|
testcase("Test float_divide");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
using namespace compare_mode;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
auto const one = api.float_one();
|
|
|
|
// divide by 0
|
|
BEAST_EXPECT(api.float_divide(one, 0).error() == DIVISION_BY_ZERO);
|
|
BEAST_EXPECT(api.float_divide(0, one).value() == 0);
|
|
|
|
// check 1
|
|
BEAST_EXPECT(api.float_divide(one, one).value() == one);
|
|
BEAST_EXPECT(
|
|
api.float_divide(one, api.float_negate(one)).value() ==
|
|
api.float_negate(one));
|
|
BEAST_EXPECT(
|
|
api.float_divide(api.float_negate(one), one).value() ==
|
|
api.float_negate(one));
|
|
BEAST_EXPECT(
|
|
api.float_divide(api.float_negate(one), api.float_negate(one))
|
|
.value() == one);
|
|
|
|
// 1 / 10 = 0.1
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_divide(one, 6107881094714392576LL).value(),
|
|
6071852297695428608LL);
|
|
|
|
// 123456789 / 1623 = 76067.0295749
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_divide(6234216452170766464LL, 6144532891733356544LL)
|
|
.value(),
|
|
6168530993200328528LL);
|
|
|
|
// -1.245678451111 / 1.3546984132111e+42 = -9.195245517106014e-43
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_divide(1478426356228633688LL, 6846826132016365020LL)
|
|
.value(),
|
|
711756787386903390LL);
|
|
|
|
// 9.134546514878452e-81 / 1
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_divide(4638834963451748340LL, one).value(),
|
|
4638834963451748340LL);
|
|
|
|
// 9.134546514878452e-81 / 1.41649684651e+75 = (underflow 0)
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_divide(4638834963451748340LL, 7441363081262569392LL)
|
|
.value(),
|
|
0);
|
|
|
|
// 1.3546984132111e+42 / 9.134546514878452e-81 = XFL_OVERFLOW
|
|
BEAST_EXPECT(
|
|
api.float_divide(6846826132016365020LL, 4638834963451748340LL)
|
|
.error() == XFL_OVERFLOW);
|
|
|
|
// clang-format off
|
|
std::vector<std::tuple<uint64_t, uint64_t, uint64_t>> tests = {
|
|
{3121244226425810900LL /* -4.753284285427668e+91 */, 2135203055881892282LL /* -9.50403176301817e+36 */, 7066645550312560102LL /* 5.001334595622374e+54 */},
|
|
{2473507938381460320LL /* -5.535342582428512e+55 */, 6365869885731270068LL /* 6787211884129716 */ , 2187897766692155363LL /* -8.155547044835299e+39 */},
|
|
{1716271542690607496LL /* -49036842898190.16 */, 3137794549622534856LL /* -3.28920897266964e+92 */, 4667220053951274769LL /* 1.490839995440913e-79 */},
|
|
{1588045991926420391LL /* -2778923.092005799 */, 5933338827267685794LL /* 6.601717648113058e-9 */, 1733591650950017206LL /* -420939403974674.2 */},
|
|
{5880783758174228306LL /* 8.089844083101523e-12 */, 1396720886139976383LL /* -0.00009612200909863615 */, 1341481714205255877LL /* -8.416224503589061e-8 */},
|
|
{5567703563029955929LL /* 1.254423600022873e-29 */, 2184969513100691140LL /* -5.227293453371076e+39 */, 236586937995245543LL /* -2.399757371979751e-69 */},
|
|
{7333313065548121054LL /* 1.452872188953566e+69 */, 1755926008837497886LL /* -8529353417745438 */, 2433647177826281173LL /* -1.703379046213333e+53 */},
|
|
{1172441975040622050LL /* -1.50607192429309e-17 */, 6692015311011173216LL /* 8.673463993357152e+33 */, 560182767210134346LL /* -1.736413416192842e-51 */},
|
|
{577964843368607493LL /* -1.504091065184005e-50 */, 6422931182144699580LL /* 9805312769113276000 */, 235721135837751035LL /* -1.533955214485243e-69 */},
|
|
{6039815413139899240LL /* 0.0049919124634346 */, 2117655488444284242LL /* -9.970862834892113e+35 */, 779625635892827768LL /* -5.006499985102456e-39 */},
|
|
{1353563835098586141LL /* -2.483946887437341e-7 */, 6450909070545770298LL /* 175440415122002600000 */, 992207753070525611LL /* -1.415835049016491e-27 */},
|
|
{6382158843584616121LL /* 50617712279937850 */, 5373794957212741595LL /* 5.504201387110363e-40 */, 7088854809772330055LL /* 9.196195545910343e+55 */},
|
|
{2056891719200540975LL /* -3.250289119594799e+32 */, 1754532627802542730LL /* -7135972382790282 */, 6381651867337939070LL /* 45547949813167340 */},
|
|
{5730152450208688630LL /* 1.573724193417718e-20 */, 1663581695074866883LL /* -62570322025.24355 */, 921249452789827075LL /* -2.515128806245891e-31 */},
|
|
{6234301156018475310LL /* 131927173.7708846 */, 2868710604383082256LL /* -4.4212413754468e+77 */, 219156721749007916LL /* -2.983939635224108e-70 */},
|
|
{2691125731495874243LL /* -6.980353583058627e+67 */, 7394070851520237320LL /* 8.16746263262388e+72 */, 1377640825464715759LL /* -0.000008546538744084975 */},
|
|
{5141867696142208039LL /* 7.764120939842599e-53 */, 5369434678231981897LL /* 1.143922406350665e-40 */, 5861466794943198400LL /* 6.7872793615536e-13 */},
|
|
{638296190872832492LL /* -7.792243040963052e-47 */, 5161669734904371378LL /* 9.551761192523954e-52 */, 1557396184145861422LL /* -81579.12330410798 */},
|
|
{2000727145906286285LL /* -1.128911353786061e+29 */, 2096625200460673392LL /* -6.954973360763248e+34 */, 5982403476503576795LL /* 0.000001623171355558107 */},
|
|
{640472838055334326LL /* -9.968890223464885e-47 */, 5189754252349396763LL /* 1.607481618585371e-50 */, 1537425431139169736LL /* -6201.557833201096 */},
|
|
};
|
|
// clang-format on
|
|
|
|
for (auto const& test : tests)
|
|
{
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_divide(std::get<0>(test), std::get<1>(test)).value(),
|
|
std::get<2>(test));
|
|
}
|
|
}
|
|
|
|
void
|
|
test_float_int(FeatureBitset features)
|
|
{
|
|
testcase("Test float_int");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
auto const one = api.float_one();
|
|
|
|
// clang-format off
|
|
|
|
// check 1
|
|
BEAST_EXPECT(api.float_int(one, 0, 0).value() == 1LL);
|
|
|
|
// check 1.23e-20 always returns 0 (too small to display)
|
|
BEAST_EXPECT(api.float_int(5729808726015270912LL, 0, 0).value() == 0);
|
|
BEAST_EXPECT(api.float_int(5729808726015270912LL, 15, 0).value() == 0);
|
|
BEAST_EXPECT(
|
|
api.float_int(5729808726015270912LL, 16, 0).error() ==
|
|
INVALID_ARGUMENT);
|
|
|
|
BEAST_EXPECT(api.float_int(one, 15, 0).value() == 1000000000000000LL);
|
|
BEAST_EXPECT(api.float_int(one, 14, 0).value() == 100000000000000LL);
|
|
BEAST_EXPECT(api.float_int(one, 13, 0).value() == 10000000000000LL);
|
|
BEAST_EXPECT(api.float_int(one, 12, 0).value() == 1000000000000LL);
|
|
BEAST_EXPECT(api.float_int(one, 11, 0).value() == 100000000000LL);
|
|
BEAST_EXPECT(api.float_int(one, 10, 0).value() == 10000000000LL);
|
|
BEAST_EXPECT(api.float_int(one, 9, 0).value() == 1000000000LL);
|
|
BEAST_EXPECT(api.float_int(one, 8, 0).value() == 100000000LL);
|
|
BEAST_EXPECT(api.float_int(one, 7, 0).value() == 10000000LL);
|
|
BEAST_EXPECT(api.float_int(one, 6, 0).value() == 1000000LL);
|
|
BEAST_EXPECT(api.float_int(one, 5, 0).value() == 100000LL);
|
|
BEAST_EXPECT(api.float_int(one, 4, 0).value() == 10000LL);
|
|
BEAST_EXPECT(api.float_int(one, 3, 0).value() == 1000LL);
|
|
BEAST_EXPECT(api.float_int(one, 2, 0).value() == 100LL);
|
|
BEAST_EXPECT(api.float_int(one, 1, 0).value() == 10LL);
|
|
BEAST_EXPECT(api.float_int(one, 0, 0).value() == 1LL);
|
|
|
|
// normal upper limit on exponent
|
|
BEAST_EXPECT(api.float_int(6360317241828374919LL, 0, 0).value() == 1234567981234567LL);
|
|
|
|
// ask for one decimal above limit
|
|
BEAST_EXPECT(api.float_int(6360317241828374919LL, 1, 0).error() == TOO_BIG);
|
|
|
|
// ask for 15 decimals above limit
|
|
BEAST_EXPECT(api.float_int(6360317241828374919LL, 15, 0).error() == TOO_BIG);
|
|
|
|
// every combination for 1.234567981234567
|
|
BEAST_EXPECT(api.float_int(6090101264186145159LL, 0, 0).value() == 1LL);
|
|
BEAST_EXPECT(api.float_int(6090101264186145159LL, 1, 0).value() == 12LL);
|
|
BEAST_EXPECT(api.float_int(6090101264186145159LL, 2, 0).value() == 123LL);
|
|
BEAST_EXPECT(api.float_int(6090101264186145159LL, 3, 0).value() == 1234LL);
|
|
BEAST_EXPECT(api.float_int(6090101264186145159LL, 4, 0).value() == 12345LL);
|
|
BEAST_EXPECT(api.float_int(6090101264186145159LL, 5, 0).value() == 123456LL);
|
|
BEAST_EXPECT(api.float_int(6090101264186145159LL, 6, 0).value() == 1234567LL);
|
|
BEAST_EXPECT(api.float_int(6090101264186145159LL, 7, 0).value() == 12345679LL);
|
|
BEAST_EXPECT(api.float_int(6090101264186145159LL, 8, 0).value() == 123456798LL);
|
|
BEAST_EXPECT(api.float_int(6090101264186145159LL, 9, 0).value() == 1234567981LL);
|
|
BEAST_EXPECT(api.float_int(6090101264186145159LL, 10, 0).value() == 12345679812LL);
|
|
BEAST_EXPECT(api.float_int(6090101264186145159LL, 11, 0).value() == 123456798123LL);
|
|
BEAST_EXPECT(api.float_int(6090101264186145159LL, 12, 0).value() == 1234567981234LL);
|
|
BEAST_EXPECT(api.float_int(6090101264186145159LL, 13, 0).value() == 12345679812345LL);
|
|
BEAST_EXPECT(api.float_int(6090101264186145159LL, 14, 0).value() == 123456798123456LL);
|
|
BEAST_EXPECT(api.float_int(6090101264186145159LL, 15, 0).value() == 1234567981234567LL);
|
|
|
|
// same with absolute parameter
|
|
BEAST_EXPECT(api.float_int(1478415245758757255LL, 0, 1) .value() ==1LL);
|
|
BEAST_EXPECT(api.float_int(1478415245758757255LL, 1, 1) .value() ==12LL);
|
|
BEAST_EXPECT(api.float_int(1478415245758757255LL, 2, 1) .value() ==123LL);
|
|
BEAST_EXPECT(api.float_int(1478415245758757255LL, 3, 1) .value() ==1234LL);
|
|
BEAST_EXPECT(api.float_int(1478415245758757255LL, 4, 1) .value() ==12345LL);
|
|
BEAST_EXPECT(api.float_int(1478415245758757255LL, 5, 1) .value() ==123456LL);
|
|
BEAST_EXPECT(api.float_int(1478415245758757255LL, 6, 1) .value() ==1234567LL);
|
|
BEAST_EXPECT(api.float_int(1478415245758757255LL, 7, 1) .value() ==12345679LL);
|
|
BEAST_EXPECT(api.float_int(1478415245758757255LL, 8, 1) .value() ==123456798LL);
|
|
BEAST_EXPECT(api.float_int(1478415245758757255LL, 9, 1) .value() ==1234567981LL);
|
|
BEAST_EXPECT(api.float_int(1478415245758757255LL, 10, 1).value() == 12345679812LL);
|
|
BEAST_EXPECT(api.float_int(1478415245758757255LL, 11, 1).value() == 123456798123LL);
|
|
BEAST_EXPECT(api.float_int(1478415245758757255LL, 12, 1).value() == 1234567981234LL);
|
|
BEAST_EXPECT(api.float_int(1478415245758757255LL, 13, 1).value() == 12345679812345LL);
|
|
BEAST_EXPECT(api.float_int(1478415245758757255LL, 14, 1).value() == 123456798123456LL);
|
|
BEAST_EXPECT(api.float_int(1478415245758757255LL, 15, 1).value() == 1234567981234567LL);
|
|
|
|
// neg xfl sans absolute parameter
|
|
BEAST_EXPECT(api.float_int(1478415245758757255LL, 15, 0).error() == CANT_RETURN_NEGATIVE);
|
|
|
|
// 1.234567981234567e-16
|
|
BEAST_EXPECT(api.float_int(5819885286543915399LL, 15, 0).value() == 1LL);
|
|
for (uint32_t i = 1; i < 15; ++i)
|
|
BEAST_EXPECT(api.float_int(5819885286543915399LL, i, 0).value() == 0);
|
|
|
|
// clang-format on
|
|
}
|
|
|
|
void
|
|
test_float_invert(FeatureBitset features)
|
|
{
|
|
testcase("Test float_invert");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
// divide by 0
|
|
BEAST_EXPECT(api.float_invert(0).error() == DIVISION_BY_ZERO);
|
|
|
|
// check 1
|
|
BEAST_EXPECT(
|
|
api.float_invert(api.float_one()).value() == api.float_one());
|
|
|
|
// 1 / 10 = 0.1
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_invert(6107881094714392576LL).value(),
|
|
6071852297695428608LL);
|
|
|
|
// 1 / 123 = 0.008130081300813009
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_invert(6126125493223874560LL).value(),
|
|
6042953581977277649LL);
|
|
|
|
// 1 / 1234567899999999 = 8.100000008100007e-16
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_invert(6360317241747140351LL).value(),
|
|
5808736320061298855LL);
|
|
|
|
// 1/ 1*10^-81 = 10**81
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_invert(4630700416936869888LL).value(),
|
|
7540018576963469311LL);
|
|
}
|
|
|
|
void
|
|
test_float_log(FeatureBitset features)
|
|
{
|
|
testcase("Test float_log");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
// check 0 is not allowed
|
|
BEAST_EXPECT(api.float_log(0).error() == INVALID_ARGUMENT);
|
|
|
|
// log10( 846513684968451 ) = 14.92763398342338
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_log(6349533412187342878LL).value(),
|
|
6108373858112734914LL);
|
|
|
|
// log10 ( -1000 ) = invalid (complex not supported)
|
|
BEAST_EXPECT(
|
|
api.float_log(1532223873305968640LL).error() ==
|
|
COMPLEX_NOT_SUPPORTED);
|
|
|
|
// log10 (1000) == 3
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_log(6143909891733356544LL).value(),
|
|
6091866696204910592LL);
|
|
|
|
// log10 (0.112381) == -0.949307107740766
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_log(6071976107695428608LL).value(),
|
|
1468659350345448364LL);
|
|
|
|
// log10 (0.00000000000000001123) = -16.94962024373854221
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_log(5783744921543716864LL).value(),
|
|
1496890038311378526LL);
|
|
|
|
// log10(100000000000000000000000000000000000000000000000000000000000000)
|
|
// = 62
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_log(7206759403792793600LL).value(),
|
|
6113081094714392576LL);
|
|
}
|
|
|
|
void
|
|
test_float_mantissa(FeatureBitset features)
|
|
{
|
|
testcase("Test float_mantissa");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
// test canonical zero
|
|
BEAST_EXPECT(api.float_mantissa(0).value() == 0);
|
|
|
|
// test one, negative one
|
|
{
|
|
BEAST_EXPECT(
|
|
api.float_mantissa(api.float_one()).value() ==
|
|
1000000000000000LL);
|
|
BEAST_EXPECT(
|
|
api.float_mantissa(api.float_negate(api.float_one())).value() ==
|
|
1000000000000000LL);
|
|
}
|
|
|
|
// test random numbers
|
|
{
|
|
// clang-format off
|
|
std::vector<std::tuple<uint64_t, uint64_t>> tests = {
|
|
{4763370308433150973LL /* 7.569101929907197e-74 */, 7569101929907197LL},
|
|
{668909658849475214LL /* -2.376913998641806e-45 */, 2376913998641806LL},
|
|
{962271544155031248LL /* -7.508423152486096e-29 */, 7508423152486096LL},
|
|
{7335644976228470276LL /* 3.784782869302788e+69 */, 3784782869302788LL},
|
|
{2837780149340315954LL /* -9.519583351644467e+75 */, 9519583351644466LL},
|
|
{2614004940018599738LL /* -1.917156143712058e+63 */, 1917156143712058LL},
|
|
{4812250541755005603LL /* 2.406139723315875e-71 */, 2406139723315875LL},
|
|
{5140304866732560580LL /* 6.20129153019514e-53 */, 6201291530195140LL},
|
|
{1124677839589482624LL /* -7.785132001599617e-20 */, 7785132001599616LL},
|
|
{5269336076015865585LL /* 9.131711247126257e-46 */, 9131711247126257LL},
|
|
{2296179634826760368LL /* -8.3510241225484e+45 */, 8351024122548400LL},
|
|
{1104028240398536470LL /* -5.149931320135446e-21 */, 5149931320135446LL},
|
|
{2691222059222981864LL /* -7.076681310166248e+67 */, 7076681310166248LL},
|
|
{6113256168823855946LL /* 63.7507410946337 */, 6375074109463370LL},
|
|
{311682216630003626LL /* -5.437441968809898e-65 */, 5437441968809898LL},
|
|
{794955605753965262LL /* -2.322071336757966e-38 */, 2322071336757966LL},
|
|
{204540636400815950LL /* -6.382252796514126e-71 */, 6382252796514126LL},
|
|
{5497195278343034975LL /* 2.803732951029855e-33 */, 2803732951029855LL},
|
|
{1450265914369875626LL /* -0.09114033611316906 */, 9114033611316906LL},
|
|
{7481064015089962668LL /* 5.088633654939308e+77 */, 5088633654939308LL},
|
|
};
|
|
// clang-format on
|
|
|
|
for (auto const& test : tests)
|
|
{
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_mantissa(std::get<0>(test)).value(),
|
|
std::get<1>(test));
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
test_float_mulratio(FeatureBitset features)
|
|
{
|
|
testcase("Test float_mulratio");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
auto const one = api.float_one();
|
|
auto const neg_one = api.float_negate(one);
|
|
|
|
// multiply by 0
|
|
BEAST_EXPECT(api.float_mulratio(one, 0, 0, 1).value() == 0);
|
|
BEAST_EXPECT(api.float_mulratio(0, 0, 1, 1).value() == 0);
|
|
|
|
// check 1
|
|
BEAST_EXPECT(api.float_mulratio(one, 0, 1, 1).value() == one);
|
|
BEAST_EXPECT(api.float_mulratio(neg_one, 0, 1, 1).value() == neg_one);
|
|
|
|
// check overflow
|
|
// 1e+95 * 1e+95
|
|
BEAST_EXPECT(
|
|
api.float_mulratio(7801234554605699072LL, 0, 0xFFFFFFFFUL, 1)
|
|
.error() == XFL_OVERFLOW);
|
|
// 1e+95 * 10
|
|
BEAST_EXPECT(
|
|
api.float_mulratio(7801234554605699072LL, 0, 10, 1).error() ==
|
|
XFL_OVERFLOW);
|
|
// -1e+95 * 10
|
|
BEAST_EXPECT(
|
|
api.float_mulratio(3189548536178311168LL, 0, 10, 1).error() ==
|
|
XFL_OVERFLOW);
|
|
|
|
// identity
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_mulratio(3189548536178311168LL, 0, 1, 1).value(),
|
|
3189548536178311168LL);
|
|
|
|
// random mulratios
|
|
|
|
// clang-format off
|
|
std::vector<std::tuple<uint64_t, uint32_t, uint32_t, uint64_t>> tests = {
|
|
{2296131684119423544LL, 2210828011U, 2814367554U, 2294351094683836182LL},
|
|
{565488225163275031LL, 2373474507U, 4203973264U, 562422045628095449LL},
|
|
{2292703263479286183LL, 3170020147U, 773892643U, 2307839765178024100LL},
|
|
{758435948837102675LL, 3802740780U, 1954123588U, 760168290112163547LL},
|
|
{3063742137774439410LL, 2888815591U, 4122448592U, 3053503824756415637LL},
|
|
{974014561126802184LL, 689168634U, 3222648522U, 957408554638995792LL},
|
|
{2978333847445611553LL, 1718558513U, 2767410870U, 2976075722223325259LL},
|
|
{6577058837932757648LL, 1423256719U, 1338068927U, 6577173649752398013LL},
|
|
{2668681541248816636LL, 345215754U, 4259223936U, 2650183845127530219LL},
|
|
{651803640367065917LL, 327563234U, 1191613855U, 639534906402789368LL},
|
|
{3154958130393015979LL, 1304112625U, 3024066701U, 3153571282364880740LL},
|
|
{1713286099776800976LL, 1902151138U, 2927030061U, 1712614441093927706LL},
|
|
{2333142120591277120LL, 914099656U, 108514965U, 2349692988167140475LL},
|
|
{995968561418010814LL, 1334462574U, 846156977U, 998955931389416094LL},
|
|
{6276035843030312442LL, 2660687613U, 236740983U, 6294920527635363073LL},
|
|
{7333118474702086419LL, 46947714U, 2479204760U, 7298214153648998535LL},
|
|
{2873297486994296492LL, 880591893U, 436034100U, 2884122995598532757LL},
|
|
{1935815261812737573LL, 3123665800U, 3786746543U, 1934366328810191207LL},
|
|
{7249556282125616118LL, 2378803159U, 2248850590U, 7250005170160875417LL},
|
|
{311005347529659996LL, 992915590U, 2433548552U, 308187142737041830LL},
|
|
};
|
|
// clang-format on
|
|
|
|
for (auto const& test : tests)
|
|
{
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_mulratio(
|
|
std::get<0>(test),
|
|
0U,
|
|
std::get<1>(test),
|
|
std::get<2>(test))
|
|
.value(),
|
|
std::get<3>(test));
|
|
}
|
|
}
|
|
|
|
void
|
|
test_float_multiply(FeatureBitset features)
|
|
{
|
|
testcase("Test float_multiply");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
auto const one = api.float_one();
|
|
auto const neg_one = api.float_negate(one);
|
|
|
|
// multiply by 0
|
|
BEAST_EXPECT(api.float_multiply(one, 0).value() == 0);
|
|
BEAST_EXPECT(api.float_multiply(0, one).value() == 0);
|
|
|
|
// check 1
|
|
BEAST_EXPECT(api.float_multiply(one, one).value() == one);
|
|
BEAST_EXPECT(api.float_multiply(one, neg_one).value() == neg_one);
|
|
BEAST_EXPECT(api.float_multiply(neg_one, one).value() == neg_one);
|
|
BEAST_EXPECT(api.float_multiply(neg_one, neg_one).value() == one);
|
|
|
|
// check overflow
|
|
// 1e+95 * 1e+95
|
|
BEAST_EXPECT(
|
|
api.float_multiply(7801234554605699072LL, 7801234554605699072LL)
|
|
.error() == XFL_OVERFLOW);
|
|
// 1e+95 * 10
|
|
BEAST_EXPECT(
|
|
api.float_multiply(7801234554605699072LL, 6107881094714392576LL)
|
|
.error() == XFL_OVERFLOW);
|
|
BEAST_EXPECT(
|
|
api.float_multiply(6107881094714392576LL, 7801234554605699072LL)
|
|
.error() == XFL_OVERFLOW);
|
|
// -1e+95 * 10
|
|
BEAST_EXPECT(
|
|
api.float_multiply(3189548536178311168LL, 6107881094714392576LL)
|
|
.error() == XFL_OVERFLOW);
|
|
|
|
// identity
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_multiply(3189548536178311168LL, one).value(),
|
|
3189548536178311168LL);
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_multiply(one, 3189548536178311168LL).value(),
|
|
3189548536178311168LL);
|
|
|
|
// random multiplications
|
|
|
|
// clang-format off
|
|
std::vector<std::tuple<uint64_t, uint64_t, uint64_t>> tests = {
|
|
{7791757438262485039LL /* 9.537282166267951e+94 */, 4759088999670263908LL /* 3.287793167020132e-74 */, 6470304726017852129LL /* 3.135661113819873e+21 */},
|
|
{7534790022873909775LL /* 4.771445910440463e+80 */, 1017891960669847079LL /* -9.085644138855975e-26 */, 2472307761756037979LL /* -4.335165957006171e+55 */},
|
|
{2813999069907898454LL /* -3.75290242870895e+74 */, 4962524721184225460LL /* 8.56513107667986e-63 */, 1696567870013294731LL /* -3214410121988.235 */},
|
|
{2151742066453140308LL /* -8.028643824784212e+37 */, 437647738130579252LL /* -5.302173903011636e-58 */, 5732835652591705549LL /* 4.256926576434637e-20 */},
|
|
{5445302332922546340LL /* 4.953983058987172e-36 */, 7770966530708354172LL /* 6.760773121619068e+93 */, 7137051085305881332LL /* 3.349275551015668e+58 */},
|
|
{2542989542826132533LL /* -2.959352989172789e+59 */, 6308418769944702613LL /* 3379291626008.213 */, 2775217422137696934LL /* -1.000051677471398e+72 */},
|
|
{5017652318929433511LL /* 9.649533293441959e-60 */, 6601401767766764916LL /* 8.131913296358772e+28 */, 5538267259220228820LL /* 7.846916809259732e-31 */},
|
|
{892430323307269235LL /* -9.724796342652019e-33 */, 1444078017997143500LL /* -0.0292613723858478 */, 5479222755754111850LL /* 2.845608871588714e-34 */},
|
|
{7030632722283214253LL /* 5.017303585240493e+52 */, 297400838197636668LL /* -9.170462045924924e-66 */, 1247594596364389994LL /* -4.601099210133098e-13 */},
|
|
{1321751204165279730LL /* -6.700112973094898e-9 */, 2451801790748530375LL /* -1.843593458980551e+54 */, 6918764256086244704LL /* 1.235228445162848e+46 */},
|
|
{2055496484261758590LL /* -1.855054180812414e+32 */, 2079877890137711361LL /* -8.222061547283201e+33 */, 7279342234795540005LL /* 1.525236964818469e+66 */},
|
|
{2439875962311968674LL /* -7.932163531900834e+53 */, 4707485682591872793LL /* 5.727671617074969e-77 */, 1067392794851803610LL /* -4.543282792366554e-23 */},
|
|
{6348574818322812800LL /* 750654298515443.2 */, 6474046245013515838LL /* 6.877180109483582e+21 */, 6742547427357110773LL /* 5.162384810848757e+36 */},
|
|
{1156137305783593424LL /* -3.215801176746448e-18 */, 351790564990861307LL /* -9.516993310703611e-63 */, 4650775291275116747LL /* 3.060475828764875e-80 */},
|
|
{5786888485280994123LL /* 4.266563737277259e-17 */, 6252137323085080394LL /* 1141040294.831946 */, 5949619829273756852LL /* 4.868321144702132e-8 */},
|
|
{2078182880999439640LL /* -6.52705240901148e+33 */, 1662438186251269392LL /* -51135233789.26864 */, 6884837854131013998LL /* 3.33762350889611e+44 */},
|
|
{1823781083140711248LL /* -43268336830308640000 */, 1120252241608199010LL /* -3.359534020316002e-20 */, 6090320310700749729LL /* 1.453614495839137 */},
|
|
{6617782604883935174LL /* 6.498351904047046e+29 */, 6185835042802056262LL /* 689635.404973575 */, 6723852137583788319LL /* 4.481493547008287e+35 */},
|
|
{333952667495151166LL /* -9.693494324475454e-64 */, 1556040883317758614LL /* -68026.1150230799 */, 5032611291744396930LL /* 6.594107598923394e-59 */},
|
|
{2326968399632616779LL /* -3.110991909440843e+47 */, 707513695207834635LL /* -4.952153338037259e-43 */, 6180479299649214949LL /* 154061.0896894437 */},
|
|
{1271003508324696477LL /* -9.995612660957597e-12 */, 5321949753651889765LL /* 7.702193354704484e-43 */, 512101972406838314LL /* -7.698814141342762e-54 */},
|
|
{1928646740923345323LL /* -1.106100408773035e+25 */, 4639329980209973352LL /* 9.629563273103463e-81 */, 487453886143282122LL /* -1.065126387268554e-55 */},
|
|
{6023906813956669432LL /* 0.0007097711789686777 */, 944348444470060009LL /* -7.599721976996842e-30 */, 888099590592064434LL /* -5.394063627447218e-33 */},
|
|
{6580290597764062787LL /* 5.035141803138627e+27 */, 6164319297265300034LL /* 33950.07022461506 */, 6667036882686408593LL /* 1.709434178074513e+32 */},
|
|
{2523439530503240484LL /* -1.423739175762724e+58 */, 5864448766677980801LL /* 9.769251096336e-13 */, 2307233895764065602LL /* -1.39088655037165e+46 */},
|
|
{6760707453987140465LL /* 5.308012931396465e+37 */, 5951641080643457645LL /* 6.889572514402925e-8 */, 6632955645489194550LL /* 3.656993999824438e+30 */},
|
|
{6494270716308443375LL /* 9.087252894929135e+22 */, 564752637895553836LL /* -6.306284101612332e-51 */, 978508199357889360LL /* -5.730679845862224e-28 */},
|
|
{6759145618427534062LL /* 3.746177371790062e+37 */, 4721897842483633304LL /* 2.125432999353496e-76 */, 5394267403342547165LL /* 7.962249007433949e-39 */},
|
|
{1232673571201806425LL /* -7.694472557031513e-14 */, 6884256144221925318LL /* 2.75591359980743e+44 */, 2037747561727791012LL /* -2.12053015632682e+31 */},
|
|
{1427694775835421031LL /* -0.004557293586344295 */, 4883952867277976402LL /* 2.050871208358738e-67 */, 225519204318055258LL /* -9.34642220427145e-70 */},
|
|
{5843509949864662087LL /* 6.84483279249927e-14 */, 5264483986612843822LL /* 4.279621844104494e-46 */, 5028946513739275800LL /* 2.929329593802264e-59 */},
|
|
{6038444022009738988LL /* 0.003620521333274348 */, 7447499078040748850LL /* 7.552493624689458e+75 */, 7406652183825856093LL /* 2.734396428760669e+73 */},
|
|
{939565473697468970LL /* -2.816751204405802e-30 */, 1100284903077087966LL /* -1.406593998686942e-21 */, 5174094397561240825LL /* 3.962025339911417e-51 */},
|
|
{5694071830210473617LL /* 1.521901214166673e-22 */, 5536709154363579683LL /* 6.288811952610595e-31 */, 5143674525748709391LL /* 9.570950546343951e-53 */},
|
|
{600729862341871819LL /* -6.254711528966347e-49 */, 6330630279715378440LL /* 75764028872020.56 */, 851415551394320910LL /* -4.738821448667662e-35 */},
|
|
{1876763139233864902LL /* -3.265694247738566e+22 */, 4849561230315278754LL /* 3.688031264625058e-69 */, 649722744589988028LL /* -1.204398248636604e-46 */},
|
|
{3011947542126279863LL /* -3.542991042788535e+85 */, 1557732559110376235LL /* -84942.87294925611 */,7713172080438368541LL /* 3.009518380079389e+90 */},
|
|
{5391579936313268788LL /* 5.274781978155572e-39 */, 1018647290024655822LL /* -9.840973493664718e-26 */, 329450072133864644LL /* -5.190898963188932e-64 */},
|
|
{2815029221608845312LL /* -4.783054129655808e+74 */, 4943518985822088837LL /* 7.57379422402522e-64 */,1678961648155863225LL /* -362258677403.8713 */ },
|
|
{1377509900308195934LL /* -0.00000841561358756515 */, 7702104197062186199LL /* 9.95603351337903e+89 */, 2998768765665354000LL /* -8.378613091344656e+84 */},
|
|
};
|
|
// clang-format on
|
|
|
|
for (auto const& test : tests)
|
|
{
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_multiply(std::get<0>(test), std::get<1>(test))
|
|
.value(),
|
|
std::get<2>(test));
|
|
}
|
|
}
|
|
|
|
void
|
|
test_float_negate(FeatureBitset features)
|
|
{
|
|
testcase("Test float_negate");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
auto const one = api.float_one();
|
|
|
|
// test canonical zero
|
|
BEAST_EXPECT(api.float_negate(0) == 0);
|
|
|
|
// test double negation
|
|
{
|
|
BEAST_EXPECT(api.float_negate(one) != one);
|
|
BEAST_EXPECT(api.float_negate(api.float_negate(one)) == one);
|
|
}
|
|
|
|
// test random numbers
|
|
{
|
|
// +/- 3.463476342523e+22
|
|
BEAST_EXPECT(
|
|
api.float_negate(6488646939756037240LL) ==
|
|
1876960921328649336LL);
|
|
|
|
BEAST_EXPECT(api.float_negate(one) == 1478180677777522688LL);
|
|
|
|
BEAST_EXPECT(
|
|
api.float_negate(1838620299498162368LL) ==
|
|
6450306317925550272LL);
|
|
}
|
|
}
|
|
|
|
void
|
|
test_float_one(FeatureBitset features)
|
|
{
|
|
testcase("Test float_one");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
auto const one = api.float_one();
|
|
BEAST_EXPECT(one == 6089866696204910592ULL);
|
|
}
|
|
|
|
void
|
|
test_float_root(FeatureBitset features)
|
|
{
|
|
testcase("Test float_root");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
using namespace compare_mode;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
auto const one = api.float_one();
|
|
|
|
// sqrt 1 is 1
|
|
ASSERT_FLOAT_EQUAL(api, api.float_root(one, 2).value(), one);
|
|
|
|
// sqrt 9 is 3
|
|
auto const nine = 6097866696204910592LL;
|
|
auto const three = 6091866696204910592LL;
|
|
ASSERT_FLOAT_EQUAL(api, api.float_root(nine, 2).value(), three);
|
|
|
|
// cube root of 1000 is 10
|
|
auto const thousand = 6143909891733356544LL;
|
|
auto const ten = 6107881094714392576LL;
|
|
ASSERT_FLOAT_EQUAL(api, api.float_root(thousand, 3).value(), ten);
|
|
|
|
// sqrt of negative is "complex not supported error"
|
|
auto const negative_one = 1478180677777522688LL;
|
|
BEAST_EXPECT(
|
|
api.float_root(negative_one, 2).error() == COMPLEX_NOT_SUPPORTED);
|
|
|
|
// tenth root of 0 is 0
|
|
ASSERT_FLOAT_EQUAL(api, api.float_root(0, 10).value(), 0);
|
|
}
|
|
|
|
void
|
|
test_float_set(FeatureBitset features)
|
|
{
|
|
testcase("Test float_set");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
// zero mantissa should return canonical zero
|
|
BEAST_EXPECT(api.float_set(-5, 0).value() == 0);
|
|
BEAST_EXPECT(api.float_set(50, 0).value() == 0);
|
|
BEAST_EXPECT(api.float_set(-50, 0).value() == 0);
|
|
BEAST_EXPECT(api.float_set(0, 0).value() == 0);
|
|
|
|
// an exponent lower than -96 should produce an invalid float error
|
|
BEAST_EXPECT(api.float_set(-97, 1).error() == INVALID_FLOAT);
|
|
|
|
// an exponent larger than +96 should produce an invalid float error
|
|
BEAST_EXPECT(api.float_set(+97, 1).error() == INVALID_FLOAT);
|
|
|
|
// clang-format off
|
|
std::vector<std::tuple<int32_t, int64_t, uint64_t>> tests = {
|
|
{-5, 6541432897943971LL, 6275552114197674403LL},
|
|
{-83, 7906202688397446LL, 4871793800248533126LL},
|
|
{76, 4760131426754533LL, 7732937091994525669LL},
|
|
{37, -8019384286534438LL, 2421948784557120294LL},
|
|
{50, 5145342538007840LL, 7264947941859247392LL},
|
|
{-70, 4387341302202416LL, 5102462119485603888LL},
|
|
{-26, -1754544005819476LL, 1280776838179040340LL},
|
|
{36, 8261761545780560LL, 7015862781734272336LL},
|
|
{35, 7975622850695472LL, 6997562244529705264LL},
|
|
{17, -4478222822793996LL, 2058119652903740172LL},
|
|
{-53, 5506604247857835LL, 5409826157092453035LL},
|
|
{-60, 5120164869507050LL, 5283338928147728362LL},
|
|
{41, 5176113875683063LL, 7102849126611584759LL},
|
|
{-54, -3477931844992923LL, 778097067752718235LL},
|
|
{21, 6345031894305479LL, 6743730074440567495LL},
|
|
{-23, 5091583691147091LL, 5949843091820201811LL},
|
|
{-33, 7509684078851678LL, 5772117207113086558LL},
|
|
{-72, -1847771838890268LL, 452207734575939868LL},
|
|
{71, -9138413713437220LL, 3035557363306410532LL},
|
|
{28, 4933894067102586LL, 6868419726179738490LL},
|
|
};
|
|
// clang-format on
|
|
|
|
for (auto const& test : tests)
|
|
{
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_set(std::get<0>(test), std::get<1>(test)).value(),
|
|
std::get<2>(test));
|
|
}
|
|
}
|
|
|
|
void
|
|
test_float_sign(FeatureBitset features)
|
|
{
|
|
testcase("Test float_sign");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
// test canonical zero
|
|
BEAST_EXPECT(api.float_sign(0) == 0);
|
|
|
|
// test one
|
|
auto const one = api.float_one();
|
|
BEAST_EXPECT(api.float_sign(one) == 0);
|
|
BEAST_EXPECT(api.float_sign(api.float_negate(one)) == 1);
|
|
|
|
// test random numbers
|
|
|
|
// clang-format off
|
|
std::vector<std::tuple<int64_t, int64_t>> tests = {
|
|
{7248434512952957686LL /* 6.646312141200119e+64 */, 0LL},
|
|
{889927818394811978LL /* -7.222291430194763e-33 */, 1LL},
|
|
{5945816149233111421LL /* 1.064641104056701e-8 */, 0LL},
|
|
{6239200145838704863LL /* 621826155.7938399 */, 0LL},
|
|
{6992780785042190360LL /* 3.194163363180568e+50 */, 0LL},
|
|
{6883099933108789087LL /* 1.599702486671199e+44 */, 0LL},
|
|
{890203738162163464LL /* -7.498211197546248e-33 */, 1LL},
|
|
{4884803073052080964LL /* 2.9010769824633e-67 */, 0LL},
|
|
{2688292350356944394LL /* -4.146972444128778e+67 */, 1LL},
|
|
{4830109852288093280LL /* 2.251051746921568e-70 */, 0LL},
|
|
{294175951907940320LL /* -5.945575756228576e-66 */, 1LL},
|
|
{7612037404955382316LL /* 9.961233953985069e+84 */, 0LL},
|
|
{7520840929603658997LL /* 8.83675114967167e+79 */, 0LL},
|
|
{4798982086157926282LL /* 7.152082635718538e-72 */, 0LL},
|
|
{689790136568817905LL /* -5.242993208502513e-44 */, 1LL},
|
|
{5521738045011558042LL /* 9.332101110070938e-32 */, 0LL},
|
|
{728760820583452906LL /* -8.184880204173546e-42 */, 1LL},
|
|
{2272937984362856794LL /* -3.12377216812681e+44 */, 1LL},
|
|
{1445723661896317830LL /* -0.0457178113775911 */, 1LL},
|
|
{5035721527359772724LL /* 9.704343214299189e-59 */, 0LL},
|
|
};
|
|
// clang-format on
|
|
|
|
for (auto const& test : tests)
|
|
{
|
|
BEAST_EXPECT(
|
|
api.float_sign(std::get<0>(test)) == std::get<1>(test));
|
|
}
|
|
}
|
|
|
|
void
|
|
test_float_sto(FeatureBitset features)
|
|
{
|
|
testcase("Test float_sto");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
uint64_t const xfl = 6198187654261802496ULL; // 1234567.0 as in SetHook
|
|
// XRP serialization
|
|
auto const ser = api.float_sto(std::nullopt, std::nullopt, xfl, 0, 8);
|
|
BEAST_EXPECT(ser.has_value());
|
|
auto const parsed = api.float_sto_set(ser.value());
|
|
BEAST_EXPECT(parsed.has_value());
|
|
BEAST_EXPECT(
|
|
api.float_compare(parsed.value(), xfl, compare_mode::EQUAL)
|
|
.value() == 1);
|
|
}
|
|
|
|
void
|
|
test_float_sto_set(FeatureBitset features)
|
|
{
|
|
testcase("Test float_sto_set");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
using namespace hook;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
// Not enough bytes
|
|
BEAST_EXPECT(
|
|
api.float_sto_set(Bytes{1, 2, 3}).error() == NOT_AN_OBJECT);
|
|
|
|
uint64_t const xfl = 6198187654261802496ULL; // 1234567.0 as in SetHook
|
|
auto const ser = api.float_sto(std::nullopt, std::nullopt, xfl, 0, 8);
|
|
BEAST_EXPECT(ser.has_value());
|
|
auto const parsed = api.float_sto_set(ser.value());
|
|
BEAST_EXPECT(parsed.has_value());
|
|
BEAST_EXPECT(
|
|
api.float_compare(parsed.value(), xfl, compare_mode::EQUAL)
|
|
.value() == 1);
|
|
}
|
|
|
|
void
|
|
test_float_sum(FeatureBitset features)
|
|
{
|
|
testcase("Test float_sum");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
auto const one = api.float_one();
|
|
auto const neg_one = api.float_negate(api.float_one());
|
|
|
|
// 1 + 1 = 2
|
|
ASSERT_FLOAT_EQUAL(
|
|
api, 6090866696204910592LL, api.float_sum(one, one).value());
|
|
|
|
// 1 - 1 = 0
|
|
ASSERT_FLOAT_EQUAL(api, 0, api.float_sum(one, neg_one).value());
|
|
// 45678 + 0.345678 = 45678.345678
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
6165492124810638528LL,
|
|
api.float_sum(6165492090242838528LL, 6074309077695428608LL)
|
|
.value());
|
|
// -151864512641 + 100000000000000000 = 99999848135487359
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
6387097057170171072LL,
|
|
api.float_sum(1676857706508234512LL, 6396111470866104320LL)
|
|
.value());
|
|
|
|
// auto generated random sums
|
|
// clang-format off
|
|
std::vector<std::tuple<uint64_t, uint64_t, uint64_t>> tests = {
|
|
{7607324992379065667 /* 5.248821377668419e+84 */, 95785354843184473 /* -5.713362295774553e-77 */, 7607324992379065667 /* 5.248821377668419e+84 */},
|
|
{1011203427860697296 /* -2.397111329706192e-26 */, 7715811566197737722 /* 5.64900413944857e+90 */, 7715811566197737722 /* 5.64900413944857e+90 */},
|
|
{6507979072644559603 /* 4.781210721563379e+23 */, 422214339164556094 /* -7.883173446470462e-59 */, 6507979072644559603 /* 4.781210721563379e+23 */},
|
|
{129493221419941559 /* -3.392431853567671e-75 */, 6742079437952459317 /* 4.694395406197301e+36 */, 6742079437952459317 /* 4.694395406197301e+36 */},
|
|
{5172806703808250354 /* 2.674331586920946e-51 */, 3070396690523275533 /* -7.948943911338253e+88 */, 3070396690523275533 /* -7.948943911338253e+88 */},
|
|
{2440992231195047997 /* -9.048432414980156e+53 */, 4937813945440933271 /* 1.868753842869655e-64 */, 2440992231195047996 /* -9.048432414980156e+53 */},
|
|
{7351918685453062372 /* 2.0440935844129e+70 */, 6489541496844182832 /* 4.358033430668592e+22 */, 7351918685453062372 /* 2.0440935844129e+70 */},
|
|
{4960621423606196948 /* 6.661833498651348e-63 */, 6036716382996689576 /* 0.001892882320224936 */, 6036716382996689576 /* 0.001892882320224936 */},
|
|
{1342689232407435206 /* -9.62374270576839e-8 */, 5629833007898276923 /* 9.340672939897915e-26 */, 1342689232407435206 /* -9.62374270576839e-8 */},
|
|
{7557687707019793516 /* 9.65473154684222e+81 */, 528084028396448719 /* -5.666471621471183e-53 */, 7557687707019793516 /* 9.65473154684222e+81 */},
|
|
{130151633377050812 /* -4.050843810676924e-75 */, 2525286695563827336 /* -3.270904236349576e+58 */, 2525286695563827336 /* -3.270904236349576e+58 */},
|
|
{5051914485221832639 /* 7.88290256687712e-58 */, 7518727241611221951 /* 6.723063157234623e+79 */, 7518727241611221951 /* 6.723063157234623e+79 */},
|
|
{3014788764095798870 /* -6.384213012307542e+85 */, 7425019819707800346 /* 3.087633801222938e+74 */, 3014788764095767995 /* -6.384213012276667e+85 */},
|
|
{4918950856932792129 /* 1.020063844210497e-65 */, 7173510242188034581 /* 3.779635414204949e+60 */, 7173510242188034581 /* 3.779635414204949e+60 */},
|
|
{20028000442705357 /* -2.013601933223373e-81 */, 95248745393457140 /* -5.17675284604722e-77 */, 95248946753650462 /* -5.176954206240542e-77 */},
|
|
{5516870225060928024 /* 4.46428115944092e-32 */, 7357202055584617194 /* 7.327463715967722e+70 */, 7357202055584617194 /* 7.327463715967722e+70 */},
|
|
{2326103538819088036 /* -2.2461310959121e+47 */, 1749360946246242122 /* -1964290826489674 */, 2326103538819088036 /* -2.2461310959121e+47 */},
|
|
{1738010758208819410 /* -862850129854894.6 */, 2224610859005732191 /* -8.83984233944816e+41 */, 2224610859005732192 /* -8.83984233944816e+41 */},
|
|
{4869534730307487904 /* 5.647132747352224e-68 */, 2166841923565712115 /* -5.114102427874035e+38 */, 2166841923565712115 /* -5.114102427874035e+38 */},
|
|
{1054339559322014937 /* -9.504445772059864e-24 */, 1389511416678371338 /* -0.0000240273144825857 */, 1389511416678371338 /* -0.0000240273144825857 */},
|
|
};
|
|
// clang-format on
|
|
|
|
for (auto const& test : tests)
|
|
{
|
|
ASSERT_FLOAT_EQUAL(
|
|
api,
|
|
api.float_sum(std::get<0>(test), std::get<1>(test)).value(),
|
|
std::get<2>(test));
|
|
}
|
|
}
|
|
|
|
void
|
|
test_hook_account(FeatureBitset features)
|
|
{
|
|
testcase("Test hook_account");
|
|
|
|
using namespace jtx;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
StubHookContext stubCtx{};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
BEAST_EXPECT(api.hook_account() == alice.id());
|
|
}
|
|
|
|
void
|
|
test_hook_again(FeatureBitset features)
|
|
{
|
|
testcase("Test hook_again");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
|
|
{
|
|
// Already requested
|
|
StubHookContext stubCtx{};
|
|
stubCtx.result.executeAgainAsWeak = true;
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
BEAST_EXPECT(hookCtx.result.executeAgainAsWeak);
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.hook_again().error() == ALREADY_SET);
|
|
}
|
|
{
|
|
// Strong hook requests weak re-exec
|
|
StubHookContext stubCtx{};
|
|
stubCtx.result.isStrong = true;
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(!hookCtx.result.executeAgainAsWeak);
|
|
auto const result = api.hook_again();
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() == 1);
|
|
BEAST_EXPECT(hookCtx.result.executeAgainAsWeak);
|
|
}
|
|
|
|
{
|
|
// Not strong -> prerequisite not met
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
BEAST_EXPECT(!hookCtx.result.executeAgainAsWeak);
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.hook_again().error() == PREREQUISITE_NOT_MET);
|
|
}
|
|
}
|
|
|
|
void
|
|
test_hook_hash(FeatureBitset features)
|
|
{
|
|
testcase("Test hook_hash");
|
|
|
|
using namespace jtx;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
uint256 const expectedHash{2};
|
|
StubHookContext stubCtx{
|
|
.result = {.hookHash = expectedHash},
|
|
};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
// TODO
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_hook_param(FeatureBitset features)
|
|
{
|
|
testcase("Test hook_param");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
using namespace hook;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
|
|
{
|
|
// TOO_SMALL / TOO_BIG
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.hook_param({}).error() == TOO_SMALL);
|
|
BEAST_EXPECT(api.hook_param(Bytes(33, 1)).error() == TOO_BIG);
|
|
}
|
|
|
|
// {
|
|
// // Hook params map
|
|
// Bytes const name{'k'};
|
|
// Bytes const value{1, 2, 3};
|
|
// StubHookContext stubCtx{
|
|
// .result = {
|
|
// .hookParams = {{{name, value}}},
|
|
// }};
|
|
// auto hookCtx =
|
|
// makeStubHookContext(applyCtx, alice.id(), alice.id(),
|
|
// stubCtx);
|
|
// hook::HookAPI api(hookCtx);
|
|
// auto const result = api.hook_param(name);
|
|
// BEAST_EXPECT(result.has_value());
|
|
// BEAST_EXPECT(result.value() == value);
|
|
// }
|
|
|
|
{
|
|
// Override deletion wins
|
|
Bytes const name{'d'};
|
|
StubHookContext stubCtx{};
|
|
stubCtx.result.hookParamOverrides = {
|
|
{stubCtx.result.hookHash, {{name, Bytes{}}}}};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.hook_param(name).error() == DOESNT_EXIST);
|
|
}
|
|
|
|
{
|
|
// Override takes precedence
|
|
Bytes const name{'o'};
|
|
Bytes const overrideValue{9};
|
|
StubHookContext stubCtx{};
|
|
stubCtx.result.hookParams =
|
|
std::map<Bytes, Bytes>{{name, Bytes{1}}}; // base value
|
|
stubCtx.result.hookParamOverrides = {
|
|
{stubCtx.result.hookHash, {{name, overrideValue}}}};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.hook_param(name);
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() == overrideValue);
|
|
}
|
|
}
|
|
|
|
void
|
|
test_hook_param_set(FeatureBitset features)
|
|
{
|
|
testcase("Test hook_param_set");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
using namespace hook;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hash = uint256{7};
|
|
|
|
{
|
|
// TOO_SMALL
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(
|
|
api.hook_param_set(hash, {}, Bytes{1}).error() == TOO_SMALL);
|
|
}
|
|
|
|
{
|
|
// TOO_BIG key/value and TOO_MANY_PARAMS
|
|
StubHookContext stubCtx{};
|
|
stubCtx.result.overrideCount = hook_api::max_params;
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(
|
|
api.hook_param_set(
|
|
hash,
|
|
Bytes(hook::maxHookParameterKeySize() + 1, 1),
|
|
Bytes{})
|
|
.error() == TOO_BIG);
|
|
BEAST_EXPECT(
|
|
api.hook_param_set(
|
|
hash,
|
|
Bytes{1},
|
|
Bytes(hook::maxHookParameterValueSize() + 1, 1))
|
|
.error() == TOO_BIG);
|
|
BEAST_EXPECT(
|
|
api.hook_param_set(hash, Bytes{1}, Bytes{1}).error() ==
|
|
TOO_MANY_PARAMS);
|
|
}
|
|
|
|
{
|
|
// SUCCESS and override stored
|
|
Bytes const name{'x'};
|
|
Bytes const value{5, 6};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.hook_param_set(hash, name, value);
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() == value.size());
|
|
BEAST_EXPECT(hookCtx.result.overrideCount == 1);
|
|
BEAST_EXPECT(
|
|
hookCtx.result.hookParamOverrides[hash].at(name) == value);
|
|
}
|
|
}
|
|
|
|
void
|
|
test_hook_pos(FeatureBitset features)
|
|
{
|
|
testcase("Test hook_pos");
|
|
|
|
using namespace jtx;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
StubHookContext stubCtx{};
|
|
stubCtx.result.hookChainPosition = 3;
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
BEAST_EXPECT(api.hook_pos() == 3);
|
|
}
|
|
|
|
void
|
|
test_hook_skip(FeatureBitset features)
|
|
{
|
|
testcase("Test hook_skip");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
// TODO
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_ledger_keylet(FeatureBitset features)
|
|
{
|
|
testcase("Test ledger_keylet");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
// TODO
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_ledger_last_hash(FeatureBitset features)
|
|
{
|
|
testcase("Test ledger_last_hash");
|
|
|
|
using namespace jtx;
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
BEAST_EXPECT(api.ledger_last_hash() == ov.info().parentHash);
|
|
}
|
|
|
|
void
|
|
test_ledger_last_time(FeatureBitset features)
|
|
{
|
|
testcase("Test ledger_last_time");
|
|
|
|
using namespace jtx;
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
auto const expected =
|
|
ov.info().parentCloseTime.time_since_epoch().count();
|
|
BEAST_EXPECT(api.ledger_last_time() == expected);
|
|
}
|
|
|
|
void
|
|
test_ledger_nonce(FeatureBitset features)
|
|
{
|
|
testcase("Test ledger_nonce");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
|
|
{
|
|
// TOO_MANY_NONCES
|
|
StubHookContext stubCtx{
|
|
.ledger_nonce_counter =
|
|
static_cast<uint16_t>(hook_api::max_nonce + 1)};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.ledger_nonce();
|
|
BEAST_EXPECT(result.error() == TOO_MANY_NONCES);
|
|
}
|
|
|
|
{
|
|
// SUCCESS
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
auto const info = hookCtx.applyCtx.view().info();
|
|
auto const expected = ripple::sha512Half(
|
|
ripple::HashPrefix::hookNonce,
|
|
info.seq,
|
|
info.parentCloseTime.time_since_epoch().count(),
|
|
info.parentHash,
|
|
hookCtx.applyCtx.tx.getTransactionID(),
|
|
hookCtx.ledger_nonce_counter,
|
|
hookCtx.result.account);
|
|
auto const result = api.ledger_nonce();
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() == expected);
|
|
BEAST_EXPECT(hookCtx.ledger_nonce_counter == 1);
|
|
}
|
|
}
|
|
|
|
void
|
|
test_ledger_seq(FeatureBitset features)
|
|
{
|
|
testcase("Test ledger_seq");
|
|
|
|
using namespace jtx;
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
BEAST_EXPECT(api.ledger_seq() == ov.info().seq);
|
|
}
|
|
|
|
void
|
|
test_meta_slot(FeatureBitset features)
|
|
{
|
|
testcase("Test meta_slot");
|
|
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_xpop_slot(FeatureBitset features)
|
|
{
|
|
testcase("Test xpop_slot");
|
|
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_otxn_burden(FeatureBitset features)
|
|
{
|
|
testcase("Test otxn_burden");
|
|
using namespace jtx;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx baseTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
|
|
{
|
|
// Cached burden
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, baseTx);
|
|
StubHookContext stubCtx{.burden = 7};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.otxn_burden() == 7);
|
|
}
|
|
|
|
{
|
|
// No sfEmitDetails
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, baseTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.otxn_burden() == 1);
|
|
}
|
|
|
|
{
|
|
// sfEmitDetails without sfEmitBurden
|
|
STTx tx = STTx(ttINVOKE, [&](STObject& obj) {
|
|
obj.peekFieldObject(sfEmitDetails);
|
|
});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, tx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.otxn_burden() == 1);
|
|
}
|
|
|
|
{
|
|
// sfEmitBurden with high bit set is masked
|
|
STTx tx = STTx(ttINVOKE, [&](STObject& obj) {
|
|
auto& details = obj.peekFieldObject(sfEmitDetails);
|
|
details.setFieldU64(
|
|
sfEmitBurden, (static_cast<uint64_t>(1) << 63) | 25);
|
|
});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, tx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
auto const burden = api.otxn_burden();
|
|
BEAST_EXPECT(burden == 25);
|
|
BEAST_EXPECT(api.otxn_burden() == burden);
|
|
}
|
|
}
|
|
|
|
void
|
|
test_otxn_generation(FeatureBitset features)
|
|
{
|
|
testcase("Test otxn_generation");
|
|
using namespace jtx;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx baseTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
|
|
{
|
|
// Cached generation
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, baseTx);
|
|
StubHookContext stubCtx{.generation = 9};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.otxn_generation() == 9);
|
|
}
|
|
|
|
{
|
|
// No sfEmitDetails
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, baseTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.otxn_generation() == 0);
|
|
}
|
|
|
|
{
|
|
// sfEmitDetails without sfEmitGeneration
|
|
STTx tx = STTx(ttINVOKE, [&](STObject& obj) {
|
|
obj.peekFieldObject(sfEmitDetails);
|
|
});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, tx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.otxn_generation() == 0);
|
|
}
|
|
|
|
{
|
|
// sfEmitGeneration present
|
|
STTx tx = STTx(ttINVOKE, [&](STObject& obj) {
|
|
obj.peekFieldObject(sfEmitDetails)
|
|
.setFieldU32(sfEmitGeneration, 4);
|
|
});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, tx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.otxn_generation() == 4);
|
|
BEAST_EXPECT(api.otxn_generation() == 4);
|
|
}
|
|
}
|
|
|
|
void
|
|
test_otxn_field(FeatureBitset features)
|
|
{
|
|
testcase("Test otxn_field");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx tx =
|
|
STTx(ttINVOKE, [&](STObject& obj) { obj[sfAccount] = alice.id(); });
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, tx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
// TODO: INVALID_FIELD now returns DOESNT_EXIST
|
|
// BEAST_EXPECT(api.otxn_field(0).error() == INVALID_FIELD);
|
|
BEAST_EXPECT(
|
|
api.otxn_field(sfDestination.getCode()).error() == DOESNT_EXIST);
|
|
auto const result = api.otxn_field(sfAccount.getCode());
|
|
BEAST_EXPECT(result.has_value());
|
|
auto const* acct = dynamic_cast<STAccount const*>(result.value());
|
|
BEAST_EXPECT(acct != nullptr);
|
|
BEAST_EXPECT(acct->value() == alice.id());
|
|
}
|
|
|
|
void
|
|
test_otxn_id(FeatureBitset features)
|
|
{
|
|
testcase("Test otxn_id");
|
|
|
|
using namespace jtx;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
|
|
{
|
|
STTx tx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
auto const txID = tx.getTransactionID();
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, tx);
|
|
// Originating transaction ID
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.otxn_id(0);
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() == txID);
|
|
}
|
|
|
|
{
|
|
STTx tx = STTx(ttEMIT_FAILURE, [&](STObject& obj) {
|
|
obj.setFieldH256(sfTransactionHash, uint256{5});
|
|
});
|
|
STTx emitFailedTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
auto const txID = tx.getTransactionID();
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, tx);
|
|
// Emit failure transaction ID
|
|
StubHookContext stubCtx{.emitFailure = emitFailedTx};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(
|
|
api.otxn_id(0).value() == tx.getFieldH256(sfTransactionHash));
|
|
// flags bypass emitFailure
|
|
BEAST_EXPECT(api.otxn_id(1).value() == txID);
|
|
}
|
|
}
|
|
|
|
void
|
|
test_otxn_slot(FeatureBitset features)
|
|
{
|
|
testcase("Test otxn_slot");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx tx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, tx);
|
|
|
|
{
|
|
// Invalid slot argument
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.otxn_slot(hook_api::max_slots + 1);
|
|
BEAST_EXPECT(result.error() == INVALID_ARGUMENT);
|
|
}
|
|
|
|
{
|
|
// No free slots
|
|
StubHookContext stubCtx{};
|
|
for (uint32_t i = 1; i <= hook_api::max_slots; ++i)
|
|
stubCtx.slot[i] = hook::SlotEntry{};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
for (uint32_t i = 1; i <= hook_api::max_slots; ++i)
|
|
hookCtx.slot[i] = hook::SlotEntry{};
|
|
BEAST_EXPECT(stubCtx.slot.size() == hook_api::max_slots);
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.otxn_slot(0).error() == NO_FREE_SLOTS);
|
|
}
|
|
|
|
{
|
|
// SUCCESS allocate new slot (slot = 0)
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
for (uint32_t i = 1; i <= 111; ++i)
|
|
hookCtx.slot[i] = hook::SlotEntry{};
|
|
BEAST_EXPECT(!hookCtx.slot.contains(112));
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.otxn_slot(0);
|
|
BEAST_EXPECT(result.has_value());
|
|
auto const newSlot = result.value();
|
|
BEAST_EXPECT(newSlot == 112);
|
|
BEAST_EXPECT(hookCtx.slot.contains(112));
|
|
BEAST_EXPECT(hookCtx.slot[112].entry != nullptr);
|
|
// TODO: test slot content
|
|
}
|
|
|
|
{
|
|
// SUCCESS allocate new slot (slot != 0)
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
BEAST_EXPECT(!hookCtx.slot.contains(200));
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.otxn_slot(200);
|
|
BEAST_EXPECT(result.has_value());
|
|
auto const newSlot = result.value();
|
|
BEAST_EXPECT(newSlot == 200);
|
|
BEAST_EXPECT(hookCtx.slot.contains(200));
|
|
BEAST_EXPECT(hookCtx.slot[newSlot].entry != nullptr);
|
|
// TODO: test slot content
|
|
}
|
|
}
|
|
|
|
void
|
|
test_otxn_type(FeatureBitset features)
|
|
{
|
|
testcase("Test otxn_type");
|
|
|
|
using namespace jtx;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx tx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, tx);
|
|
|
|
{
|
|
STObject failure(sfHook);
|
|
failure.setFieldU16(sfTransactionType, ttACCOUNT_SET);
|
|
StubHookContext stubCtx{.emitFailure = failure};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.otxn_type() == ttACCOUNT_SET);
|
|
}
|
|
|
|
{
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.otxn_type() == ttINVOKE);
|
|
}
|
|
}
|
|
|
|
void
|
|
test_otxn_param(FeatureBitset features)
|
|
{
|
|
testcase("Test otxn_param");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
using namespace hook;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
|
|
// Build tx with hook parameters
|
|
STTx tx = STTx(ttINVOKE, [&](STObject& obj) {
|
|
STArray params{sfHookParameters, 2};
|
|
{
|
|
STObject p{sfHookParameter};
|
|
p.setFieldVL(sfHookParameterName, Bytes{'a'});
|
|
p.setFieldVL(sfHookParameterValue, Bytes{1, 2});
|
|
params.emplace_back(std::move(p));
|
|
}
|
|
{
|
|
STObject p{sfHookParameter};
|
|
p.setFieldVL(sfHookParameterName, Bytes{'b'});
|
|
// missing value to test DOESNT_EXIST
|
|
params.emplace_back(std::move(p));
|
|
}
|
|
{
|
|
STObject p{sfHookParameter};
|
|
p.setFieldVL(sfHookParameterName, Bytes{'c'});
|
|
// empty value to test DOESNT_EXIST
|
|
p.setFieldVL(sfHookParameterValue, Bytes{});
|
|
params.emplace_back(std::move(p));
|
|
}
|
|
obj.setFieldArray(sfHookParameters, params);
|
|
});
|
|
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, tx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
BEAST_EXPECT(api.otxn_param({}).error() == TOO_SMALL);
|
|
BEAST_EXPECT(api.otxn_param(Bytes(33, 1)).error() == TOO_BIG);
|
|
auto const result = api.otxn_param(Bytes{'a'});
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() == Bytes({1, 2}));
|
|
BEAST_EXPECT(api.otxn_param(Bytes{'b'}).error() == DOESNT_EXIST);
|
|
BEAST_EXPECT(api.otxn_param(Bytes{'c'}).error() == DOESNT_EXIST);
|
|
BEAST_EXPECT(api.otxn_param(Bytes{'d'}).error() == DOESNT_EXIST);
|
|
}
|
|
|
|
void
|
|
test_slot(FeatureBitset features)
|
|
{
|
|
testcase("Test slot");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
|
|
// Missing slot
|
|
{
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.slot(1).error() == DOESNT_EXIST);
|
|
}
|
|
|
|
// Present slot pointing to an STAmount field
|
|
{
|
|
printf("test\n");
|
|
STObject obj(sfGeneric);
|
|
obj.setFieldAmount(sfAmount, drops(1));
|
|
auto storage = std::make_shared<STObject const>(obj);
|
|
StubHookContext stubCtx{};
|
|
stubCtx.slot[1] = hook::SlotEntry{.storage = storage, .entry = 0};
|
|
stubCtx.slot[1].entry = &(*stubCtx.slot[1].storage);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.slot(1);
|
|
BEAST_EXPECT(result.has_value());
|
|
|
|
Serializer s;
|
|
(*result)->add(s);
|
|
|
|
STObject resultObj{s.slice(), sfGeneric};
|
|
printf(
|
|
"resultObj: %s\n",
|
|
resultObj.getJson(JsonOptions::none).toStyledString().c_str());
|
|
}
|
|
}
|
|
|
|
void
|
|
test_slot_clear(FeatureBitset features)
|
|
{
|
|
testcase("Test slot_clear");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
|
|
// Missing slot
|
|
{
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.slot_clear(1).error() == DOESNT_EXIST);
|
|
}
|
|
|
|
// Clear existing slot and push to free queue
|
|
{
|
|
STObject obj(sfGeneric);
|
|
auto storage = std::make_shared<STObject const>(obj);
|
|
StubHookContext stubCtx{};
|
|
stubCtx.slot[2] = {.storage = storage, .entry = &(*storage)};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.slot_clear(2).has_value());
|
|
BEAST_EXPECT(hookCtx.slot.find(2) == hookCtx.slot.end());
|
|
BEAST_EXPECT(!hookCtx.slot_free.empty());
|
|
}
|
|
}
|
|
|
|
void
|
|
test_slot_count(FeatureBitset features)
|
|
{
|
|
testcase("Test slot_count");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
|
|
// Nonexistent
|
|
{
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.slot_count(1).error() == DOESNT_EXIST);
|
|
}
|
|
|
|
// Not an array
|
|
{
|
|
STObject obj(sfGeneric);
|
|
auto storage = std::make_shared<STObject const>(obj);
|
|
StubHookContext stubCtx{};
|
|
stubCtx.slot[1] = {.storage = storage, .entry = &(*storage)};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.slot_count(1).error() == NOT_AN_ARRAY);
|
|
}
|
|
|
|
// Array count
|
|
{
|
|
STArray arr{sfGeneric};
|
|
arr.emplace_back(STObject(sfGeneric));
|
|
arr.emplace_back(STObject(sfGeneric));
|
|
auto storage = std::make_shared<STArray const>(arr);
|
|
StubHookContext stubCtx{};
|
|
stubCtx.slot[3] = {
|
|
.storage =
|
|
std::reinterpret_pointer_cast<const STObject>(storage),
|
|
.entry = &(*storage)};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.slot_count(3).value() == 2);
|
|
}
|
|
}
|
|
|
|
void
|
|
test_slot_float(FeatureBitset features)
|
|
{
|
|
testcase("Test slot_float");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
|
|
// Missing slot
|
|
{
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.slot_float(1).error() == DOESNT_EXIST);
|
|
}
|
|
|
|
// Not an amount
|
|
{
|
|
STObject obj(sfGeneric);
|
|
auto storage = std::make_shared<STObject const>(obj);
|
|
StubHookContext stubCtx{};
|
|
stubCtx.slot[1] = {.storage = storage, .entry = &(*storage)};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.slot_float(1).error() == NOT_AN_AMOUNT);
|
|
}
|
|
|
|
// Native amount
|
|
{
|
|
STObject obj(sfGeneric);
|
|
obj.setFieldAmount(sfAmount, drops(10));
|
|
auto storage = std::make_shared<STObject const>(obj);
|
|
auto* amtPtr = &storage->getFieldAmount(sfAmount);
|
|
StubHookContext stubCtx{};
|
|
stubCtx.slot[2] = {.storage = storage, .entry = amtPtr};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.slot_float(2);
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() != 0);
|
|
}
|
|
}
|
|
|
|
void
|
|
test_slot_set(FeatureBitset features)
|
|
{
|
|
testcase("Test slot_set");
|
|
|
|
using namespace jtx;
|
|
using namespace hook;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
// Invalid argument (wrong size)
|
|
BEAST_EXPECT(
|
|
api.slot_set(Bytes{1, 2, 3}, 0).error() == INVALID_ARGUMENT);
|
|
// Invalid argument (slot_no beyond max)
|
|
BEAST_EXPECT(
|
|
api.slot_set(Bytes(32, 0), hook_api::max_slots + 1).error() ==
|
|
INVALID_ARGUMENT);
|
|
}
|
|
|
|
void
|
|
test_slot_size(FeatureBitset features)
|
|
{
|
|
testcase("Test slot_size");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
|
|
// Missing slot
|
|
{
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.slot_size(1).error() == DOESNT_EXIST);
|
|
}
|
|
|
|
// Size of STObject
|
|
{
|
|
STObject obj(sfGeneric);
|
|
obj.setFieldAmount(sfAmount, drops(1));
|
|
auto storage = std::make_shared<STObject const>(obj);
|
|
StubHookContext stubCtx{};
|
|
stubCtx.slot[1] = {.storage = storage, .entry = &(*storage)};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.slot_size(1).value() > 0);
|
|
}
|
|
}
|
|
|
|
void
|
|
test_slot_subarray(FeatureBitset features)
|
|
{
|
|
testcase("Test slot_subarray");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
|
|
// Missing parent
|
|
{
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.slot_subarray(1, 0, 0).error() == DOESNT_EXIST);
|
|
}
|
|
|
|
// Valid subarray extraction
|
|
{
|
|
STArray arr{sfGeneric};
|
|
arr.emplace_back(STObject(sfGeneric));
|
|
arr.emplace_back(STObject(sfGeneric));
|
|
auto storage = std::make_shared<STArray const>(arr);
|
|
StubHookContext stubCtx{};
|
|
stubCtx.slot[1] = {
|
|
.storage =
|
|
std::reinterpret_pointer_cast<const STObject>(storage),
|
|
.entry = &(*storage)};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.slot_subarray(1, 1, 0);
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() != 0);
|
|
BEAST_EXPECT(hookCtx.slot[result.value()].entry != nullptr);
|
|
}
|
|
}
|
|
|
|
void
|
|
test_slot_subfield(FeatureBitset features)
|
|
{
|
|
testcase("Test slot_subfield");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
|
|
// Missing parent
|
|
{
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(
|
|
api.slot_subfield(1, sfAccount.getCode(), 0).error() ==
|
|
DOESNT_EXIST);
|
|
}
|
|
|
|
// No free slots
|
|
{
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
for (int i = 0; i < hook_api::max_slots; i++)
|
|
hookCtx.slot[i] = hook::SlotEntry{};
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(
|
|
api.slot_subfield(1, sfAccount.getCode(), 0).error() ==
|
|
NO_FREE_SLOTS);
|
|
BEAST_EXPECT(
|
|
api.slot_subfield(
|
|
1, sfAccount.getCode(), hook_api::max_slots + 1)
|
|
.error() == INVALID_ARGUMENT);
|
|
}
|
|
|
|
// Invalid field
|
|
{
|
|
STObject obj(sfGeneric);
|
|
auto storage = std::make_shared<STObject const>(obj);
|
|
StubHookContext stubCtx{};
|
|
stubCtx.slot[1] = {.storage = storage, .entry = &(*storage)};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(
|
|
api.slot_subfield(1, ((99U << 16U) + 99U), 0).error() ==
|
|
INVALID_FIELD);
|
|
BEAST_EXPECT(
|
|
api.slot_subfield(1, sfAccount.getCode(), 0).error() ==
|
|
DOESNT_EXIST);
|
|
}
|
|
|
|
// Valid field
|
|
{
|
|
STObject obj(sfGeneric);
|
|
obj.setFieldAmount(sfAmount, drops(1));
|
|
auto storage = std::make_shared<STObject const>(obj);
|
|
StubHookContext stubCtx{};
|
|
stubCtx.slot[1] = {.storage = storage, .entry = &(*storage)};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.slot_subfield(1, sfAmount.getCode(), 0);
|
|
BEAST_EXPECT(result.has_value());
|
|
BEAST_EXPECT(result.value() != 0);
|
|
BEAST_EXPECT(hookCtx.slot[result.value()].entry != nullptr);
|
|
// test the amount bytes
|
|
}
|
|
}
|
|
|
|
void
|
|
test_slot_type(FeatureBitset features)
|
|
{
|
|
testcase("Test slot_type");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
|
|
// Missing slot
|
|
{
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
BEAST_EXPECT(api.slot_type(1, 0).error() == DOESNT_EXIST);
|
|
}
|
|
|
|
// Generic object
|
|
{
|
|
STObject obj(sfGeneric);
|
|
obj.setFieldAmount(sfAmount, drops(5));
|
|
auto storage = std::make_shared<STObject const>(obj);
|
|
StubHookContext stubCtx{};
|
|
stubCtx.slot[1] = {.storage = storage, .entry = &(*storage)};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.slot_type(1, 0);
|
|
BEAST_EXPECT(result.has_value());
|
|
}
|
|
|
|
// Amount with flag 1
|
|
{
|
|
STObject obj(sfGeneric);
|
|
obj.setFieldAmount(sfAmount, drops(7));
|
|
auto storage = std::make_shared<STObject const>(obj);
|
|
auto* amtPtr = &storage->getFieldAmount(sfAmount);
|
|
StubHookContext stubCtx{};
|
|
stubCtx.slot[2] = {.storage = storage, .entry = amtPtr};
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), stubCtx);
|
|
hook::HookAPI api(hookCtx);
|
|
auto const result = api.slot_type(2, 1);
|
|
BEAST_EXPECT(result.has_value());
|
|
}
|
|
}
|
|
|
|
void
|
|
test_state(FeatureBitset features)
|
|
{
|
|
testcase("Test state");
|
|
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_state_foreign(FeatureBitset features)
|
|
{
|
|
testcase("Test state_foreign");
|
|
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_state_foreign_set_max(FeatureBitset features)
|
|
{
|
|
testcase("Test state_foreign_set max");
|
|
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_state_foreign_set(FeatureBitset features)
|
|
{
|
|
testcase("Test state_foreign_set");
|
|
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_state_set(FeatureBitset features)
|
|
{
|
|
testcase("Test state_set");
|
|
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_sto_emplace(FeatureBitset features)
|
|
{
|
|
testcase("Test sto_emplace");
|
|
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_sto_erase(FeatureBitset features)
|
|
{
|
|
testcase("Test sto_erase");
|
|
|
|
BEAST_EXPECT(true);
|
|
}
|
|
void
|
|
test_sto_subarray(FeatureBitset features)
|
|
{
|
|
testcase("Test sto_subarray");
|
|
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_sto_subfield(FeatureBitset features)
|
|
{
|
|
testcase("Test sto_subfield");
|
|
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_sto_validate(FeatureBitset features)
|
|
{
|
|
testcase("Test sto_validate");
|
|
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_trace(FeatureBitset features)
|
|
{
|
|
testcase("Test trace");
|
|
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_trace_float(FeatureBitset features)
|
|
{
|
|
testcase("Test trace_float");
|
|
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_trace_num(FeatureBitset features)
|
|
{
|
|
testcase("Test trace_num");
|
|
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_util_accid(FeatureBitset features)
|
|
{
|
|
testcase("Test util_accid");
|
|
|
|
using namespace jtx;
|
|
using namespace hook;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
// Invalid base58 string
|
|
BEAST_EXPECT(api.util_accid("invalid").error() == INVALID_ARGUMENT);
|
|
|
|
// Valid r-address round-trip via util_raddr
|
|
auto accid = api.util_accid(alice.human());
|
|
BEAST_EXPECT(accid.has_value());
|
|
auto aliceid = alice.id();
|
|
BEAST_EXPECT(accid.value() == Bytes(aliceid.begin(), aliceid.end()));
|
|
}
|
|
|
|
void
|
|
test_util_keylet(FeatureBitset features)
|
|
{
|
|
testcase("Test util_keylet");
|
|
|
|
// TODO
|
|
BEAST_EXPECT(true);
|
|
}
|
|
|
|
void
|
|
test_util_raddr(FeatureBitset features)
|
|
{
|
|
testcase("Test util_raddr");
|
|
|
|
using namespace jtx;
|
|
using namespace hook;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
// Wrong size
|
|
BEAST_EXPECT(api.util_raddr(Bytes(10, 0)).error() == INVALID_ARGUMENT);
|
|
|
|
// Valid accountID
|
|
auto aliceid = alice.id();
|
|
auto id = Bytes(aliceid.begin(), aliceid.end());
|
|
auto addr = api.util_raddr(id);
|
|
BEAST_EXPECT(addr.has_value());
|
|
BEAST_EXPECT(addr.value() == alice.human());
|
|
}
|
|
|
|
void
|
|
test_util_sha512h(FeatureBitset features)
|
|
{
|
|
testcase("Test util_sha512h");
|
|
|
|
using namespace jtx;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
std::string msg{"hello"};
|
|
auto hash = api.util_sha512h(Slice(msg.data(), msg.size()));
|
|
auto expected = ripple::sha512Half(Slice(msg.data(), msg.size()));
|
|
BEAST_EXPECT(hash == expected);
|
|
}
|
|
|
|
void
|
|
test_util_verify(FeatureBitset features)
|
|
{
|
|
testcase("Test util_verify");
|
|
|
|
using namespace jtx;
|
|
using namespace hook;
|
|
using namespace hook_api;
|
|
|
|
auto const alice = Account{"alice"};
|
|
Env env{*this, features};
|
|
STTx invokeTx = STTx(ttINVOKE, [&](STObject& obj) {});
|
|
OpenView ov{*env.current()};
|
|
ApplyContext applyCtx = createApplyContext(env, ov, invokeTx);
|
|
auto hookCtx =
|
|
makeStubHookContext(applyCtx, alice.id(), alice.id(), {});
|
|
hook::HookAPI api(hookCtx);
|
|
|
|
// Generate keypair and signature
|
|
KeyType type = KeyType::secp256k1;
|
|
auto kp = generateKeyPair(type, generateSeed("util_verify_test"));
|
|
Serializer s;
|
|
s.add32(42);
|
|
auto const msg = s.slice();
|
|
auto sig = ripple::sign(kp.first, kp.second, msg);
|
|
|
|
// Invalid key size
|
|
BEAST_EXPECT(
|
|
api.util_verify(msg, sig, kp.first.slice().substr(0, 32)).error() ==
|
|
INVALID_KEY);
|
|
|
|
// Invalid data size
|
|
BEAST_EXPECT(
|
|
api.util_verify(Slice{}, sig, kp.first.slice()).error() ==
|
|
TOO_SMALL);
|
|
|
|
// Invalid sig size
|
|
BEAST_EXPECT(
|
|
api.util_verify(msg, Slice(sig.data(), 29), kp.first.slice())
|
|
.error() == TOO_SMALL);
|
|
|
|
// Invalid sig type
|
|
auto const& invalidKey = kp.first.slice().substr(1, 34);
|
|
BEAST_EXPECT(
|
|
api.util_verify(msg, sig, invalidKey).error() == INVALID_KEY);
|
|
return;
|
|
|
|
// Success
|
|
auto const ok = api.util_verify(msg, sig, kp.first.slice());
|
|
BEAST_EXPECT(ok.has_value());
|
|
BEAST_EXPECT(ok.value());
|
|
}
|
|
|
|
void
|
|
testWithFeatures(FeatureBitset features)
|
|
{
|
|
using namespace test::jtx;
|
|
test_accept(features);
|
|
test_rollback(features);
|
|
testGuards(features);
|
|
|
|
test_emit(features);
|
|
test_etxn_burden(features);
|
|
test_etxn_generation(features);
|
|
test_otxn_burden(features);
|
|
test_otxn_generation(features);
|
|
test_etxn_details(features);
|
|
test_etxn_fee_base(features - fixEtxnFeeBase);
|
|
test_etxn_fee_base(features);
|
|
test_etxn_nonce(features);
|
|
test_etxn_reserve(features);
|
|
test_fee_base(features);
|
|
|
|
test_otxn_field(features);
|
|
|
|
test_ledger_keylet(features);
|
|
|
|
test_float_compare(features);
|
|
test_float_divide(features);
|
|
test_float_int(features);
|
|
test_float_invert(features);
|
|
test_float_log(features);
|
|
test_float_mantissa(features);
|
|
test_float_mulratio(features);
|
|
test_float_multiply(features);
|
|
test_float_negate(features);
|
|
test_float_one(features);
|
|
test_float_root(features);
|
|
test_float_set(features);
|
|
test_float_sign(features);
|
|
test_float_sto(features);
|
|
test_float_sto_set(features);
|
|
test_float_sum(features);
|
|
|
|
test_hook_account(features);
|
|
test_hook_again(features);
|
|
test_hook_hash(features);
|
|
test_hook_param(features);
|
|
test_hook_param_set(features);
|
|
test_hook_pos(features);
|
|
test_hook_skip(features);
|
|
|
|
test_ledger_last_hash(features);
|
|
test_ledger_last_time(features);
|
|
test_ledger_nonce(features);
|
|
test_ledger_seq(features);
|
|
|
|
test_meta_slot(features);
|
|
test_xpop_slot(features);
|
|
|
|
test_otxn_id(features);
|
|
test_otxn_slot(features);
|
|
test_otxn_type(features);
|
|
test_otxn_param(features);
|
|
|
|
test_slot(features);
|
|
test_slot_clear(features);
|
|
test_slot_count(features);
|
|
test_slot_float(features);
|
|
test_slot_set(features);
|
|
test_slot_size(features);
|
|
test_slot_subarray(features);
|
|
test_slot_subfield(features);
|
|
test_slot_type(features);
|
|
|
|
test_state(features);
|
|
test_state_foreign(features);
|
|
test_state_foreign_set(features);
|
|
test_state_foreign_set_max(features);
|
|
test_state_set(features);
|
|
|
|
test_sto_emplace(features);
|
|
test_sto_erase(features);
|
|
test_sto_subarray(features);
|
|
test_sto_subfield(features);
|
|
test_sto_validate(features);
|
|
|
|
test_trace(features);
|
|
test_trace_float(features);
|
|
test_trace_num(features);
|
|
|
|
test_util_accid(features);
|
|
test_util_keylet(features);
|
|
test_util_raddr(features);
|
|
test_util_sha512h(features);
|
|
test_util_verify(features);
|
|
}
|
|
|
|
public:
|
|
void
|
|
run() override
|
|
{
|
|
using namespace test::jtx;
|
|
testWithFeatures(supported_amendments());
|
|
}
|
|
};
|
|
|
|
BEAST_DEFINE_TESTSUITE_PRIO(HookAPI, app, ripple, 2);
|
|
} // namespace test
|
|
} // namespace ripple
|
|
#undef M
|