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:
Nik Bougalis
2016-10-26 01:18:31 -07:00
parent d198b439fd
commit d69b16895c
14 changed files with 772 additions and 224 deletions

View File

@@ -20,7 +20,6 @@
#include <BeastConfig.h>
#include <ripple/test/jtx.h>
#include <ripple/app/tx/applySteps.h>
#include <ripple/protocol/digest.h>
#include <ripple/protocol/Feature.h>
#include <ripple/protocol/Indexes.h>
#include <ripple/protocol/JsonFields.h>
@@ -31,38 +30,72 @@ namespace test {
struct SusPay_test : public beast::unit_test::suite
{
template <class... Args>
static
uint256
digest (Args&&... args)
{
sha256_hasher h;
using beast::hash_append;
hash_append(h, args...);
auto const d = static_cast<
sha256_hasher::result_type>(h);
uint256 result;
std::memcpy(result.data(), d.data(), d.size());
return result;
}
// An Ed25519 conditional trigger fulfillment and its
// condition
std::array<std::uint8_t, 99> const fb1 =
{{
0x00, 0x04, 0x60, 0x3B, 0x6A, 0x27, 0xBC, 0xCE, 0xB6, 0xA4, 0x2D, 0x62,
0xA3, 0xA8, 0xD0, 0x2A, 0x6F, 0x0D, 0x73, 0x65, 0x32, 0x15, 0x77, 0x1D,
0xE2, 0x43, 0xA6, 0x3A, 0xC0, 0x48, 0xA1, 0x8B, 0x59, 0xDA, 0x29, 0x8F,
0x89, 0x5B, 0x3C, 0xAF, 0xE2, 0xC9, 0x50, 0x60, 0x39, 0xD0, 0xE2, 0xA6,
0x63, 0x82, 0x56, 0x80, 0x04, 0x67, 0x4F, 0xE8, 0xD2, 0x37, 0x78, 0x50,
0x92, 0xE4, 0x0D, 0x6A, 0xAF, 0x48, 0x3E, 0x4F, 0xC6, 0x01, 0x68, 0x70,
0x5F, 0x31, 0xF1, 0x01, 0x59, 0x61, 0x38, 0xCE, 0x21, 0xAA, 0x35, 0x7C,
0x0D, 0x32, 0xA0, 0x64, 0xF4, 0x23, 0xDC, 0x3E, 0xE4, 0xAA, 0x3A, 0xBF,
0x53, 0xF8, 0x03,
}};
// Create condition
// First is digest, second is pre-image
static
std::pair<uint256, uint256>
cond (std::string const& receipt)
{
std::pair<uint256, uint256> result;
result.second = digest(receipt);
result.first = digest(result.second);
return result;
}
std::array<std::uint8_t, 39> const cb1 =
{{
0x00, 0x04, 0x01, 0x20, 0x20, 0x3B, 0x6A, 0x27, 0xBC, 0xCE, 0xB6, 0xA4,
0x2D, 0x62, 0xA3, 0xA8, 0xD0, 0x2A, 0x6F, 0x0D, 0x73, 0x65, 0x32, 0x15,
0x77, 0x1D, 0xE2, 0x43, 0xA6, 0x3A, 0xC0, 0x48, 0xA1, 0x8B, 0x59, 0xDA,
0x29, 0x01, 0x60
}};
// A prefix.prefix.ed25519 conditional trigger fulfillment:
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
Json::Value
condpay (jtx::Account const& account, jtx::Account const& to,
STAmount const& amount, uint256 const& digest,
NetClock::time_point const& expiry)
STAmount const& amount, Slice condition,
NetClock::time_point const& cancelAfter)
{
using namespace jtx;
Json::Value jv;
@@ -72,8 +105,20 @@ struct SusPay_test : public beast::unit_test::suite
jv[jss::Destination] = to.human();
jv[jss::Amount] = amount.getJson(0);
jv["CancelAfter"] =
expiry.time_since_epoch().count();
jv["Digest"] = to_string(digest);
cancelAfter.time_since_epoch().count();
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;
}
@@ -94,6 +139,25 @@ struct SusPay_test : public beast::unit_test::suite
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
Json::Value
finish (jtx::Account const& account,
@@ -108,12 +172,11 @@ struct SusPay_test : public beast::unit_test::suite
return jv;
}
template <class Proof>
static
Json::Value
finish (jtx::Account const& account,
jtx::Account const& from, std::uint32_t seq,
uint256 const& digest, Proof const& proof)
Slice condition, Slice fulfillment)
{
Json::Value jv;
jv[jss::TransactionType] = "SuspendedPaymentFinish";
@@ -121,9 +184,8 @@ struct SusPay_test : public beast::unit_test::suite
jv[jss::Account] = account.human();
jv["Owner"] = from.human();
jv["OfferSequence"] = seq;
jv["Method"] = 1;
jv["Digest"] = to_string(digest);
jv["Proof"] = to_string(proof);
jv["Condition"] = strHex(condition);
jv["Fulfillment"] = strHex(fulfillment);
return jv;
}
@@ -144,46 +206,100 @@ struct SusPay_test : public beast::unit_test::suite
void
testEnablement()
{
testcase ("Enablement");
using namespace jtx;
using namespace std::chrono;
using S = seconds;
auto const c = cond("receipt");
{
{ // SusPay enabled
Env env(*this, features(featureSusPay));
auto T = [&env](NetClock::duration const& d)
{ return env.now() + d; };
env.fund(XRP(5000), "alice", "bob");
// syntax
env(condpay("alice", "bob", XRP(1000), c.first, T(S{1})));
env(lockup("alice", "bob", XRP(1000), env.now() + 1s));
}
{
{ // SusPay not enabled
Env env(*this);
auto T = [&env](NetClock::duration const& d)
{ return env.now() + d; };
env.fund(XRP(5000), "alice", "bob");
// disabled in production
env(condpay("alice", "bob", XRP(1000), c.first, T(S{1})), ter(temDISABLED));
env(finish("bob", "alice", 1), ter(temDISABLED));
env(cancel("bob", "alice", 1), ter(temDISABLED));
env(lockup("alice", "bob", XRP(1000), env.now() + 1s), ter(temDISABLED));
env(finish("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
testTags()
{
testcase ("Tags");
using namespace jtx;
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 T = [&env](NetClock::duration const& d)
{ return env.now() + d; };
env.fund(XRP(5000), alice, "bob");
auto const c = cond("receipt");
auto const seq = env.seq(alice);
// 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));
BEAST_EXPECT((*sle)[sfSourceTag] == 1);
BEAST_EXPECT((*sle)[sfDestinationTag] == 2);
@@ -193,80 +309,203 @@ struct SusPay_test : public beast::unit_test::suite
void
testFails()
{
testcase ("Failure Cases");
using namespace jtx;
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 T = [&env](NetClock::duration const& d)
{ return env.now() + d; };
env.fund(XRP(5000), "alice", "bob");
auto const c = cond("receipt");
// VFALCO Should we enforce this?
// expiration in the past
//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));
// no destination account
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})), ter(tecNO_DST));
env.fund(XRP(5000), "carol");
env(condpay("alice", "carol",
XRP(1000), c.first, T(S{1})), stag(2));
env(condpay("alice", "carol",
XRP(1000), c.first, T(S{1})), stag(3), dtag(4));
env(fset("carol", asfRequireDest));
// missing destination tag
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})), ter(tecDST_TAG_NEEDED));
env(condpay("alice", "carol",
XRP(1000), c.first, T(S{1})), dtag(1));
auto j1 = lockup("alice", "carol", XRP(1), env.now() + 1s);
j1.removeMember ("FinishAfter");
env (j1, ter(temBAD_EXPIRATION));
auto j2 = condpay("alice", "carol", XRP(1), makeSlice(cb1), env.now() + 1s);
j2.removeMember ("CancelAfter");
env (j2, ter(temBAD_EXPIRATION));
}
// Fail if FinishAfter has already passed:
env (lockup("alice", "carol", XRP(1), env.now() - 1s), ter (tecNO_PERMISSION));
// Both CancelAfter and FinishAfter
env(condpay("alice", "carol", XRP(1), makeSlice(cb1),
env.now() + 10s, env.now() + 10s), ter (temBAD_EXPIRATION));
env(condpay("alice", "carol", XRP(1), makeSlice(cb1),
env.now() + 10s, env.now() + 15s), ter (temBAD_EXPIRATION));
// Fail if the sender wants to send more than he has:
auto const accountReserve =
drops(env.current()->fees().reserve);
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
testLockup()
{
testcase ("Lockup");
using namespace jtx;
using namespace std::chrono;
using S = seconds;
{
{ // Unconditional
Env env(*this, features(featureSusPay));
auto T = [&env](NetClock::duration const& d)
{ return env.now() + d; };
env.fund(XRP(5000), "alice", "bob");
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(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(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
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
testCondPay()
{
testcase ("Conditional Payments");
using namespace jtx;
using namespace std::chrono;
using S = seconds;
{
Env env(*this, features(featureSusPay));
{ // Test cryptoconditions
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");
auto const c = cond("receipt");
auto const seq = env.seq("alice");
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);
env.require(balance("alice", XRP(4000) - drops(10)));
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);
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);
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
BEAST_EXPECT(! env.le(keylet::susPay(Account("alice").id(), seq)));
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.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)
{ return env.now() + d; };
env.fund(XRP(5000), "alice", "bob", "carol");
auto const c = cond("receipt");
auto const seq = env.seq("alice");
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.require(balance("alice", XRP(4000) - drops(10)));
// balance restored on cancel
@@ -293,83 +534,235 @@ struct SusPay_test : public beast::unit_test::suite
// SLE removed on cancel
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)
{ return env.now() + d; };
env.fund(XRP(5000), "alice", "bob", "carol");
env.close();
auto const c = cond("receipt");
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);
// 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);
env.close();
// 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);
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)
{ return env.now() + d; };
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");
env(condpay("alice", "carol", XRP(1000), c.first, T(S{1})));
// wrong digest
auto const cx = cond("bad");
env(finish("bob", "alice", seq, cx.first, cx.second), ter(tecNO_PERMISSION));
env(condpay("alice", "carol", XRP(1000),
Slice{p + 1, s - 2}, T(S{1})), fee(100));
env(finish("bob", "alice", seq,
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)
{ return env.now() + d; };
env.fund(XRP(5000), "alice", "bob", "carol");
auto const p = from_hex_text<uint128>(
"0102030405060708090A0B0C0D0E0F");
auto const d = digest(p);
std::vector<std::uint8_t> cv;
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");
env(condpay("alice", "carol", XRP(1000), d, T(S{1})));
// bad digest size
env(finish("bob", "alice", seq, d, p), ter(temMALFORMED));
env(condpay("alice", "carol", XRP(1000),
Slice{cp + 1, cs - 2}, T(S{1})), fee(100));
// 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
testMeta()
{
testcase ("Metadata");
using namespace jtx;
using namespace std::chrono;
Env env(*this, features(featureSusPay));
auto T = [&env](NetClock::duration const& d)
{ return env.now() + d; };
Env env(*this,
features(featureSusPay),
features(featureCryptoConditions));
env.fund(XRP(5000), "alice", "bob", "carol");
auto const c = cond("receipt");
env(condpay("alice", "carol", XRP(1000), c.first, T(1s)));
env(condpay("alice", "carol", XRP(1000), makeSlice(cb1), env.now() + 1s));
auto const m = env.meta();
BEAST_EXPECT((*m)[sfTransactionResult] == tesSUCCESS);
}
void testConsequences()
{
testcase ("Consequences");
using namespace jtx;
using namespace std::chrono;
Env env(*this, features(featureSusPay));
auto T = [&env](NetClock::duration const& d)
{
return env.now() + d;
};
Env env(*this,
features(featureSusPay),
features(featureCryptoConditions));
env.memoize("alice");
env.memoize("bob");
env.memoize("carol");
auto const c = cond("receipt");
{
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));
auto const pf = preflight(env.app(), env.current()->rules(),
*jtx.stx, tapNONE, env.journal);
@@ -394,7 +787,8 @@ struct SusPay_test : public beast::unit_test::suite
{
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));
auto const pf = preflight(env.app(), env.current()->rules(),
*jtx.stx, tapNONE, env.journal);