mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-29 15:05:50 +00:00
Conditional Suspended Payments (RIPD-1140):
A conditional suspended payment is a suspended payment where completion of the payment is contingent upon the fulfillment of a condition defined by the sender during creation of the suspended payment. This commit also introduces the "CryptoConditions" amendment which controls whether cryptoconditions will be supported in suspended payments. The existing "SusPay" amendment can be used to enable suspended payments without enabling the cryptoconditions code.
This commit is contained in:
@@ -45,9 +45,10 @@ supportedAmendments ()
|
|||||||
{ "DA1BD556B42D85EA9C84066D028D355B52416734D3283F85E216EA5DA6DB7E13 SusPay" },
|
{ "DA1BD556B42D85EA9C84066D028D355B52416734D3283F85E216EA5DA6DB7E13 SusPay" },
|
||||||
{ "6781F8368C4771B83E8B821D88F580202BCB4228075297B19E4FDC5233F1EFDC TrustSetAuth" },
|
{ "6781F8368C4771B83E8B821D88F580202BCB4228075297B19E4FDC5233F1EFDC TrustSetAuth" },
|
||||||
{ "42426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97ABF044EE FeeEscalation" },
|
{ "42426C4D4F1009EE67080A9B7965B44656D7714D104A72F9B4369F97ABF044EE FeeEscalation" },
|
||||||
{ "9178256A980A86CF3D70D0260A7DA6402AAFE43632FDBCB88037978404188871 OwnerPaysFee"},
|
{ "9178256A980A86CF3D70D0260A7DA6402AAFE43632FDBCB88037978404188871 OwnerPaysFee" },
|
||||||
{ "08DE7D96082187F6E6578530258C77FAABABE4C20474BDB82F04B021F1A68647 PayChan"},
|
{ "08DE7D96082187F6E6578530258C77FAABABE4C20474BDB82F04B021F1A68647 PayChan" },
|
||||||
{ "740352F2412A9909880C23A559FCECEDA3BE2126FED62FC7660D628A06927F11 Flow"}
|
{ "740352F2412A9909880C23A559FCECEDA3BE2126FED62FC7660D628A06927F11 Flow" },
|
||||||
|
{ "1562511F573A19AE9BD103B5D6B9E01B3B46805AEC5D3C4805C902B514399146 CryptoConditions" }
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -38,10 +38,12 @@ namespace ripple {
|
|||||||
#define SF_TRUSTED 0x10 // comes from trusted source
|
#define SF_TRUSTED 0x10 // comes from trusted source
|
||||||
// Private flags, used internally in apply.cpp.
|
// Private flags, used internally in apply.cpp.
|
||||||
// Do not attempt to read, set, or reuse.
|
// Do not attempt to read, set, or reuse.
|
||||||
#define SF_PRIVATE1 0x100
|
#define SF_PRIVATE1 0x0100
|
||||||
#define SF_PRIVATE2 0x200
|
#define SF_PRIVATE2 0x0200
|
||||||
#define SF_PRIVATE3 0x400
|
#define SF_PRIVATE3 0x0400
|
||||||
#define SF_PRIVATE4 0x800
|
#define SF_PRIVATE4 0x0800
|
||||||
|
#define SF_PRIVATE5 0x1000
|
||||||
|
#define SF_PRIVATE6 0x2000
|
||||||
|
|
||||||
/** Routing table for objects identified by hash.
|
/** Routing table for objects identified by hash.
|
||||||
|
|
||||||
|
|||||||
@@ -19,8 +19,11 @@
|
|||||||
|
|
||||||
#include <BeastConfig.h>
|
#include <BeastConfig.h>
|
||||||
#include <ripple/app/tx/impl/SusPay.h>
|
#include <ripple/app/tx/impl/SusPay.h>
|
||||||
|
#include <ripple/app/misc/HashRouter.h>
|
||||||
#include <ripple/basics/chrono.h>
|
#include <ripple/basics/chrono.h>
|
||||||
#include <ripple/basics/Log.h>
|
#include <ripple/basics/Log.h>
|
||||||
|
#include <ripple/conditions/Condition.h>
|
||||||
|
#include <ripple/conditions/Fulfillment.h>
|
||||||
#include <ripple/protocol/digest.h>
|
#include <ripple/protocol/digest.h>
|
||||||
#include <ripple/protocol/st.h>
|
#include <ripple/protocol/st.h>
|
||||||
#include <ripple/protocol/Feature.h>
|
#include <ripple/protocol/Feature.h>
|
||||||
@@ -29,6 +32,12 @@
|
|||||||
#include <ripple/protocol/XRPAmount.h>
|
#include <ripple/protocol/XRPAmount.h>
|
||||||
#include <ripple/ledger/View.h>
|
#include <ripple/ledger/View.h>
|
||||||
|
|
||||||
|
// During a SusPayFinish, the transaction must specify both
|
||||||
|
// a condition and a fulfillment. We track whether that
|
||||||
|
// fulfillment matches and validates the condition.
|
||||||
|
#define SF_CF_INVALID SF_PRIVATE5
|
||||||
|
#define SF_CF_VALID SF_PRIVATE6
|
||||||
|
|
||||||
namespace ripple {
|
namespace ripple {
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@@ -59,8 +68,10 @@ namespace ripple {
|
|||||||
|
|
||||||
Validation rules:
|
Validation rules:
|
||||||
|
|
||||||
sfDigest
|
sfCondition
|
||||||
If present, proof is required on a SusPayFinish.
|
If present, specifies a condition, and the
|
||||||
|
condition along with its matching fulfillment
|
||||||
|
is required on a SusPayFinish.
|
||||||
|
|
||||||
sfCancelAfter
|
sfCancelAfter
|
||||||
If present, SusPay may be canceled after the
|
If present, SusPay may be canceled after the
|
||||||
@@ -83,16 +94,9 @@ namespace ripple {
|
|||||||
|
|
||||||
Any account may submit a SusPayFinish. If the SusPay
|
Any account may submit a SusPayFinish. If the SusPay
|
||||||
ledger entry specifies a condition, the SusPayFinish
|
ledger entry specifies a condition, the SusPayFinish
|
||||||
must provide the sfMethod, original sfDigest, and
|
must provide the same condition and its associated
|
||||||
sfProof fields. Depending on the method, a
|
fulfillment in the sfFulfillment field, or else the
|
||||||
cryptographic operation will be performed on sfProof
|
SusPayFinish will fail.
|
||||||
and the result must match the sfDigest or else the
|
|
||||||
SusPayFinish is considered as having an invalid
|
|
||||||
signature.
|
|
||||||
|
|
||||||
Only sfMethod==1 is supported, where sfProof must be a
|
|
||||||
256-bit unsigned big-endian integer which when hashed
|
|
||||||
using SHA256 produces digest == sfDigest.
|
|
||||||
|
|
||||||
If the SusPay ledger entry specifies sfFinishAfter, the
|
If the SusPay ledger entry specifies sfFinishAfter, the
|
||||||
transaction will fail if parentCloseTime <= sfFinishAfter.
|
transaction will fail if parentCloseTime <= sfFinishAfter.
|
||||||
@@ -103,8 +107,10 @@ namespace ripple {
|
|||||||
If the SusPay ledger entry specifies sfCancelAfter, the
|
If the SusPay ledger entry specifies sfCancelAfter, the
|
||||||
transaction will fail if sfCancelAfter <= parentCloseTime.
|
transaction will fail if sfCancelAfter <= parentCloseTime.
|
||||||
|
|
||||||
NOTE: It must always be possible to verify the condition
|
NOTE: The reason the condition must be specified again
|
||||||
without retrieving the SusPay ledger entry.
|
is because it must always be possible to verify
|
||||||
|
the condition without retrieving the SusPay
|
||||||
|
ledger entry.
|
||||||
|
|
||||||
SusPayCancel
|
SusPayCancel
|
||||||
|
|
||||||
@@ -159,32 +165,62 @@ SusPayCreate::preflight (PreflightContext const& ctx)
|
|||||||
ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
|
ctx.tx[sfCancelAfter] <= ctx.tx[sfFinishAfter])
|
||||||
return temBAD_EXPIRATION;
|
return temBAD_EXPIRATION;
|
||||||
|
|
||||||
|
if (auto const cb = ctx.tx[~sfCondition])
|
||||||
|
{
|
||||||
|
if (! ctx.rules.enabled(featureCryptoConditions,
|
||||||
|
ctx.app.config().features))
|
||||||
|
return temDISABLED;
|
||||||
|
|
||||||
|
using namespace ripple::cryptoconditions;
|
||||||
|
|
||||||
|
// TODO: Remove this try/catch once cryptoconditions
|
||||||
|
// no longer use exceptions.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto condition = loadCondition(*cb);
|
||||||
|
|
||||||
|
if (!condition)
|
||||||
|
return temMALFORMED;
|
||||||
|
|
||||||
|
{
|
||||||
|
// TODO: This is here temporarily to ensure
|
||||||
|
// that the condition given doesn't
|
||||||
|
// contain unnecessary trailing junk.
|
||||||
|
// The new parsing API will simplify
|
||||||
|
// the checking here.
|
||||||
|
|
||||||
|
auto b = to_blob(*condition);
|
||||||
|
if (*cb != makeSlice(b))
|
||||||
|
return temMALFORMED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
return temMALFORMED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return preflight2 (ctx);
|
return preflight2 (ctx);
|
||||||
}
|
}
|
||||||
|
|
||||||
TER
|
TER
|
||||||
SusPayCreate::doApply()
|
SusPayCreate::doApply()
|
||||||
{
|
{
|
||||||
// For now, require that all operations can be
|
auto const closeTime = ctx_.view ().info ().parentCloseTime;
|
||||||
// canceled, or finished without proof, within a
|
|
||||||
// reasonable period of time for the first release.
|
if (ctx_.tx[~sfCancelAfter])
|
||||||
using namespace std::chrono;
|
|
||||||
auto const maxExpire = (ctx_.view().info().parentCloseTime +
|
|
||||||
weeks{1}).time_since_epoch().count();
|
|
||||||
if (ctx_.tx[~sfDigest])
|
|
||||||
{
|
{
|
||||||
if (! ctx_.tx[~sfCancelAfter])
|
auto const cancelAfter = ctx_.tx[sfCancelAfter];
|
||||||
return tecNO_PERMISSION;
|
|
||||||
if (maxExpire <= ctx_.tx[sfCancelAfter])
|
if (closeTime.time_since_epoch().count() >= cancelAfter)
|
||||||
return tecNO_PERMISSION;
|
return tecNO_PERMISSION;
|
||||||
}
|
}
|
||||||
else
|
|
||||||
|
if (ctx_.tx[~sfFinishAfter])
|
||||||
{
|
{
|
||||||
if (ctx_.tx[~sfCancelAfter] &&
|
auto const finishAfter = ctx_.tx[sfFinishAfter];
|
||||||
maxExpire <= ctx_.tx[sfCancelAfter])
|
|
||||||
return tecNO_PERMISSION;
|
if (closeTime.time_since_epoch().count() >= finishAfter)
|
||||||
if (ctx_.tx[~sfFinishAfter] &&
|
|
||||||
maxExpire <= ctx_.tx[sfFinishAfter])
|
|
||||||
return tecNO_PERMISSION;
|
return tecNO_PERMISSION;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +259,7 @@ SusPayCreate::doApply()
|
|||||||
keylet::susPay(account, (*sle)[sfSequence] - 1));
|
keylet::susPay(account, (*sle)[sfSequence] - 1));
|
||||||
(*slep)[sfAmount] = ctx_.tx[sfAmount];
|
(*slep)[sfAmount] = ctx_.tx[sfAmount];
|
||||||
(*slep)[sfAccount] = account;
|
(*slep)[sfAccount] = account;
|
||||||
(*slep)[~sfDigest] = ctx_.tx[~sfDigest];
|
(*slep)[~sfCondition] = ctx_.tx[~sfCondition];
|
||||||
(*slep)[~sfSourceTag] = ctx_.tx[~sfSourceTag];
|
(*slep)[~sfSourceTag] = ctx_.tx[~sfSourceTag];
|
||||||
(*slep)[sfDestination] = ctx_.tx[sfDestination];
|
(*slep)[sfDestination] = ctx_.tx[sfDestination];
|
||||||
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
|
(*slep)[~sfCancelAfter] = ctx_.tx[~sfCancelAfter];
|
||||||
@@ -253,6 +289,50 @@ SusPayCreate::doApply()
|
|||||||
|
|
||||||
//------------------------------------------------------------------------------
|
//------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
static
|
||||||
|
bool
|
||||||
|
checkCondition (Slice f, Slice c)
|
||||||
|
{
|
||||||
|
using namespace ripple::cryptoconditions;
|
||||||
|
|
||||||
|
// TODO: Remove this try/catch once cryptoconditions
|
||||||
|
// no longer use exceptions.
|
||||||
|
try
|
||||||
|
{
|
||||||
|
auto condition = loadCondition(c);
|
||||||
|
|
||||||
|
if (!condition)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto fulfillment = loadFulfillment(f);
|
||||||
|
|
||||||
|
if (!fulfillment)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
{
|
||||||
|
// TODO: This is here temporarily to ensure
|
||||||
|
// that the condition & fulfillment
|
||||||
|
// given don't contain unnecessary
|
||||||
|
// trailing junk. The new parsing API
|
||||||
|
// will simplify the checking here.
|
||||||
|
|
||||||
|
auto cb = to_blob(*condition);
|
||||||
|
if (c != makeSlice(cb))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
auto fb = to_blob(*fulfillment);
|
||||||
|
if (f != makeSlice(fb))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return validateTrigger (*fulfillment, *condition);
|
||||||
|
}
|
||||||
|
catch (...)
|
||||||
|
{
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
TER
|
TER
|
||||||
SusPayFinish::preflight (PreflightContext const& ctx)
|
SusPayFinish::preflight (PreflightContext const& ctx)
|
||||||
{
|
{
|
||||||
@@ -260,53 +340,72 @@ SusPayFinish::preflight (PreflightContext const& ctx)
|
|||||||
ctx.app.config().features))
|
ctx.app.config().features))
|
||||||
return temDISABLED;
|
return temDISABLED;
|
||||||
|
|
||||||
auto const ret = preflight1 (ctx);
|
|
||||||
if (!isTesSuccess (ret))
|
|
||||||
return ret;
|
|
||||||
|
|
||||||
if (ctx.tx[~sfMethod])
|
|
||||||
{
|
{
|
||||||
// Condition
|
auto const ret = preflight1 (ctx);
|
||||||
switch(ctx.tx[sfMethod])
|
if (!isTesSuccess (ret))
|
||||||
{
|
return ret;
|
||||||
case 1:
|
|
||||||
{
|
|
||||||
if (! ctx.tx[~sfDigest])
|
|
||||||
return temMALFORMED;
|
|
||||||
if (! ctx.tx[~sfProof])
|
|
||||||
return temMALFORMED;
|
|
||||||
if (ctx.tx[~sfProof]->size() != 32)
|
|
||||||
return temMALFORMED;
|
|
||||||
sha256_hasher h;
|
|
||||||
using beast::hash_append;
|
|
||||||
hash_append(h, ctx.tx[sfProof]);
|
|
||||||
uint256 digest;
|
|
||||||
{
|
|
||||||
auto const result = static_cast<
|
|
||||||
sha256_hasher::result_type>(h);
|
|
||||||
std::memcpy(digest.data(),
|
|
||||||
result.data(), result.size());
|
|
||||||
}
|
|
||||||
if (digest != ctx.tx[sfDigest])
|
|
||||||
return temBAD_SIGNATURE;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
|
||||||
return temMALFORMED;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
// No Condition
|
|
||||||
if (ctx.tx[~sfDigest])
|
|
||||||
return temMALFORMED;
|
|
||||||
if (ctx.tx[~sfProof])
|
|
||||||
return temMALFORMED;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return preflight2 (ctx);
|
auto const cb = ctx.tx[~sfCondition];
|
||||||
|
auto const fb = ctx.tx[~sfFulfillment];
|
||||||
|
|
||||||
|
if (cb || fb)
|
||||||
|
{
|
||||||
|
if (! ctx.rules.enabled(featureCryptoConditions,
|
||||||
|
ctx.app.config().features))
|
||||||
|
return temDISABLED;
|
||||||
|
}
|
||||||
|
|
||||||
|
// If you specify a condition, then you must also specify
|
||||||
|
// a fulfillment.
|
||||||
|
if (static_cast<bool>(cb) != static_cast<bool>(fb))
|
||||||
|
return temMALFORMED;
|
||||||
|
|
||||||
|
// Verify the transaction signature. If it doesn't work
|
||||||
|
// then don't do any more work.
|
||||||
|
{
|
||||||
|
auto const ret = preflight2 (ctx);
|
||||||
|
if (!isTesSuccess (ret))
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cb && fb)
|
||||||
|
{
|
||||||
|
auto& router = ctx.app.getHashRouter();
|
||||||
|
|
||||||
|
auto const id = ctx.tx.getTransactionID();
|
||||||
|
auto const flags = router.getFlags (id);
|
||||||
|
|
||||||
|
// If we haven't checked the condition, check it
|
||||||
|
// now. Whether it passes or not isn't important
|
||||||
|
// in preflight.
|
||||||
|
if (!(flags & (SF_CF_INVALID | SF_CF_VALID)))
|
||||||
|
{
|
||||||
|
if (checkCondition (*fb, *cb))
|
||||||
|
router.setFlags (id, SF_CF_VALID);
|
||||||
|
else
|
||||||
|
router.setFlags (id, SF_CF_INVALID);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return tesSUCCESS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::uint64_t
|
||||||
|
SusPayFinish::calculateBaseFee (PreclaimContext const& ctx)
|
||||||
|
{
|
||||||
|
std::uint64_t extraFee = 0;
|
||||||
|
|
||||||
|
if (auto const fb = ctx.tx[~sfFulfillment])
|
||||||
|
{
|
||||||
|
extraFee += ctx.view.fees().units *
|
||||||
|
(32 + static_cast<std::uint64_t> (fb->size() / 16));
|
||||||
|
}
|
||||||
|
|
||||||
|
return Transactor::calculateBaseFee (ctx) + extraFee;
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
TER
|
TER
|
||||||
SusPayFinish::doApply()
|
SusPayFinish::doApply()
|
||||||
{
|
{
|
||||||
@@ -329,10 +428,52 @@ SusPayFinish::doApply()
|
|||||||
ctx_.view().info().parentCloseTime.time_since_epoch().count())
|
ctx_.view().info().parentCloseTime.time_since_epoch().count())
|
||||||
return tecNO_PERMISSION;
|
return tecNO_PERMISSION;
|
||||||
|
|
||||||
// Same digest?
|
// Check cryptocondition fulfillment
|
||||||
if ((*slep)[~sfDigest] && (! ctx_.tx[~sfMethod] ||
|
{
|
||||||
(ctx_.tx[~sfDigest] != (*slep)[~sfDigest])))
|
auto const id = ctx_.tx.getTransactionID();
|
||||||
return tecNO_PERMISSION;
|
auto flags = ctx_.app.getHashRouter().getFlags (id);
|
||||||
|
|
||||||
|
auto const cb = ctx_.tx[~sfCondition];
|
||||||
|
|
||||||
|
// It's unlikely that the results of the check will
|
||||||
|
// expire from the hash router, but if it happens,
|
||||||
|
// simply re-run the check.
|
||||||
|
if (cb && ! (flags & (SF_CF_INVALID | SF_CF_VALID)))
|
||||||
|
{
|
||||||
|
auto const fb = ctx_.tx[~sfFulfillment];
|
||||||
|
|
||||||
|
if (!fb)
|
||||||
|
return tecINTERNAL;
|
||||||
|
|
||||||
|
if (checkCondition (*fb, *cb))
|
||||||
|
flags = SF_CF_VALID;
|
||||||
|
else
|
||||||
|
flags = SF_CF_INVALID;
|
||||||
|
|
||||||
|
ctx_.app.getHashRouter().setFlags (id, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
// If the check failed, then simply return an error
|
||||||
|
// and don't look at anything else.
|
||||||
|
if (flags & SF_CF_INVALID)
|
||||||
|
return tecCRYPTOCONDITION_ERROR;
|
||||||
|
|
||||||
|
// Check against condition in the ledger entry:
|
||||||
|
auto const cond = (*slep)[~sfCondition];
|
||||||
|
|
||||||
|
// If a condition wasn't specified during creation,
|
||||||
|
// one shouldn't be included now.
|
||||||
|
if (!cond && cb)
|
||||||
|
return tecCRYPTOCONDITION_ERROR;
|
||||||
|
|
||||||
|
// If a condition was specified during creation of
|
||||||
|
// the suspended payment, the identical condition
|
||||||
|
// must be presented again. We don't check if the
|
||||||
|
// fulfillment matches the condition since we did
|
||||||
|
// that in preflight.
|
||||||
|
if (cond && (cond != cb))
|
||||||
|
return tecCRYPTOCONDITION_ERROR;
|
||||||
|
}
|
||||||
|
|
||||||
AccountID const account = (*slep)[sfAccount];
|
AccountID const account = (*slep)[sfAccount];
|
||||||
|
|
||||||
|
|||||||
@@ -62,6 +62,10 @@ public:
|
|||||||
TER
|
TER
|
||||||
preflight (PreflightContext const& ctx);
|
preflight (PreflightContext const& ctx);
|
||||||
|
|
||||||
|
static
|
||||||
|
std::uint64_t
|
||||||
|
calculateBaseFee (PreclaimContext const& ctx);
|
||||||
|
|
||||||
TER
|
TER
|
||||||
doApply() override;
|
doApply() override;
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ preflight2 (PreflightContext const& ctx)
|
|||||||
auto const sigValid = checkValidity(ctx.app.getHashRouter(),
|
auto const sigValid = checkValidity(ctx.app.getHashRouter(),
|
||||||
ctx.tx, ctx.rules, ctx.app.config());
|
ctx.tx, ctx.rules, ctx.app.config());
|
||||||
if (sigValid.first == Validity::SigBad)
|
if (sigValid.first == Validity::SigBad)
|
||||||
{
|
{
|
||||||
JLOG(ctx.j.debug()) <<
|
JLOG(ctx.j.debug()) <<
|
||||||
"preflight2: bad signature. " << sigValid.second;
|
"preflight2: bad signature. " << sigValid.second;
|
||||||
return temINVALID;
|
return temINVALID;
|
||||||
|
|||||||
@@ -44,6 +44,7 @@ extern uint256 const featureCompareFlowV1V2;
|
|||||||
extern uint256 const featureSHAMapV2;
|
extern uint256 const featureSHAMapV2;
|
||||||
extern uint256 const featurePayChan;
|
extern uint256 const featurePayChan;
|
||||||
extern uint256 const featureFlow;
|
extern uint256 const featureFlow;
|
||||||
|
extern uint256 const featureCryptoConditions;
|
||||||
|
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|
||||||
|
|||||||
@@ -447,10 +447,12 @@ extern SF_Blob const sfCreateCode;
|
|||||||
extern SF_Blob const sfMemoType;
|
extern SF_Blob const sfMemoType;
|
||||||
extern SF_Blob const sfMemoData;
|
extern SF_Blob const sfMemoData;
|
||||||
extern SF_Blob const sfMemoFormat;
|
extern SF_Blob const sfMemoFormat;
|
||||||
extern SF_Blob const sfMasterSignature;
|
|
||||||
|
|
||||||
// variable length (uncommon)
|
// variable length (uncommon)
|
||||||
extern SF_Blob const sfProof;
|
extern SF_Blob const sfFulfillment;
|
||||||
|
extern SF_Blob const sfCondition;
|
||||||
|
extern SF_Blob const sfMasterSignature;
|
||||||
|
|
||||||
// account
|
// account
|
||||||
extern SF_Account const sfAccount;
|
extern SF_Account const sfAccount;
|
||||||
extern SF_Account const sfOwner;
|
extern SF_Account const sfOwner;
|
||||||
|
|||||||
@@ -205,6 +205,7 @@ enum TER
|
|||||||
tecDST_TAG_NEEDED = 143,
|
tecDST_TAG_NEEDED = 143,
|
||||||
tecINTERNAL = 144,
|
tecINTERNAL = 144,
|
||||||
tecOVERSIZE = 145,
|
tecOVERSIZE = 145,
|
||||||
|
tecCRYPTOCONDITION_ERROR = 146
|
||||||
};
|
};
|
||||||
|
|
||||||
inline bool isTelLocal(TER x)
|
inline bool isTelLocal(TER x)
|
||||||
|
|||||||
@@ -55,5 +55,6 @@ uint256 const featureCompareFlowV1V2 = feature("CompareFlowV1V2");
|
|||||||
uint256 const featureSHAMapV2 = feature("SHAMapV2");
|
uint256 const featureSHAMapV2 = feature("SHAMapV2");
|
||||||
uint256 const featurePayChan = feature("PayChan");
|
uint256 const featurePayChan = feature("PayChan");
|
||||||
uint256 const featureFlow = feature("Flow");
|
uint256 const featureFlow = feature("Flow");
|
||||||
|
uint256 const featureCryptoConditions = feature("CryptoConditions");
|
||||||
|
|
||||||
} // ripple
|
} // ripple
|
||||||
|
|||||||
@@ -85,7 +85,7 @@ LedgerFormats::LedgerFormats ()
|
|||||||
SOElement (sfAccount, SOE_REQUIRED) <<
|
SOElement (sfAccount, SOE_REQUIRED) <<
|
||||||
SOElement (sfDestination, SOE_REQUIRED) <<
|
SOElement (sfDestination, SOE_REQUIRED) <<
|
||||||
SOElement (sfAmount, SOE_REQUIRED) <<
|
SOElement (sfAmount, SOE_REQUIRED) <<
|
||||||
SOElement (sfDigest, SOE_OPTIONAL) <<
|
SOElement (sfCondition, SOE_OPTIONAL) <<
|
||||||
SOElement (sfCancelAfter, SOE_OPTIONAL) <<
|
SOElement (sfCancelAfter, SOE_OPTIONAL) <<
|
||||||
SOElement (sfFinishAfter, SOE_OPTIONAL) <<
|
SOElement (sfFinishAfter, SOE_OPTIONAL) <<
|
||||||
SOElement (sfSourceTag, SOE_OPTIONAL) <<
|
SOElement (sfSourceTag, SOE_OPTIONAL) <<
|
||||||
|
|||||||
@@ -201,10 +201,11 @@ SF_Blob const sfMemoFormat = make::one<SF_Blob::type>(&sfMemoFormat, STI
|
|||||||
|
|
||||||
|
|
||||||
// variable length (uncommon)
|
// variable length (uncommon)
|
||||||
// 16 has not been used yet...
|
SF_Blob const sfFulfillment = make::one<SF_Blob::type>(&sfFulfillment, STI_VL, 16, "Fulfillment");
|
||||||
SF_Blob const sfProof = make::one<SF_Blob::type>(&sfProof, STI_VL, 17, "Proof");
|
SF_Blob const sfCondition = make::one<SF_Blob::type>(&sfCondition, STI_VL, 17, "Condition");
|
||||||
SF_Blob const sfMasterSignature = make::one<SF_Blob::type>(&sfMasterSignature, STI_VL, 18, "MasterSignature", SField::sMD_Default, SField::notSigning);
|
SF_Blob const sfMasterSignature = make::one<SF_Blob::type>(&sfMasterSignature, STI_VL, 18, "MasterSignature", SField::sMD_Default, SField::notSigning);
|
||||||
|
|
||||||
|
|
||||||
// account
|
// account
|
||||||
SF_Account const sfAccount = make::one<SF_Account::type>(&sfAccount, STI_ACCOUNT, 1, "Account");
|
SF_Account const sfAccount = make::one<SF_Account::type>(&sfAccount, STI_ACCOUNT, 1, "Account");
|
||||||
SF_Account const sfOwner = make::one<SF_Account::type>(&sfOwner, STI_ACCOUNT, 2, "Owner");
|
SF_Account const sfOwner = make::one<SF_Account::type>(&sfOwner, STI_ACCOUNT, 2, "Owner");
|
||||||
|
|||||||
@@ -63,6 +63,7 @@ bool transResultInfo (TER code, std::string& token, std::string& text)
|
|||||||
{ tecNEED_MASTER_KEY, { "tecNEED_MASTER_KEY", "The operation requires the use of the Master Key." } },
|
{ tecNEED_MASTER_KEY, { "tecNEED_MASTER_KEY", "The operation requires the use of the Master Key." } },
|
||||||
{ tecDST_TAG_NEEDED, { "tecDST_TAG_NEEDED", "A destination tag is required." } },
|
{ tecDST_TAG_NEEDED, { "tecDST_TAG_NEEDED", "A destination tag is required." } },
|
||||||
{ tecINTERNAL, { "tecINTERNAL", "An internal error has occurred during processing." } },
|
{ tecINTERNAL, { "tecINTERNAL", "An internal error has occurred during processing." } },
|
||||||
|
{ tecCRYPTOCONDITION_ERROR, { "tecCRYPTOCONDITION_ERROR", "Malformed, invalid, or mismatched conditional or fulfillment." } },
|
||||||
|
|
||||||
{ tefALREADY, { "tefALREADY", "The exact transaction was already in this ledger." } },
|
{ tefALREADY, { "tefALREADY", "The exact transaction was already in this ledger." } },
|
||||||
{ tefBAD_ADD_AUTH, { "tefBAD_ADD_AUTH", "Not authorized to add account." } },
|
{ tefBAD_ADD_AUTH, { "tefBAD_ADD_AUTH", "Not authorized to add account." } },
|
||||||
|
|||||||
@@ -69,7 +69,7 @@ TxFormats::TxFormats ()
|
|||||||
add ("SuspendedPaymentCreate", ttSUSPAY_CREATE) <<
|
add ("SuspendedPaymentCreate", ttSUSPAY_CREATE) <<
|
||||||
SOElement (sfDestination, SOE_REQUIRED) <<
|
SOElement (sfDestination, SOE_REQUIRED) <<
|
||||||
SOElement (sfAmount, SOE_REQUIRED) <<
|
SOElement (sfAmount, SOE_REQUIRED) <<
|
||||||
SOElement (sfDigest, SOE_OPTIONAL) <<
|
SOElement (sfCondition, SOE_OPTIONAL) <<
|
||||||
SOElement (sfCancelAfter, SOE_OPTIONAL) <<
|
SOElement (sfCancelAfter, SOE_OPTIONAL) <<
|
||||||
SOElement (sfFinishAfter, SOE_OPTIONAL) <<
|
SOElement (sfFinishAfter, SOE_OPTIONAL) <<
|
||||||
SOElement (sfDestinationTag, SOE_OPTIONAL);
|
SOElement (sfDestinationTag, SOE_OPTIONAL);
|
||||||
@@ -77,9 +77,8 @@ TxFormats::TxFormats ()
|
|||||||
add ("SuspendedPaymentFinish", ttSUSPAY_FINISH) <<
|
add ("SuspendedPaymentFinish", ttSUSPAY_FINISH) <<
|
||||||
SOElement (sfOwner, SOE_REQUIRED) <<
|
SOElement (sfOwner, SOE_REQUIRED) <<
|
||||||
SOElement (sfOfferSequence, SOE_REQUIRED) <<
|
SOElement (sfOfferSequence, SOE_REQUIRED) <<
|
||||||
SOElement (sfMethod, SOE_OPTIONAL) <<
|
SOElement (sfFulfillment, SOE_OPTIONAL) <<
|
||||||
SOElement (sfDigest, SOE_OPTIONAL) <<
|
SOElement (sfCondition, SOE_OPTIONAL);
|
||||||
SOElement (sfProof, SOE_OPTIONAL);
|
|
||||||
|
|
||||||
add ("SuspendedPaymentCancel", ttSUSPAY_CANCEL) <<
|
add ("SuspendedPaymentCancel", ttSUSPAY_CANCEL) <<
|
||||||
SOElement (sfOwner, SOE_REQUIRED) <<
|
SOElement (sfOwner, SOE_REQUIRED) <<
|
||||||
|
|||||||
@@ -20,7 +20,6 @@
|
|||||||
#include <BeastConfig.h>
|
#include <BeastConfig.h>
|
||||||
#include <ripple/test/jtx.h>
|
#include <ripple/test/jtx.h>
|
||||||
#include <ripple/app/tx/applySteps.h>
|
#include <ripple/app/tx/applySteps.h>
|
||||||
#include <ripple/protocol/digest.h>
|
|
||||||
#include <ripple/protocol/Feature.h>
|
#include <ripple/protocol/Feature.h>
|
||||||
#include <ripple/protocol/Indexes.h>
|
#include <ripple/protocol/Indexes.h>
|
||||||
#include <ripple/protocol/JsonFields.h>
|
#include <ripple/protocol/JsonFields.h>
|
||||||
@@ -31,38 +30,72 @@ namespace test {
|
|||||||
|
|
||||||
struct SusPay_test : public beast::unit_test::suite
|
struct SusPay_test : public beast::unit_test::suite
|
||||||
{
|
{
|
||||||
template <class... Args>
|
// An Ed25519 conditional trigger fulfillment and its
|
||||||
static
|
// condition
|
||||||
uint256
|
std::array<std::uint8_t, 99> const fb1 =
|
||||||
digest (Args&&... args)
|
{{
|
||||||
{
|
0x00, 0x04, 0x60, 0x3B, 0x6A, 0x27, 0xBC, 0xCE, 0xB6, 0xA4, 0x2D, 0x62,
|
||||||
sha256_hasher h;
|
0xA3, 0xA8, 0xD0, 0x2A, 0x6F, 0x0D, 0x73, 0x65, 0x32, 0x15, 0x77, 0x1D,
|
||||||
using beast::hash_append;
|
0xE2, 0x43, 0xA6, 0x3A, 0xC0, 0x48, 0xA1, 0x8B, 0x59, 0xDA, 0x29, 0x8F,
|
||||||
hash_append(h, args...);
|
0x89, 0x5B, 0x3C, 0xAF, 0xE2, 0xC9, 0x50, 0x60, 0x39, 0xD0, 0xE2, 0xA6,
|
||||||
auto const d = static_cast<
|
0x63, 0x82, 0x56, 0x80, 0x04, 0x67, 0x4F, 0xE8, 0xD2, 0x37, 0x78, 0x50,
|
||||||
sha256_hasher::result_type>(h);
|
0x92, 0xE4, 0x0D, 0x6A, 0xAF, 0x48, 0x3E, 0x4F, 0xC6, 0x01, 0x68, 0x70,
|
||||||
uint256 result;
|
0x5F, 0x31, 0xF1, 0x01, 0x59, 0x61, 0x38, 0xCE, 0x21, 0xAA, 0x35, 0x7C,
|
||||||
std::memcpy(result.data(), d.data(), d.size());
|
0x0D, 0x32, 0xA0, 0x64, 0xF4, 0x23, 0xDC, 0x3E, 0xE4, 0xAA, 0x3A, 0xBF,
|
||||||
return result;
|
0x53, 0xF8, 0x03,
|
||||||
}
|
}};
|
||||||
|
|
||||||
// Create condition
|
std::array<std::uint8_t, 39> const cb1 =
|
||||||
// First is digest, second is pre-image
|
{{
|
||||||
static
|
0x00, 0x04, 0x01, 0x20, 0x20, 0x3B, 0x6A, 0x27, 0xBC, 0xCE, 0xB6, 0xA4,
|
||||||
std::pair<uint256, uint256>
|
0x2D, 0x62, 0xA3, 0xA8, 0xD0, 0x2A, 0x6F, 0x0D, 0x73, 0x65, 0x32, 0x15,
|
||||||
cond (std::string const& receipt)
|
0x77, 0x1D, 0xE2, 0x43, 0xA6, 0x3A, 0xC0, 0x48, 0xA1, 0x8B, 0x59, 0xDA,
|
||||||
{
|
0x29, 0x01, 0x60
|
||||||
std::pair<uint256, uint256> result;
|
}};
|
||||||
result.second = digest(receipt);
|
|
||||||
result.first = digest(result.second);
|
// A prefix.prefix.ed25519 conditional trigger fulfillment:
|
||||||
return result;
|
std::array<std::uint8_t, 106> const fb2 =
|
||||||
}
|
{{
|
||||||
|
0x00, 0x01, 0x67, 0x03, 0x61, 0x62, 0x63, 0x00, 0x04, 0x60, 0x76, 0xA1,
|
||||||
|
0x59, 0x20, 0x44, 0xA6, 0xE4, 0xF5, 0x11, 0x26, 0x5B, 0xCA, 0x73, 0xA6,
|
||||||
|
0x04, 0xD9, 0x0B, 0x05, 0x29, 0xD1, 0xDF, 0x60, 0x2B, 0xE3, 0x0A, 0x19,
|
||||||
|
0xA9, 0x25, 0x76, 0x60, 0xD1, 0xF5, 0xAE, 0xC6, 0xAB, 0x6A, 0x91, 0x22,
|
||||||
|
0xAF, 0xF0, 0xF7, 0xDC, 0xB9, 0x66, 0x7F, 0xF6, 0x13, 0x13, 0x68, 0x94,
|
||||||
|
0x73, 0x2B, 0x6E, 0x78, 0xC2, 0x6F, 0x5B, 0x67, 0x31, 0x01, 0xE2, 0x67,
|
||||||
|
0xFE, 0x2E, 0x2B, 0x65, 0xFA, 0x4D, 0x53, 0xDA, 0xD4, 0x78, 0xA1, 0xAD,
|
||||||
|
0xA6, 0x4D, 0x50, 0xFD, 0x1D, 0xFD, 0xB7, 0xD9, 0x49, 0x20, 0xDC, 0x3E,
|
||||||
|
0x1A, 0x56, 0x4A, 0x64, 0x7B, 0x1C, 0xBA, 0x35, 0x60, 0x01,
|
||||||
|
}};
|
||||||
|
|
||||||
|
std::array<std::uint8_t, 39> const cb2 =
|
||||||
|
{{
|
||||||
|
|
||||||
|
0x00, 0x01, 0x01, 0x25, 0x20, 0x28, 0x7A, 0x8B, 0xD8, 0xAD, 0xAE, 0x8A,
|
||||||
|
0xCA, 0x0C, 0x87, 0x1C, 0xE7, 0xC2, 0x5F, 0xBA, 0xA5, 0xA8, 0xBE, 0x10,
|
||||||
|
0xD0, 0xE4, 0xDB, 0x1F, 0x56, 0xAE, 0xEE, 0x8B, 0xB3, 0xAD, 0xCE, 0xE5,
|
||||||
|
0x5B, 0x01, 0x64
|
||||||
|
}};
|
||||||
|
|
||||||
|
// A prefix+preimage conditional trigger fulfillment
|
||||||
|
std::array<std::uint8_t, 7> const fb3 =
|
||||||
|
{{
|
||||||
|
0x00, 0x01, 0x04, 0x00, 0x00, 0x00, 0x00,
|
||||||
|
}};
|
||||||
|
|
||||||
|
std::array<std::uint8_t, 39> const cb3 =
|
||||||
|
{{
|
||||||
|
|
||||||
|
0x00, 0x01, 0x01, 0x07, 0x20, 0x62, 0x36, 0xB7, 0xA8, 0x58, 0xFB, 0x35,
|
||||||
|
0x2F, 0xD5, 0xC3, 0x01, 0x3B, 0x68, 0x98, 0xCF, 0x26, 0x8B, 0x3E, 0xB8,
|
||||||
|
0x50, 0xB3, 0x4A, 0xD2, 0x65, 0x24, 0xB0, 0xF8, 0x56, 0xC3, 0x72, 0xD9,
|
||||||
|
0x73, 0x01, 0x01
|
||||||
|
}};
|
||||||
|
|
||||||
static
|
static
|
||||||
Json::Value
|
Json::Value
|
||||||
condpay (jtx::Account const& account, jtx::Account const& to,
|
condpay (jtx::Account const& account, jtx::Account const& to,
|
||||||
STAmount const& amount, uint256 const& digest,
|
STAmount const& amount, Slice condition,
|
||||||
NetClock::time_point const& expiry)
|
NetClock::time_point const& cancelAfter)
|
||||||
{
|
{
|
||||||
using namespace jtx;
|
using namespace jtx;
|
||||||
Json::Value jv;
|
Json::Value jv;
|
||||||
@@ -72,8 +105,20 @@ struct SusPay_test : public beast::unit_test::suite
|
|||||||
jv[jss::Destination] = to.human();
|
jv[jss::Destination] = to.human();
|
||||||
jv[jss::Amount] = amount.getJson(0);
|
jv[jss::Amount] = amount.getJson(0);
|
||||||
jv["CancelAfter"] =
|
jv["CancelAfter"] =
|
||||||
expiry.time_since_epoch().count();
|
cancelAfter.time_since_epoch().count();
|
||||||
jv["Digest"] = to_string(digest);
|
jv["Condition"] = strHex(condition);
|
||||||
|
return jv;
|
||||||
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
Json::Value
|
||||||
|
condpay (jtx::Account const& account, jtx::Account const& to,
|
||||||
|
STAmount const& amount, Slice condition,
|
||||||
|
NetClock::time_point const& cancelAfter,
|
||||||
|
NetClock::time_point const& finishAfter)
|
||||||
|
{
|
||||||
|
auto jv = condpay (account, to, amount, condition, cancelAfter);
|
||||||
|
jv ["FinishAfter"] = finishAfter.time_since_epoch().count();
|
||||||
return jv;
|
return jv;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -94,6 +139,25 @@ struct SusPay_test : public beast::unit_test::suite
|
|||||||
return jv;
|
return jv;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static
|
||||||
|
Json::Value
|
||||||
|
lockup (jtx::Account const& account, jtx::Account const& to,
|
||||||
|
STAmount const& amount, Slice condition,
|
||||||
|
NetClock::time_point const& expiry)
|
||||||
|
{
|
||||||
|
using namespace jtx;
|
||||||
|
Json::Value jv;
|
||||||
|
jv[jss::TransactionType] = "SuspendedPaymentCreate";
|
||||||
|
jv[jss::Flags] = tfUniversal;
|
||||||
|
jv[jss::Account] = account.human();
|
||||||
|
jv[jss::Destination] = to.human();
|
||||||
|
jv[jss::Amount] = amount.getJson(0);
|
||||||
|
jv["FinishAfter"] =
|
||||||
|
expiry.time_since_epoch().count();
|
||||||
|
jv["Condition"] = strHex(condition);
|
||||||
|
return jv;
|
||||||
|
}
|
||||||
|
|
||||||
static
|
static
|
||||||
Json::Value
|
Json::Value
|
||||||
finish (jtx::Account const& account,
|
finish (jtx::Account const& account,
|
||||||
@@ -108,12 +172,11 @@ struct SusPay_test : public beast::unit_test::suite
|
|||||||
return jv;
|
return jv;
|
||||||
}
|
}
|
||||||
|
|
||||||
template <class Proof>
|
|
||||||
static
|
static
|
||||||
Json::Value
|
Json::Value
|
||||||
finish (jtx::Account const& account,
|
finish (jtx::Account const& account,
|
||||||
jtx::Account const& from, std::uint32_t seq,
|
jtx::Account const& from, std::uint32_t seq,
|
||||||
uint256 const& digest, Proof const& proof)
|
Slice condition, Slice fulfillment)
|
||||||
{
|
{
|
||||||
Json::Value jv;
|
Json::Value jv;
|
||||||
jv[jss::TransactionType] = "SuspendedPaymentFinish";
|
jv[jss::TransactionType] = "SuspendedPaymentFinish";
|
||||||
@@ -121,9 +184,8 @@ struct SusPay_test : public beast::unit_test::suite
|
|||||||
jv[jss::Account] = account.human();
|
jv[jss::Account] = account.human();
|
||||||
jv["Owner"] = from.human();
|
jv["Owner"] = from.human();
|
||||||
jv["OfferSequence"] = seq;
|
jv["OfferSequence"] = seq;
|
||||||
jv["Method"] = 1;
|
jv["Condition"] = strHex(condition);
|
||||||
jv["Digest"] = to_string(digest);
|
jv["Fulfillment"] = strHex(fulfillment);
|
||||||
jv["Proof"] = to_string(proof);
|
|
||||||
return jv;
|
return jv;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,46 +206,100 @@ struct SusPay_test : public beast::unit_test::suite
|
|||||||
void
|
void
|
||||||
testEnablement()
|
testEnablement()
|
||||||
{
|
{
|
||||||
|
testcase ("Enablement");
|
||||||
|
|
||||||
using namespace jtx;
|
using namespace jtx;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
using S = seconds;
|
|
||||||
auto const c = cond("receipt");
|
{ // SusPay enabled
|
||||||
{
|
|
||||||
Env env(*this, features(featureSusPay));
|
Env env(*this, features(featureSusPay));
|
||||||
auto T = [&env](NetClock::duration const& d)
|
|
||||||
{ return env.now() + d; };
|
|
||||||
env.fund(XRP(5000), "alice", "bob");
|
env.fund(XRP(5000), "alice", "bob");
|
||||||
// syntax
|
env(lockup("alice", "bob", XRP(1000), env.now() + 1s));
|
||||||
env(condpay("alice", "bob", XRP(1000), c.first, T(S{1})));
|
|
||||||
}
|
}
|
||||||
{
|
|
||||||
|
{ // SusPay not enabled
|
||||||
Env env(*this);
|
Env env(*this);
|
||||||
auto T = [&env](NetClock::duration const& d)
|
|
||||||
{ return env.now() + d; };
|
|
||||||
env.fund(XRP(5000), "alice", "bob");
|
env.fund(XRP(5000), "alice", "bob");
|
||||||
// disabled in production
|
env(lockup("alice", "bob", XRP(1000), env.now() + 1s), ter(temDISABLED));
|
||||||
env(condpay("alice", "bob", XRP(1000), c.first, T(S{1})), ter(temDISABLED));
|
env(finish("bob", "alice", 1), ter(temDISABLED));
|
||||||
env(finish("bob", "alice", 1), ter(temDISABLED));
|
env(cancel("bob", "alice", 1), ter(temDISABLED));
|
||||||
env(cancel("bob", "alice", 1), ter(temDISABLED));
|
}
|
||||||
|
|
||||||
|
{ // SusPay enabled, CryptoConditions disabled
|
||||||
|
Env env(*this,
|
||||||
|
features(featureSusPay));
|
||||||
|
|
||||||
|
env.fund(XRP(5000), "alice", "bob");
|
||||||
|
|
||||||
|
auto const seq = env.seq("alice");
|
||||||
|
|
||||||
|
// Fail: no cryptoconditions allowed
|
||||||
|
env(condpay("alice", "bob", XRP(1000),
|
||||||
|
makeSlice (cb1), env.now() + 1s), ter(temDISABLED));
|
||||||
|
|
||||||
|
// Succeed: doesn't have a cryptocondition
|
||||||
|
env(lockup("alice", "bob", XRP(1000),
|
||||||
|
env.now() + 1s));
|
||||||
|
|
||||||
|
// Fail: can't specify conditional finishes if
|
||||||
|
// cryptoconditions aren't allowed.
|
||||||
|
{
|
||||||
|
auto f = finish("bob", "alice", seq,
|
||||||
|
makeSlice(cb1), makeSlice(fb1));
|
||||||
|
env (f, ter(temDISABLED));
|
||||||
|
|
||||||
|
auto fnc = f;
|
||||||
|
fnc.removeMember ("Condition");
|
||||||
|
env (fnc, ter(temDISABLED));
|
||||||
|
|
||||||
|
auto fnf = f;
|
||||||
|
fnf.removeMember ("Fulfillment");
|
||||||
|
env (fnf, ter(temDISABLED));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
// Succeeds
|
||||||
|
env.close();
|
||||||
|
env(finish("bob", "alice", seq));
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // SusPay enabled, CryptoConditions enabled
|
||||||
|
Env env(*this,
|
||||||
|
features(featureSusPay),
|
||||||
|
features(featureCryptoConditions));
|
||||||
|
|
||||||
|
env.fund(XRP(5000), "alice", "bob");
|
||||||
|
|
||||||
|
auto const seq = env.seq("alice");
|
||||||
|
|
||||||
|
env(condpay("alice", "bob", XRP(1000),
|
||||||
|
makeSlice (cb1), env.now() + 1s), fee(1500));
|
||||||
|
env(finish("bob", "alice", seq,
|
||||||
|
makeSlice(cb1), makeSlice(fb1)), fee(1500));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
testTags()
|
testTags()
|
||||||
{
|
{
|
||||||
|
testcase ("Tags");
|
||||||
|
|
||||||
using namespace jtx;
|
using namespace jtx;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
using S = seconds;
|
|
||||||
{
|
{
|
||||||
Env env(*this, features(featureSusPay));
|
Env env(*this,
|
||||||
|
features(featureSusPay),
|
||||||
|
features(featureCryptoConditions));
|
||||||
|
|
||||||
auto const alice = Account("alice");
|
auto const alice = Account("alice");
|
||||||
auto T = [&env](NetClock::duration const& d)
|
|
||||||
{ return env.now() + d; };
|
|
||||||
env.fund(XRP(5000), alice, "bob");
|
env.fund(XRP(5000), alice, "bob");
|
||||||
auto const c = cond("receipt");
|
|
||||||
auto const seq = env.seq(alice);
|
auto const seq = env.seq(alice);
|
||||||
// set source and dest tags
|
// set source and dest tags
|
||||||
env(condpay(alice, "bob", XRP(1000), c.first, T(S{1})), stag(1), dtag(2));
|
env(condpay(alice, "bob", XRP(1000),
|
||||||
|
makeSlice (cb1), env.now() + 1s),
|
||||||
|
stag(1), dtag(2));
|
||||||
auto const sle = env.le(keylet::susPay(alice.id(), seq));
|
auto const sle = env.le(keylet::susPay(alice.id(), seq));
|
||||||
BEAST_EXPECT((*sle)[sfSourceTag] == 1);
|
BEAST_EXPECT((*sle)[sfSourceTag] == 1);
|
||||||
BEAST_EXPECT((*sle)[sfDestinationTag] == 2);
|
BEAST_EXPECT((*sle)[sfDestinationTag] == 2);
|
||||||
@@ -193,80 +309,203 @@ struct SusPay_test : public beast::unit_test::suite
|
|||||||
void
|
void
|
||||||
testFails()
|
testFails()
|
||||||
{
|
{
|
||||||
|
testcase ("Failure Cases");
|
||||||
|
|
||||||
using namespace jtx;
|
using namespace jtx;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
using S = seconds;
|
|
||||||
|
Env env(*this,
|
||||||
|
features(featureSusPay),
|
||||||
|
features(featureCryptoConditions));
|
||||||
|
env.fund(XRP(5000), "alice", "bob");
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
// Expiration in the past
|
||||||
|
env(condpay("alice", "bob", XRP(1000),
|
||||||
|
makeSlice(cb1), env.now() - 1s), ter(tecNO_PERMISSION));
|
||||||
|
|
||||||
|
// no destination account
|
||||||
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
|
makeSlice(cb1), env.now() + 1s), ter(tecNO_DST));
|
||||||
|
|
||||||
|
env.fund(XRP(5000), "carol");
|
||||||
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
|
makeSlice(cb1), env.now() + 1s), stag(2));
|
||||||
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
|
makeSlice(cb1), env.now() + 1s), stag(3), dtag(4));
|
||||||
|
env(fset("carol", asfRequireDest));
|
||||||
|
|
||||||
|
// missing destination tag
|
||||||
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
|
makeSlice(cb1), env.now() + 1s), ter(tecDST_TAG_NEEDED));
|
||||||
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
|
makeSlice(cb1), env.now() + 1s), dtag(1));
|
||||||
|
|
||||||
|
// Using non-XRP:
|
||||||
|
env (lockup("alice", "carol", Account("alice")["USD"](500),
|
||||||
|
env.now() + 1s), ter(temBAD_AMOUNT));
|
||||||
|
|
||||||
|
// Sending zero or no XRP:
|
||||||
|
env (lockup("alice", "carol", XRP(0),
|
||||||
|
env.now() + 1s), ter(temBAD_AMOUNT));
|
||||||
|
env (lockup("alice", "carol", XRP(-1000),
|
||||||
|
env.now() + 1s), ter(temBAD_AMOUNT));
|
||||||
|
|
||||||
|
// Fail if neither CancelAfter nor FinishAfter are specified:
|
||||||
{
|
{
|
||||||
Env env(*this, features(featureSusPay));
|
auto j1 = lockup("alice", "carol", XRP(1), env.now() + 1s);
|
||||||
auto T = [&env](NetClock::duration const& d)
|
j1.removeMember ("FinishAfter");
|
||||||
{ return env.now() + d; };
|
env (j1, ter(temBAD_EXPIRATION));
|
||||||
env.fund(XRP(5000), "alice", "bob");
|
|
||||||
auto const c = cond("receipt");
|
auto j2 = condpay("alice", "carol", XRP(1), makeSlice(cb1), env.now() + 1s);
|
||||||
// VFALCO Should we enforce this?
|
j2.removeMember ("CancelAfter");
|
||||||
// expiration in the past
|
env (j2, ter(temBAD_EXPIRATION));
|
||||||
//env(condpay("alice", "bob", XRP(1000), c.first, T(S{-1})), ter(tecNO_PERMISSION));
|
}
|
||||||
// expiration beyond the limit
|
|
||||||
env(condpay("alice", "bob", XRP(1000), c.first, T(days(7+1))), ter(tecNO_PERMISSION));
|
// Fail if FinishAfter has already passed:
|
||||||
// no destination account
|
env (lockup("alice", "carol", XRP(1), env.now() - 1s), ter (tecNO_PERMISSION));
|
||||||
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})), ter(tecNO_DST));
|
|
||||||
env.fund(XRP(5000), "carol");
|
// Both CancelAfter and FinishAfter
|
||||||
env(condpay("alice", "carol",
|
env(condpay("alice", "carol", XRP(1), makeSlice(cb1),
|
||||||
XRP(1000), c.first, T(S{1})), stag(2));
|
env.now() + 10s, env.now() + 10s), ter (temBAD_EXPIRATION));
|
||||||
env(condpay("alice", "carol",
|
env(condpay("alice", "carol", XRP(1), makeSlice(cb1),
|
||||||
XRP(1000), c.first, T(S{1})), stag(3), dtag(4));
|
env.now() + 10s, env.now() + 15s), ter (temBAD_EXPIRATION));
|
||||||
env(fset("carol", asfRequireDest));
|
|
||||||
// missing destination tag
|
// Fail if the sender wants to send more than he has:
|
||||||
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})), ter(tecDST_TAG_NEEDED));
|
auto const accountReserve =
|
||||||
env(condpay("alice", "carol",
|
drops(env.current()->fees().reserve);
|
||||||
XRP(1000), c.first, T(S{1})), dtag(1));
|
auto const accountIncrement =
|
||||||
|
drops(env.current()->fees().increment);
|
||||||
|
|
||||||
|
env.fund (accountReserve + accountIncrement + XRP(50), "daniel");
|
||||||
|
env(lockup("daniel", "bob", XRP(51), env.now() + 1s), ter (tecUNFUNDED));
|
||||||
|
|
||||||
|
env.fund (accountReserve + accountIncrement + XRP(50), "evan");
|
||||||
|
env(lockup("evan", "bob", XRP(50), env.now() + 1s), ter (tecUNFUNDED));
|
||||||
|
|
||||||
|
env.fund (accountReserve, "frank");
|
||||||
|
env(lockup("frank", "bob", XRP(1), env.now() + 1s), ter (tecINSUFFICIENT_RESERVE));
|
||||||
|
|
||||||
|
// Respect the "asfDisallowXRP" account flag:
|
||||||
|
env.fund (accountReserve + accountIncrement, "george");
|
||||||
|
env(fset("george", asfDisallowXRP));
|
||||||
|
env(lockup("bob", "george", XRP(10), env.now() + 1s), ter (tecNO_TARGET));
|
||||||
|
|
||||||
|
{ // Specify incorrect sequence number
|
||||||
|
env.fund (XRP(5000), "hannah");
|
||||||
|
auto const seq = env.seq("hannah");
|
||||||
|
env(lockup("hannah", "hannah", XRP(10), env.now() + 1s));
|
||||||
|
env(finish ("hannah", "hannah", seq + 7), ter (tecNO_TARGET));
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Try to specify a condition for a non-conditional payment
|
||||||
|
env.fund (XRP(5000), "ivan");
|
||||||
|
auto const seq = env.seq("ivan");
|
||||||
|
|
||||||
|
auto j = lockup("ivan", "ivan", XRP(10), env.now() + 1s);
|
||||||
|
j["CancelAfter"] = j.removeMember ("FinishAfter");
|
||||||
|
env (j);
|
||||||
|
env(finish("ivan", "ivan", seq,
|
||||||
|
makeSlice(cb1), makeSlice(fb1)), fee(1500), ter (tecCRYPTOCONDITION_ERROR));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
testLockup()
|
testLockup()
|
||||||
{
|
{
|
||||||
|
testcase ("Lockup");
|
||||||
|
|
||||||
using namespace jtx;
|
using namespace jtx;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
using S = seconds;
|
|
||||||
{
|
{ // Unconditional
|
||||||
Env env(*this, features(featureSusPay));
|
Env env(*this, features(featureSusPay));
|
||||||
auto T = [&env](NetClock::duration const& d)
|
|
||||||
{ return env.now() + d; };
|
|
||||||
env.fund(XRP(5000), "alice", "bob");
|
env.fund(XRP(5000), "alice", "bob");
|
||||||
auto const seq = env.seq("alice");
|
auto const seq = env.seq("alice");
|
||||||
env(lockup("alice", "alice", XRP(1000), T(S{1})));
|
env(lockup("alice", "alice", XRP(1000), env.now() + 1s));
|
||||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
|
||||||
env(finish("bob", "alice", seq), ter(tecNO_PERMISSION));
|
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
|
env(finish("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
env.close();
|
env.close();
|
||||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
|
||||||
|
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
env(finish("bob", "alice", seq));
|
env(finish("bob", "alice", seq));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
{ // Conditional
|
||||||
|
Env env(*this,
|
||||||
|
features(featureSusPay),
|
||||||
|
features(featureCryptoConditions));
|
||||||
|
env.fund(XRP(5000), "alice", "bob");
|
||||||
|
auto const seq = env.seq("alice");
|
||||||
|
env(lockup("alice", "alice", XRP(1000), makeSlice(cb2), env.now() + 1s));
|
||||||
|
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||||
|
|
||||||
|
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
|
env(finish("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
|
env(finish("bob", "alice", seq,
|
||||||
|
makeSlice(cb2), makeSlice(fb2)), fee(1500), ter(tecNO_PERMISSION));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
|
env(finish("bob", "alice", seq), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("bob", "alice", seq), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env.close();
|
||||||
|
|
||||||
|
env(finish("bob", "alice", seq,
|
||||||
|
makeSlice(cb2), makeSlice(fb2)), fee(1500));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
testCondPay()
|
testCondPay()
|
||||||
{
|
{
|
||||||
|
testcase ("Conditional Payments");
|
||||||
|
|
||||||
using namespace jtx;
|
using namespace jtx;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
using S = seconds;
|
using S = seconds;
|
||||||
{
|
|
||||||
Env env(*this, features(featureSusPay));
|
{ // Test cryptoconditions
|
||||||
|
Env env(*this,
|
||||||
|
features(featureSusPay),
|
||||||
|
features(featureCryptoConditions));
|
||||||
auto T = [&env](NetClock::duration const& d)
|
auto T = [&env](NetClock::duration const& d)
|
||||||
{ return env.now() + d; };
|
{ return env.now() + d; };
|
||||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||||
auto const c = cond("receipt");
|
|
||||||
auto const seq = env.seq("alice");
|
auto const seq = env.seq("alice");
|
||||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0);
|
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0);
|
||||||
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
|
env(condpay("alice", "carol", XRP(1000), makeSlice(cb1), T(S{1})));
|
||||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||||
env.require(balance("carol", XRP(5000)));
|
env.require(balance("carol", XRP(5000)));
|
||||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||||
env(finish("bob", "alice", seq, c.first, c.first), ter(temBAD_SIGNATURE));
|
|
||||||
|
// Attempt to finish without a fulfillment
|
||||||
|
env(finish("bob", "alice", seq), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||||
env(finish("bob", "alice", seq, c.first, c.second));
|
|
||||||
|
// Attempt to finish with a condition instead of a fulfillment
|
||||||
|
env(finish("bob", "alice", seq, makeSlice(cb1), makeSlice(cb1)), fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||||
|
env(finish("bob", "alice", seq, makeSlice(cb1), makeSlice(cb2)), fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||||
|
env(finish("bob", "alice", seq, makeSlice(cb1), makeSlice(cb3)), fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||||
|
|
||||||
|
// Attempt to finish with an incorrect condition and various
|
||||||
|
// combinations of correct and incorrect fulfillments.
|
||||||
|
env(finish("bob", "alice", seq, makeSlice(cb2), makeSlice(fb1)), fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||||
|
env(finish("bob", "alice", seq, makeSlice(cb2), makeSlice(fb2)), fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||||
|
env(finish("bob", "alice", seq, makeSlice(cb2), makeSlice(fb3)), fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||||
|
|
||||||
|
// Attempt to finish with the correct condition & fulfillment
|
||||||
|
env(finish("bob", "alice", seq, makeSlice(cb1), makeSlice(fb1)), fee(1500));
|
||||||
// SLE removed on finish
|
// SLE removed on finish
|
||||||
BEAST_EXPECT(! env.le(keylet::susPay(Account("alice").id(), seq)));
|
BEAST_EXPECT(! env.le(keylet::susPay(Account("alice").id(), seq)));
|
||||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0);
|
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0);
|
||||||
@@ -276,15 +515,17 @@ struct SusPay_test : public beast::unit_test::suite
|
|||||||
env(cancel("bob", "carol", 1), ter(tecNO_TARGET));
|
env(cancel("bob", "carol", 1), ter(tecNO_TARGET));
|
||||||
env.close();
|
env.close();
|
||||||
}
|
}
|
||||||
{
|
|
||||||
Env env(*this, features(featureSusPay));
|
{ // Test cancel when condition is present
|
||||||
|
Env env(*this,
|
||||||
|
features(featureSusPay),
|
||||||
|
features(featureCryptoConditions));
|
||||||
auto T = [&env](NetClock::duration const& d)
|
auto T = [&env](NetClock::duration const& d)
|
||||||
{ return env.now() + d; };
|
{ return env.now() + d; };
|
||||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||||
auto const c = cond("receipt");
|
|
||||||
auto const seq = env.seq("alice");
|
auto const seq = env.seq("alice");
|
||||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0);
|
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 0);
|
||||||
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
|
env(condpay("alice", "carol", XRP(1000), makeSlice(cb2), T(S{1})));
|
||||||
env.close();
|
env.close();
|
||||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||||
// balance restored on cancel
|
// balance restored on cancel
|
||||||
@@ -293,83 +534,235 @@ struct SusPay_test : public beast::unit_test::suite
|
|||||||
// SLE removed on cancel
|
// SLE removed on cancel
|
||||||
BEAST_EXPECT(! env.le(keylet::susPay(Account("alice").id(), seq)));
|
BEAST_EXPECT(! env.le(keylet::susPay(Account("alice").id(), seq)));
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
Env env(*this, features(featureSusPay));
|
Env env(*this,
|
||||||
|
features(featureSusPay),
|
||||||
|
features(featureCryptoConditions));
|
||||||
auto T = [&env](NetClock::duration const& d)
|
auto T = [&env](NetClock::duration const& d)
|
||||||
{ return env.now() + d; };
|
{ return env.now() + d; };
|
||||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||||
env.close();
|
env.close();
|
||||||
auto const c = cond("receipt");
|
|
||||||
auto const seq = env.seq("alice");
|
auto const seq = env.seq("alice");
|
||||||
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
|
env(condpay("alice", "carol", XRP(1000), makeSlice(cb3), T(S{1})));
|
||||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||||
// cancel fails before expiration
|
// cancel fails before expiration
|
||||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||||
env.close();
|
env.close();
|
||||||
// finish fails after expiration
|
// finish fails after expiration
|
||||||
env(finish("bob", "alice", seq, c.first, c.second), ter(tecNO_PERMISSION));
|
env(finish("bob", "alice", seq, makeSlice(cb3), makeSlice(fb3)),
|
||||||
|
fee(1500), ter(tecNO_PERMISSION));
|
||||||
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
BEAST_EXPECT((*env.le("alice"))[sfOwnerCount] == 1);
|
||||||
env.require(balance("carol", XRP(5000)));
|
env.require(balance("carol", XRP(5000)));
|
||||||
}
|
}
|
||||||
{
|
|
||||||
Env env(*this, features(featureSusPay));
|
{ // Test long & short conditions during creation
|
||||||
|
Env env(*this,
|
||||||
|
features(featureSusPay),
|
||||||
|
features(featureCryptoConditions));
|
||||||
auto T = [&env](NetClock::duration const& d)
|
auto T = [&env](NetClock::duration const& d)
|
||||||
{ return env.now() + d; };
|
{ return env.now() + d; };
|
||||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||||
auto const c = cond("receipt");
|
|
||||||
|
std::vector<std::uint8_t> v;
|
||||||
|
v.resize(cb1.size() + 2, 0x78);
|
||||||
|
std::memcpy (v.data() + 1, cb1.data(), cb1.size());
|
||||||
|
|
||||||
|
auto const p = v.data();
|
||||||
|
auto const s = v.size();
|
||||||
|
|
||||||
|
// All these are expected to fail, because the
|
||||||
|
// condition we pass in is malformed in some way
|
||||||
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
|
Slice{p, s}, T(S{1})), ter(temMALFORMED));
|
||||||
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
|
Slice{p, s - 1}, T(S{1})), ter(temMALFORMED));
|
||||||
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
|
Slice{p, s - 2}, T(S{1})), ter(temMALFORMED));
|
||||||
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
|
Slice{p + 1, s - 1}, T(S{1})), ter(temMALFORMED));
|
||||||
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
|
Slice{p + 1, s - 3}, T(S{1})), ter(temMALFORMED));
|
||||||
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
|
Slice{p + 2, s - 2}, T(S{1})), ter(temMALFORMED));
|
||||||
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
|
Slice{p + 2, s - 3}, T(S{1})), ter(temMALFORMED));
|
||||||
|
|
||||||
auto const seq = env.seq("alice");
|
auto const seq = env.seq("alice");
|
||||||
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
// wrong digest
|
Slice{p + 1, s - 2}, T(S{1})), fee(100));
|
||||||
auto const cx = cond("bad");
|
env(finish("bob", "alice", seq,
|
||||||
env(finish("bob", "alice", seq, cx.first, cx.second), ter(tecNO_PERMISSION));
|
makeSlice(cb1), makeSlice(fb1)), fee(1500));
|
||||||
|
env.require(balance("alice", XRP(4000) - drops(100)));
|
||||||
|
env.require(balance("bob", XRP(5000) - drops(1500)));
|
||||||
|
env.require(balance("carol", XRP(6000)));
|
||||||
}
|
}
|
||||||
{
|
|
||||||
Env env(*this, features(featureSusPay));
|
{ // Test long and short conditions & fulfillments during finish
|
||||||
|
Env env(*this,
|
||||||
|
features(featureSusPay),
|
||||||
|
features(featureCryptoConditions));
|
||||||
auto T = [&env](NetClock::duration const& d)
|
auto T = [&env](NetClock::duration const& d)
|
||||||
{ return env.now() + d; };
|
{ return env.now() + d; };
|
||||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||||
auto const p = from_hex_text<uint128>(
|
|
||||||
"0102030405060708090A0B0C0D0E0F");
|
std::vector<std::uint8_t> cv;
|
||||||
auto const d = digest(p);
|
cv.resize(cb2.size() + 2, 0x78);
|
||||||
|
std::memcpy (cv.data() + 1, cb2.data(), cb2.size());
|
||||||
|
|
||||||
|
auto const cp = cv.data();
|
||||||
|
auto const cs = cv.size();
|
||||||
|
|
||||||
|
std::vector<std::uint8_t> fv;
|
||||||
|
fv.resize(fb2.size() + 2, 0x13);
|
||||||
|
std::memcpy(fv.data() + 1, fb2.data(), fb2.size());
|
||||||
|
|
||||||
|
auto const fp = fv.data();
|
||||||
|
auto const fs = fv.size();
|
||||||
|
|
||||||
|
// All these are expected to fail, because the
|
||||||
|
// condition we pass in is malformed in some way
|
||||||
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
|
Slice{cp, cs}, T(S{1})), ter(temMALFORMED));
|
||||||
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
|
Slice{cp, cs - 1}, T(S{1})), ter(temMALFORMED));
|
||||||
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
|
Slice{cp, cs - 2}, T(S{1})), ter(temMALFORMED));
|
||||||
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
|
Slice{cp + 1, cs - 1}, T(S{1})), ter(temMALFORMED));
|
||||||
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
|
Slice{cp + 1, cs - 3}, T(S{1})), ter(temMALFORMED));
|
||||||
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
|
Slice{cp + 2, cs - 2}, T(S{1})), ter(temMALFORMED));
|
||||||
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
|
Slice{cp + 2, cs - 3}, T(S{1})), ter(temMALFORMED));
|
||||||
|
|
||||||
auto const seq = env.seq("alice");
|
auto const seq = env.seq("alice");
|
||||||
env(condpay("alice", "carol", XRP(1000), d, T(S{1})));
|
env(condpay("alice", "carol", XRP(1000),
|
||||||
// bad digest size
|
Slice{cp + 1, cs - 2}, T(S{1})), fee(100));
|
||||||
env(finish("bob", "alice", seq, d, p), ter(temMALFORMED));
|
|
||||||
|
// Now, try to fulfill using the same sequence of
|
||||||
|
// malformed conditions.
|
||||||
|
env(finish("bob", "alice", seq, Slice{cp, cs}, Slice{fp, fs}),
|
||||||
|
fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("bob", "alice", seq, Slice{cp, cs - 1}, Slice{fp, fs}),
|
||||||
|
fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("bob", "alice", seq, Slice{cp, cs - 2}, Slice{fp, fs}),
|
||||||
|
fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("bob", "alice", seq, Slice{cp + 1, cs - 1}, Slice{fp, fs}),
|
||||||
|
fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("bob", "alice", seq, Slice{cp + 1, cs - 3}, Slice{fp, fs}),
|
||||||
|
fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("bob", "alice", seq, Slice{cp + 2, cs - 2}, Slice{fp, fs}),
|
||||||
|
fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("bob", "alice", seq, Slice{cp + 2, cs - 3}, Slice{fp, fs}),
|
||||||
|
fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
|
||||||
|
// Now, using the correct condition, try malformed
|
||||||
|
// fulfillments:
|
||||||
|
env(finish("bob", "alice", seq, Slice{cp + 1, cs - 2}, Slice{fp, fs}),
|
||||||
|
fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("bob", "alice", seq, Slice{cp + 1, cs - 2}, Slice{fp, fs - 1}),
|
||||||
|
fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("bob", "alice", seq, Slice{cp + 1, cs - 2}, Slice{fp, fs - 2}),
|
||||||
|
fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("bob", "alice", seq, Slice{cp + 1, cs - 2}, Slice{fp + 1, fs - 1}),
|
||||||
|
fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("bob", "alice", seq, Slice{cp + 1, cs - 2}, Slice{fp + 1, fs - 3}),
|
||||||
|
fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("bob", "alice", seq, Slice{cp + 1, cs - 2}, Slice{fp + 1, fs - 3}),
|
||||||
|
fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("bob", "alice", seq, Slice{cp + 1, cs - 2}, Slice{fp + 2, fs - 2}),
|
||||||
|
fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("bob", "alice", seq, Slice{cp + 1, cs - 2}, Slice{fp + 2, fs - 3}),
|
||||||
|
fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
|
||||||
|
// Now try for the right one
|
||||||
|
env(finish("bob", "alice", seq,
|
||||||
|
makeSlice(cb2), makeSlice(fb2)), fee(1500));
|
||||||
|
env.require(balance("alice", XRP(4000) - drops(100)));
|
||||||
|
env.require(balance("carol", XRP(6000)));
|
||||||
|
}
|
||||||
|
|
||||||
|
{ // Test empty condition during creation and
|
||||||
|
// empty condition & fulfillment during finish
|
||||||
|
Env env(*this,
|
||||||
|
features(featureSusPay),
|
||||||
|
features(featureCryptoConditions));
|
||||||
|
auto T = [&env](NetClock::duration const& d)
|
||||||
|
{ return env.now() + d; };
|
||||||
|
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||||
|
|
||||||
|
env(condpay("alice", "carol", XRP(1000), {}, T(S{1})), ter(temMALFORMED));
|
||||||
|
|
||||||
|
auto const seq = env.seq("alice");
|
||||||
|
env(condpay("alice", "carol", XRP(1000), makeSlice(cb3), T(S{1})));
|
||||||
|
|
||||||
|
env(finish("bob", "alice", seq, {}, {}),
|
||||||
|
fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("bob", "alice", seq, makeSlice(cb3), {}),
|
||||||
|
fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
env(finish("bob", "alice", seq, {}, makeSlice(fb3)),
|
||||||
|
fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||||
|
|
||||||
|
auto correctFinish = finish("bob", "alice", seq,
|
||||||
|
makeSlice(cb3), makeSlice(fb3));
|
||||||
|
|
||||||
|
// Manually assemble finish that is missing the
|
||||||
|
// Condition or the Fulfillment (either both must
|
||||||
|
// be present, or neither can):
|
||||||
|
{
|
||||||
|
auto finishNoCondition = correctFinish;
|
||||||
|
finishNoCondition.removeMember ("Condition");
|
||||||
|
env (finishNoCondition, ter(temMALFORMED));
|
||||||
|
|
||||||
|
auto finishNoFulfillment = correctFinish;
|
||||||
|
finishNoFulfillment.removeMember ("Fulfillment");
|
||||||
|
env (finishNoFulfillment, ter(temMALFORMED));
|
||||||
|
}
|
||||||
|
|
||||||
|
env(correctFinish, fee(1500));
|
||||||
|
env.require(balance ("carol", XRP(6000)));
|
||||||
|
env.require(balance ("alice", XRP(4000) - drops(10)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
testMeta()
|
testMeta()
|
||||||
{
|
{
|
||||||
|
testcase ("Metadata");
|
||||||
|
|
||||||
using namespace jtx;
|
using namespace jtx;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
Env env(*this, features(featureSusPay));
|
Env env(*this,
|
||||||
auto T = [&env](NetClock::duration const& d)
|
features(featureSusPay),
|
||||||
{ return env.now() + d; };
|
features(featureCryptoConditions));
|
||||||
|
|
||||||
env.fund(XRP(5000), "alice", "bob", "carol");
|
env.fund(XRP(5000), "alice", "bob", "carol");
|
||||||
auto const c = cond("receipt");
|
env(condpay("alice", "carol", XRP(1000), makeSlice(cb1), env.now() + 1s));
|
||||||
env(condpay("alice", "carol", XRP(1000), c.first, T(1s)));
|
|
||||||
auto const m = env.meta();
|
auto const m = env.meta();
|
||||||
BEAST_EXPECT((*m)[sfTransactionResult] == tesSUCCESS);
|
BEAST_EXPECT((*m)[sfTransactionResult] == tesSUCCESS);
|
||||||
}
|
}
|
||||||
|
|
||||||
void testConsequences()
|
void testConsequences()
|
||||||
{
|
{
|
||||||
|
testcase ("Consequences");
|
||||||
|
|
||||||
using namespace jtx;
|
using namespace jtx;
|
||||||
using namespace std::chrono;
|
using namespace std::chrono;
|
||||||
Env env(*this, features(featureSusPay));
|
Env env(*this,
|
||||||
auto T = [&env](NetClock::duration const& d)
|
features(featureSusPay),
|
||||||
{
|
features(featureCryptoConditions));
|
||||||
return env.now() + d;
|
|
||||||
};
|
|
||||||
env.memoize("alice");
|
env.memoize("alice");
|
||||||
env.memoize("bob");
|
env.memoize("bob");
|
||||||
env.memoize("carol");
|
env.memoize("carol");
|
||||||
auto const c = cond("receipt");
|
|
||||||
{
|
{
|
||||||
auto const jtx = env.jt(
|
auto const jtx = env.jt(
|
||||||
condpay("alice", "carol", XRP(1000), c.first, T(1s)),
|
condpay("alice", "carol", XRP(1000),
|
||||||
|
makeSlice(cb1), env.now() + 1s),
|
||||||
seq(1), fee(10));
|
seq(1), fee(10));
|
||||||
auto const pf = preflight(env.app(), env.current()->rules(),
|
auto const pf = preflight(env.app(), env.current()->rules(),
|
||||||
*jtx.stx, tapNONE, env.journal);
|
*jtx.stx, tapNONE, env.journal);
|
||||||
@@ -394,7 +787,8 @@ struct SusPay_test : public beast::unit_test::suite
|
|||||||
|
|
||||||
{
|
{
|
||||||
auto const jtx = env.jt(
|
auto const jtx = env.jt(
|
||||||
finish("bob", "alice", 3, c.first, c.second),
|
finish("bob", "alice", 3,
|
||||||
|
makeSlice(cb1), makeSlice(fb1)),
|
||||||
seq(1), fee(10));
|
seq(1), fee(10));
|
||||||
auto const pf = preflight(env.app(), env.current()->rules(),
|
auto const pf = preflight(env.app(), env.current()->rules(),
|
||||||
*jtx.stx, tapNONE, env.journal);
|
*jtx.stx, tapNONE, env.journal);
|
||||||
|
|||||||
Reference in New Issue
Block a user