mirror of
				https://github.com/Xahau/xahaud.git
				synced 2025-11-04 02:35:48 +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