feat: return an int instead of boolean from finish, display in metadata (#5641)

* create STInt32 and STInt64, use it for sfWasmReturnCode in metadata

* get it actually working

* add tests

* update comment

* change type

* respond to comments
This commit is contained in:
Mayukha Vadari
2025-08-04 09:25:47 -04:00
committed by GitHub
parent 8426470506
commit 58741d2791
20 changed files with 281 additions and 31 deletions

View File

@@ -71,8 +71,10 @@ class STCurrency;
STYPE(STI_VL, 7) \
STYPE(STI_ACCOUNT, 8) \
STYPE(STI_NUMBER, 9) \
STYPE(STI_INT32, 10) \
STYPE(STI_INT64, 11) \
\
/* 10-13 are reserved */ \
/* 12-13 are reserved */ \
STYPE(STI_OBJECT, 14) \
STYPE(STI_ARRAY, 15) \
\
@@ -352,6 +354,9 @@ using SF_UINT256 = TypedField<STBitString<256>>;
using SF_UINT384 = TypedField<STBitString<384>>;
using SF_UINT512 = TypedField<STBitString<512>>;
using SF_INT32 = TypedField<STInteger<std::int32_t>>;
using SF_INT64 = TypedField<STInteger<std::int64_t>>;
using SF_ACCOUNT = TypedField<STAccount>;
using SF_AMOUNT = TypedField<STAmount>;
using SF_ISSUE = TypedField<STIssue>;

View File

@@ -81,6 +81,9 @@ using STUInt16 = STInteger<std::uint16_t>;
using STUInt32 = STInteger<std::uint32_t>;
using STUInt64 = STInteger<std::uint64_t>;
using STInt32 = STInteger<std::int32_t>;
using STInt64 = STInteger<std::int64_t>;
template <typename Integer>
inline STInteger<Integer>::STInteger(Integer v) : value_(v)
{

View File

@@ -231,6 +231,10 @@ public:
getFieldH192(SField const& field) const;
uint256
getFieldH256(SField const& field) const;
std::int32_t
getFieldI32(SField const& field) const;
std::int64_t
getFieldI64(SField const& field) const;
AccountID
getAccountID(SField const& field) const;
@@ -365,6 +369,10 @@ public:
void
setFieldH256(SField const& field, uint256 const&);
void
setFieldI32(SField const& field, std::int32_t);
void
setFieldI64(SField const& field, std::int64_t);
void
setFieldVL(SField const& field, Blob const&);
void
setFieldVL(SField const& field, Slice const&);

View File

@@ -161,7 +161,8 @@ public:
getGasUsed() const
{
XRPL_ASSERT(
hasGasUsed(), "ripple::TxMeta::getGasUsed : non-null batch id");
hasGasUsed(),
"ripple::TxMeta::getGasUsed : non-null gas used field");
return *gasUsed_;
}
@@ -171,6 +172,27 @@ public:
return static_cast<bool>(gasUsed_);
}
void
setWasmReturnCode(std::int32_t const& wasmReturnCode)
{
wasmReturnCode_ = wasmReturnCode;
}
std::int32_t
getWasmReturnCode() const
{
XRPL_ASSERT(
hasWasmReturnCode(),
"ripple::TxMeta::getWasmReturnCode : non-null wasm return code");
return *wasmReturnCode_;
}
bool
hasWasmReturnCode() const
{
return static_cast<bool>(wasmReturnCode_);
}
private:
uint256 mTransactionID;
std::uint32_t mLedger;
@@ -178,8 +200,9 @@ private:
int mResult;
std::optional<STAmount> mDelivered;
std::optional<std::uint32_t> gasUsed_;
std::optional<uint256> parentBatchId_;
std::optional<std::uint32_t> gasUsed_;
std::optional<std::int32_t> wasmReturnCode_;
STArray mNodes;
};

View File

