mirror of
https://github.com/XRPLF/rippled.git
synced 2025-12-06 17:27:55 +00:00
Add DepositPreauth ledger type and transaction (RIPD-1624):
The lsfDepositAuth flag limits the AccountIDs that can deposit into the account that has the flag set. The original design only allowed deposits to complete if the account with the flag set also signed the transaction that caused the deposit. The DepositPreauth ledger type allows an account with the lsfDepositAuth flag set to preauthorize additional accounts. This preauthorization allows them to sign deposits as well. An account can add DepositPreauth objects to the ledger (and remove them as well) using the DepositPreauth transaction.
This commit is contained in:
@@ -17,37 +17,28 @@
|
||||
*/
|
||||
//==============================================================================
|
||||
|
||||
#include <ripple/app/paths/Flow.h>
|
||||
#include <ripple/app/paths/impl/Steps.h>
|
||||
#include <ripple/basics/contract.h>
|
||||
#include <ripple/core/Config.h>
|
||||
#include <ripple/ledger/ApplyViewImpl.h>
|
||||
#include <ripple/ledger/PaymentSandbox.h>
|
||||
#include <ripple/ledger/Sandbox.h>
|
||||
#include <ripple/protocol/Feature.h>
|
||||
#include <ripple/protocol/JsonFields.h>
|
||||
#include <test/jtx.h>
|
||||
#include <test/jtx/PathSet.h>
|
||||
|
||||
namespace ripple {
|
||||
namespace test {
|
||||
|
||||
// Helper function that returns the reserve on an account based on
|
||||
// the passed in number of owners.
|
||||
static XRPAmount reserve (jtx::Env& env, std::uint32_t count)
|
||||
{
|
||||
return env.current()->fees().accountReserve (count);
|
||||
}
|
||||
|
||||
// Helper function that returns true if acct has the lsfDepostAuth flag set.
|
||||
static bool hasDepositAuth (jtx::Env const& env, jtx::Account const& acct)
|
||||
{
|
||||
return ((*env.le(acct))[sfFlags] & lsfDepositAuth) == lsfDepositAuth;
|
||||
}
|
||||
|
||||
|
||||
struct DepositAuth_test : public beast::unit_test::suite
|
||||
{
|
||||
// Helper function that returns the reserve on an account based on
|
||||
// the passed in number of owners.
|
||||
static XRPAmount reserve(jtx::Env& env, std::uint32_t count)
|
||||
{
|
||||
return env.current()->fees().accountReserve (count);
|
||||
}
|
||||
|
||||
// Helper function that returns true if acct has the lsfDepostAuth flag set.
|
||||
static bool hasDepositAuth (jtx::Env const& env, jtx::Account const& acct)
|
||||
{
|
||||
return ((*env.le(acct))[sfFlags] & lsfDepositAuth) == lsfDepositAuth;
|
||||
}
|
||||
|
||||
|
||||
void testEnable()
|
||||
{
|
||||
testcase ("Enable");
|
||||
@@ -389,7 +380,325 @@ struct DepositAuth_test : public beast::unit_test::suite
|
||||
}
|
||||
};
|
||||
|
||||
struct DepositPreauth_test : public beast::unit_test::suite
|
||||
{
|
||||
void testEnable()
|
||||
{
|
||||
testcase ("Enable");
|
||||
|
||||
using namespace jtx;
|
||||
Account const alice {"alice"};
|
||||
Account const becky {"becky"};
|
||||
{
|
||||
// featureDepositPreauth is disabled.
|
||||
Env env (*this, supported_amendments() - featureDepositPreauth);
|
||||
env.fund (XRP (10000), alice, becky);
|
||||
env.close();
|
||||
|
||||
// Should not be able to add a DepositPreauth to alice.
|
||||
env (deposit::auth (alice, becky), ter (temDISABLED));
|
||||
env.close();
|
||||
env.require (owners (alice, 0));
|
||||
env.require (owners (becky, 0));
|
||||
|
||||
// Should not be able to remove a DepositPreauth from alice.
|
||||
env (deposit::unauth (alice, becky), ter (temDISABLED));
|
||||
env.close();
|
||||
env.require (owners (alice, 0));
|
||||
env.require (owners (becky, 0));
|
||||
}
|
||||
{
|
||||
// featureDepositPreauth is enabled. The valid case is really
|
||||
// simple:
|
||||
// o We should be able to add and remove an entry, and
|
||||
// o That entry should cost one reserve.
|
||||
// o The reserve should be returned when the entry is removed.
|
||||
Env env (*this);
|
||||
env.fund (XRP (10000), alice, becky);
|
||||
env.close();
|
||||
|
||||
// Add a DepositPreauth to alice.
|
||||
env (deposit::auth (alice, becky));
|
||||
env.close();
|
||||
env.require (owners (alice, 1));
|
||||
env.require (owners (becky, 0));
|
||||
|
||||
// Remove a DepositPreauth from alice.
|
||||
env (deposit::unauth (alice, becky));
|
||||
env.close();
|
||||
env.require (owners (alice, 0));
|
||||
env.require (owners (becky, 0));
|
||||
}
|
||||
}
|
||||
|
||||
void testInvalid()
|
||||
{
|
||||
testcase ("Invalid");
|
||||
|
||||
using namespace jtx;
|
||||
Account const alice {"alice"};
|
||||
Account const becky {"becky"};
|
||||
Account const carol {"carol"};
|
||||
|
||||
Env env (*this);
|
||||
|
||||
// Tell env about alice, becky and carol since they are not yet funded.
|
||||
env.memoize (alice);
|
||||
env.memoize (becky);
|
||||
env.memoize (carol);
|
||||
|
||||
// Add DepositPreauth to an unfunded account.
|
||||
env (deposit::auth (alice, becky), seq (1), ter (terNO_ACCOUNT));
|
||||
|
||||
env.fund (XRP (10000), alice, becky);
|
||||
env.close();
|
||||
|
||||
// Bad fee.
|
||||
env (deposit::auth (alice, becky), fee (drops(-10)), ter (temBAD_FEE));
|
||||
env.close();
|
||||
|
||||
// Bad flags.
|
||||
env (deposit::auth (alice, becky),
|
||||
txflags (tfSell), ter (temINVALID_FLAG));
|
||||
env.close();
|
||||
|
||||
{
|
||||
// Neither auth not unauth.
|
||||
Json::Value tx {deposit::auth (alice, becky)};
|
||||
tx.removeMember (sfAuthorize.jsonName);
|
||||
env (tx, ter (temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// Both auth and unauth.
|
||||
Json::Value tx {deposit::auth (alice, becky)};
|
||||
tx[sfUnauthorize.jsonName] = becky.human();
|
||||
env (tx, ter (temMALFORMED));
|
||||
env.close();
|
||||
}
|
||||
{
|
||||
// Alice authorizes a zero account.
|
||||
Json::Value tx {deposit::auth (alice, becky)};
|
||||
tx[sfAuthorize.jsonName] = to_string (xrpAccount());
|
||||
env (tx, ter (temINVALID_ACCOUNT_ID));
|
||||
env.close();
|
||||
}
|
||||
|
||||
// alice authorizes herself.
|
||||
env (deposit::auth (alice, alice), ter (temCANNOT_PREAUTH_SELF));
|
||||
env.close();
|
||||
|
||||
// alice authorizes an unfunded account.
|
||||
env (deposit::auth (alice, carol), ter (tecNO_TARGET));
|
||||
env.close();
|
||||
|
||||
// alice successfully authorizes becky.
|
||||
env.require (owners (alice, 0));
|
||||
env.require (owners (becky, 0));
|
||||
env (deposit::auth (alice, becky));
|
||||
env.close();
|
||||
env.require (owners (alice, 1));
|
||||
env.require (owners (becky, 0));
|
||||
|
||||
// alice attempts to create a duplicate authorization.
|
||||
env (deposit::auth (alice, becky), ter (tecDUPLICATE));
|
||||
env.close();
|
||||
env.require (owners (alice, 1));
|
||||
env.require (owners (becky, 0));
|
||||
|
||||
// carol attempts to preauthorize but doesn't have enough reserve.
|
||||
env.fund (drops (249'999'999), carol);
|
||||
env.close();
|
||||
|
||||
env (deposit::auth (carol, becky), ter (tecINSUFFICIENT_RESERVE));
|
||||
env.close();
|
||||
env.require (owners (carol, 0));
|
||||
env.require (owners (becky, 0));
|
||||
|
||||
// carol gets enough XRP to (barely) meet the reserve.
|
||||
env (pay (alice, carol, drops (11)));
|
||||
env.close();
|
||||
env (deposit::auth (carol, becky));
|
||||
env.close();
|
||||
env.require (owners (carol, 1));
|
||||
env.require (owners (becky, 0));
|
||||
|
||||
// But carol can't meet the reserve for another preauthorization.
|
||||
env (deposit::auth (carol, alice), ter (tecINSUFFICIENT_RESERVE));
|
||||
env.close();
|
||||
env.require (owners (carol, 1));
|
||||
env.require (owners (becky, 0));
|
||||
env.require (owners (alice, 1));
|
||||
|
||||
// alice attempts to remove an authorization she doesn't have.
|
||||
env (deposit::unauth (alice, carol), ter (tecNO_ENTRY));
|
||||
env.close();
|
||||
env.require (owners (alice, 1));
|
||||
env.require (owners (becky, 0));
|
||||
|
||||
// alice successfully removes her authorization of becky.
|
||||
env (deposit::unauth (alice, becky));
|
||||
env.close();
|
||||
env.require (owners (alice, 0));
|
||||
env.require (owners (becky, 0));
|
||||
|
||||
// alice removes becky again and gets an error.
|
||||
env (deposit::unauth (alice, becky), ter (tecNO_ENTRY));
|
||||
env.close();
|
||||
env.require (owners (alice, 0));
|
||||
env.require (owners (becky, 0));
|
||||
}
|
||||
|
||||
void testPayment (FeatureBitset features)
|
||||
{
|
||||
testcase ("Payment");
|
||||
|
||||
using namespace jtx;
|
||||
Account const alice {"alice"};
|
||||
Account const becky {"becky"};
|
||||
Account const gw {"gw"};
|
||||
IOU const USD (gw["USD"]);
|
||||
|
||||
bool const supportsPreauth = {features[featureDepositPreauth]};
|
||||
|
||||
{
|
||||
// The initial implementation of DepositAuth had a bug where an
|
||||
// account with the DepositAuth flag set could not make a payment
|
||||
// to itself. That bug was fixed in the DepositPreauth amendment.
|
||||
Env env (*this, features);
|
||||
env.fund (XRP (5000), alice, becky, gw);
|
||||
env.close();
|
||||
|
||||
env.trust (USD (1000), alice);
|
||||
env.trust (USD (1000), becky);
|
||||
env.close();
|
||||
|
||||
env (pay (gw, alice, USD (500)));
|
||||
env.close();
|
||||
|
||||
env (offer (alice, XRP (100), USD (100), tfPassive),
|
||||
require (offers (alice, 1)));
|
||||
env.close();
|
||||
|
||||
// becky pays herself USD (10) by consuming part of alice's offer.
|
||||
// Make sure the payment works if PaymentAuth is not involved.
|
||||
env (pay (becky, becky, USD (10)),
|
||||
path (~USD), sendmax (XRP (10)));
|
||||
env.close();
|
||||
|
||||
// becky decides to require authorization for deposits.
|
||||
env(fset (becky, asfDepositAuth));
|
||||
env.close();
|
||||
|
||||
// becky pays herself again. Whether it succeeds depends on
|
||||
// whether featureDepositPreauth is enabled.
|
||||
TER const expect {
|
||||
supportsPreauth ? TER {tesSUCCESS} : TER {tecNO_PERMISSION}};
|
||||
|
||||
env (pay (becky, becky, USD (10)),
|
||||
path (~USD), sendmax (XRP (10)), ter (expect));
|
||||
env.close();
|
||||
}
|
||||
|
||||
if (supportsPreauth)
|
||||
{
|
||||
// Make sure DepositPreauthorization works for payments.
|
||||
|
||||
Account const carol {"carol"};
|
||||
|
||||
Env env (*this, features);
|
||||
env.fund (XRP (5000), alice, becky, carol, gw);
|
||||
env.close();
|
||||
|
||||
env.trust (USD (1000), alice);
|
||||
env.trust (USD (1000), becky);
|
||||
env.trust (USD (1000), carol);
|
||||
env.close();
|
||||
|
||||
env (pay (gw, alice, USD (1000)));
|
||||
env.close();
|
||||
|
||||
// Make XRP and IOU payments from alice to becky. Should be fine.
|
||||
env (pay (alice, becky, XRP (100)));
|
||||
env (pay (alice, becky, USD (100)));
|
||||
env.close();
|
||||
|
||||
// becky decides to require authorization for deposits.
|
||||
env(fset (becky, asfDepositAuth));
|
||||
env.close();
|
||||
|
||||
// alice can no longer pay becky.
|
||||
env (pay (alice, becky, XRP (100)), ter (tecNO_PERMISSION));
|
||||
env (pay (alice, becky, USD (100)), ter (tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// becky preauthorizes carol for deposit, which doesn't provide
|
||||
// authorization for alice.
|
||||
env (deposit::auth (becky, carol));
|
||||
env.close();
|
||||
|
||||
// alice still can't pay becky.
|
||||
env (pay (alice, becky, XRP (100)), ter (tecNO_PERMISSION));
|
||||
env (pay (alice, becky, USD (100)), ter (tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// becky preauthorizes alice for deposit.
|
||||
env (deposit::auth (becky, alice));
|
||||
env.close();
|
||||
|
||||
// alice can now pay becky.
|
||||
env (pay (alice, becky, XRP (100)));
|
||||
env (pay (alice, becky, USD (100)));
|
||||
env.close();
|
||||
|
||||
// alice decides to require authorization for deposits.
|
||||
env(fset (alice, asfDepositAuth));
|
||||
env.close();
|
||||
|
||||
// Even though alice is authorized to pay becky, becky is not
|
||||
// authorized to pay alice.
|
||||
env (pay (becky, alice, XRP (100)), ter (tecNO_PERMISSION));
|
||||
env (pay (becky, alice, USD (100)), ter (tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// becky unauthorizes carol. Should have no impact on alice.
|
||||
env (deposit::unauth (becky, carol));
|
||||
env.close();
|
||||
|
||||
env (pay (alice, becky, XRP (100)));
|
||||
env (pay (alice, becky, USD (100)));
|
||||
env.close();
|
||||
|
||||
// becky unauthorizes alice. alice now can't pay becky.
|
||||
env (deposit::unauth (becky, alice));
|
||||
env.close();
|
||||
|
||||
env (pay (alice, becky, XRP (100)), ter (tecNO_PERMISSION));
|
||||
env (pay (alice, becky, USD (100)), ter (tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// becky decides to remove authorization for deposits. Now
|
||||
// alice can pay becky again.
|
||||
env(fclear (becky, asfDepositAuth));
|
||||
env.close();
|
||||
|
||||
env (pay (alice, becky, XRP (100)));
|
||||
env (pay (alice, becky, USD (100)));
|
||||
env.close();
|
||||
}
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
testEnable();
|
||||
testInvalid();
|
||||
testPayment (jtx::supported_amendments() - featureDepositPreauth);
|
||||
testPayment (jtx::supported_amendments());
|
||||
}
|
||||
};
|
||||
|
||||
BEAST_DEFINE_TESTSUITE(DepositAuth,app,ripple);
|
||||
BEAST_DEFINE_TESTSUITE(DepositPreauth,app,ripple);
|
||||
|
||||
} // test
|
||||
} // ripple
|
||||
|
||||
@@ -90,7 +90,7 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
void
|
||||
operator()(jtx::Env&, jtx::JTx& jt) const
|
||||
{
|
||||
jt.jv["FinishAfter"] = value_.time_since_epoch().count();
|
||||
jt.jv[sfFinishAfter.jsonName] = value_.time_since_epoch().count();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -110,7 +110,7 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
void
|
||||
operator()(jtx::Env&, jtx::JTx& jt) const
|
||||
{
|
||||
jt.jv["CancelAfter"] = value_.time_since_epoch().count();
|
||||
jt.jv[sfCancelAfter.jsonName] = value_.time_since_epoch().count();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -135,7 +135,7 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
void
|
||||
operator()(jtx::Env&, jtx::JTx& jt) const
|
||||
{
|
||||
jt.jv["Condition"] = value_;
|
||||
jt.jv[sfCondition.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -160,7 +160,7 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
void
|
||||
operator()(jtx::Env&, jtx::JTx& jt) const
|
||||
{
|
||||
jt.jv["Fulfillment"] = value_;
|
||||
jt.jv[sfFulfillment.jsonName] = value_;
|
||||
}
|
||||
};
|
||||
|
||||
@@ -188,8 +188,8 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
jv[jss::TransactionType] = "EscrowFinish";
|
||||
jv[jss::Flags] = tfUniversal;
|
||||
jv[jss::Account] = account.human();
|
||||
jv["Owner"] = from.human();
|
||||
jv["OfferSequence"] = seq;
|
||||
jv[sfOwner.jsonName] = from.human();
|
||||
jv[sfOfferSequence.jsonName] = seq;
|
||||
return jv;
|
||||
}
|
||||
|
||||
@@ -202,8 +202,8 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
jv[jss::TransactionType] = "EscrowCancel";
|
||||
jv[jss::Flags] = tfUniversal;
|
||||
jv[jss::Account] = account.human();
|
||||
jv["Owner"] = from.human();
|
||||
jv["OfferSequence"] = seq;
|
||||
jv[sfOwner.jsonName] = from.human();
|
||||
jv[sfOfferSequence.jsonName] = seq;
|
||||
return jv;
|
||||
}
|
||||
|
||||
@@ -587,15 +587,15 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
using namespace jtx;
|
||||
using namespace std::chrono;
|
||||
|
||||
{ // Unconditional
|
||||
|
||||
{
|
||||
// Unconditional
|
||||
Env env(*this);
|
||||
env.fund(XRP(5000), "alice", "bob");
|
||||
auto const seq = env.seq("alice");
|
||||
env(escrow("alice", "alice", XRP(1000)), finish_time(env.now() + 5s));
|
||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||
|
||||
// Not enough time has elapsed for a finish and cancelling isn't
|
||||
// Not enough time has elapsed for a finish and canceling isn't
|
||||
// possible.
|
||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(finish("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||
@@ -609,37 +609,37 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
env.require(balance("alice", XRP(5000) - drops(10)));
|
||||
}
|
||||
{
|
||||
// Unconditionally pay from alice to bob. jack (neither source nor
|
||||
// Unconditionally pay from Alice to Bob. Zelda (neither source nor
|
||||
// destination) signs all cancels and finishes. This shows that
|
||||
// Escrow will make a payment to bob with no intervention from bob.
|
||||
// Escrow will make a payment to Bob with no intervention from Bob.
|
||||
Env env(*this);
|
||||
env.fund(XRP(5000), "alice", "bob", "jack");
|
||||
env.fund(XRP(5000), "alice", "bob", "zelda");
|
||||
auto const seq = env.seq("alice");
|
||||
env(escrow("alice", "bob", XRP(1000)), finish_time(env.now() + 5s));
|
||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||
|
||||
// Not enough time has elapsed for a finish and cancelling isn't
|
||||
// Not enough time has elapsed for a finish and canceling isn't
|
||||
// possible.
|
||||
env(cancel("jack", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(finish("jack", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(cancel("zelda", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(finish("zelda", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// Cancel continues to not be possible
|
||||
env(cancel("jack", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(cancel("zelda", "alice", seq), ter(tecNO_PERMISSION));
|
||||
|
||||
// Finish should succeed. Verify funds.
|
||||
env(finish("jack", "alice", seq));
|
||||
env(finish("zelda", "alice", seq));
|
||||
env.close();
|
||||
|
||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||
env.require(balance("bob", XRP(6000)));
|
||||
env.require(balance("jack", XRP(5000) - drops(40)));
|
||||
env.require(balance("zelda", XRP(5000) - drops(40)));
|
||||
}
|
||||
{
|
||||
// bob sets PaymentAuth so only bob can finish the escrow.
|
||||
// Bob sets DepositAuth so only Bob can finish the escrow.
|
||||
Env env(*this);
|
||||
|
||||
env.fund(XRP(5000), "alice", "bob", "jack");
|
||||
env.fund(XRP(5000), "alice", "bob", "zelda");
|
||||
env(fset ("bob", asfDepositAuth));
|
||||
env.close();
|
||||
|
||||
@@ -647,22 +647,22 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
env(escrow("alice", "bob", XRP(1000)), finish_time(env.now() + 5s));
|
||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||
|
||||
// Not enough time has elapsed for a finish and cancelling isn't
|
||||
// Not enough time has elapsed for a finish and canceling isn't
|
||||
// possible.
|
||||
env(cancel("jack", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(cancel("zelda", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(cancel("alice", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(finish("jack", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(finish("zelda", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(finish("alice", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(finish("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// Cancel continues to not be possible. Finish will only succeed for
|
||||
// Bob, because of PaymentAuth.
|
||||
env(cancel("jack", "alice", seq), ter(tecNO_PERMISSION));
|
||||
// Bob, because of DepositAuth.
|
||||
env(cancel("zelda", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(cancel("alice", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(finish("jack", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(finish("zelda", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(finish("alice", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(finish("bob", "alice", seq));
|
||||
env.close();
|
||||
@@ -670,7 +670,35 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
env.require(balance("alice", XRP(4000) - (baseFee * 5)));
|
||||
env.require(balance("bob", XRP(6000) - (baseFee * 5)));
|
||||
env.require(balance("jack", XRP(5000) - (baseFee * 4)));
|
||||
env.require(balance("zelda", XRP(5000) - (baseFee * 4)));
|
||||
}
|
||||
{
|
||||
// Bob sets DepositAuth but preauthorizes Zelda, so Zelda can
|
||||
// finish the escrow.
|
||||
Env env(*this);
|
||||
|
||||
env.fund(XRP(5000), "alice", "bob", "zelda");
|
||||
env(fset ("bob", asfDepositAuth));
|
||||
env.close();
|
||||
env(deposit::auth ("bob", "zelda"));
|
||||
env.close();
|
||||
|
||||
auto const seq = env.seq("alice");
|
||||
env(escrow("alice", "bob", XRP(1000)), finish_time(env.now() + 5s));
|
||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||
env.close();
|
||||
|
||||
// DepositPreauth allows Finish to succeed for either Zelda or
|
||||
// Bob. But Finish won't succeed for Alice since she is not
|
||||
// preauthorized.
|
||||
env(finish("alice", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(finish("zelda", "alice", seq));
|
||||
env.close();
|
||||
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
env.require(balance("alice", XRP(4000) - (baseFee * 2)));
|
||||
env.require(balance("bob", XRP(6000) - (baseFee * 2)));
|
||||
env.require(balance("zelda", XRP(5000) - (baseFee * 1)));
|
||||
}
|
||||
{
|
||||
// Conditional
|
||||
@@ -680,7 +708,7 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
env(escrow("alice", "alice", XRP(1000)), condition(cb2), finish_time(env.now() + 5s));
|
||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||
|
||||
// Not enough time has elapsed for a finish and cancelling isn't
|
||||
// Not enough time has elapsed for a finish and canceling isn't
|
||||
// possible.
|
||||
env(cancel("alice", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||
@@ -704,45 +732,63 @@ struct Escrow_test : public beast::unit_test::suite
|
||||
condition(cb2), fulfillment(fb2), fee(1500));
|
||||
}
|
||||
{
|
||||
// Self-escrowed conditional with PaymentAuth
|
||||
// Self-escrowed conditional with DepositAuth.
|
||||
Env env(*this);
|
||||
|
||||
env.fund(XRP(5000), "alice", "bob");
|
||||
auto const seq = env.seq("alice");
|
||||
env(escrow("alice", "alice", XRP(1000)), condition(cb3), finish_time(env.now() + 5s));
|
||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||
|
||||
// Not enough time has elapsed for a finish and cancelling isn't
|
||||
// possible.
|
||||
env(cancel("alice", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(finish("alice", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(finish("alice", "alice", seq),
|
||||
condition(cb3), fulfillment(fb3), fee(1500), ter(tecNO_PERMISSION));
|
||||
env(finish("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(finish("bob", "alice", seq),
|
||||
condition(cb3), fulfillment(fb3), fee(1500), ter(tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// Cancel continues to not be possible. Finish is now possible but
|
||||
// requires the associated cryptocondition.
|
||||
env(cancel("alice", "alice", seq), ter(tecNO_PERMISSION));
|
||||
env(cancel("bob", "alice", seq), ter(tecNO_PERMISSION));
|
||||
// Finish is now possible but requires the cryptocondition.
|
||||
env(finish("bob", "alice", seq), ter(tecCRYPTOCONDITION_ERROR));
|
||||
env(finish("alice", "alice", seq), ter(tecCRYPTOCONDITION_ERROR));
|
||||
|
||||
// Enable deposit authorization. After this, only Alice can finish
|
||||
// Enable deposit authorization. After this only Alice can finish
|
||||
// the escrow.
|
||||
env(fset ("alice", asfDepositAuth));
|
||||
env.close();
|
||||
|
||||
env(finish("bob", "alice", seq), condition(cb2),
|
||||
fulfillment(fb2), fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||
env(finish("alice", "alice", seq), condition(cb2),
|
||||
env(finish("alice", "alice", seq), condition(cb2),
|
||||
fulfillment(fb2), fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||
env(finish("bob", "alice", seq), condition(cb3),
|
||||
fulfillment(fb3), fee(1500), ter(tecNO_PERMISSION));
|
||||
env(finish("alice", "alice", seq), condition(cb3),
|
||||
fulfillment(fb3), fee(1500));
|
||||
}
|
||||
{
|
||||
// Self-escrowed conditional with DepositAuth and DepositPreauth.
|
||||
Env env(*this);
|
||||
|
||||
env.fund(XRP(5000), "alice", "bob", "zelda");
|
||||
auto const seq = env.seq("alice");
|
||||
env(escrow("alice", "alice", XRP(1000)), condition(cb3), finish_time(env.now() + 5s));
|
||||
env.require(balance("alice", XRP(4000) - drops(10)));
|
||||
env.close();
|
||||
|
||||
// Alice preauthorizes Zelda for deposit, even though Alice has not
|
||||
// set the lsfDepositAuth flag (yet).
|
||||
env(deposit::auth("alice", "zelda"));
|
||||
env.close();
|
||||
|
||||
// Finish is now possible but requires the cryptocondition.
|
||||
env(finish("alice", "alice", seq), ter(tecCRYPTOCONDITION_ERROR));
|
||||
env(finish("bob", "alice", seq), ter(tecCRYPTOCONDITION_ERROR));
|
||||
env(finish("zelda", "alice", seq), ter(tecCRYPTOCONDITION_ERROR));
|
||||
|
||||
// Alice enables deposit authorization. After this only Alice or
|
||||
// Zelda (because Zelda is preauthorized) can finish the escrow.
|
||||
env(fset ("alice", asfDepositAuth));
|
||||
env.close();
|
||||
|
||||
env(finish("alice", "alice", seq), condition(cb2),
|
||||
fulfillment(fb2), fee(1500), ter(tecCRYPTOCONDITION_ERROR));
|
||||
env(finish("bob", "alice", seq), condition(cb3),
|
||||
fulfillment(fb3), fee(1500), ter(tecNO_PERMISSION));
|
||||
env(finish("zelda", "alice", seq), condition(cb3),
|
||||
fulfillment(fb3), fee(1500));
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
|
||||
@@ -233,13 +233,13 @@ public:
|
||||
// an offer. Show that the attempt to remove the offer fails.
|
||||
env.require (offers (alice, 2));
|
||||
|
||||
// featureChecks changes the return code on an expired Offer. Adapt
|
||||
// to that.
|
||||
bool const featChecks {features[featureChecks]};
|
||||
// featureDepositPreauths changes the return code on an expired Offer.
|
||||
// Adapt to that.
|
||||
bool const featPreauth {features[featureDepositPreauth]};
|
||||
env (offer (alice, XRP (5), USD (2)),
|
||||
json (sfExpiration.fieldName, lastClose(env)),
|
||||
json (jss::OfferSequence, offer2Seq),
|
||||
ter (featChecks ? TER {tecEXPIRED} : TER {tesSUCCESS}));
|
||||
ter (featPreauth ? TER {tecEXPIRED} : TER {tesSUCCESS}));
|
||||
env.close();
|
||||
|
||||
env.require (offers (alice, 2));
|
||||
@@ -954,12 +954,12 @@ public:
|
||||
owners (alice, 1));
|
||||
|
||||
// Place an offer that should have already expired.
|
||||
// The Checks amendment changes the return code; adapt to that.
|
||||
bool const featChecks {features[featureChecks]};
|
||||
// The DepositPreauth amendment changes the return code; adapt to that.
|
||||
bool const featPreauth {features[featureDepositPreauth]};
|
||||
|
||||
env (offer (alice, xrpOffer, usdOffer),
|
||||
json (sfExpiration.fieldName, lastClose(env)),
|
||||
ter (featChecks ? TER {tecEXPIRED} : TER {tesSUCCESS}));
|
||||
ter (featPreauth ? TER {tecEXPIRED} : TER {tesSUCCESS}));
|
||||
|
||||
env.require (
|
||||
balance (alice, startBalance - f - f),
|
||||
@@ -4390,20 +4390,20 @@ public:
|
||||
env(fset (gw, asfRequireAuth));
|
||||
env.close();
|
||||
|
||||
// The test behaves differently with or without FlowCross.
|
||||
bool const flowCross = features[featureFlowCross];
|
||||
// The test behaves differently with or without DepositPreauth.
|
||||
bool const preauth = features[featureDepositPreauth];
|
||||
|
||||
// Before FlowCross an account with lsfRequireAuth set could not
|
||||
// create an offer to buy their own currency. After FlowCross
|
||||
// Before DepositPreauth an account with lsfRequireAuth set could not
|
||||
// create an offer to buy their own currency. After DepositPreauth
|
||||
// they can.
|
||||
env (offer (gw, gwUSD(40), XRP(4000)),
|
||||
ter (flowCross ? TER {tesSUCCESS} : TER {tecNO_LINE}));
|
||||
ter (preauth ? TER {tesSUCCESS} : TER {tecNO_LINE}));
|
||||
env.close();
|
||||
|
||||
env.require (offers (gw, flowCross ? 1 : 0));
|
||||
env.require (offers (gw, preauth ? 1 : 0));
|
||||
|
||||
if (!flowCross)
|
||||
// The rest of the test verifies FlowCross behavior.
|
||||
if (!preauth)
|
||||
// The rest of the test verifies DepositPreauth behavior.
|
||||
return;
|
||||
|
||||
// Set up an authorized trust line and pay alice gwUSD 50.
|
||||
|
||||
@@ -644,7 +644,7 @@ struct PayChan_test : public beast::unit_test::suite
|
||||
{
|
||||
// Create a channel where dst disallows XRP. Ignore that flag,
|
||||
// since it's just advisory.
|
||||
Env env (*this);
|
||||
Env env (*this, supported_amendments());
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
env (fset (bob, asfDisallowXRP));
|
||||
env (create (alice, bob, XRP (1000), 3600s, alice.pk()));
|
||||
@@ -669,7 +669,7 @@ struct PayChan_test : public beast::unit_test::suite
|
||||
// Claim to a channel where dst disallows XRP (channel is
|
||||
// created before disallow xrp is set). Ignore that flag
|
||||
// since it is just advisory.
|
||||
Env env (*this);
|
||||
Env env (*this, supported_amendments());
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
env (create (alice, bob, XRP (1000), 3600s, alice.pk()));
|
||||
auto const chan = channel (*env.current (), alice, bob);
|
||||
@@ -716,10 +716,11 @@ struct PayChan_test : public beast::unit_test::suite
|
||||
|
||||
auto const alice = Account ("alice");
|
||||
auto const bob = Account ("bob");
|
||||
auto const carol = Account ("carol");
|
||||
auto USDA = alice["USD"];
|
||||
{
|
||||
Env env (*this);
|
||||
env.fund (XRP (10000), alice, bob);
|
||||
env.fund (XRP (10000), alice, bob, carol);
|
||||
|
||||
env (fset (bob, asfDepositAuth));
|
||||
env.close();
|
||||
@@ -757,22 +758,76 @@ struct PayChan_test : public beast::unit_test::suite
|
||||
env.close();
|
||||
BEAST_EXPECT (env.balance (bob) == preBob);
|
||||
|
||||
// bob claims but omits the signature. Fails because only
|
||||
// alice can claim without a signature.
|
||||
env (claim (bob, chan, delta, delta), ter (temBAD_SIGNATURE));
|
||||
env.close();
|
||||
|
||||
// bob claims with signature. Succeeds even though bob's
|
||||
// lsfDepositAuth flag is set since bob signed the transaction.
|
||||
// lsfDepositAuth flag is set since bob submitted the
|
||||
// transaction.
|
||||
env (claim (bob, chan, delta, delta, Slice (sig), pk));
|
||||
env.close();
|
||||
BEAST_EXPECT (env.balance (bob) == preBob + delta - baseFee);
|
||||
}
|
||||
{
|
||||
// Explore the limits of deposit preauthorization.
|
||||
auto const delta = XRP (600).value();
|
||||
auto const sig = signClaimAuth (pk, alice.sk (), chan, delta);
|
||||
|
||||
// bob clears lsfDepositAuth. Now alice can use an unsigned claim.
|
||||
env (fclear (bob, asfDepositAuth));
|
||||
env.close();
|
||||
// carol claims and fails. Only channel participants (bob or
|
||||
// alice) may claim.
|
||||
env (claim (carol, chan,
|
||||
delta, delta, Slice (sig), pk), ter (tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// alice claims successfully.
|
||||
env (claim (alice, chan, XRP (800).value(), XRP (800).value()));
|
||||
env.close();
|
||||
BEAST_EXPECT (
|
||||
env.balance (bob) == preBob + XRP (800) - (2 * baseFee));
|
||||
// bob preauthorizes carol for deposit. But after that carol
|
||||
// still can't claim since only channel participants may claim.
|
||||
env(deposit::auth (bob, carol));
|
||||
env.close();
|
||||
|
||||
env (claim (carol, chan,
|
||||
delta, delta, Slice (sig), pk), ter (tecNO_PERMISSION));
|
||||
|
||||
// Since alice is not preauthorized she also may not claim
|
||||
// for bob.
|
||||
env (claim (alice, chan, delta, delta,
|
||||
Slice (sig), pk), ter (tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// However if bob preauthorizes alice for deposit then she can
|
||||
// successfully submit a claim.
|
||||
env(deposit::auth (bob, alice));
|
||||
env.close();
|
||||
|
||||
env (claim (alice, chan, delta, delta, Slice (sig), pk));
|
||||
env.close();
|
||||
|
||||
BEAST_EXPECT (
|
||||
env.balance (bob) == preBob + delta - (3 * baseFee));
|
||||
}
|
||||
{
|
||||
// bob removes preauthorization of alice. Once again she
|
||||
// cannot submit a claim.
|
||||
auto const delta = XRP (800).value();
|
||||
|
||||
env(deposit::unauth (bob, alice));
|
||||
env.close();
|
||||
|
||||
// alice claims and fails since she is no longer preauthorized.
|
||||
env (claim (alice, chan, delta, delta), ter (tecNO_PERMISSION));
|
||||
env.close();
|
||||
|
||||
// bob clears lsfDepositAuth. Now alice can claim.
|
||||
env (fclear (bob, asfDepositAuth));
|
||||
env.close();
|
||||
|
||||
// alice claims successfully.
|
||||
env (claim (alice, chan, delta, delta));
|
||||
env.close();
|
||||
BEAST_EXPECT (
|
||||
env.balance (bob) == preBob + XRP (800) - (5 * baseFee));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user