mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-28 23:15:52 +00:00
Compare commits
6 Commits
ripple/was
...
ripple/wam
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
337358a065 | ||
|
|
5c3b1f6f29 | ||
|
|
6733689102 | ||
|
|
c14bc53aa3 | ||
|
|
ce3eec85ee | ||
|
|
817f9c4f8c |
@@ -74,6 +74,18 @@ public:
|
||||
deliver_ = amount;
|
||||
}
|
||||
|
||||
void
|
||||
setGasUsed(std::optional<std::uint32_t> const gasUsed)
|
||||
{
|
||||
gasUsed_ = gasUsed;
|
||||
}
|
||||
|
||||
void
|
||||
setWasmReturnCode(std::int32_t const wasmReturnCode)
|
||||
{
|
||||
wasmReturnCode_ = wasmReturnCode;
|
||||
}
|
||||
|
||||
/** Get the number of modified entries
|
||||
*/
|
||||
std::size_t
|
||||
@@ -92,6 +104,8 @@ public:
|
||||
|
||||
private:
|
||||
std::optional<STAmount> deliver_;
|
||||
std::optional<std::uint32_t> gasUsed_;
|
||||
std::optional<std::int32_t> wasmReturnCode_;
|
||||
};
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -72,6 +72,8 @@ public:
|
||||
TER ter,
|
||||
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);
|
||||
|
||||
|
||||
@@ -24,6 +24,8 @@
|
||||
|
||||
namespace ripple {
|
||||
|
||||
constexpr std::uint32_t MICRO_DROPS_PER_DROP{1'000'000};
|
||||
|
||||
/** Reflects the fee settings for a particular ledger.
|
||||
|
||||
The fees are always the same for any transactions applied
|
||||
@@ -34,6 +36,10 @@ struct Fees
|
||||
XRPAmount base{0}; // Reference tx cost (drops)
|
||||
XRPAmount reserve{0}; // Reserve base (drops)
|
||||
XRPAmount increment{0}; // Reserve increment (drops)
|
||||
std::uint32_t extensionComputeLimit{
|
||||
0}; // Extension compute limit (instructions)
|
||||
std::uint32_t extensionSizeLimit{0}; // Extension size limit (bytes)
|
||||
std::uint32_t gasPrice{0}; // price of WASM gas (micro-drops)
|
||||
|
||||
explicit Fees() = default;
|
||||
Fees(Fees const&) = default;
|
||||
|
||||
@@ -231,6 +231,12 @@ page(Keylet const& root, std::uint64_t index = 0) noexcept
|
||||
Keylet
|
||||
escrow(AccountID const& src, std::uint32_t seq) noexcept;
|
||||
|
||||
inline Keylet
|
||||
escrow(uint256 const& key) noexcept
|
||||
{
|
||||
return {ltESCROW, key};
|
||||
}
|
||||
|
||||
/** A PaymentChannel */
|
||||
Keylet
|
||||
payChan(AccountID const& src, AccountID const& dst, std::uint32_t seq) noexcept;
|
||||
|
||||
@@ -132,13 +132,6 @@ std::uint8_t constexpr vaultMaximumIOUScale = 18;
|
||||
* another vault; counted from 0 */
|
||||
std::uint8_t constexpr maxAssetCheckDepth = 5;
|
||||
|
||||
/** The maximum length of a Data field in Escrow object that can be updated by
|
||||
* Wasm code */
|
||||
std::size_t constexpr maxWasmDataLength = 4 * 1024;
|
||||
|
||||
/** The maximum length of a parameters passed from Wasm code*/
|
||||
std::size_t constexpr maxWasmParamLength = 1024;
|
||||
|
||||
/** A ledger index. */
|
||||
using LedgerIndex = std::uint32_t;
|
||||
|
||||
@@ -185,6 +178,13 @@ std::size_t constexpr permissionMaxSize = 10;
|
||||
/** The maximum number of transactions that can be in a batch. */
|
||||
std::size_t constexpr maxBatchTxCount = 8;
|
||||
|
||||
/** The maximum length of a Data field in Escrow object that can be updated by
|
||||
* Wasm code */
|
||||
std::size_t constexpr maxWasmDataLength = 4 * 1024;
|
||||
|
||||
/** The maximum length of a parameters passed from Wasm code*/
|
||||
std::size_t constexpr maxWasmParamLength = 1024;
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
#endif
|
||||
|
||||
@@ -72,8 +72,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) \
|
||||
\
|
||||
@@ -356,6 +358,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>;
|
||||
|
||||
@@ -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>; // Can be added if&when needed
|
||||
|
||||
template <typename Integer>
|
||||
inline STInteger<Integer>::STInteger(Integer v) : value_(v)
|
||||
{
|
||||
|
||||
@@ -231,6 +231,8 @@ public:
|
||||
getFieldH192(SField const& field) const;
|
||||
uint256
|
||||
getFieldH256(SField const& field) const;
|
||||
std::int32_t
|
||||
getFieldI32(SField const& field) const;
|
||||
AccountID
|
||||
getAccountID(SField const& field) const;
|
||||
|
||||
@@ -365,6 +367,8 @@ public:
|
||||
void
|
||||
setFieldH256(SField const& field, uint256 const&);
|
||||
void
|
||||
setFieldI32(SField const& field, std::int32_t);
|
||||
void
|
||||
setFieldVL(SField const& field, Blob const&);
|
||||
void
|
||||
setFieldVL(SField const& field, Slice const&);
|
||||
|
||||
@@ -187,6 +187,8 @@ enum TEFcodes : TERUnderlyingType {
|
||||
tefNO_TICKET,
|
||||
tefNFTOKEN_IS_NOT_TRANSFERABLE,
|
||||
tefINVALID_LEDGER_FIX_TYPE,
|
||||
tefNO_WASM,
|
||||
tefWASM_FIELD_NOT_INCLUDED,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -364,6 +366,7 @@ enum TECcodes : TERUnderlyingType {
|
||||
tecPSEUDO_ACCOUNT = 196,
|
||||
tecPRECISION_LOSS = 197,
|
||||
tecNO_DELEGATE_PERMISSION = 198,
|
||||
tecWASM_REJECTED = 199,
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -46,10 +46,7 @@ private:
|
||||
CtorHelper);
|
||||
|
||||
public:
|
||||
TxMeta(
|
||||
uint256 const& transactionID,
|
||||
std::uint32_t ledger,
|
||||
std::optional<uint256> parentBatchId = std::nullopt);
|
||||
TxMeta(uint256 const& transactionID, std::uint32_t ledger);
|
||||
TxMeta(uint256 const& txID, std::uint32_t ledger, Blob const&);
|
||||
TxMeta(uint256 const& txID, std::uint32_t ledger, std::string const&);
|
||||
TxMeta(uint256 const& txID, std::uint32_t ledger, STObject const&);
|
||||
@@ -136,7 +133,7 @@ public:
|
||||
void
|
||||
setParentBatchId(uint256 const& parentBatchId)
|
||||
{
|
||||
mParentBatchId = parentBatchId;
|
||||
parentBatchId_ = parentBatchId;
|
||||
}
|
||||
|
||||
uint256
|
||||
@@ -145,13 +142,55 @@ public:
|
||||
XRPL_ASSERT(
|
||||
hasParentBatchId(),
|
||||
"ripple::TxMeta::getParentBatchId : non-null batch id");
|
||||
return *mParentBatchId;
|
||||
return *parentBatchId_;
|
||||
}
|
||||
|
||||
bool
|
||||
hasParentBatchId() const
|
||||
{
|
||||
return static_cast<bool>(mParentBatchId);
|
||||
return static_cast<bool>(parentBatchId_);
|
||||
}
|
||||
|
||||
void
|
||||
setGasUsed(std::uint32_t const& gasUsed)
|
||||
{
|
||||
gasUsed_ = gasUsed;
|
||||
}
|
||||
|
||||
std::uint32_t
|
||||
getGasUsed() const
|
||||
{
|
||||
XRPL_ASSERT(
|
||||
hasGasUsed(),
|
||||
"ripple::TxMeta::getGasUsed : non-null gas used field");
|
||||
return *gasUsed_;
|
||||
}
|
||||
|
||||
bool
|
||||
hasGasUsed() const
|
||||
{
|
||||
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:
|
||||
@@ -161,7 +200,9 @@ private:
|
||||
int mResult;
|
||||
|
||||
std::optional<STAmount> mDelivered;
|
||||
std::optional<uint256> mParentBatchId;
|
||||
std::optional<uint256> parentBatchId_;
|
||||
std::optional<std::uint32_t> gasUsed_;
|
||||
std::optional<std::int32_t> wasmReturnCode_;
|
||||
|
||||
STArray mNodes;
|
||||
};
|
||||
|
||||
@@ -32,6 +32,7 @@
|
||||
// If you add an amendment here, then do not forget to increment `numFeatures`
|
||||
// in include/xrpl/protocol/Feature.h.
|
||||
|
||||
XRPL_FEATURE(SmartEscrow, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (IncludeKeyletFields, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DynamicMPT, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (TokenEscrowV1, Supported::yes, VoteBehavior::DefaultNo)
|
||||
@@ -47,7 +48,6 @@ XRPL_FEATURE(Batch, Supported::yes, VoteBehavior::DefaultNo
|
||||
XRPL_FEATURE(SingleAssetVault, Supported::no, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(PermissionDelegation, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (PayChanCancelAfter, Supported::yes, VoteBehavior::DefaultNo)
|
||||
// Check flags in Credential transactions
|
||||
XRPL_FIX (InvalidTxFlags, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FIX (FrozenLPTokenTransfer, Supported::yes, VoteBehavior::DefaultNo)
|
||||
XRPL_FEATURE(DeepFreeze, Supported::yes, VoteBehavior::DefaultNo)
|
||||
|
||||
@@ -350,6 +350,8 @@ LEDGER_ENTRY(ltESCROW, 0x0075, Escrow, escrow, ({
|
||||
{sfCondition, soeOPTIONAL},
|
||||
{sfCancelAfter, soeOPTIONAL},
|
||||
{sfFinishAfter, soeOPTIONAL},
|
||||
{sfFinishFunction, soeOPTIONAL},
|
||||
{sfData, soeOPTIONAL},
|
||||
{sfSourceTag, soeOPTIONAL},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
{sfOwnerNode, soeREQUIRED},
|
||||
|
||||
@@ -115,6 +115,11 @@ TYPED_SFIELD(sfFirstNFTokenSequence, UINT32, 50)
|
||||
TYPED_SFIELD(sfOracleDocumentID, UINT32, 51)
|
||||
TYPED_SFIELD(sfPermissionValue, UINT32, 52)
|
||||
TYPED_SFIELD(sfMutableFlags, UINT32, 53)
|
||||
TYPED_SFIELD(sfExtensionComputeLimit, UINT32, 54)
|
||||
TYPED_SFIELD(sfExtensionSizeLimit, UINT32, 55)
|
||||
TYPED_SFIELD(sfGasPrice, UINT32, 56)
|
||||
TYPED_SFIELD(sfComputationAllowance, UINT32, 57)
|
||||
TYPED_SFIELD(sfGasUsed, UINT32, 58)
|
||||
|
||||
// 64-bit integers (common)
|
||||
TYPED_SFIELD(sfIndexNext, UINT64, 1)
|
||||
@@ -208,6 +213,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)
|
||||
@@ -236,7 +244,7 @@ TYPED_SFIELD(sfBaseFeeDrops, AMOUNT, 22)
|
||||
TYPED_SFIELD(sfReserveBaseDrops, AMOUNT, 23)
|
||||
TYPED_SFIELD(sfReserveIncrementDrops, AMOUNT, 24)
|
||||
|
||||
// currency amount (AMM)
|
||||
// currency amount (more)
|
||||
TYPED_SFIELD(sfLPTokenOut, AMOUNT, 25)
|
||||
TYPED_SFIELD(sfLPTokenIn, AMOUNT, 26)
|
||||
TYPED_SFIELD(sfEPrice, AMOUNT, 27)
|
||||
@@ -244,6 +252,7 @@ TYPED_SFIELD(sfPrice, AMOUNT, 28)
|
||||
TYPED_SFIELD(sfSignatureReward, AMOUNT, 29)
|
||||
TYPED_SFIELD(sfMinAccountCreateAmount, AMOUNT, 30)
|
||||
TYPED_SFIELD(sfLPTokenBalance, AMOUNT, 31)
|
||||
TYPED_SFIELD(sfFinishFunction, VL, 32)
|
||||
|
||||
// variable length (common)
|
||||
TYPED_SFIELD(sfPublicKey, VL, 1)
|
||||
|
||||
@@ -69,11 +69,13 @@ TRANSACTION(ttESCROW_CREATE, 1, EscrowCreate,
|
||||
noPriv,
|
||||
({
|
||||
{sfDestination, soeREQUIRED},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
{sfAmount, soeREQUIRED, soeMPTSupported},
|
||||
{sfCondition, soeOPTIONAL},
|
||||
{sfCancelAfter, soeOPTIONAL},
|
||||
{sfFinishAfter, soeOPTIONAL},
|
||||
{sfDestinationTag, soeOPTIONAL},
|
||||
{sfFinishFunction, soeOPTIONAL},
|
||||
{sfData, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
/** This transaction type completes an existing escrow. */
|
||||
@@ -87,6 +89,7 @@ TRANSACTION(ttESCROW_FINISH, 2, EscrowFinish,
|
||||
{sfFulfillment, soeOPTIONAL},
|
||||
{sfCondition, soeOPTIONAL},
|
||||
{sfCredentialIDs, soeOPTIONAL},
|
||||
{sfComputationAllowance, soeOPTIONAL},
|
||||
}))
|
||||
|
||||
|
||||
|
||||
@@ -116,6 +116,8 @@ ApplyStateTable::apply(
|
||||
TER ter,
|
||||
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)
|
||||
{
|
||||
@@ -126,11 +128,16 @@ ApplyStateTable::apply(
|
||||
std::optional<TxMeta> metadata;
|
||||
if (!to.open() || isDryRun)
|
||||
{
|
||||
TxMeta meta(tx.getTransactionID(), to.seq(), parentBatchId);
|
||||
TxMeta meta(tx.getTransactionID(), to.seq());
|
||||
|
||||
if (deliver)
|
||||
meta.setDeliveredAmount(*deliver);
|
||||
|
||||
if (parentBatchId)
|
||||
meta.setParentBatchId(*parentBatchId);
|
||||
if (gasUsed)
|
||||
meta.setGasUsed(*gasUsed);
|
||||
if (wasmReturnCode)
|
||||
meta.setWasmReturnCode(*wasmReturnCode);
|
||||
Mods newMod;
|
||||
for (auto& item : items_)
|
||||
{
|
||||
|
||||
@@ -35,7 +35,16 @@ ApplyViewImpl::apply(
|
||||
bool isDryRun,
|
||||
beast::Journal j)
|
||||
{
|
||||
return items_.apply(to, tx, ter, deliver_, parentBatchId, isDryRun, j);
|
||||
return items_.apply(
|
||||
to,
|
||||
tx,
|
||||
ter,
|
||||
deliver_,
|
||||
parentBatchId,
|
||||
gasUsed_,
|
||||
wasmReturnCode_,
|
||||
isDryRun,
|
||||
j);
|
||||
}
|
||||
|
||||
std::size_t
|
||||
|
||||
@@ -251,4 +251,33 @@ 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_;
|
||||
}
|
||||
|
||||
} // namespace ripple
|
||||
|
||||
@@ -647,6 +647,12 @@ STObject::getFieldH256(SField const& field) const
|
||||
return getFieldByValue<STUInt256>(field);
|
||||
}
|
||||
|
||||
std::int32_t
|
||||
STObject::getFieldI32(SField const& field) const
|
||||
{
|
||||
return getFieldByValue<STInt32>(field);
|
||||
}
|
||||
|
||||
AccountID
|
||||
STObject::getAccountID(SField const& field) const
|
||||
{
|
||||
@@ -761,6 +767,12 @@ 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::setFieldV256(SField const& field, STVector256 const& v)
|
||||
{
|
||||
|
||||
@@ -558,30 +558,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())
|
||||
{
|
||||
@@ -606,6 +582,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())
|
||||
{
|
||||
@@ -630,6 +630,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())
|
||||
{
|
||||
@@ -1109,8 +1149,7 @@ parseArray(
|
||||
Json::Value const objectFields(json[i][objectName]);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << json_name << "."
|
||||
<< "[" << i << "]." << objectName;
|
||||
ss << json_name << "." << "[" << i << "]." << objectName;
|
||||
|
||||
auto ret = parseObject(
|
||||
ss.str(), objectFields, nameField, depth + 1, error);
|
||||
|
||||
@@ -208,6 +208,9 @@ 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_VECTOR256:
|
||||
construct<STVector256>(std::forward<Args>(args)...);
|
||||
return;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -128,6 +128,7 @@ transResults()
|
||||
MAKE_ERROR(tecPSEUDO_ACCOUNT, "This operation is not allowed against a pseudo-account."),
|
||||
MAKE_ERROR(tecPRECISION_LOSS, "The amounts used by the transaction cannot interact."),
|
||||
MAKE_ERROR(tecNO_DELEGATE_PERMISSION, "Delegated account lacks permission to perform this transaction."),
|
||||
MAKE_ERROR(tecWASM_REJECTED, "The custom WASM code that was run rejected your transaction."),
|
||||
|
||||
MAKE_ERROR(tefALREADY, "The exact transaction was already in this ledger."),
|
||||
MAKE_ERROR(tefBAD_ADD_AUTH, "Not authorized to add account."),
|
||||
@@ -151,6 +152,8 @@ transResults()
|
||||
MAKE_ERROR(tefNO_TICKET, "Ticket is not in ledger."),
|
||||
MAKE_ERROR(tefNFTOKEN_IS_NOT_TRANSFERABLE, "The specified NFToken is not transferable."),
|
||||
MAKE_ERROR(tefINVALID_LEDGER_FIX_TYPE, "The LedgerFixType field has an invalid value."),
|
||||
MAKE_ERROR(tefNO_WASM, "There is no WASM code to run, but a WASM-specific field was included."),
|
||||
MAKE_ERROR(tefWASM_FIELD_NOT_INCLUDED, "WASM code requires a field to be included that was not included."),
|
||||
|
||||
MAKE_ERROR(telLOCAL_ERROR, "Local failure."),
|
||||
MAKE_ERROR(telBAD_DOMAIN, "Domain too long."),
|
||||
|
||||
@@ -56,9 +56,12 @@ TxMeta::TxMeta(
|
||||
|
||||
if (obj.isFieldPresent(sfDeliveredAmount))
|
||||
setDeliveredAmount(obj.getFieldAmount(sfDeliveredAmount));
|
||||
|
||||
if (obj.isFieldPresent(sfParentBatchID))
|
||||
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)
|
||||
@@ -82,6 +85,12 @@ TxMeta::TxMeta(uint256 const& txid, std::uint32_t ledger, STObject const& obj)
|
||||
|
||||
if (obj.isFieldPresent(sfParentBatchID))
|
||||
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, Blob const& vec)
|
||||
@@ -97,15 +106,11 @@ TxMeta::TxMeta(
|
||||
{
|
||||
}
|
||||
|
||||
TxMeta::TxMeta(
|
||||
uint256 const& transactionID,
|
||||
std::uint32_t ledger,
|
||||
std::optional<uint256> parentBatchId)
|
||||
TxMeta::TxMeta(uint256 const& transactionID, std::uint32_t ledger)
|
||||
: mTransactionID(transactionID)
|
||||
, mLedger(ledger)
|
||||
, mIndex(static_cast<std::uint32_t>(-1))
|
||||
, mResult(255)
|
||||
, mParentBatchId(parentBatchId)
|
||||
, mNodes(sfAffectedNodes)
|
||||
{
|
||||
mNodes.reserve(32);
|
||||
@@ -253,9 +258,12 @@ TxMeta::getAsObject() const
|
||||
metaData.emplace_back(mNodes);
|
||||
if (hasDeliveredAmount())
|
||||
metaData.setFieldAmount(sfDeliveredAmount, getDeliveredAmount());
|
||||
|
||||
if (hasParentBatchId())
|
||||
metaData.setFieldH256(sfParentBatchID, getParentBatchId());
|
||||
if (hasGasUsed())
|
||||
metaData.setFieldU32(sfGasUsed, getGasUsed());
|
||||
if (hasWasmReturnCode())
|
||||
metaData.setFieldI32(sfWasmReturnCode, getWasmReturnCode());
|
||||
|
||||
return metaData;
|
||||
}
|
||||
|
||||
@@ -17,9 +17,11 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <test/app/wasm_fixtures/fixtures.h>
|
||||
#include <test/jtx.h>
|
||||
|
||||
#include <xrpld/app/tx/applySteps.h>
|
||||
#include <xrpld/app/wasm/WasmVM.h>
|
||||
|
||||
#include <xrpl/ledger/Dir.h>
|
||||
#include <xrpl/protocol/Feature.h>
|
||||
@@ -253,14 +255,6 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT(sle);
|
||||
BEAST_EXPECT((*sle)[sfSourceTag] == 1);
|
||||
BEAST_EXPECT((*sle)[sfDestinationTag] == 2);
|
||||
if (features[fixIncludeKeyletFields])
|
||||
{
|
||||
BEAST_EXPECT((*sle)[sfSequence] == seq);
|
||||
}
|
||||
else
|
||||
{
|
||||
BEAST_EXPECT(!sle->isFieldPresent(sfSequence));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
@@ -302,7 +296,7 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
{
|
||||
testcase("Implied Finish Time (without fix1571)");
|
||||
|
||||
Env env(*this, testable_amendments() - fix1571);
|
||||
Env env(*this, features - fix1571);
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||
env.close();
|
||||
@@ -1559,7 +1553,7 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
Account const alice{"alice"};
|
||||
Account const bob{"bob"};
|
||||
Account const carol{"carol"};
|
||||
Account const dillon{"dillon "};
|
||||
Account const dillon{"dillon"};
|
||||
Account const zelda{"zelda"};
|
||||
|
||||
char const credType[] = "abcde";
|
||||
@@ -1701,21 +1695,785 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testCreateFinishFunctionPreflight(FeatureBitset features)
|
||||
{
|
||||
testcase("Test preflight checks involving FinishFunction");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
// Tests whether the ledger index is >= 5
|
||||
// getLedgerSqn() >= 5}
|
||||
static auto wasmHex = ledgerSqnWasmHex;
|
||||
|
||||
{
|
||||
// featureSmartEscrow disabled
|
||||
Env env(*this, features - featureSmartEscrow);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
XRPAmount const txnFees = env.current()->fees().base + 1000;
|
||||
auto escrowCreate = escrow::create(alice, carol, XRP(100));
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(txnFees),
|
||||
ter(temDISABLED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
// FinishFunction > max length
|
||||
Env env(
|
||||
*this,
|
||||
envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->FEES.extension_size_limit = 10; // 10 bytes
|
||||
return cfg;
|
||||
}),
|
||||
features);
|
||||
XRPAmount const txnFees = env.current()->fees().base + 1000;
|
||||
// create escrow
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
|
||||
auto escrowCreate = escrow::create(alice, carol, XRP(200));
|
||||
|
||||
// 11-byte string
|
||||
std::string longWasmHex = "00112233445566778899AA";
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(longWasmHex),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(txnFees),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
Env env(
|
||||
*this,
|
||||
envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->START_UP = Config::FRESH;
|
||||
return cfg;
|
||||
}),
|
||||
features);
|
||||
XRPAmount const txnFees =
|
||||
env.current()->fees().base * 10 + wasmHex.size() / 2 * 5;
|
||||
// create escrow
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
|
||||
auto escrowCreate = escrow::create(alice, carol, XRP(300));
|
||||
|
||||
// Success situations
|
||||
{
|
||||
// FinishFunction + CancelAfter
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 20s),
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction + Condition + CancelAfter
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 30s),
|
||||
escrow::condition(escrow::cb1),
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction + FinishAfter + CancelAfter
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 40s),
|
||||
escrow::finish_time(env.now() + 2s),
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction + FinishAfter + Condition + CancelAfter
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 50s),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::finish_time(env.now() + 2s),
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// Failure situations (i.e. all other combinations)
|
||||
{
|
||||
// only FinishFunction
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
fee(txnFees),
|
||||
ter(temBAD_EXPIRATION));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction + FinishAfter
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::finish_time(env.now() + 2s),
|
||||
fee(txnFees),
|
||||
ter(temBAD_EXPIRATION));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction + Condition
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::condition(escrow::cb1),
|
||||
fee(txnFees),
|
||||
ter(temBAD_EXPIRATION));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction + FinishAfter + Condition
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::finish_time(env.now() + 2s),
|
||||
fee(txnFees),
|
||||
ter(temBAD_EXPIRATION));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// FinishFunction 0 length
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(""),
|
||||
escrow::cancel_time(env.now() + 60s),
|
||||
fee(txnFees),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// Not enough fees
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 70s),
|
||||
fee(txnFees - 1),
|
||||
ter(telINSUF_FEE_P));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
// FinishFunction nonexistent host function
|
||||
// pub fn finish() -> bool {
|
||||
// unsafe { host_lib::bad() >= 5 }
|
||||
// }
|
||||
auto const badWasmHex =
|
||||
"0061736d010000000105016000017f02100108686f73745f6c696203626164"
|
||||
"00000302010005030100100611027f00418080c0000b7f00418080c0000b07"
|
||||
"2e04066d656d6f727902000666696e69736800010a5f5f646174615f656e64"
|
||||
"03000b5f5f686561705f6261736503010a09010700100041044a0b004d0970"
|
||||
"726f64756365727302086c616e6775616765010452757374000c70726f6365"
|
||||
"737365642d6279010572757374631d312e38352e3120283465623136313235"
|
||||
"3020323032352d30332d31352900490f7461726765745f6665617475726573"
|
||||
"042b0f6d757461626c652d676c6f62616c732b087369676e2d6578742b0f72"
|
||||
"65666572656e63652d74797065732b0a6d756c746976616c7565";
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(badWasmHex),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(txnFees),
|
||||
ter(temBAD_WASM));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFinishWasmFailures(FeatureBitset features)
|
||||
{
|
||||
testcase("EscrowFinish Smart Escrow failures");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
// Tests whether the ledger index is >= 5
|
||||
// getLedgerSqn() >= 5}
|
||||
static auto const& wasmHex = ledgerSqnWasmHex;
|
||||
|
||||
{
|
||||
// featureSmartEscrow disabled
|
||||
Env env(*this, features - featureSmartEscrow);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
XRPAmount const txnFees =
|
||||
env.current()->fees().base * 10 + wasmHex.size() / 2 * 5;
|
||||
env(escrow::finish(carol, alice, 1),
|
||||
fee(txnFees),
|
||||
escrow::comp_allowance(4),
|
||||
ter(temDISABLED));
|
||||
env.close();
|
||||
}
|
||||
|
||||
{
|
||||
// ComputationAllowance > max compute limit
|
||||
Env env(
|
||||
*this,
|
||||
envconfig([](std::unique_ptr<Config> cfg) {
|
||||
cfg->FEES.extension_compute_limit = 1'000; // in gas
|
||||
return cfg;
|
||||
}),
|
||||
features);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
// Run past the flag ledger so that a Fee change vote occurs and
|
||||
// updates FeeSettings. (It also activates all supported
|
||||
// amendments.)
|
||||
for (auto i = env.current()->seq(); i <= 257; ++i)
|
||||
env.close();
|
||||
|
||||
auto const allowance = 1'001;
|
||||
env(escrow::finish(carol, alice, 1),
|
||||
fee(env.current()->fees().base + allowance),
|
||||
escrow::comp_allowance(allowance),
|
||||
ter(temBAD_LIMIT));
|
||||
}
|
||||
|
||||
Env env(*this, features);
|
||||
|
||||
// Run past the flag ledger so that a Fee change vote occurs and
|
||||
// updates FeeSettings. (It also activates all supported
|
||||
// amendments.)
|
||||
for (auto i = env.current()->seq(); i <= 257; ++i)
|
||||
env.close();
|
||||
|
||||
XRPAmount const txnFees =
|
||||
env.current()->fees().base * 10 + wasmHex.size() / 2 * 5;
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
|
||||
// create escrow
|
||||
auto const seq = env.seq(alice);
|
||||
env(escrow::create(alice, carol, XRP(400)),
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
|
||||
{
|
||||
// no ComputationAllowance field
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
ter(tefWASM_FIELD_NOT_INCLUDED));
|
||||
}
|
||||
|
||||
{
|
||||
// ComputationAllowance value of 0
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(0),
|
||||
ter(temBAD_LIMIT));
|
||||
}
|
||||
|
||||
{
|
||||
// not enough fees
|
||||
// This function takes 4 gas
|
||||
// In testing, 1 gas costs 1 drop
|
||||
auto const finishFee = env.current()->fees().base + 3;
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
fee(finishFee),
|
||||
escrow::comp_allowance(4),
|
||||
ter(telINSUF_FEE_P));
|
||||
}
|
||||
|
||||
{
|
||||
// not enough gas
|
||||
// This function takes 4 gas
|
||||
// In testing, 1 gas costs 1 drop
|
||||
auto const finishFee = env.current()->fees().base + 4;
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
fee(finishFee),
|
||||
escrow::comp_allowance(2),
|
||||
ter(tecFAILED_PROCESSING));
|
||||
}
|
||||
|
||||
{
|
||||
// ComputationAllowance field included w/no FinishFunction on
|
||||
// escrow
|
||||
auto const seq2 = env.seq(alice);
|
||||
env(escrow::create(alice, carol, XRP(500)),
|
||||
escrow::finish_time(env.now() + 10s),
|
||||
escrow::cancel_time(env.now() + 100s));
|
||||
env.close();
|
||||
|
||||
auto const allowance = 100;
|
||||
env(escrow::finish(carol, alice, seq2),
|
||||
fee(env.current()->fees().base +
|
||||
(allowance * env.current()->fees().gasPrice) /
|
||||
MICRO_DROPS_PER_DROP +
|
||||
1),
|
||||
escrow::comp_allowance(allowance),
|
||||
ter(tefNO_WASM));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testFinishFunction(FeatureBitset features)
|
||||
{
|
||||
testcase("Example escrow function");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
// Tests whether the ledger index is >= 5
|
||||
// getLedgerSqn() >= 5}
|
||||
auto const& wasmHex = ledgerSqnWasmHex;
|
||||
std::uint32_t const allowance = 66;
|
||||
auto escrowCreate = escrow::create(alice, carol, XRP(600));
|
||||
auto [createFee, finishFee] = [&]() {
|
||||
Env env(*this, features);
|
||||
auto createFee =
|
||||
env.current()->fees().base * 10 + wasmHex.size() / 2 * 5;
|
||||
auto finishFee = env.current()->fees().base +
|
||||
(allowance * env.current()->fees().gasPrice) /
|
||||
MICRO_DROPS_PER_DROP +
|
||||
1;
|
||||
return std::make_pair(createFee, finishFee);
|
||||
}();
|
||||
|
||||
{
|
||||
// basic FinishFunction situation
|
||||
Env env(*this, features);
|
||||
// create escrow
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
auto const seq = env.seq(alice);
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(createFee));
|
||||
env.close();
|
||||
|
||||
if (BEAST_EXPECT(env.ownerCount(alice) == 2))
|
||||
{
|
||||
env.require(balance(alice, XRP(4000) - createFee));
|
||||
env.require(balance(carol, XRP(5000)));
|
||||
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tecWASM_REJECTED));
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tecWASM_REJECTED));
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tecWASM_REJECTED));
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tecWASM_REJECTED));
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tecWASM_REJECTED));
|
||||
env.close();
|
||||
|
||||
{
|
||||
auto const txMeta = env.meta();
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECTS(
|
||||
env.meta()->getFieldU32(sfGasUsed) == allowance,
|
||||
std::to_string(env.meta()->getFieldU32(sfGasUsed)));
|
||||
}
|
||||
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
fee(finishFee),
|
||||
escrow::comp_allowance(allowance),
|
||||
ter(tesSUCCESS));
|
||||
|
||||
auto const txMeta = env.meta();
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECTS(
|
||||
txMeta->getFieldU32(sfGasUsed) == allowance,
|
||||
std::to_string(txMeta->getFieldU32(sfGasUsed)));
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
|
||||
BEAST_EXPECTS(
|
||||
txMeta->getFieldI32(sfWasmReturnCode) == 5,
|
||||
std::to_string(txMeta->getFieldI32(sfWasmReturnCode)));
|
||||
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// FinishFunction + Condition
|
||||
Env env(*this, features);
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
auto const seq = env.seq(alice);
|
||||
// create escrow
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(createFee));
|
||||
env.close();
|
||||
auto const conditionFinishFee = finishFee +
|
||||
env.current()->fees().base * (32 + (escrow::fb1.size() / 16));
|
||||
|
||||
if (BEAST_EXPECT(env.ownerCount(alice) == 2))
|
||||
{
|
||||
env.require(balance(alice, XRP(4000) - createFee));
|
||||
env.require(balance(carol, XRP(5000)));
|
||||
|
||||
// no fulfillment provided, function fails
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tecCRYPTOCONDITION_ERROR));
|
||||
// fulfillment provided, function fails
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::fulfillment(escrow::fb1),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(conditionFinishFee),
|
||||
ter(tecWASM_REJECTED));
|
||||
if (BEAST_EXPECT(env.meta()->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECTS(
|
||||
env.meta()->getFieldU32(sfGasUsed) == allowance,
|
||||
std::to_string(env.meta()->getFieldU32(sfGasUsed)));
|
||||
env.close();
|
||||
// no fulfillment provided, function succeeds
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(conditionFinishFee),
|
||||
ter(tecCRYPTOCONDITION_ERROR));
|
||||
// wrong fulfillment provided, function succeeds
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::fulfillment(escrow::fb2),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(conditionFinishFee),
|
||||
ter(tecCRYPTOCONDITION_ERROR));
|
||||
// fulfillment provided, function succeeds, tx succeeds
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
escrow::condition(escrow::cb1),
|
||||
escrow::fulfillment(escrow::fb1),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(conditionFinishFee),
|
||||
ter(tesSUCCESS));
|
||||
|
||||
auto const txMeta = env.meta();
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
|
||||
BEAST_EXPECTS(
|
||||
txMeta->getFieldI32(sfWasmReturnCode) == 6,
|
||||
std::to_string(txMeta->getFieldI32(sfWasmReturnCode)));
|
||||
|
||||
env.close();
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// FinishFunction + FinishAfter
|
||||
Env env(*this, features);
|
||||
// create escrow
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
auto const seq = env.seq(alice);
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
auto const ts = env.now() + 97s;
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::finish_time(ts),
|
||||
escrow::cancel_time(env.now() + 1000s),
|
||||
fee(createFee));
|
||||
env.close();
|
||||
|
||||
if (BEAST_EXPECT(env.ownerCount(alice) == 2))
|
||||
{
|
||||
env.require(balance(alice, XRP(4000) - createFee));
|
||||
env.require(balance(carol, XRP(5000)));
|
||||
|
||||
// finish time hasn't passed, function fails
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee + 1),
|
||||
ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
// finish time hasn't passed, function succeeds
|
||||
for (; env.now() < ts; env.close())
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee + 2),
|
||||
ter(tecNO_PERMISSION));
|
||||
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee + 1),
|
||||
ter(tesSUCCESS));
|
||||
|
||||
auto const txMeta = env.meta();
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
|
||||
BEAST_EXPECTS(
|
||||
txMeta->getFieldI32(sfWasmReturnCode) == 13,
|
||||
std::to_string(txMeta->getFieldI32(sfWasmReturnCode)));
|
||||
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
// FinishFunction + FinishAfter #2
|
||||
Env env(*this, features);
|
||||
// create escrow
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
auto const seq = env.seq(alice);
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::finish_time(env.now() + 2s),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(createFee));
|
||||
// Don't close the ledger here
|
||||
|
||||
if (BEAST_EXPECT(env.ownerCount(alice) == 2))
|
||||
{
|
||||
env.require(balance(alice, XRP(4000) - createFee));
|
||||
env.require(balance(carol, XRP(5000)));
|
||||
|
||||
// finish time hasn't passed, function fails
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// finish time has passed, function fails
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tecWASM_REJECTED));
|
||||
if (BEAST_EXPECT(env.meta()->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECTS(
|
||||
env.meta()->getFieldU32(sfGasUsed) == allowance,
|
||||
std::to_string(env.meta()->getFieldU32(sfGasUsed)));
|
||||
env.close();
|
||||
// finish time has passed, function succeeds, tx succeeds
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tesSUCCESS));
|
||||
|
||||
auto const txMeta = env.meta();
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECT(txMeta->getFieldU32(sfGasUsed) == allowance);
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
|
||||
BEAST_EXPECTS(
|
||||
txMeta->getFieldI32(sfWasmReturnCode) == 6,
|
||||
std::to_string(txMeta->getFieldI32(sfWasmReturnCode)));
|
||||
|
||||
env.close();
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testAllHostFunctions(FeatureBitset features)
|
||||
{
|
||||
testcase("Test all host functions");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
// TODO: create wasm module for all host functions
|
||||
static auto wasmHex = allHostFunctionsWasmHex;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
{
|
||||
Env env(*this, features);
|
||||
// create escrow
|
||||
env.fund(XRP(5000), alice, carol);
|
||||
auto const seq = env.seq(alice);
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
auto escrowCreate = escrow::create(alice, carol, XRP(700));
|
||||
XRPAmount txnFees =
|
||||
env.current()->fees().base * 10 + wasmHex.size() / 2 * 5;
|
||||
env(escrowCreate,
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::finish_time(env.now() + 11s),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
escrow::data("1000000000"), // 1000 XRP in drops
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
|
||||
if (BEAST_EXPECT(
|
||||
env.ownerCount(alice) == (1 + wasmHex.size() / 2 / 500)))
|
||||
{
|
||||
env.require(balance(alice, XRP(4000) - txnFees));
|
||||
env.require(balance(carol, XRP(5000)));
|
||||
|
||||
auto const allowance = 1'000'000;
|
||||
XRPAmount const finishFee = env.current()->fees().base +
|
||||
(allowance * env.current()->fees().gasPrice) /
|
||||
MICRO_DROPS_PER_DROP +
|
||||
1;
|
||||
|
||||
// FinishAfter time hasn't passed
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
// reduce the destination balance
|
||||
env(pay(carol, alice, XRP(4500)));
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
env(escrow::finish(alice, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee),
|
||||
ter(tesSUCCESS));
|
||||
|
||||
auto const txMeta = env.meta();
|
||||
if (BEAST_EXPECT(txMeta && txMeta->isFieldPresent(sfGasUsed)))
|
||||
BEAST_EXPECTS(
|
||||
txMeta->getFieldU32(sfGasUsed) == 38'571,
|
||||
std::to_string(txMeta->getFieldU32(sfGasUsed)));
|
||||
if (BEAST_EXPECT(txMeta->isFieldPresent(sfWasmReturnCode)))
|
||||
BEAST_EXPECT(txMeta->getFieldI32(sfWasmReturnCode) == 1);
|
||||
|
||||
env.close();
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testKeyletHostFunctions(FeatureBitset features)
|
||||
{
|
||||
testcase("Test all keylet host functions");
|
||||
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
// TODO: create wasm module for all host functions
|
||||
static auto wasmHex = allKeyletsWasmHex;
|
||||
|
||||
Account const alice{"alice"};
|
||||
Account const carol{"carol"};
|
||||
|
||||
{
|
||||
Env env{*this};
|
||||
env.fund(XRP(10000), alice, carol);
|
||||
|
||||
BEAST_EXPECT(env.seq(alice) == 4);
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 0);
|
||||
|
||||
// base objects that need to be created first
|
||||
auto const tokenId =
|
||||
token::getNextID(env, alice, 0, tfTransferable);
|
||||
env(token::mint(alice, 0u), txflags(tfTransferable));
|
||||
env(trust(alice, carol["USD"](1'000'000)));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == 6);
|
||||
BEAST_EXPECT(env.ownerCount(alice) == 2);
|
||||
|
||||
// set up a bunch of objects to check their keylets
|
||||
AMM amm(env, carol, XRP(10), carol["USD"](1000));
|
||||
env(check::create(alice, carol, XRP(100)));
|
||||
env(credentials::create(alice, alice, "termsandconditions"));
|
||||
env(delegate::set(alice, carol, {"TrustSet"}));
|
||||
env(deposit::auth(alice, carol));
|
||||
env(did::set(alice), did::data("alice_did"));
|
||||
env(escrow::create(alice, carol, XRP(800)),
|
||||
escrow::finish_time(env.now() + 100s));
|
||||
MPTTester mptTester{env, alice, {.fund = false}};
|
||||
mptTester.create();
|
||||
mptTester.authorize({.account = carol});
|
||||
env(token::createOffer(carol, tokenId, XRP(100)),
|
||||
token::owner(alice));
|
||||
env(offer(alice, carol["GBP"](0.1), XRP(100)));
|
||||
env(create(alice, carol, XRP(1000), 100s, alice.pk()));
|
||||
pdomain::Credentials credentials{{alice, "first credential"}};
|
||||
env(pdomain::setTx(alice, credentials));
|
||||
env(signers(alice, 1, {{carol, 1}}));
|
||||
env(ticket::create(alice, 1));
|
||||
Vault vault{env};
|
||||
auto [tx, _keylet] =
|
||||
vault.create({.owner = alice, .asset = xrpIssue()});
|
||||
env(tx);
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECTS(
|
||||
env.ownerCount(alice) == 16,
|
||||
std::to_string(env.ownerCount(alice)));
|
||||
if (BEAST_EXPECTS(
|
||||
env.seq(alice) == 20, std::to_string(env.seq(alice))))
|
||||
{
|
||||
auto const seq = env.seq(alice);
|
||||
XRPAmount txnFees =
|
||||
env.current()->fees().base * 10 + wasmHex.size() / 2 * 5;
|
||||
env(escrow::create(alice, carol, XRP(900)),
|
||||
escrow::finish_function(wasmHex),
|
||||
escrow::finish_time(env.now() + 2s),
|
||||
escrow::cancel_time(env.now() + 100s),
|
||||
fee(txnFees));
|
||||
env.close();
|
||||
env.close();
|
||||
env.close();
|
||||
|
||||
auto const allowance = 137'596;
|
||||
auto const finishFee = env.current()->fees().base +
|
||||
(allowance * env.current()->fees().gasPrice) /
|
||||
MICRO_DROPS_PER_DROP +
|
||||
1;
|
||||
env(escrow::finish(carol, alice, seq),
|
||||
escrow::comp_allowance(allowance),
|
||||
fee(finishFee));
|
||||
env.close();
|
||||
|
||||
auto const txMeta = env.meta();
|
||||
if (BEAST_EXPECT(txMeta && txMeta->isFieldPresent(sfGasUsed)))
|
||||
{
|
||||
auto const gasUsed = txMeta->getFieldU32(sfGasUsed);
|
||||
BEAST_EXPECTS(
|
||||
gasUsed == allowance, std::to_string(gasUsed));
|
||||
}
|
||||
BEAST_EXPECTS(
|
||||
env.ownerCount(alice) == 16,
|
||||
std::to_string(env.ownerCount(alice)));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testWithFeats(FeatureBitset features)
|
||||
{
|
||||
testEnablement(features);
|
||||
testTiming(features);
|
||||
testTags(features);
|
||||
testDisallowXRP(features);
|
||||
test1571(features);
|
||||
testFails(features);
|
||||
testLockup(features);
|
||||
testEscrowConditions(features);
|
||||
testMetaAndOwnership(features);
|
||||
testConsequences(features);
|
||||
testEscrowWithTickets(features);
|
||||
testCredentials(features);
|
||||
// testEnablement(features);
|
||||
// testTiming(features);
|
||||
// testTags(features);
|
||||
// testDisallowXRP(features);
|
||||
// test1571(features);
|
||||
// testFails(features);
|
||||
// testLockup(features);
|
||||
// testEscrowConditions(features);
|
||||
// testMetaAndOwnership(features);
|
||||
// testConsequences(features);
|
||||
// testEscrowWithTickets(features);
|
||||
// testCredentials(features);
|
||||
testCreateFinishFunctionPreflight(features);
|
||||
testFinishWasmFailures(features);
|
||||
testFinishFunction(features);
|
||||
|
||||
// TODO: Update module with new host functions
|
||||
testAllHostFunctions(features);
|
||||
testKeyletHostFunctions(features);
|
||||
}
|
||||
|
||||
public:
|
||||
@@ -1725,9 +2483,8 @@ public:
|
||||
using namespace test::jtx;
|
||||
FeatureBitset const all{testable_amendments()};
|
||||
testWithFeats(all);
|
||||
testWithFeats(all - featureTokenEscrow);
|
||||
testTags(all - fixIncludeKeyletFields);
|
||||
}
|
||||
// testWithFeats(all - featureTokenEscrow);
|
||||
};
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(Escrow, app, ripple);
|
||||
|
||||
@@ -708,7 +708,7 @@ struct Wasm_test : public beast::unit_test::suite
|
||||
testWasmSha();
|
||||
testWasmB58();
|
||||
|
||||
// running too long
|
||||
// runing too long
|
||||
// testWasmSP1Verifier();
|
||||
testWasmBG16Verifier();
|
||||
|
||||
|
||||
@@ -94,16 +94,81 @@ std::array<std::uint8_t, 39> const cb3 = {
|
||||
0x26, 0x4A, 0x2D, 0x85, 0x7B, 0xE8, 0xA0, 0x9C, 0x1D, 0xFD,
|
||||
0x57, 0x0D, 0x15, 0x85, 0x8B, 0xD4, 0x81, 0x01, 0x04}};
|
||||
|
||||
/** Set the "FinishAfter" time tag on a JTx */
|
||||
auto const finish_time = JTxFieldWrapper<timePointField>(sfFinishAfter);
|
||||
|
||||
/** Set the "CancelAfter" time tag on a JTx */
|
||||
auto const cancel_time = JTxFieldWrapper<timePointField>(sfCancelAfter);
|
||||
|
||||
auto const condition = JTxFieldWrapper<blobField>(sfCondition);
|
||||
|
||||
auto const fulfillment = JTxFieldWrapper<blobField>(sfFulfillment);
|
||||
|
||||
struct finish_function
|
||||
{
|
||||
private:
|
||||
std::string value_;
|
||||
|
||||
public:
|
||||
explicit finish_function(std::string func) : value_(func)
|
||||
{
|
||||
}
|
||||
|
||||
explicit finish_function(Slice const& func) : value_(strHex(func))
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit finish_function(std::array<std::uint8_t, N> const& f)
|
||||
: finish_function(makeSlice(f))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfFinishFunction.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
struct data
|
||||
{
|
||||
private:
|
||||
std::string value_;
|
||||
|
||||
public:
|
||||
explicit data(std::string func) : value_(func)
|
||||
{
|
||||
}
|
||||
|
||||
explicit data(Slice const& func) : value_(strHex(func))
|
||||
{
|
||||
}
|
||||
|
||||
template <size_t N>
|
||||
explicit data(std::array<std::uint8_t, N> const& f) : data(makeSlice(f))
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfData.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
struct comp_allowance
|
||||
{
|
||||
private:
|
||||
std::uint32_t value_;
|
||||
|
||||
public:
|
||||
explicit comp_allowance(std::uint32_t const& value) : value_(value)
|
||||
{
|
||||
}
|
||||
|
||||
void
|
||||
operator()(Env&, JTx& jt) const
|
||||
{
|
||||
jt.jv[sfComputationAllowance.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace escrow
|
||||
|
||||
} // namespace jtx
|
||||
|
||||
@@ -33,9 +33,14 @@ setupConfigForUnitTests(Config& cfg)
|
||||
using namespace jtx;
|
||||
// Default fees to old values, so tests don't have to worry about changes in
|
||||
// Config.h
|
||||
// NOTE: For new `FEES` fields, you need to wait for the first flag ledger
|
||||
// to close for the values to be activated.
|
||||
cfg.FEES.reference_fee = UNIT_TEST_REFERENCE_FEE;
|
||||
cfg.FEES.account_reserve = XRP(200).value().xrp().drops();
|
||||
cfg.FEES.owner_reserve = XRP(50).value().xrp().drops();
|
||||
cfg.FEES.extension_compute_limit = 1'000'000;
|
||||
cfg.FEES.extension_size_limit = 100'000;
|
||||
cfg.FEES.gas_price = 1'000'000; // 1 drop = 1,000,000 micro-drops
|
||||
|
||||
// The Beta API (currently v2) is always available to tests
|
||||
cfg.BETA_RPC_API = true;
|
||||
|
||||
@@ -122,10 +122,27 @@ struct STAccount_test : public beast::unit_test::suite
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testAccountID()
|
||||
{
|
||||
auto const s = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
|
||||
if (auto const parsed = parseBase58<AccountID>(s); BEAST_EXPECT(parsed))
|
||||
{
|
||||
BEAST_EXPECT(toBase58(*parsed) == s);
|
||||
}
|
||||
|
||||
{
|
||||
auto const s =
|
||||
"âabcd1rNxp4h8apvRis6mJf9Sh8C6iRxfrDWNâabcdAVâ\xc2\x80\xc2\x8f";
|
||||
BEAST_EXPECT(!parseBase58<AccountID>(s));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testSTAccount();
|
||||
testAccountID();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
94
src/test/protocol/STInteger_test.cpp
Normal file
94
src/test/protocol/STInteger_test.cpp
Normal file
@@ -0,0 +1,94 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
This file is part of rippled: https://github.com/ripple/rippled
|
||||
Copyright (c) 2025 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 <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/protocol/STInteger.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
struct STInteger_test : public beast::unit_test::suite
|
||||
{
|
||||
void
|
||||
testUInt8()
|
||||
{
|
||||
STUInt8 u8(42);
|
||||
BEAST_EXPECT(u8.value() == 42);
|
||||
BEAST_EXPECT(u8.getText() == "42");
|
||||
BEAST_EXPECT(u8.getSType() == STI_UINT8);
|
||||
BEAST_EXPECT(u8.getJson(JsonOptions::none) == 42);
|
||||
}
|
||||
|
||||
void
|
||||
testUInt16()
|
||||
{
|
||||
STUInt16 u16(65535);
|
||||
BEAST_EXPECT(u16.value() == 65535);
|
||||
BEAST_EXPECT(u16.getText() == "65535");
|
||||
BEAST_EXPECT(u16.getSType() == STI_UINT16);
|
||||
BEAST_EXPECT(u16.getJson(JsonOptions::none) == 65535);
|
||||
}
|
||||
|
||||
void
|
||||
testUInt32()
|
||||
{
|
||||
STUInt32 u32(1234567890);
|
||||
BEAST_EXPECT(u32.value() == 1234567890);
|
||||
BEAST_EXPECT(u32.getText() == "1234567890");
|
||||
BEAST_EXPECT(u32.getSType() == STI_UINT32);
|
||||
BEAST_EXPECT(u32.getJson(JsonOptions::none) == 1234567890);
|
||||
}
|
||||
|
||||
void
|
||||
testUInt64()
|
||||
{
|
||||
STUInt64 u64(0x123456789ABCDEF0ull);
|
||||
BEAST_EXPECT(u64.value() == 0x123456789ABCDEF0ull);
|
||||
BEAST_EXPECT(u64.getText() == "1311768467463790320");
|
||||
BEAST_EXPECT(u64.getSType() == STI_UINT64);
|
||||
|
||||
// By default, getJson returns hex string
|
||||
auto jsonVal = u64.getJson(JsonOptions::none);
|
||||
BEAST_EXPECT(jsonVal.isString());
|
||||
BEAST_EXPECT(jsonVal.asString() == "123456789abcdef0");
|
||||
}
|
||||
|
||||
void
|
||||
testInt32()
|
||||
{
|
||||
STInt32 i32(-123456789);
|
||||
BEAST_EXPECT(i32.value() == -123456789);
|
||||
BEAST_EXPECT(i32.getText() == "-123456789");
|
||||
BEAST_EXPECT(i32.getSType() == STI_INT32);
|
||||
BEAST_EXPECT(i32.getJson(JsonOptions::none) == -123456789);
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testUInt8();
|
||||
testUInt16();
|
||||
testUInt32();
|
||||
testUInt64();
|
||||
testInt32();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(STInteger, protocol, ripple);
|
||||
|
||||
} // namespace ripple
|
||||
@@ -1,52 +0,0 @@
|
||||
//------------------------------------------------------------------------------
|
||||
/*
|
||||
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 <xrpl/beast/unit_test.h>
|
||||
#include <xrpl/protocol/UintTypes.h>
|
||||
|
||||
namespace ripple {
|
||||
|
||||
struct types_test : public beast::unit_test::suite
|
||||
{
|
||||
void
|
||||
testAccountID()
|
||||
{
|
||||
auto const s = "rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh";
|
||||
if (auto const parsed = parseBase58<AccountID>(s); BEAST_EXPECT(parsed))
|
||||
{
|
||||
BEAST_EXPECT(toBase58(*parsed) == s);
|
||||
}
|
||||
|
||||
{
|
||||
auto const s =
|
||||
"âabcd1rNxp4h8apvRis6mJf9Sh8C6iRxfrDWNâabcdAVâ\xc2\x80\xc2\x8f";
|
||||
BEAST_EXPECT(!parseBase58<AccountID>(s));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
run() override
|
||||
{
|
||||
testAccountID();
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(types, protocol, ripple);
|
||||
|
||||
} // namespace ripple
|
||||
@@ -59,6 +59,11 @@ 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);
|
||||
}
|
||||
|
||||
@@ -106,6 +106,20 @@ public:
|
||||
view_->deliver(amount);
|
||||
}
|
||||
|
||||
/** Sets the gas used in the metadata */
|
||||
void
|
||||
setGasUsed(std::uint32_t const gasUsed)
|
||||
{
|
||||
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();
|
||||
@@ -157,6 +171,8 @@ 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
|
||||
|
||||
@@ -20,6 +20,8 @@
|
||||
#include <xrpld/app/misc/HashRouter.h>
|
||||
#include <xrpld/app/tx/detail/Escrow.h>
|
||||
#include <xrpld/app/tx/detail/MPTokenAuthorize.h>
|
||||
#include <xrpld/app/wasm/HostFuncImpl.h>
|
||||
#include <xrpld/app/wasm/WasmVM.h>
|
||||
#include <xrpld/conditions/Condition.h>
|
||||
#include <xrpld/conditions/Fulfillment.h>
|
||||
|
||||
@@ -34,6 +36,7 @@
|
||||
#include <xrpl/protocol/TxFlags.h>
|
||||
#include <xrpl/protocol/XRPAmount.h>
|
||||
|
||||
#include <algorithm>
|
||||
namespace ripple {
|
||||
|
||||
// During an EscrowFinish, the transaction must specify both
|
||||
@@ -118,9 +121,29 @@ escrowCreatePreflightHelper<MPTIssue>(PreflightContext const& ctx)
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
XRPAmount
|
||||
EscrowCreate::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
XRPAmount txnFees{Transactor::calculateBaseFee(view, tx)};
|
||||
if (tx.isFieldPresent(sfFinishFunction))
|
||||
{
|
||||
// 10 base fees for the transaction (1 is in
|
||||
// `Transactor::calculateBaseFee`), plus 5 drops per byte
|
||||
txnFees += 9 * view.fees().base + 5 * tx[sfFinishFunction].size();
|
||||
}
|
||||
return txnFees;
|
||||
}
|
||||
|
||||
NotTEC
|
||||
EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
{
|
||||
if (ctx.tx.isFieldPresent(sfFinishFunction) &&
|
||||
!ctx.rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "SmartEscrow not enabled";
|
||||
return temDISABLED;
|
||||
}
|
||||
|
||||
if (ctx.rules.enabled(fix1543) && ctx.tx.getFlags() & tfUniversalMask)
|
||||
return temINVALID_FLAG;
|
||||
|
||||
@@ -157,14 +180,23 @@ EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
|
||||
return temBAD_EXPIRATION;
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfFinishFunction) &&
|
||||
!ctx.tx.isFieldPresent(sfCancelAfter))
|
||||
return temBAD_EXPIRATION;
|
||||
|
||||
if (ctx.rules.enabled(fix1571))
|
||||
{
|
||||
// In the absence of a FinishAfter, the escrow can be finished
|
||||
// immediately, which can be confusing. When creating an escrow,
|
||||
// we want to ensure that either a FinishAfter time is explicitly
|
||||
// specified or a completion condition is attached.
|
||||
if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition])
|
||||
if (!ctx.tx[~sfFinishAfter] && !ctx.tx[~sfCondition] &&
|
||||
!ctx.tx[~sfFinishFunction])
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Must have at least one of FinishAfter, "
|
||||
"Condition, or FinishFunction.";
|
||||
return temMALFORMED;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const cb = ctx.tx[~sfCondition])
|
||||
@@ -189,6 +221,27 @@ EscrowCreate::preflight(PreflightContext const& ctx)
|
||||
return temDISABLED;
|
||||
}
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfFinishFunction))
|
||||
{
|
||||
auto const code = ctx.tx.getFieldVL(sfFinishFunction);
|
||||
if (code.size() == 0 ||
|
||||
code.size() > ctx.app.config().FEES.extension_size_limit)
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "EscrowCreate.FinishFunction bad size " << code.size();
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
HostFunctions mock;
|
||||
auto const re =
|
||||
preflightEscrowWasm(code, ESCROW_FUNCTION_NAME, {}, &mock, ctx.j);
|
||||
if (!isTesSuccess(re))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "EscrowCreate.FinishFunction bad WASM";
|
||||
return re;
|
||||
}
|
||||
}
|
||||
|
||||
return preflight2(ctx);
|
||||
}
|
||||
|
||||
@@ -451,6 +504,17 @@ escrowLockApplyHelper<MPTIssue>(
|
||||
return tesSUCCESS;
|
||||
}
|
||||
|
||||
template <class T>
|
||||
static uint32_t
|
||||
calculateAdditionalReserve(T const& finishFunction)
|
||||
{
|
||||
if (!finishFunction)
|
||||
return 1;
|
||||
// First 500 bytes included in the normal reserve
|
||||
// Each additional 500 bytes requires an additional reserve
|
||||
return 1 + (finishFunction->size() / 500);
|
||||
}
|
||||
|
||||
TER
|
||||
EscrowCreate::doApply()
|
||||
{
|
||||
@@ -494,9 +558,11 @@ EscrowCreate::doApply()
|
||||
|
||||
// Check reserve and funds availability
|
||||
STAmount const amount{ctx_.tx[sfAmount]};
|
||||
auto const reserveToAdd =
|
||||
calculateAdditionalReserve(ctx_.tx[~sfFinishFunction]);
|
||||
|
||||
auto const reserve =
|
||||
ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + 1);
|
||||
ctx_.view().fees().accountReserve((*sle)[sfOwnerCount] + reserveToAdd);
|
||||
|
||||
if (mSourceBalance < reserve)
|
||||
return tecINSUFFICIENT_RESERVE;
|
||||
@@ -537,6 +603,8 @@ EscrowCreate::doApply()
|
||||
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
|
||||
(*slep)[~sfFinishAfter] = ctx_.tx[~sfFinishAfter];
|
||||
(*slep)[~sfDestinationTag] = ctx_.tx[~sfDestinationTag];
|
||||
(*slep)[~sfFinishFunction] = ctx_.tx[~sfFinishFunction];
|
||||
(*slep)[~sfData] = ctx_.tx[~sfData];
|
||||
|
||||
if (ctx_.view().rules().enabled(fixIncludeKeyletFields))
|
||||
{
|
||||
@@ -604,7 +672,8 @@ EscrowCreate::doApply()
|
||||
}
|
||||
|
||||
// increment owner count
|
||||
adjustOwnerCount(ctx_.view(), sle, 1, ctx_.journal);
|
||||
// TODO: determine actual reserve based on FinishFunction size
|
||||
adjustOwnerCount(ctx_.view(), sle, reserveToAdd, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
return tesSUCCESS;
|
||||
}
|
||||
@@ -639,6 +708,13 @@ EscrowFinish::preflight(PreflightContext const& ctx)
|
||||
!ctx.rules.enabled(featureCredentials))
|
||||
return temDISABLED;
|
||||
|
||||
if (ctx.tx.isFieldPresent(sfComputationAllowance) &&
|
||||
!ctx.rules.enabled(featureSmartEscrow))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "SmartEscrow not enabled";
|
||||
return temDISABLED;
|
||||
}
|
||||
|
||||
if (auto const ret = preflight1(ctx); !isTesSuccess(ret))
|
||||
return ret;
|
||||
|
||||
@@ -648,7 +724,10 @@ EscrowFinish::preflight(PreflightContext const& ctx)
|
||||
// If you specify a condition, then you must also specify
|
||||
// a fulfillment.
|
||||
if (static_cast<bool>(cb) != static_cast<bool>(fb))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "Condition != Fulfillment";
|
||||
return temMALFORMED;
|
||||
}
|
||||
|
||||
// Verify the transaction signature. If it doesn't work
|
||||
// then don't do any more work.
|
||||
@@ -677,6 +756,20 @@ EscrowFinish::preflight(PreflightContext const& ctx)
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const allowance = ctx.tx[~sfComputationAllowance]; allowance)
|
||||
{
|
||||
if (*allowance == 0)
|
||||
{
|
||||
return temBAD_LIMIT;
|
||||
}
|
||||
if (*allowance > ctx.app.config().FEES.extension_compute_limit)
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "ComputationAllowance too large: " << *allowance;
|
||||
return temBAD_LIMIT;
|
||||
}
|
||||
}
|
||||
|
||||
if (auto const err = credentials::checkFields(ctx.tx, ctx.j);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
@@ -693,7 +786,14 @@ EscrowFinish::calculateBaseFee(ReadView const& view, STTx const& tx)
|
||||
{
|
||||
extraFee += view.fees().base * (32 + (fb->size() / 16));
|
||||
}
|
||||
|
||||
if (auto const allowance = tx[~sfComputationAllowance]; allowance)
|
||||
{
|
||||
// The extra fee is the allowance in drops, rounded up to the nearest
|
||||
// whole drop.
|
||||
// Integer math rounds down by default, so we add 1 to round up.
|
||||
extraFee +=
|
||||
((*allowance) * view.fees().gasPrice) / MICRO_DROPS_PER_DROP + 1;
|
||||
}
|
||||
return Transactor::calculateBaseFee(view, tx) + extraFee;
|
||||
}
|
||||
|
||||
@@ -773,25 +873,52 @@ EscrowFinish::preclaim(PreclaimContext const& ctx)
|
||||
return err;
|
||||
}
|
||||
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow))
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow) ||
|
||||
ctx.view.rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
// this check is done in doApply before this amendment is enabled
|
||||
auto const k = keylet::escrow(ctx.tx[sfOwner], ctx.tx[sfOfferSequence]);
|
||||
auto const slep = ctx.view.read(k);
|
||||
if (!slep)
|
||||
return tecNO_TARGET;
|
||||
|
||||
AccountID const dest = (*slep)[sfDestination];
|
||||
STAmount const amount = (*slep)[sfAmount];
|
||||
|
||||
if (!isXRP(amount))
|
||||
if (ctx.view.rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowFinishPreclaimHelper<T>(ctx, dest, amount);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
if (slep->isFieldPresent(sfFinishFunction))
|
||||
{
|
||||
if (!ctx.tx.isFieldPresent(sfComputationAllowance))
|
||||
{
|
||||
JLOG(ctx.j.debug())
|
||||
<< "FinishFunction requires ComputationAllowance";
|
||||
return tefWASM_FIELD_NOT_INCLUDED;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
if (ctx.tx.isFieldPresent(sfComputationAllowance))
|
||||
{
|
||||
JLOG(ctx.j.debug()) << "FinishFunction not present, "
|
||||
"ComputationAllowance present";
|
||||
return tefNO_WASM;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (ctx.view.rules().enabled(featureTokenEscrow))
|
||||
{
|
||||
AccountID const dest = (*slep)[sfDestination];
|
||||
STAmount const amount = (*slep)[sfAmount];
|
||||
|
||||
if (!isXRP(amount))
|
||||
{
|
||||
if (auto const ret = std::visit(
|
||||
[&]<typename T>(T const&) {
|
||||
return escrowFinishPreclaimHelper<T>(
|
||||
ctx, dest, amount);
|
||||
},
|
||||
amount.asset().value());
|
||||
!isTesSuccess(ret))
|
||||
return ret;
|
||||
}
|
||||
}
|
||||
}
|
||||
return tesSUCCESS;
|
||||
@@ -1012,6 +1139,7 @@ escrowUnlockApplyHelper<MPTIssue>(
|
||||
// compute balance to transfer
|
||||
finalAmt = amount.value() - xferFee;
|
||||
}
|
||||
|
||||
return rippleUnlockEscrowMPT(
|
||||
view,
|
||||
sender,
|
||||
@@ -1028,7 +1156,8 @@ EscrowFinish::doApply()
|
||||
auto const slep = ctx_.view().peek(k);
|
||||
if (!slep)
|
||||
{
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow))
|
||||
if (ctx_.view().rules().enabled(featureTokenEscrow) ||
|
||||
ctx_.view().rules().enabled(featureSmartEscrow))
|
||||
return tecINTERNAL; // LCOV_EXCL_LINE
|
||||
|
||||
return tecNO_TARGET;
|
||||
@@ -1043,11 +1172,17 @@ EscrowFinish::doApply()
|
||||
|
||||
// Too soon: can't execute before the finish time
|
||||
if ((*slep)[~sfFinishAfter] && !after(now, (*slep)[sfFinishAfter]))
|
||||
{
|
||||
JLOG(j_.debug()) << "Too soon";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// Too late: can't execute after the cancel time
|
||||
if ((*slep)[~sfCancelAfter] && after(now, (*slep)[sfCancelAfter]))
|
||||
{
|
||||
JLOG(j_.debug()) << "Too late";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
@@ -1055,13 +1190,36 @@ EscrowFinish::doApply()
|
||||
if ((*slep)[~sfFinishAfter] &&
|
||||
ctx_.view().info().parentCloseTime.time_since_epoch().count() <=
|
||||
(*slep)[sfFinishAfter])
|
||||
{
|
||||
JLOG(j_.debug()) << "Too soon?";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
|
||||
// Too late?
|
||||
if ((*slep)[~sfCancelAfter] &&
|
||||
ctx_.view().info().parentCloseTime.time_since_epoch().count() <=
|
||||
(*slep)[sfCancelAfter])
|
||||
{
|
||||
JLOG(j_.debug()) << "Too late?";
|
||||
return tecNO_PERMISSION;
|
||||
}
|
||||
}
|
||||
|
||||
AccountID const destID = (*slep)[sfDestination];
|
||||
auto const sled = ctx_.view().peek(keylet::account(destID));
|
||||
if (ctx_.view().rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
// NOTE: Escrow payments cannot be used to fund accounts.
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
if (ctx_.view().rules().enabled(featureDepositAuth))
|
||||
{
|
||||
if (auto err = verifyDepositPreauth(
|
||||
ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// Check cryptocondition fulfillment
|
||||
@@ -1111,18 +1269,65 @@ EscrowFinish::doApply()
|
||||
return tecCRYPTOCONDITION_ERROR;
|
||||
}
|
||||
|
||||
// NOTE: Escrow payments cannot be used to fund accounts.
|
||||
AccountID const destID = (*slep)[sfDestination];
|
||||
auto const sled = ctx_.view().peek(keylet::account(destID));
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
if (ctx_.view().rules().enabled(featureDepositAuth))
|
||||
if (!ctx_.view().rules().enabled(featureSmartEscrow))
|
||||
{
|
||||
if (auto err = verifyDepositPreauth(
|
||||
ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
// NOTE: Escrow payments cannot be used to fund accounts.
|
||||
if (!sled)
|
||||
return tecNO_DST;
|
||||
|
||||
if (ctx_.view().rules().enabled(featureDepositAuth))
|
||||
{
|
||||
if (auto err = verifyDepositPreauth(
|
||||
ctx_.tx, ctx_.view(), account_, destID, sled, ctx_.journal);
|
||||
!isTesSuccess(err))
|
||||
return err;
|
||||
}
|
||||
}
|
||||
|
||||
// Execute custom release function
|
||||
if ((*slep)[~sfFinishFunction])
|
||||
{
|
||||
JLOG(j_.trace())
|
||||
<< "The escrow has a finish function, running WASM code...";
|
||||
// WASM execution
|
||||
auto const wasmStr = slep->getFieldVL(sfFinishFunction);
|
||||
std::vector<uint8_t> wasm(wasmStr.begin(), wasmStr.end());
|
||||
|
||||
WasmHostFunctionsImpl ledgerDataProvider(ctx_, k);
|
||||
|
||||
if (!ctx_.tx.isFieldPresent(sfComputationAllowance))
|
||||
{
|
||||
// already checked above, this check is just in case
|
||||
return tecINTERNAL;
|
||||
}
|
||||
std::uint32_t allowance = ctx_.tx[sfComputationAllowance];
|
||||
auto re = runEscrowWasm(
|
||||
wasm, ESCROW_FUNCTION_NAME, {}, &ledgerDataProvider, allowance);
|
||||
JLOG(j_.trace()) << "Escrow WASM ran";
|
||||
|
||||
if (auto const& data = ledgerDataProvider.getData(); data.has_value())
|
||||
{
|
||||
slep->setFieldVL(sfData, makeSlice(*data));
|
||||
}
|
||||
|
||||
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 <= 0)
|
||||
{
|
||||
return tecWASM_REJECTED;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
JLOG(j_.debug()) << "WASM Failure: " + transHuman(re.error());
|
||||
return re.error();
|
||||
}
|
||||
}
|
||||
|
||||
AccountID const account = (*slep)[sfAccount];
|
||||
@@ -1195,9 +1400,12 @@ EscrowFinish::doApply()
|
||||
|
||||
ctx_.view().update(sled);
|
||||
|
||||
auto const reserveToSubtract =
|
||||
calculateAdditionalReserve((*slep)[~sfFinishFunction]);
|
||||
|
||||
// Adjust source owner count
|
||||
auto const sle = ctx_.view().peek(keylet::account(account));
|
||||
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
|
||||
adjustOwnerCount(ctx_.view(), sle, -1 * reserveToSubtract, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
// Remove escrow from ledger
|
||||
@@ -1408,7 +1616,9 @@ EscrowCancel::doApply()
|
||||
}
|
||||
}
|
||||
|
||||
adjustOwnerCount(ctx_.view(), sle, -1, ctx_.journal);
|
||||
auto const reserveToSubtract =
|
||||
calculateAdditionalReserve((*slep)[~sfFinishFunction]);
|
||||
adjustOwnerCount(ctx_.view(), sle, -1 * reserveToSubtract, ctx_.journal);
|
||||
ctx_.view().update(sle);
|
||||
|
||||
// Remove escrow from ledger
|
||||
|
||||
@@ -36,6 +36,9 @@ public:
|
||||
static TxConsequences
|
||||
makeTxConsequences(PreflightContext const& ctx);
|
||||
|
||||
static XRPAmount
|
||||
calculateBaseFee(ReadView const& view, STTx const& tx);
|
||||
|
||||
static NotTEC
|
||||
preflight(PreflightContext const& ctx);
|
||||
|
||||
|
||||
@@ -967,6 +967,22 @@ removeExpiredCredentials(
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
modifyWasmDataFields(
|
||||
ApplyView& view,
|
||||
std::vector<std::pair<uint256, Blob>> const& wasmObjects,
|
||||
beast::Journal viewJ)
|
||||
{
|
||||
for (auto const& [index, data] : wasmObjects)
|
||||
{
|
||||
if (auto const sle = view.peek(keylet::escrow(index)))
|
||||
{
|
||||
sle->setFieldVL(sfData, data);
|
||||
view.update(sle);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
removeDeletedTrustLines(
|
||||
ApplyView& view,
|
||||
@@ -1124,6 +1140,7 @@ Transactor::operator()()
|
||||
else if (
|
||||
(result == tecOVERSIZE) || (result == tecKILLED) ||
|
||||
(result == tecINCOMPLETE) || (result == tecEXPIRED) ||
|
||||
(result == tecWASM_REJECTED) ||
|
||||
(isTecClaimHardFail(result, view().flags())))
|
||||
{
|
||||
JLOG(j_.trace()) << "reapplying because of " << transToken(result);
|
||||
@@ -1136,13 +1153,16 @@ Transactor::operator()()
|
||||
std::vector<uint256> removedTrustLines;
|
||||
std::vector<uint256> expiredNFTokenOffers;
|
||||
std::vector<uint256> expiredCredentials;
|
||||
std::vector<std::pair<uint256, Blob>> modifiedWasmObjects;
|
||||
|
||||
bool const doOffers =
|
||||
((result == tecOVERSIZE) || (result == tecKILLED));
|
||||
bool const doLines = (result == tecINCOMPLETE);
|
||||
bool const doNFTokenOffers = (result == tecEXPIRED);
|
||||
bool const doCredentials = (result == tecEXPIRED);
|
||||
if (doOffers || doLines || doNFTokenOffers || doCredentials)
|
||||
bool const doWasmData = (result == tecWASM_REJECTED);
|
||||
if (doOffers || doLines || doNFTokenOffers || doCredentials ||
|
||||
doWasmData)
|
||||
{
|
||||
ctx_.visit([doOffers,
|
||||
&removedOffers,
|
||||
@@ -1151,7 +1171,9 @@ Transactor::operator()()
|
||||
doNFTokenOffers,
|
||||
&expiredNFTokenOffers,
|
||||
doCredentials,
|
||||
&expiredCredentials](
|
||||
&expiredCredentials,
|
||||
doWasmData,
|
||||
&modifiedWasmObjects](
|
||||
uint256 const& index,
|
||||
bool isDelete,
|
||||
std::shared_ptr<SLE const> const& before,
|
||||
@@ -1186,6 +1208,13 @@ Transactor::operator()()
|
||||
(before->getType() == ltCREDENTIAL))
|
||||
expiredCredentials.push_back(index);
|
||||
}
|
||||
|
||||
if (doWasmData && before && after &&
|
||||
(before->getType() == ltESCROW))
|
||||
{
|
||||
modifiedWasmObjects.push_back(
|
||||
std::make_pair(index, after->getFieldVL(sfData)));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1215,6 +1244,10 @@ Transactor::operator()()
|
||||
removeExpiredCredentials(
|
||||
view(), expiredCredentials, ctx_.app.journal("View"));
|
||||
|
||||
if (result == tecWASM_REJECTED)
|
||||
modifyWasmDataFields(
|
||||
view(), modifiedWasmObjects, ctx_.app.journal("View"));
|
||||
|
||||
applied = isTecClaim(result);
|
||||
}
|
||||
|
||||
|
||||
@@ -73,6 +73,15 @@ struct FeeSetup
|
||||
/** The per-owned item reserve requirement in drops. */
|
||||
XRPAmount owner_reserve{2 * DROPS_PER_XRP};
|
||||
|
||||
/** The compute limit for Feature Extensions. */
|
||||
std::uint32_t extension_compute_limit{1'000'000};
|
||||
|
||||
/** The WASM size limit for Feature Extensions. */
|
||||
std::uint32_t extension_size_limit{100'000};
|
||||
|
||||
/** The price of 1 WASM gas, in micro-drops. */
|
||||
std::uint32_t gas_price{1'000'000};
|
||||
|
||||
/* (Remember to update the example cfg files when changing any of these
|
||||
* values.) */
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user