Files
rippled/src/test/app/Wasm_test.cpp
Mayukha Vadari aef32ead2c better WASM logging to match rippled (#5395)
* basic logging

* pass in Journal

* log level based on journal level

* clean up

* attempt at adding WAMR logging properly

* improve logline

* maybe_unused

* fix

* fix

* fix segfault

* add test
2025-05-23 10:31:02 -04:00

338 lines
12 KiB
C++

//------------------------------------------------------------------------------
/*
This file is part of rippled: https://github.com/ripple/rippled
Copyright (c) 2012, 2013 Ripple Labs Inc.
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 <test/app/wasm_fixtures/fixtures.h>
#include <test/jtx.h>
#include <xrpld/app/misc/WasmVM.h>
#include <iwasm/wasm_c_api.h>
#include <filesystem>
namespace ripple {
namespace test {
/* Host function body definition. */
using Add_proto = int32_t(int32_t, int32_t);
wasm_trap_t*
Add(void* env, const wasm_val_vec_t* params, wasm_val_vec_t* results)
{
int32_t Val1 = params->data[0].of.i32;
int32_t Val2 = params->data[1].of.i32;
// printf("Host function \"Add\": %d + %d\n", Val1, Val2);
results->data[0] = WASM_I32_VAL(Val1 + Val2);
return nullptr;
}
struct Wasm_test : public beast::unit_test::suite
{
void
testWasmtimeLib()
{
testcase("wasmtime lib test");
// clang-format off
/* The WASM module buffer. */
wbytes const wasm = {/* WASM header */
0x00, 0x61, 0x73, 0x6D, 0x01, 0x00, 0x00, 0x00,
/* Type section */
0x01, 0x07, 0x01,
/* function type {i32, i32} -> {i32} */
0x60, 0x02, 0x7F, 0x7F, 0x01, 0x7F,
/* Import section */
0x02, 0x13, 0x01,
/* module name: "extern" */
0x06, 0x65, 0x78, 0x74, 0x65, 0x72, 0x6E,
/* extern name: "func-add" */
0x08, 0x66, 0x75, 0x6E, 0x63, 0x2D, 0x61, 0x64, 0x64,
/* import desc: func 0 */
0x00, 0x00,
/* Function section */
0x03, 0x02, 0x01, 0x00,
/* Export section */
0x07, 0x0A, 0x01,
/* export name: "addTwo" */
0x06, 0x61, 0x64, 0x64, 0x54, 0x77, 0x6F,
/* export desc: func 0 */
0x00, 0x01,
/* Code section */
0x0A, 0x0A, 0x01,
/* code body */
0x08, 0x00, 0x20, 0x00, 0x20, 0x01, 0x10, 0x00, 0x0B};
// clang-format on
auto& vm = WasmEngine::instance();
std::vector<WasmImportFunc> imports;
WasmImpFunc<Add_proto>(
imports, "func-add", reinterpret_cast<void*>(&Add));
auto res = vm.run(wasm, "addTwo", imports, wasmParams(1234, 5678));
// if (res) printf("invokeAdd get the result: %d\n", res.value());
BEAST_EXPECT(res.has_value() && res.value() == 6912);
}
void
testBadWasm()
{
testcase("bad wasm test");
HostFunctions hfs;
auto wasmHex = "00000000";
auto wasmStr = boost::algorithm::unhex(std::string(wasmHex));
std::vector<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
std::string funcName("mock_escrow");
auto re = runEscrowWasm(wasm, funcName, &hfs, 15);
BEAST_EXPECT(re.error());
}
void
testEscrowWasmDN1()
{
testcase("escrow wasm devnet 1 test");
auto wasmHex = allHostFunctionsHex;
auto wasmStr = boost::algorithm::unhex(std::string(wasmHex));
std::vector<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
// let sender = get_tx_account_id();
// let owner = get_current_escrow_account_id();
// let dest = get_current_escrow_destination();
// let dest_balance = get_account_balance(dest);
// let escrow_data = get_current_escrow_data();
// let ed_str = String::from_utf8(escrow_data).unwrap();
// let threshold_balance = ed_str.parse::<u64>().unwrap();
// let pl_time = host_lib::getParentLedgerTime();
// let e_time = get_current_escrow_finish_after();
// sender == owner && dest_balance <= threshold_balance &&
// pl_time >= e_time
using namespace test::jtx;
struct TestHostFunctions : public HostFunctions
{
Env* env_;
Bytes accountID_;
Bytes data_;
int clock_drift_ = 0;
test::StreamSink sink_;
beast::Journal jlog_;
public:
explicit TestHostFunctions(Env* env, int cd = 0)
: env_(env)
, clock_drift_(cd)
, sink_(beast::severities::kTrace)
, jlog_(sink_)
{
std::string s = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
accountID_ = Bytes{s.begin(), s.end()};
std::string t = "10000";
data_ = Bytes{t.begin(), t.end()};
}
test::StreamSink&
getSink()
{
return sink_;
}
beast::Journal
getJournal() override
{
return jlog_;
}
int32_t
getLedgerSqn() override
{
return (int32_t)env_->current()->seq();
}
int32_t
getParentLedgerTime() override
{
return env_->current()
->parentCloseTime()
.time_since_epoch()
.count() +
clock_drift_;
}
std::optional<Bytes>
getTxField(std::string const& fname) override
{
return accountID_;
}
std::optional<Bytes>
getLedgerEntryField(
int32_t type,
Bytes const& kdata,
std::string const& fname) override
{
return data_;
}
std::optional<Bytes>
getCurrentLedgerEntryField(std::string const& fname) override
{
if (fname == "Destination" || fname == "Account")
return accountID_;
else if (fname == "Data")
return data_;
else if (fname == "FinishAfter")
{
auto t = env_->current()
->parentCloseTime()
.time_since_epoch()
.count();
std::string s = std::to_string(t);
return Bytes{s.begin(), s.end()};
}
return std::nullopt;
}
};
Env env{*this};
{
TestHostFunctions nfs(&env, 0);
std::string funcName("finish");
auto re = runEscrowWasm(wasm, funcName, &nfs, 100000);
if (BEAST_EXPECT(re.has_value()))
{
BEAST_EXPECT(re.value().result);
// std::cout << "good case result " << re.value().result
// << " cost: " << re.value().cost << std::endl;
}
}
env.close();
env.close();
env.close();
env.close();
{ // fail because current time < escrow_finish_after time
TestHostFunctions nfs(&env, -1);
std::string funcName("finish");
auto re = runEscrowWasm(wasm, funcName, &nfs, 100000);
if (BEAST_EXPECT(re.has_value()))
{
BEAST_EXPECT(!re.value().result);
// std::cout << "bad case (current time < escrow_finish_after "
// "time) result "
// << re.value().result << " cost: " <<
// re.value().cost
// << std::endl;
}
}
{ // fail because trying to access nonexistent field
struct BadTestHostFunctions : public TestHostFunctions
{
explicit BadTestHostFunctions(Env* env) : TestHostFunctions(env)
{
}
std::optional<Bytes>
getTxField(std::string const& fname) override
{
return std::nullopt;
}
};
BadTestHostFunctions nfs(&env);
std::string funcName("finish");
auto re = runEscrowWasm(wasm, funcName, &nfs, 100000);
BEAST_EXPECT(re.error());
// std::cout << "bad case (access nonexistent field) result "
// << re.error() << std::endl;
}
{ // fail because trying to allocate more than MAX_PAGES memory
struct BadTestHostFunctions : public TestHostFunctions
{
explicit BadTestHostFunctions(Env* env) : TestHostFunctions(env)
{
}
std::optional<Bytes>
getTxField(std::string const& fname) override
{
return Bytes((MAX_PAGES + 1) * 64 * 1024, 1);
}
};
BadTestHostFunctions nfs(&env);
std::string funcName("finish");
auto re = runEscrowWasm(wasm, funcName, &nfs, 100000);
BEAST_EXPECT(!re);
// std::cout << "bad case (more than MAX_PAGES) result "
// << re.error() << std::endl;
}
{ // fail because recursion too deep
auto wasmHex = deepRecursionHex;
auto wasmStr = boost::algorithm::unhex(std::string(wasmHex));
std::vector<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
TestHostFunctions nfs(&env);
std::string funcName("recursive");
auto re = runEscrowWasm(wasm, funcName, &nfs, 1000'000'000);
BEAST_EXPECT(re.error());
// std::cout << "bad case (deep recursion) result " << re.error()
// << std::endl;
auto const& sink = nfs.getSink();
auto countSubstr = [](std::string const& str,
std::string const& substr) {
std::size_t pos = 0;
int occurrences = 0;
while ((pos = str.find(substr, pos)) != std::string::npos)
{
occurrences++;
pos += substr.length();
}
return occurrences;
};
BEAST_EXPECT(
countSubstr(
sink.messages().str(), "WAMR error: failed to call func") ==
1);
BEAST_EXPECT(
countSubstr(
sink.messages().str(),
"WAMR trap: Exception: wasm operand stack overflow") == 1);
}
}
void
run() override
{
using namespace test::jtx;
testWasmtimeLib();
testBadWasm();
testEscrowWasmDN1();
}
};
BEAST_DEFINE_TESTSUITE(Wasm, app, ripple);
} // namespace test
} // namespace ripple