@@ -210,6 +210,9 @@ TYPED_SFIELD(sfAssetsMaximum, NUMBER, 3)
TYPED_SFIELD(sfAssetsTotal, NUMBER, 4)
TYPED_SFIELD(sfLossUnrealized, NUMBER, 5)
// 32-bit signed (common)
TYPED_SFIELD(sfWasmReturnCode, INT32, 1)
// currency amount (common)
TYPED_SFIELD(sfAmount, AMOUNT, 1)
TYPED_SFIELD(sfBalance, AMOUNT, 2)

View File

@@ -251,4 +251,82 @@ STUInt64::getJson(JsonOptions) const
return convertToString(value_, 16); // Convert to base 16
}
//------------------------------------------------------------------------------
template <>
STInteger<std::int32_t>::STInteger(SerialIter& sit, SField const& name)
: STInteger(name, sit.get32())
{
}
template <>
SerializedTypeID
STInt32::getSType() const
{
return STI_INT32;
}
template <>
std::string
STInt32::getText() const
{
return std::to_string(value_);
}
template <>
Json::Value
STInt32::getJson(JsonOptions) const
{
return value_;
}
//------------------------------------------------------------------------------
template <>
STInteger<std::int64_t>::STInteger(SerialIter& sit, SField const& name)
: STInteger(name, sit.get64())
{
}
template <>
SerializedTypeID
STInt64::getSType() const
{
return STI_INT64;
}
template <>
std::string
STInt64::getText() const
{
return std::to_string(value_);
}
template <>
Json::Value
STInt64::getJson(JsonOptions) const
{
auto convertToString = [](int64_t const value, int const base) {
XRPL_ASSERT(
base == 10 || base == 16,
"ripple::STInt64::getJson : base 10 or 16");
std::string str(
base == 10 ? 20 : 16, 0); // Allocate space depending on base
auto ret =
std::to_chars(str.data(), str.data() + str.size(), value, base);
XRPL_ASSERT(
ret.ec == std::errc(),
"ripple::STInt64::getJson : to_chars succeeded");
str.resize(std::distance(str.data(), ret.ptr));
return str;
};
if (auto const& fName = getFName(); fName.shouldMeta(SField::sMD_BaseTen))
{
return convertToString(value_, 10); // Convert to base 10
}
return convertToString(value_, 16); // Convert to base 16
}
} // namespace ripple

View File

