mirror of
https://github.com/XRPLF/rippled.git
synced 2025-11-04 11:15:56 +00:00
Implement MultiSignReserve amendment [RIPD-1647]:
Reduces the account reserve for a multisigning SignerList from (conditionally) 3 to 10 OwnerCounts to (unconditionally) 1 OwnerCount. Includes a transition process.
This commit is contained in:
@@ -218,11 +218,19 @@ SetSignerList::replaceSignerList ()
|
||||
auto const sle = view().peek(accountKeylet);
|
||||
|
||||
// Compute new reserve. Verify the account has funds to meet the reserve.
|
||||
auto const oldOwnerCount = (*sle)[sfOwnerCount];
|
||||
std::uint32_t const addedOwnerCount = ownerCountDelta (signers_.size ());
|
||||
std::uint32_t const oldOwnerCount {(*sle)[sfOwnerCount]};
|
||||
|
||||
auto const newReserve =
|
||||
view().fees().accountReserve(oldOwnerCount + addedOwnerCount);
|
||||
// The required reserve changes based on featureMultiSignReserve...
|
||||
int addedOwnerCount {1};
|
||||
std::uint32_t flags {lsfOneOwnerCount};
|
||||
if (! ctx_.view().rules().enabled(featureMultiSignReserve))
|
||||
{
|
||||
addedOwnerCount = signerCountBasedOwnerCountDelta (signers_.size ());
|
||||
flags = 0;
|
||||
}
|
||||
|
||||
XRPAmount const newReserve {
|
||||
view().fees().accountReserve(oldOwnerCount + addedOwnerCount)};
|
||||
|
||||
// We check the reserve against the starting balance because we want to
|
||||
// allow dipping into the reserve to pay fees. This behavior is consistent
|
||||
@@ -233,15 +241,15 @@ SetSignerList::replaceSignerList ()
|
||||
// Everything's ducky. Add the ltSIGNER_LIST to the ledger.
|
||||
auto signerList = std::make_shared<SLE>(signerListKeylet);
|
||||
view().insert (signerList);
|
||||
writeSignersToSLE (signerList);
|
||||
writeSignersToSLE (signerList, flags);
|
||||
|
||||
auto viewJ = ctx_.app.journal ("View");
|
||||
// Add the signer list to the account's directory.
|
||||
auto page = dirAdd(ctx_.view (), ownerDirKeylet,
|
||||
auto const page = dirAdd (ctx_.view (), ownerDirKeylet,
|
||||
signerListKeylet.key, false, describeOwnerDir (account_), viewJ);
|
||||
|
||||
JLOG(j_.trace()) << "Create signer list for account " <<
|
||||
toBase58(account_) << ": " << (page ? "success" : "failure");
|
||||
toBase58 (account_) << ": " << (page ? "success" : "failure");
|
||||
|
||||
if (!page)
|
||||
return tecDIR_FULL;
|
||||
@@ -283,8 +291,16 @@ SetSignerList::removeSignersFromLedger (Keylet const& accountKeylet,
|
||||
if (!signers)
|
||||
return tesSUCCESS;
|
||||
|
||||
STArray const& actualList = signers->getFieldArray (sfSignerEntries);
|
||||
int const removeFromOwnerCount = ownerCountDelta (actualList.size()) * -1;
|
||||
// There are two different ways that the OwnerCount could be managed.
|
||||
// If the lsfOneOwnerCount bit is set then remove just one owner count.
|
||||
// Otherwise use the pre-MultiSignReserve amendment calculation.
|
||||
int removeFromOwnerCount = -1;
|
||||
if ((signers->getFlags() & lsfOneOwnerCount) == 0)
|
||||
{
|
||||
STArray const& actualList = signers->getFieldArray (sfSignerEntries);
|
||||
removeFromOwnerCount =
|
||||
signerCountBasedOwnerCountDelta (actualList.size()) * -1;
|
||||
}
|
||||
|
||||
// Remove the node from the account directory.
|
||||
auto const hint = (*signers)[sfOwnerNode];
|
||||
@@ -305,13 +321,14 @@ SetSignerList::removeSignersFromLedger (Keylet const& accountKeylet,
|
||||
}
|
||||
|
||||
void
|
||||
SetSignerList::writeSignersToSLE (SLE::pointer const& ledgerEntry) const
|
||||
SetSignerList::writeSignersToSLE (
|
||||
SLE::pointer const& ledgerEntry, std::uint32_t flags) const
|
||||
{
|
||||
// Assign the quorum.
|
||||
// Assign the quorum, default SignerListID, and flags.
|
||||
ledgerEntry->setFieldU32 (sfSignerQuorum, quorum_);
|
||||
|
||||
// For now, assign the default SignerListID.
|
||||
ledgerEntry->setFieldU32 (sfSignerListID, defaultSignerListID_);
|
||||
if (flags) // Only set flags if they are non-default (default is zero).
|
||||
ledgerEntry->setFieldU32 (sfFlags, flags);
|
||||
|
||||
// Create the SignerListArray one SignerEntry at a time.
|
||||
STArray toLedger (signers_.size ());
|
||||
@@ -330,8 +347,12 @@ SetSignerList::writeSignersToSLE (SLE::pointer const& ledgerEntry) const
|
||||
|
||||
// The return type is signed so it is compatible with the 3rd argument
|
||||
// of adjustOwnerCount() (which must be signed).
|
||||
//
|
||||
// NOTE: This way of computing the OwnerCount associated with a SignerList
|
||||
// is valid until the featureMultiSignReserve amendment passes. Once it
|
||||
// passes then just 1 OwnerCount is associated with a SignerList.
|
||||
int
|
||||
SetSignerList::ownerCountDelta (std::size_t entryCount)
|
||||
SetSignerList::signerCountBasedOwnerCountDelta (std::size_t entryCount)
|
||||
{
|
||||
// We always compute the full change in OwnerCount, taking into account:
|
||||
// o The fact that we're adding/removing a SignerList and
|
||||
|
||||
@@ -87,9 +87,14 @@ private:
|
||||
|
||||
TER removeSignersFromLedger (Keylet const& accountKeylet,
|
||||
Keylet const& ownerDirKeylet, Keylet const& signerListKeylet);
|
||||
void writeSignersToSLE (SLE::pointer const& ledgerEntry) const;
|
||||
void writeSignersToSLE (
|
||||
SLE::pointer const& ledgerEntry, std::uint32_t flags) const;
|
||||
|
||||
static int ownerCountDelta (std::size_t entryCount);
|
||||
// Way of computing owner count prior to featureMultiSignReserve.
|
||||
// This needs to stay in the code base until no signerLists remain
|
||||
// in the ledger that were created prior to acceptance of
|
||||
// featureMultiSignReserve... Effectively forever.
|
||||
static int signerCountBasedOwnerCountDelta (std::size_t entryCount);
|
||||
};
|
||||
|
||||
} // ripple
|
||||
|
||||
@@ -81,7 +81,8 @@ class FeatureCollections
|
||||
"fix1623",
|
||||
"DepositPreauth",
|
||||
"fix1515",
|
||||
"fix1578"
|
||||
"fix1578",
|
||||
"MultiSignReserve"
|
||||
};
|
||||
|
||||
std::vector<uint256> features;
|
||||
@@ -369,6 +370,7 @@ extern uint256 const fix1623;
|
||||
extern uint256 const featureDepositPreauth;
|
||||
extern uint256 const fix1515;
|
||||
extern uint256 const fix1578;
|
||||
extern uint256 const featureMultiSignReserve;
|
||||
|
||||
} // ripple
|
||||
|
||||
|
||||
@@ -152,6 +152,9 @@ enum LedgerSpecificFlags
|
||||
lsfHighNoRipple = 0x00200000,
|
||||
lsfLowFreeze = 0x00400000, // True, low side has set freeze flag
|
||||
lsfHighFreeze = 0x00800000, // True, high side has set freeze flag
|
||||
|
||||
// ltSIGNER_LIST
|
||||
lsfOneOwnerCount = 0x00010000, // True, uses only one OwnerCount
|
||||
};
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
@@ -114,7 +114,8 @@ detail::supportedAmendments ()
|
||||
{ "3CBC5C4E630A1B82380295CDA84B32B49DD066602E74E39B85EF64137FA65194 DepositPreauth" },
|
||||
// Use liquidity from strands that consume max offers, but mark as dry
|
||||
{ "5D08145F0A4983F23AFFFF514E83FAD355C5ABFBB6CAB76FB5BC8519FF5F33BE fix1515" },
|
||||
{ "FBD513F1B893AC765B78F250E6FFA6A11B573209D1842ADC787C850696741288 fix1578" }
|
||||
{ "FBD513F1B893AC765B78F250E6FFA6A11B573209D1842ADC787C850696741288 fix1578" },
|
||||
{ "586480873651E106F1D6339B0C4A8945BA705A777F3F4524626FF1FC07EFE41D MultiSignReserve" }
|
||||
};
|
||||
return supported;
|
||||
}
|
||||
@@ -170,5 +171,6 @@ uint256 const fix1623 = *getRegisteredFeature("fix1623");
|
||||
uint256 const featureDepositPreauth = *getRegisteredFeature("DepositPreauth");
|
||||
uint256 const fix1515 = *getRegisteredFeature("fix1515");
|
||||
uint256 const fix1578 = *getRegisteredFeature("fix1578");
|
||||
uint256 const featureMultiSignReserve = *getRegisteredFeature("MultiSignReserve");
|
||||
|
||||
} // ripple
|
||||
|
||||
@@ -956,9 +956,16 @@ class Check_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 1);
|
||||
BEAST_EXPECT (ownerCount (env, bob ) == 1);
|
||||
}
|
||||
|
||||
// Use a regular key and also multisign to cash a check.
|
||||
// featureMultiSign changes the reserve on a SignerList, so
|
||||
// check both before and after.
|
||||
FeatureBitset const allSupported {supported_amendments()};
|
||||
for (auto const features :
|
||||
{allSupported - featureMultiSignReserve,
|
||||
allSupported | featureMultiSignReserve})
|
||||
{
|
||||
// Use a regular key and also multisign to cash a check.
|
||||
Env env {*this};
|
||||
Env env {*this, features};
|
||||
auto const closeTime =
|
||||
fix1449Time() + 100 * env.closed()->info().closeTimeResolution;
|
||||
env.close (closeTime);
|
||||
@@ -989,7 +996,11 @@ class Check_test : public beast::unit_test::suite
|
||||
Account const demon {"demon", KeyType::ed25519};
|
||||
env (signers (bob, 2, {{bogie, 1}, {demon, 1}}), sig (bobby));
|
||||
env.close();
|
||||
BEAST_EXPECT (ownerCount (env, bob) == 5); // signerList -> 4 owners
|
||||
|
||||
// If featureMultiSignReserve is enabled then bob's signer list
|
||||
// has an owner count of 1, otherwise it's 4.
|
||||
int const signersCount {features[featureMultiSignReserve] ? 1 : 4};
|
||||
BEAST_EXPECT (ownerCount (env, bob) == signersCount + 1);
|
||||
|
||||
// bob uses his regular key to cash a check.
|
||||
env (check::cash (bob, chkId1, (USD(1))), sig (bobby));
|
||||
@@ -999,7 +1010,7 @@ class Check_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 1);
|
||||
BEAST_EXPECT (checksOnAccount (env, bob ).size() == 1);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 2);
|
||||
BEAST_EXPECT (ownerCount (env, bob ) == 5);
|
||||
BEAST_EXPECT (ownerCount (env, bob ) == signersCount + 1);
|
||||
|
||||
// bob uses multisigning to cash a check.
|
||||
std::uint64_t const baseFeeDrops {env.current()->fees().base};
|
||||
@@ -1011,7 +1022,7 @@ class Check_test : public beast::unit_test::suite
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 0);
|
||||
BEAST_EXPECT (checksOnAccount (env, bob ).size() == 0);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 1);
|
||||
BEAST_EXPECT (ownerCount (env, bob ) == 5);
|
||||
BEAST_EXPECT (ownerCount (env, bob ) == signersCount + 1);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1596,147 +1607,160 @@ class Check_test : public beast::unit_test::suite
|
||||
Account const zoe {"zoe"};
|
||||
IOU const USD {gw["USD"]};
|
||||
|
||||
Env env {*this};
|
||||
auto const closeTime =
|
||||
fix1449Time() + 100 * env.closed()->info().closeTimeResolution;
|
||||
env.close (closeTime);
|
||||
// featureMultiSign changes the reserve on a SignerList, so
|
||||
// check both before and after.
|
||||
FeatureBitset const allSupported {supported_amendments()};
|
||||
for (auto const features :
|
||||
{allSupported - featureMultiSignReserve,
|
||||
allSupported | featureMultiSignReserve})
|
||||
{
|
||||
Env env {*this, features};
|
||||
|
||||
env.fund (XRP(1000), gw, alice, bob, zoe);
|
||||
auto const closeTime =
|
||||
fix1449Time() + 100 * env.closed()->info().closeTimeResolution;
|
||||
env.close (closeTime);
|
||||
|
||||
// alice creates her checks ahead of time.
|
||||
// Three ordinary checks with no expiration.
|
||||
uint256 const chkId1 {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, USD(10)));
|
||||
env.close();
|
||||
env.fund (XRP(1000), gw, alice, bob, zoe);
|
||||
|
||||
uint256 const chkId2 {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, XRP(10)));
|
||||
env.close();
|
||||
// alice creates her checks ahead of time.
|
||||
// Three ordinary checks with no expiration.
|
||||
uint256 const chkId1 {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, USD(10)));
|
||||
env.close();
|
||||
|
||||
uint256 const chkId3 {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, USD(10)));
|
||||
env.close();
|
||||
uint256 const chkId2 {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, XRP(10)));
|
||||
env.close();
|
||||
|
||||
// Three checks that expire in 10 minutes.
|
||||
using namespace std::chrono_literals;
|
||||
uint256 const chkIdNotExp1 {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, XRP(10)), expiration (env.now()+600s));
|
||||
env.close();
|
||||
uint256 const chkId3 {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, USD(10)));
|
||||
env.close();
|
||||
|
||||
uint256 const chkIdNotExp2 {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, USD(10)), expiration (env.now()+600s));
|
||||
env.close();
|
||||
// Three checks that expire in 10 minutes.
|
||||
using namespace std::chrono_literals;
|
||||
uint256 const chkIdNotExp1 {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, XRP(10)), expiration (env.now()+600s));
|
||||
env.close();
|
||||
|
||||
uint256 const chkIdNotExp3 {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, XRP(10)), expiration (env.now()+600s));
|
||||
env.close();
|
||||
uint256 const chkIdNotExp2 {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, USD(10)), expiration (env.now()+600s));
|
||||
env.close();
|
||||
|
||||
// Three checks that expire in one second.
|
||||
uint256 const chkIdExp1 {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, USD(10)), expiration (env.now()+1s));
|
||||
env.close();
|
||||
uint256 const chkIdNotExp3 {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, XRP(10)), expiration (env.now()+600s));
|
||||
env.close();
|
||||
|
||||
uint256 const chkIdExp2 {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, XRP(10)), expiration (env.now()+1s));
|
||||
env.close();
|
||||
// Three checks that expire in one second.
|
||||
uint256 const chkIdExp1 {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, USD(10)), expiration (env.now()+1s));
|
||||
env.close();
|
||||
|
||||
uint256 const chkIdExp3 {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, USD(10)), expiration (env.now()+1s));
|
||||
env.close();
|
||||
uint256 const chkIdExp2 {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, XRP(10)), expiration (env.now()+1s));
|
||||
env.close();
|
||||
|
||||
// Two checks to cancel using a regular key and using multisigning.
|
||||
uint256 const chkIdReg {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, USD(10)));
|
||||
env.close();
|
||||
uint256 const chkIdExp3 {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, USD(10)), expiration (env.now()+1s));
|
||||
env.close();
|
||||
|
||||
uint256 const chkIdMSig {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, XRP(10)));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 11);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 11);
|
||||
// Two checks to cancel using a regular key and using multisigning.
|
||||
uint256 const chkIdReg {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, USD(10)));
|
||||
env.close();
|
||||
|
||||
// Creator, destination, and an outsider cancel the checks.
|
||||
env (check::cancel (alice, chkId1));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 10);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 10);
|
||||
uint256 const chkIdMSig {getCheckIndex (alice, env.seq (alice))};
|
||||
env (check::create (alice, bob, XRP(10)));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 11);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 11);
|
||||
|
||||
env (check::cancel (bob, chkId2));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 9);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 9);
|
||||
// Creator, destination, and an outsider cancel the checks.
|
||||
env (check::cancel (alice, chkId1));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 10);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 10);
|
||||
|
||||
env (check::cancel (zoe, chkId3), ter (tecNO_PERMISSION));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 9);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 9);
|
||||
env (check::cancel (bob, chkId2));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 9);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 9);
|
||||
|
||||
// Creator, destination, and an outsider cancel unexpired checks.
|
||||
env (check::cancel (alice, chkIdNotExp1));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 8);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 8);
|
||||
env (check::cancel (zoe, chkId3), ter (tecNO_PERMISSION));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 9);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 9);
|
||||
|
||||
env (check::cancel (bob, chkIdNotExp2));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 7);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 7);
|
||||
// Creator, destination, and an outsider cancel unexpired checks.
|
||||
env (check::cancel (alice, chkIdNotExp1));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 8);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 8);
|
||||
|
||||
env (check::cancel (zoe, chkIdNotExp3), ter (tecNO_PERMISSION));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 7);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 7);
|
||||
env (check::cancel (bob, chkIdNotExp2));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 7);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 7);
|
||||
|
||||
// Creator, destination, and an outsider cancel expired checks.
|
||||
env (check::cancel (alice, chkIdExp1));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 6);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 6);
|
||||
env (check::cancel (zoe, chkIdNotExp3), ter (tecNO_PERMISSION));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 7);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 7);
|
||||
|
||||
env (check::cancel (bob, chkIdExp2));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 5);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 5);
|
||||
// Creator, destination, and an outsider cancel expired checks.
|
||||
env (check::cancel (alice, chkIdExp1));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 6);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 6);
|
||||
|
||||
env (check::cancel (zoe, chkIdExp3));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 4);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 4);
|
||||
env (check::cancel (bob, chkIdExp2));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 5);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 5);
|
||||
|
||||
// Use a regular key and also multisign to cancel checks.
|
||||
Account const alie {"alie", KeyType::ed25519};
|
||||
env (regkey (alice, alie));
|
||||
env.close();
|
||||
env (check::cancel (zoe, chkIdExp3));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 4);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 4);
|
||||
|
||||
Account const bogie {"bogie", KeyType::secp256k1};
|
||||
Account const demon {"demon", KeyType::ed25519};
|
||||
env (signers (alice, 2, {{bogie, 1}, {demon, 1}}), sig (alie));
|
||||
env.close();
|
||||
// Use a regular key and also multisign to cancel checks.
|
||||
Account const alie {"alie", KeyType::ed25519};
|
||||
env (regkey (alice, alie));
|
||||
env.close();
|
||||
|
||||
// alice uses her regular key to cancel a check.
|
||||
env (check::cancel (alice, chkIdReg), sig (alie));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 3);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 7);
|
||||
Account const bogie {"bogie", KeyType::secp256k1};
|
||||
Account const demon {"demon", KeyType::ed25519};
|
||||
env (signers (alice, 2, {{bogie, 1}, {demon, 1}}), sig (alie));
|
||||
env.close();
|
||||
|
||||
// alice uses multisigning to cancel a check.
|
||||
std::uint64_t const baseFeeDrops {env.current()->fees().base};
|
||||
env (check::cancel (alice, chkIdMSig),
|
||||
msig (bogie, demon), fee (3 * baseFeeDrops));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 2);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 6);
|
||||
// If featureMultiSignReserve is enabled then alices's signer list
|
||||
// has an owner count of 1, otherwise it's 4.
|
||||
int const signersCount {features[featureMultiSignReserve] ? 1 : 4};
|
||||
|
||||
// Creator and destination cancel the remaining unexpired checks.
|
||||
env (check::cancel (alice, chkId3), sig (alice));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 1);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 5);
|
||||
// alice uses her regular key to cancel a check.
|
||||
env (check::cancel (alice, chkIdReg), sig (alie));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 3);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == signersCount + 3);
|
||||
|
||||
env (check::cancel (bob, chkIdNotExp3));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 0);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == 4);
|
||||
// alice uses multisigning to cancel a check.
|
||||
std::uint64_t const baseFeeDrops {env.current()->fees().base};
|
||||
env (check::cancel (alice, chkIdMSig),
|
||||
msig (bogie, demon), fee (3 * baseFeeDrops));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 2);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == signersCount + 2);
|
||||
|
||||
// Creator and destination cancel the remaining unexpired checks.
|
||||
env (check::cancel (alice, chkId3), sig (alice));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 1);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == signersCount + 1);
|
||||
|
||||
env (check::cancel (bob, chkIdNotExp3));
|
||||
env.close();
|
||||
BEAST_EXPECT (checksOnAccount (env, alice).size() == 0);
|
||||
BEAST_EXPECT (ownerCount (env, alice) == signersCount + 0);
|
||||
}
|
||||
}
|
||||
|
||||
void testCancelInvalid()
|
||||
|
||||
@@ -36,15 +36,23 @@ class MultiSign_test : public beast::unit_test::suite
|
||||
jtx::Account const spook {"spook", KeyType::ed25519};
|
||||
|
||||
public:
|
||||
void test_noReserve()
|
||||
void test_noReserve (FeatureBitset features)
|
||||
{
|
||||
testcase ("No Reserve");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
Env env {*this, features};
|
||||
Account const alice {"alice", KeyType::secp256k1};
|
||||
|
||||
// The reserve required for a signer list changes with the passage
|
||||
// of featureMultiSignReserve. Make the required adjustments.
|
||||
bool const reserve1 {features[featureMultiSignReserve]};
|
||||
|
||||
// Pay alice enough to meet the initial reserve, but not enough to
|
||||
// meet the reserve for a SignerListSet.
|
||||
env.fund(XRP(200), alice);
|
||||
auto const fee = env.current()->fees().base;
|
||||
auto const smallSignersReserve = reserve1 ? XRP(250) : XRP(350);
|
||||
env.fund(smallSignersReserve - drops (1), alice);
|
||||
env.close();
|
||||
env.require (owners (alice, 0));
|
||||
|
||||
@@ -56,27 +64,32 @@ public:
|
||||
env.require (owners (alice, 0));
|
||||
|
||||
// Fund alice enough to set the signer list, then attach signers.
|
||||
env(pay(env.master, alice, XRP(151)));
|
||||
env(pay(env.master, alice, fee + drops (1)));
|
||||
env.close();
|
||||
env(smallSigners);
|
||||
env.close();
|
||||
env.require (owners (alice, 3));
|
||||
env.require (owners (alice, reserve1 ? 1 : 3));
|
||||
}
|
||||
{
|
||||
// Pay alice enough to almost make the reserve for the biggest
|
||||
// possible list.
|
||||
auto const addReserveBigSigners = reserve1 ? XRP(0) : XRP(350);
|
||||
env(pay(env.master, alice, addReserveBigSigners + fee - drops(1)));
|
||||
|
||||
// Replace with the biggest possible signer list. Should fail.
|
||||
Json::Value bigSigners = signers(alice, 1, {
|
||||
{ bogie, 1 }, { demon, 1 }, { ghost, 1 }, { haunt, 1 },
|
||||
{ jinni, 1 }, { phase, 1 }, { shade, 1 }, { spook, 1 }});
|
||||
env(bigSigners, ter(tecINSUFFICIENT_RESERVE));
|
||||
env.close();
|
||||
env.require (owners (alice, 3));
|
||||
env.require (owners (alice, reserve1 ? 1 : 3));
|
||||
|
||||
// Fund alice and succeed.
|
||||
env(pay(env.master, alice, XRP(350)));
|
||||
// Fund alice one more drop (plus the fee) and succeed.
|
||||
env(pay(env.master, alice, fee + drops(1)));
|
||||
env.close();
|
||||
env(bigSigners);
|
||||
env.close();
|
||||
env.require (owners (alice, 10));
|
||||
env.require (owners (alice, reserve1 ? 1 : 10));
|
||||
}
|
||||
// Remove alice's signer list and get the owner count back.
|
||||
env(signers(alice, jtx::none));
|
||||
@@ -84,10 +97,12 @@ public:
|
||||
env.require (owners (alice, 0));
|
||||
}
|
||||
|
||||
void test_signerListSet()
|
||||
void test_signerListSet (FeatureBitset features)
|
||||
{
|
||||
testcase ("SignerListSet");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
Env env {*this, features};
|
||||
Account const alice {"alice", KeyType::ed25519};
|
||||
env.fund(XRP(1000), alice);
|
||||
|
||||
@@ -129,10 +144,12 @@ public:
|
||||
env.require (owners (alice, 0));
|
||||
}
|
||||
|
||||
void test_phantomSigners()
|
||||
void test_phantomSigners (FeatureBitset features)
|
||||
{
|
||||
testcase ("Phantom Signers");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
Env env {*this, features};
|
||||
Account const alice {"alice", KeyType::ed25519};
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
@@ -140,7 +157,7 @@ public:
|
||||
// Attach phantom signers to alice and use them for a transaction.
|
||||
env(signers(alice, 1, {{bogie, 1}, {demon, 1}}));
|
||||
env.close();
|
||||
env.require (owners (alice, 4));
|
||||
env.require (owners (alice, features[featureMultiSignReserve] ? 1 : 4));
|
||||
|
||||
// This should work.
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
@@ -188,20 +205,22 @@ public:
|
||||
}
|
||||
|
||||
void
|
||||
test_enablement()
|
||||
test_enablement (FeatureBitset features)
|
||||
{
|
||||
testcase ("Enablement");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this, envconfig([](std::unique_ptr<Config> cfg)
|
||||
{
|
||||
cfg->loadFromString ("[" SECTION_SIGNING_SUPPORT "]\ntrue");
|
||||
return cfg;
|
||||
}), FeatureBitset{});
|
||||
}), features - featureMultiSign);
|
||||
|
||||
Account const alice {"alice", KeyType::ed25519};
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
// NOTE: These six tests will fail when multisign is default enabled.
|
||||
// NOTE: These six tests will fail if multisign is enabled.
|
||||
env(signers(alice, 1, {{bogie, 1}}), ter(temDISABLED));
|
||||
env.close();
|
||||
env.require (owners (alice, 0));
|
||||
@@ -235,10 +254,12 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void test_fee ()
|
||||
void test_fee (FeatureBitset features)
|
||||
{
|
||||
testcase ("Fee");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
Env env {*this, features};
|
||||
Account const alice {"alice", KeyType::ed25519};
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
@@ -247,7 +268,7 @@ public:
|
||||
env(signers(alice, 1, {{bogie, 1}, {demon, 1}, {ghost, 1}, {haunt, 1},
|
||||
{jinni, 1}, {phase, 1}, {shade, 1}, {spook, 1}}));
|
||||
env.close();
|
||||
env.require (owners (alice, 10));
|
||||
env.require (owners(alice, features[featureMultiSignReserve] ? 1 : 10));
|
||||
|
||||
// This should work.
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
@@ -285,10 +306,12 @@ public:
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq);
|
||||
}
|
||||
|
||||
void test_misorderedSigners()
|
||||
void test_misorderedSigners (FeatureBitset features)
|
||||
{
|
||||
testcase ("Misordered Signers");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
Env env {*this, features};
|
||||
Account const alice {"alice", KeyType::ed25519};
|
||||
env.fund(XRP(1000), alice);
|
||||
env.close();
|
||||
@@ -297,7 +320,7 @@ public:
|
||||
// Make sure the transaction fails if they are not.
|
||||
env(signers(alice, 1, {{bogie, 1}, {demon, 1}}));
|
||||
env.close();
|
||||
env.require (owners(alice, 4));
|
||||
env.require (owners (alice, features[featureMultiSignReserve] ? 1 : 4));
|
||||
|
||||
msig phantoms {bogie, demon};
|
||||
std::reverse (phantoms.signers.begin(), phantoms.signers.end());
|
||||
@@ -307,10 +330,12 @@ public:
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq);
|
||||
}
|
||||
|
||||
void test_masterSigners()
|
||||
void test_masterSigners (FeatureBitset features)
|
||||
{
|
||||
testcase ("Master Signers");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
Env env {*this, features};
|
||||
Account const alice {"alice", KeyType::ed25519};
|
||||
Account const becky {"becky", KeyType::secp256k1};
|
||||
Account const cheri {"cheri", KeyType::ed25519};
|
||||
@@ -330,7 +355,7 @@ public:
|
||||
//Attach signers to alice
|
||||
env(signers(alice, 4, {{becky, 3}, {cheri, 4}}), sig (alice));
|
||||
env.close();
|
||||
env.require (owners (alice, 4));
|
||||
env.require (owners (alice, features[featureMultiSignReserve] ? 1 : 4));
|
||||
|
||||
// Attempt a multisigned transaction that meets the quorum.
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
@@ -359,10 +384,12 @@ public:
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
}
|
||||
|
||||
void test_regularSigners()
|
||||
void test_regularSigners (FeatureBitset features)
|
||||
{
|
||||
testcase ("Regular Signers");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
Env env {*this, features};
|
||||
Account const alice {"alice", KeyType::secp256k1};
|
||||
Account const becky {"becky", KeyType::ed25519};
|
||||
Account const cheri {"cheri", KeyType::secp256k1};
|
||||
@@ -417,14 +444,16 @@ public:
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
}
|
||||
|
||||
void test_regularSignersUsingSubmitMulti()
|
||||
void test_regularSignersUsingSubmitMulti (FeatureBitset features)
|
||||
{
|
||||
testcase ("Regular Signers Using submit_multisigned");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this, envconfig([](std::unique_ptr<Config> cfg)
|
||||
{
|
||||
cfg->loadFromString ("[" SECTION_SIGNING_SUPPORT "]\ntrue");
|
||||
return cfg;
|
||||
}));
|
||||
}), features);
|
||||
Account const alice {"alice", KeyType::secp256k1};
|
||||
Account const becky {"becky", KeyType::ed25519};
|
||||
Account const cheri {"cheri", KeyType::secp256k1};
|
||||
@@ -624,10 +653,12 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void test_heterogeneousSigners()
|
||||
void test_heterogeneousSigners (FeatureBitset features)
|
||||
{
|
||||
testcase ("Heterogenious Signers");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
Env env {*this, features};
|
||||
Account const alice {"alice", KeyType::secp256k1};
|
||||
Account const becky {"becky", KeyType::ed25519};
|
||||
Account const cheri {"cheri", KeyType::secp256k1};
|
||||
@@ -656,7 +687,7 @@ public:
|
||||
env(signers(alice, 1,
|
||||
{{becky, 1}, {cheri, 1}, {daria, 1}, {jinni, 1}}), sig (alie));
|
||||
env.close();
|
||||
env.require (owners (alice, 6));
|
||||
env.require (owners (alice, features[featureMultiSignReserve] ? 1 : 6));
|
||||
|
||||
// Each type of signer should succeed individually.
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
@@ -696,7 +727,7 @@ public:
|
||||
env(signers(alice, 0x3FFFC, {{becky, 0xFFFF},
|
||||
{cheri, 0xFFFF}, {daria, 0xFFFF}, {jinni, 0xFFFF}}), sig (alie));
|
||||
env.close();
|
||||
env.require (owners (alice, 6));
|
||||
env.require (owners (alice, features[featureMultiSignReserve] ? 1 : 6));
|
||||
|
||||
aliceSeq = env.seq (alice);
|
||||
env(noop(alice), fee(9 * baseFee),
|
||||
@@ -716,7 +747,7 @@ public:
|
||||
{daria, 0xFFFF}, {haunt, 0xFFFF}, {jinni, 0xFFFF},
|
||||
{phase, 0xFFFF}, {shade, 0xFFFF}, {spook, 0xFFFF}}), sig (alie));
|
||||
env.close();
|
||||
env.require (owners (alice, 10));
|
||||
env.require (owners(alice, features[featureMultiSignReserve] ? 1 : 10));
|
||||
|
||||
aliceSeq = env.seq (alice);
|
||||
env(noop(alice), fee(9 * baseFee), msig(becky, msig::Reg{cheri, cher},
|
||||
@@ -739,10 +770,12 @@ public:
|
||||
|
||||
// We want to always leave an account signable. Make sure the that we
|
||||
// disallow removing the last way a transaction may be signed.
|
||||
void test_keyDisable()
|
||||
void test_keyDisable (FeatureBitset features)
|
||||
{
|
||||
testcase ("Key Disable");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
Env env {*this, features};
|
||||
Account const alice {"alice", KeyType::ed25519};
|
||||
env.fund(XRP(1000), alice);
|
||||
|
||||
@@ -814,10 +847,12 @@ public:
|
||||
|
||||
// Verify that the first regular key can be made for free using the
|
||||
// master key, but not when multisigning.
|
||||
void test_regKey()
|
||||
void test_regKey (FeatureBitset features)
|
||||
{
|
||||
testcase ("Regular Key");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
Env env {*this, features};
|
||||
Account const alice {"alice", KeyType::secp256k1};
|
||||
env.fund(XRP(1000), alice);
|
||||
|
||||
@@ -846,10 +881,12 @@ public:
|
||||
}
|
||||
|
||||
// See if every kind of transaction can be successfully multi-signed.
|
||||
void test_txTypes()
|
||||
void test_txTypes (FeatureBitset features)
|
||||
{
|
||||
testcase ("Transaction Types");
|
||||
|
||||
using namespace jtx;
|
||||
Env env(*this);
|
||||
Env env {*this, features};
|
||||
Account const alice {"alice", KeyType::secp256k1};
|
||||
Account const becky {"becky", KeyType::ed25519};
|
||||
Account const zelda {"zelda", KeyType::secp256k1};
|
||||
@@ -866,7 +903,8 @@ public:
|
||||
// Attach signers to alice.
|
||||
env(signers(alice, 2, {{becky, 1}, {bogie, 1}}), sig (alie));
|
||||
env.close();
|
||||
env.require (owners (alice, 4));
|
||||
int const signerListOwners {features[featureMultiSignReserve] ? 1 : 4};
|
||||
env.require (owners (alice, signerListOwners + 0));
|
||||
|
||||
// Multisign a ttPAYMENT.
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
@@ -893,7 +931,7 @@ public:
|
||||
env(trust("alice", USD(100)),
|
||||
msig(becky, bogie), fee(3 * baseFee), require (lines("alice", 1)));
|
||||
env.close();
|
||||
env.require (owners (alice, 5));
|
||||
env.require (owners (alice, signerListOwners + 1));
|
||||
|
||||
// Multisign a ttOFFER_CREATE transaction.
|
||||
env(pay(gw, alice, USD(50)));
|
||||
@@ -905,7 +943,7 @@ public:
|
||||
env(offer(alice, XRP(50), USD(50)),
|
||||
msig (becky, bogie), fee(3 * baseFee));
|
||||
env.close();
|
||||
env.require(owners(alice, 6));
|
||||
env.require (owners (alice, signerListOwners + 2));
|
||||
|
||||
// Now multisign a ttOFFER_CANCEL canceling the offer we just created.
|
||||
{
|
||||
@@ -918,22 +956,24 @@ public:
|
||||
msig (becky, bogie), fee(3 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
env.require(owners(alice, 5));
|
||||
env.require(owners(alice, signerListOwners + 1));
|
||||
}
|
||||
|
||||
// Multisign a ttSIGNER_LIST_SET.
|
||||
env(signers(alice, 3, {{becky, 1}, {bogie, 1}, {demon, 1}}),
|
||||
msig (becky, bogie), fee(3 * baseFee));
|
||||
env.close();
|
||||
env.require (owners (alice, 6));
|
||||
env.require (owners (alice, features[featureMultiSignReserve] ? 2 : 6));
|
||||
}
|
||||
|
||||
void test_badSignatureText()
|
||||
void test_badSignatureText (FeatureBitset features)
|
||||
{
|
||||
testcase ("Bad Signature Text");
|
||||
|
||||
// Verify that the text returned for signature failures is correct.
|
||||
using namespace jtx;
|
||||
|
||||
Env env(*this);
|
||||
Env env {*this, features};
|
||||
|
||||
// lambda that submits an STTx and returns the resulting JSON.
|
||||
auto submitSTTx = [&env] (STTx const& stx)
|
||||
@@ -1064,10 +1104,12 @@ public:
|
||||
}
|
||||
}
|
||||
|
||||
void test_noMultiSigners()
|
||||
void test_noMultiSigners (FeatureBitset features)
|
||||
{
|
||||
testcase ("No Multisigners");
|
||||
|
||||
using namespace jtx;
|
||||
Env env {*this};
|
||||
Env env {*this, features};
|
||||
Account const alice {"alice", KeyType::ed25519};
|
||||
Account const becky {"becky", KeyType::secp256k1};
|
||||
env.fund(XRP(1000), alice, becky);
|
||||
@@ -1077,15 +1119,17 @@ public:
|
||||
env(noop(alice), msig(becky, demon), fee(3 * baseFee), ter(tefNOT_MULTI_SIGNING));
|
||||
}
|
||||
|
||||
void test_multisigningMultisigner()
|
||||
void test_multisigningMultisigner (FeatureBitset features)
|
||||
{
|
||||
testcase ("Multisigning multisigner");
|
||||
|
||||
// Set up a signer list where one of the signers has both the
|
||||
// master disabled and no regular key (because that signer is
|
||||
// exclusively multisigning). That signer should no longer be
|
||||
// able to successfully sign the signer list.
|
||||
|
||||
using namespace jtx;
|
||||
Env env (*this);
|
||||
Env env {*this, features};
|
||||
Account const alice {"alice", KeyType::ed25519};
|
||||
Account const becky {"becky", KeyType::secp256k1};
|
||||
env.fund (XRP(1000), alice, becky);
|
||||
@@ -1143,8 +1187,10 @@ public:
|
||||
env.close();
|
||||
}
|
||||
|
||||
void test_signForHash()
|
||||
void test_signForHash (FeatureBitset features)
|
||||
{
|
||||
testcase ("sign_for Hash");
|
||||
|
||||
// Make sure that the "hash" field returned by the "sign_for" RPC
|
||||
// command matches the hash returned when that command is sent
|
||||
// through "submit_multisigned". Make sure that hash also locates
|
||||
@@ -1156,7 +1202,7 @@ public:
|
||||
{
|
||||
cfg->loadFromString ("[" SECTION_SIGNING_SUPPORT "]\ntrue");
|
||||
return cfg;
|
||||
}));
|
||||
}), features);
|
||||
env.fund (XRP(1000), alice);
|
||||
env.close();
|
||||
|
||||
@@ -1221,25 +1267,104 @@ public:
|
||||
[sfTransactionResult.jsonName].asString() == "tesSUCCESS");
|
||||
}
|
||||
|
||||
void test_amendmentTransition ()
|
||||
{
|
||||
testcase ("Amendment Transition");
|
||||
|
||||
// The OwnerCount associated with a SignerList changes once the
|
||||
// featureMultiSignReserve amendment goes live. Create a couple
|
||||
// of signer lists before and after the amendment goes live and
|
||||
// verify that the OwnerCount is managed properly for all of them.
|
||||
using namespace jtx;
|
||||
Account const alice {"alice", KeyType::secp256k1};
|
||||
Account const becky {"becky", KeyType::ed25519};
|
||||
Account const cheri {"cheri", KeyType::secp256k1};
|
||||
Account const daria {"daria", KeyType::ed25519};
|
||||
|
||||
Env env {*this, supported_amendments() - featureMultiSignReserve};
|
||||
env.fund (XRP(1000), alice, becky, cheri, daria);
|
||||
env.close();
|
||||
|
||||
// Give alice and becky signer lists before the amendment goes live.
|
||||
env (signers (alice, 1, {{bogie, 1}}));
|
||||
env (signers (becky, 1, {{bogie, 1}, {demon, 1}, {ghost, 1},
|
||||
{haunt, 1}, {jinni, 1}, {phase, 1}, {shade, 1}, {spook, 1}}));
|
||||
env.close();
|
||||
|
||||
env.require (owners (alice, 3));
|
||||
env.require (owners (becky, 10));
|
||||
|
||||
// Enable the amendment.
|
||||
env.enableFeature (featureMultiSignReserve);
|
||||
env.close();
|
||||
|
||||
// Give cheri and daria signer lists after the amendment goes live.
|
||||
env (signers (cheri, 1, {{bogie, 1}}));
|
||||
env (signers (daria, 1, {{bogie, 1}, {demon, 1}, {ghost, 1},
|
||||
{haunt, 1}, {jinni, 1}, {phase, 1}, {shade, 1}, {spook, 1}}));
|
||||
env.close();
|
||||
|
||||
env.require (owners (alice, 3));
|
||||
env.require (owners (becky, 10));
|
||||
env.require (owners (cheri, 1));
|
||||
env.require (owners (daria, 1));
|
||||
|
||||
// Delete becky's signer list; her OwnerCount should drop to zero.
|
||||
// Replace alice's signer list; her OwnerCount should drop to one.
|
||||
env (signers (becky, jtx::none));
|
||||
env (signers (alice, 1, {{bogie, 1}, {demon, 1}, {ghost, 1},
|
||||
{haunt, 1}, {jinni, 1}, {phase, 1}, {shade, 1}, {spook, 1}}));
|
||||
env.close();
|
||||
|
||||
env.require (owners (alice, 1));
|
||||
env.require (owners (becky, 0));
|
||||
env.require (owners (cheri, 1));
|
||||
env.require (owners (daria, 1));
|
||||
|
||||
// Delete the three remaining signer lists. Everybody's OwnerCount
|
||||
// should now be zero.
|
||||
env (signers (alice, jtx::none));
|
||||
env (signers (cheri, jtx::none));
|
||||
env (signers (daria, jtx::none));
|
||||
env.close();
|
||||
|
||||
env.require (owners (alice, 0));
|
||||
env.require (owners (becky, 0));
|
||||
env.require (owners (cheri, 0));
|
||||
env.require (owners (daria, 0));
|
||||
}
|
||||
|
||||
void testAll(FeatureBitset features)
|
||||
{
|
||||
test_noReserve (features);
|
||||
test_signerListSet (features);
|
||||
test_phantomSigners (features);
|
||||
test_enablement (features);
|
||||
test_fee (features);
|
||||
test_misorderedSigners (features);
|
||||
test_masterSigners (features);
|
||||
test_regularSigners (features);
|
||||
test_regularSignersUsingSubmitMulti (features);
|
||||
test_heterogeneousSigners (features);
|
||||
test_keyDisable (features);
|
||||
test_regKey (features);
|
||||
test_txTypes (features);
|
||||
test_badSignatureText (features);
|
||||
test_noMultiSigners (features);
|
||||
test_multisigningMultisigner (features);
|
||||
test_signForHash (features);
|
||||
}
|
||||
|
||||
void run() override
|
||||
{
|
||||
test_noReserve();
|
||||
test_signerListSet();
|
||||
test_phantomSigners();
|
||||
test_enablement();
|
||||
test_fee();
|
||||
test_misorderedSigners();
|
||||
test_masterSigners();
|
||||
test_regularSigners();
|
||||
test_regularSignersUsingSubmitMulti();
|
||||
test_heterogeneousSigners();
|
||||
test_keyDisable();
|
||||
test_regKey();
|
||||
test_txTypes();
|
||||
test_badSignatureText();
|
||||
test_noMultiSigners();
|
||||
test_multisigningMultisigner();
|
||||
test_signForHash();
|
||||
using namespace jtx;
|
||||
auto const all = supported_amendments();
|
||||
|
||||
// The reserve required on a signer list changes based on.
|
||||
// featureMultiSignReserve. Test both with and without.
|
||||
testAll (all - featureMultiSignReserve);
|
||||
testAll (all | featureMultiSignReserve);
|
||||
test_amendmentTransition();
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user