@@ -647,6 +647,18 @@ STObject::getFieldH256(SField const& field) const
return getFieldByValue<STUInt256>(field);
}
std::int32_t
STObject::getFieldI32(SField const& field) const
{
return getFieldByValue<STInt32>(field);
}
std::int64_t
STObject::getFieldI64(SField const& field) const
{
return getFieldByValue<STInt64>(field);
}
AccountID
STObject::getAccountID(SField const& field) const
{
@@ -761,6 +773,18 @@ STObject::setFieldH256(SField const& field, uint256 const& v)
setFieldUsingSetValue<STUInt256>(field, v);
}
void
STObject::setFieldI32(SField const& field, std::int32_t v)
{
setFieldUsingSetValue<STInt32>(field, v);
}
void
STObject::setFieldI64(SField const& field, std::int64_t v)
{
setFieldUsingSetValue<STInt64>(field, v);
}
void
STObject::setFieldV256(SField const& field, STVector256 const& v)
{

View File

@@ -501,30 +501,6 @@ parseLeaf(
break;
}
case STI_UINT192: {
if (!value.isString())
{
error = bad_type(json_name, fieldName);
return ret;
}
uint192 num;
if (auto const s = value.asString(); !num.parseHex(s))
{
if (!s.empty())
{
error = invalid_data(json_name, fieldName);
return ret;
}
num.zero();
}
ret = detail::make_stvar<STUInt192>(field, num);
break;
}
case STI_UINT160: {
if (!value.isString())
{
@@ -549,6 +525,30 @@ parseLeaf(
break;
}
case STI_UINT192: {
if (!value.isString())
{
error = bad_type(json_name, fieldName);
return ret;
}
uint192 num;
if (auto const s = value.asString(); !num.parseHex(s))
{
if (!s.empty())
{
error = invalid_data(json_name, fieldName);
return ret;
}
num.zero();
}
ret = detail::make_stvar<STUInt192>(field, num);
break;
}
case STI_UINT256: {
if (!value.isString())
{
@@ -573,6 +573,46 @@ parseLeaf(
break;
}
case STI_INT32:
try
{
if (value.isString())
{
ret = detail::make_stvar<STInt32>(
field,
beast::lexicalCastThrow<std::int32_t>(
value.asString()));
}
else if (value.isInt())
{
ret = detail::make_stvar<STInt32>(field, value.asInt());
}
else if (value.isUInt())
{
if (value.asUInt() >
static_cast<std::uint32_t>(
std::numeric_limits<std::int32_t>::max()))
{
error = out_of_range(json_name, fieldName);
return ret;
}
ret = detail::make_stvar<STInt32>(
field, safe_cast<std::int32_t>(value.asInt()));
}
else
{
error = bad_type(json_name, fieldName);
return ret;
}
}
catch (std::exception const&)
{
error = invalid_data(json_name, fieldName);
return ret;
}
break;
case STI_VL:
if (!value.isString())
{

View File

@@ -208,6 +208,12 @@ STVar::constructST(SerializedTypeID id, int depth, Args&&... args)
case STI_UINT256:
construct<STUInt256>(std::forward<Args>(args)...);
return;
case STI_INT32:
construct<STInt32>(std::forward<Args>(args)...);
return;
case STI_INT64:
construct<STInt64>(std::forward<Args>(args)...);
return;
case STI_VECTOR256:
construct<STVector256>(std::forward<Args>(args)...);
return;

View File

@@ -83,6 +83,18 @@ Serializer::addInteger(std::uint64_t i)
{
return add64(i);
}
template <>
int
Serializer::addInteger(std::int32_t i)
{
return add32(i);
}
template <>
int
Serializer::addInteger(std::int64_t i)
{
return add64(i);
}
int
Serializer::addRaw(Blob const& vector)

View File

@@ -60,6 +60,8 @@ TxMeta::TxMeta(
setParentBatchId(obj.getFieldH256(sfParentBatchID));
if (obj.isFieldPresent(sfGasUsed))
setGasUsed(obj.getFieldU32(sfGasUsed));
if (obj.isFieldPresent(sfWasmReturnCode))
setWasmReturnCode(obj.getFieldI32(sfWasmReturnCode));
}
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
@@ -86,6 +88,9 @@ TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
if (obj.isFieldPresent(sfGasUsed))
setGasUsed(obj.getFieldU32(sfGasUsed));
if (obj.isFieldPresent(sfWasmReturnCode))
setWasmReturnCode(obj.getFieldI32(sfWasmReturnCode));
}
TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, Blob const& vec)
@@ -257,6 +262,8 @@ TxMeta::getAsObject() const
metaData.setFieldH256(sfParentBatchID, getParentBatchId());
if (hasGasUsed())
metaData.setFieldU32(sfGasUsed, getGasUsed());
if (hasWasmReturnCode())
metaData.setFieldI32(sfWasmReturnCode, getWasmReturnCode());
return metaData;
}

View File

@@ -2096,6 +2096,8 @@ struct Escrow_test : public beast::unit_test::suite
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
BEAST_EXPECT(txMeta->getFieldI32(sfWasmReturnCode) == 1);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
@@ -2164,6 +2166,8 @@ struct Escrow_test : public beast::unit_test::suite
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
BEAST_EXPECT(txMeta->getFieldI32(sfWasmReturnCode) == 1);
env.close();
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
@@ -2213,6 +2217,8 @@ struct Escrow_test : public beast::unit_test::suite
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
BEAST_EXPECT(txMeta->getFieldI32(sfWasmReturnCode) == 1);
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
}
@@ -2264,6 +2270,8 @@ struct Escrow_test : public beast::unit_test::suite
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
BEAST_EXPECT(txMeta->getFieldI32(sfWasmReturnCode) == 1);
env.close();
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);
@@ -2330,6 +2338,8 @@ struct Escrow_test : public beast::unit_test::suite
auto const txMeta = env.meta();
if (BEAST_EXPECT(txMeta && txMeta->isFieldPresent(sfGasUsed)))
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == 39'596);
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
BEAST_EXPECT(txMeta->getFieldI32(sfWasmReturnCode) == 1);
env.close();
BEAST_EXPECT((*env.le(alice))[sfOwnerCount] == 0);

View File

@@ -48,7 +48,7 @@ struct WasmResult
T result;
int64_t cost;
};
typedef WasmResult<bool> EscrowResult;
typedef WasmResult<int32_t> EscrowResult;
////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

View File

@@ -59,6 +59,10 @@ ApplyContext::discard()
std::optional<TxMeta>
ApplyContext::apply(TER ter)
{
if (wasmReturnCode_.has_value())
{
view_->setWasmReturnCode(*wasmReturnCode_);
}
view_->setGasUsed(gasUsed_);
return view_->apply(
base_, tx, ter, parentBatchId_, flags_ & tapDRY_RUN, journal);

View File

@@ -113,6 +113,13 @@ public:
gasUsed_ = gasUsed;
}
/** Sets the gas used in the metadata */
void
setWasmReturnCode(std::int32_t const wasmReturnCode)
{
wasmReturnCode_ = wasmReturnCode;
}
/** Discard changes and start fresh. */
void
discard();
@@ -165,6 +172,7 @@ private:
// The ID of the batch transaction we are executing under, if seated.
std::optional<uint256 const> parentBatchId_;
std::optional<std::uint32_t> gasUsed_;
std::optional<std::int32_t> wasmReturnCode_;
};
} // namespace ripple

View File

@@ -1282,13 +1282,13 @@ EscrowFinish::doApply()
if (re.has_value())
{
auto reValue = re.value().result;
ctx_.setWasmReturnCode(reValue);
// TODO: better error handling for this conversion
ctx_.setGasUsed(static_cast<uint32_t>(re.value().cost));
JLOG(j_.debug()) << "WASM Success: " + std::to_string(reValue)
<< ", cost: " << re.value().cost;
if (!reValue)
if (reValue <= 0)
{
// ctx_.view().update(slep);
return tecWASM_REJECTED;
}
}

View File

@@ -81,6 +81,12 @@ public:
gasUsed_ = gasUsed;
}
void
setWasmReturnCode(std::int32_t const wasmReturnCode)
{
wasmReturnCode_ = wasmReturnCode;
}
/** Get the number of modified entries
*/
std::size_t
@@ -100,6 +106,7 @@ public:
private:
std::optional<STAmount> deliver_;
std::optional<std::uint32_t> gasUsed_;
std::optional<std::int32_t> wasmReturnCode_;
};
} // namespace ripple

View File

@@ -118,6 +118,7 @@ ApplyStateTable::apply(
std::optional<STAmount> const& deliver,
std::optional<uint256 const> const& parentBatchId,
std::optional<std::uint32_t> const& gasUsed,
std::optional<std::int32_t> const& wasmReturnCode,
bool isDryRun,
beast::Journal j)
{
@@ -136,6 +137,8 @@ ApplyStateTable::apply(
meta.setParentBatchId(*parentBatchId);
if (gasUsed)
meta.setGasUsed(*gasUsed);
if (wasmReturnCode)
meta.setWasmReturnCode(*wasmReturnCode);
Mods newMod;
for (auto& item : items_)
{

View File

@@ -74,6 +74,7 @@ public:
std::optional<STAmount> const& deliver,
std::optional<uint256 const> const& parentBatchId,
std::optional<std::uint32_t> const& gasUsed,
std::optional<std::int32_t> const& wasmReturnCode,
bool isDryRun,
beast::Journal j);

View File

@@ -36,7 +36,15 @@ ApplyViewImpl::apply(
beast::Journal j)
{
return items_.apply(
to, tx, ter, deliver_, parentBatchId, gasUsed_, isDryRun, j);
to,
tx,
ter,
deliver_,
parentBatchId,
gasUsed_,
wasmReturnCode_,
isDryRun,
j);
}
std::size_t