mirror of
https://github.com/Xahau/xahaud.git
synced 2025-11-19 18:15:50 +00:00
add test cases compiling not tested
This commit is contained in:
@@ -1659,6 +1659,354 @@ public:
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
}
|
||||
|
||||
void
|
||||
test_nestedMultiSign(FeatureBitset features)
|
||||
{
|
||||
testcase("Nested MultiSign");
|
||||
|
||||
using namespace jtx;
|
||||
Env env{*this, features};
|
||||
|
||||
Account const alice{"alice", KeyType::secp256k1};
|
||||
Account const becky{"becky", KeyType::ed25519};
|
||||
Account const cheri{"cheri", KeyType::secp256k1};
|
||||
Account const daria{"daria", KeyType::ed25519};
|
||||
Account const edgar{"edgar", KeyType::secp256k1};
|
||||
Account const fiona{"fiona", KeyType::ed25519};
|
||||
Account const grace{"grace", KeyType::secp256k1};
|
||||
Account const henry{"henry", KeyType::ed25519};
|
||||
Account const f1{"f1", KeyType::ed25519};
|
||||
Account const f2{"f2", KeyType::ed25519};
|
||||
Account const f3{"f3", KeyType::ed25519};
|
||||
env.fund(
|
||||
XRP(1000),
|
||||
alice,
|
||||
becky,
|
||||
cheri,
|
||||
daria,
|
||||
edgar,
|
||||
fiona,
|
||||
grace,
|
||||
henry,
|
||||
f1,
|
||||
f2,
|
||||
f3);
|
||||
env.close();
|
||||
|
||||
auto const baseFee = env.current()->fees().base;
|
||||
|
||||
if (!features[featureNestedMultiSign])
|
||||
{
|
||||
// When feature is disabled, nested signing should fail
|
||||
env(signers(f1, 1, {{f2, 1}}));
|
||||
env(signers(f2, 1, {{f3, 1}}));
|
||||
env.close();
|
||||
|
||||
std::uint32_t f1Seq = env.seq(f1);
|
||||
env(noop(f1),
|
||||
msig({msigner(f2, msigner(f3))}),
|
||||
fee(3 * baseFee),
|
||||
ter(temMALFORMED));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(f1) == f1Seq);
|
||||
return;
|
||||
}
|
||||
|
||||
// Test Case 1: Basic 2-level nested signing with quorum
|
||||
{
|
||||
// Set up signer lists with quorum requirements
|
||||
env(signers(becky, 2, {{bogie, 1}, {demon, 1}, {ghost, 1}}));
|
||||
env(signers(cheri, 3, {{haunt, 2}, {jinni, 2}}));
|
||||
env.close();
|
||||
|
||||
// Alice requires quorum of 3 with weighted signers
|
||||
env(signers(alice, 3, {{becky, 2}, {cheri, 2}, {daria, 1}}));
|
||||
env.close();
|
||||
|
||||
// Test 1a: becky alone (weight 2) doesn't meet alice's quorum
|
||||
std::uint32_t aliceSeq = env.seq(alice);
|
||||
env(noop(alice),
|
||||
msig({msigner(becky, msigner(bogie), msigner(demon))}),
|
||||
fee(3 * baseFee),
|
||||
ter(tefBAD_QUORUM));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq);
|
||||
|
||||
// Test 1b: becky (2) + daria (1) meets quorum of 3
|
||||
aliceSeq = env.seq(alice);
|
||||
env(noop(alice),
|
||||
msig(
|
||||
{msigner(becky, msigner(bogie), msigner(demon)),
|
||||
msigner(daria)}),
|
||||
fee(4 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
|
||||
// Test 1c: cheri's nested signers must meet her quorum
|
||||
aliceSeq = env.seq(alice);
|
||||
env(noop(alice),
|
||||
msig({msigner(
|
||||
cheri, msigner(haunt))}), // haunt has weight 2, needs 3
|
||||
fee(2 * baseFee),
|
||||
ter(tefBAD_QUORUM));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq);
|
||||
|
||||
// Test 1d: cheri with both signers meets her quorum
|
||||
aliceSeq = env.seq(alice);
|
||||
env(noop(alice),
|
||||
msig({msigner(
|
||||
cheri, msigner(haunt), msigner(jinni))}), // 2+2 >= 3
|
||||
fee(3 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
}
|
||||
|
||||
// Test Case 2: 3-level with quorum at each level
|
||||
{
|
||||
// Level 3: phase needs 2 signatures with weight 1 each
|
||||
env(signers(phase, 2, {{acc10, 1}, {acc11, 1}, {acc12, 1}}));
|
||||
|
||||
// Level 2: jinni needs weighted signatures totaling 4
|
||||
env(signers(jinni, 4, {{phase, 3}, {shade, 2}, {spook, 1}}));
|
||||
|
||||
// Level 1: edgar needs 2 from weighted signers
|
||||
env(signers(edgar, 2, {{jinni, 1}, {bogie, 1}, {demon, 1}}));
|
||||
|
||||
// Alice now requires edgar with weight 3
|
||||
env(signers(alice, 3, {{edgar, 3}, {fiona, 2}}));
|
||||
env.close();
|
||||
|
||||
// Test 2a: Full 3-level signing with quorum met at each level
|
||||
std::uint32_t aliceSeq = env.seq(alice);
|
||||
env(noop(alice),
|
||||
msig({
|
||||
msigner(
|
||||
edgar,
|
||||
msigner(
|
||||
jinni,
|
||||
msigner(
|
||||
phase,
|
||||
msigner(acc10),
|
||||
msigner(acc11))), // phase quorum: 1+1 = 2 ✓
|
||||
msigner(shade)) // jinni quorum: 3+2 = 5 >= 4 ✓
|
||||
}), // edgar quorum: 1+0 = 1 < 2 ✗
|
||||
fee(5 * baseFee),
|
||||
ter(tefBAD_QUORUM));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq);
|
||||
|
||||
// Test 2b: Edgar needs to meet his quorum too
|
||||
aliceSeq = env.seq(alice);
|
||||
env(noop(alice),
|
||||
msig({
|
||||
msigner(
|
||||
edgar,
|
||||
msigner(
|
||||
jinni,
|
||||
msigner(phase, msigner(acc10), msigner(acc11))),
|
||||
msigner(bogie)) // edgar quorum: 1+1 = 2 ✓
|
||||
}),
|
||||
fee(5 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
|
||||
// Test 2c: Phase doesn't meet quorum, should fail
|
||||
aliceSeq = env.seq(alice);
|
||||
env(noop(alice),
|
||||
msig({
|
||||
msigner(
|
||||
edgar,
|
||||
msigner(
|
||||
jinni,
|
||||
msigner(
|
||||
phase,
|
||||
msigner(acc10))), // phase quorum: 1 < 2 ✗
|
||||
msigner(shade),
|
||||
msigner(
|
||||
spook)) // jinni would have 3+2+1=6 but phase fails
|
||||
}),
|
||||
fee(5 * baseFee),
|
||||
ter(tefBAD_QUORUM));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq);
|
||||
}
|
||||
|
||||
// Test Case 3: Mixed levels - some direct, some nested at different
|
||||
// depths
|
||||
{
|
||||
// Set up complex mixed-level signing for alice
|
||||
// grace has 2-level signers
|
||||
env(signers(grace, 2, {{bogie, 1}, {demon, 1}}));
|
||||
|
||||
// henry has 3-level signers (henry -> becky -> bogie/demon)
|
||||
env(signers(henry, 1, {{becky, 1}, {cheri, 1}}));
|
||||
|
||||
// Alice has mix of direct and nested signers at different weights
|
||||
env(signers(
|
||||
alice,
|
||||
5,
|
||||
{
|
||||
{daria, 1}, // direct signer
|
||||
{edgar, 2}, // has 3-level signers
|
||||
{fiona, 1}, // direct signer
|
||||
{grace, 2}, // has 2-level signers
|
||||
{henry, 2} // has 3-level signers
|
||||
}));
|
||||
env.close();
|
||||
|
||||
// Test 3a: Mix of all levels meeting quorum exactly
|
||||
// JSON structure would show:
|
||||
// - daria: direct signature (level 0)
|
||||
// - edgar->bogie: 2-level
|
||||
// - grace->bogie,demon: 2-level
|
||||
// Total weight: 1 + 2 + 2 = 5 ✓
|
||||
std::uint32_t aliceSeq = env.seq(alice);
|
||||
env(noop(alice),
|
||||
msig({
|
||||
msigner(daria), // weight 1, direct
|
||||
msigner(edgar, msigner(bogie)), // weight 2, 2-level
|
||||
msigner(grace, msigner(bogie), msigner(demon)) // weight 2,
|
||||
// 2-level
|
||||
}),
|
||||
fee(5 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
|
||||
// Test 3b: 3-level signing through henry
|
||||
aliceSeq = env.seq(alice);
|
||||
env(noop(alice),
|
||||
msig(
|
||||
{msigner(fiona), // weight 1, direct
|
||||
msigner(
|
||||
grace, msigner(bogie)), // weight 2, 2-level (partial)
|
||||
msigner(
|
||||
henry, // weight 2, 3-level
|
||||
msigner(becky, msigner(bogie), msigner(demon)))}),
|
||||
fee(5 * baseFee),
|
||||
ter(tefBAD_QUORUM)); // grace didn't meet quorum
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq);
|
||||
|
||||
// Test 3c: Correct version with all quorums met
|
||||
aliceSeq = env.seq(alice);
|
||||
env(noop(alice),
|
||||
msig({
|
||||
msigner(fiona), // weight 1
|
||||
msigner(
|
||||
henry, // weight 2
|
||||
msigner(becky, msigner(bogie), msigner(demon))),
|
||||
msigner(edgar, msigner(bogie), msigner(demon)) // weight 2
|
||||
}),
|
||||
fee(6 * baseFee)); // Total weight: 1+2+2 = 5 ✓
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
}
|
||||
|
||||
// Test Case 4: Complex scenario with maximum signers at mixed depths
|
||||
{
|
||||
// Create a signing tree that uses close to maximum signers
|
||||
// and tests weight accumulation across all levels
|
||||
|
||||
// Set up for alice: needs 15 out of possible 20 weight
|
||||
env(signers(
|
||||
alice,
|
||||
15,
|
||||
{
|
||||
{becky, 3}, // will use 3-level
|
||||
{cheri, 3}, // will use 2-level
|
||||
{daria, 3}, // will use direct
|
||||
{edgar, 3}, // will use 2-level
|
||||
{fiona, 3}, // will use 3-level
|
||||
{grace, 3}, // will use direct
|
||||
{henry, 2} // will use 2-level
|
||||
}));
|
||||
env.close();
|
||||
|
||||
// Complex multi-level transaction just meeting quorum
|
||||
std::uint32_t aliceSeq = env.seq(alice);
|
||||
env(noop(alice),
|
||||
msig({
|
||||
msigner(
|
||||
becky, // weight 3, 3-level
|
||||
msigner(demon),
|
||||
msigner(ghost)),
|
||||
msigner(
|
||||
cheri, // weight 3, 2-level
|
||||
msigner(haunt),
|
||||
msigner(jinni)),
|
||||
msigner(daria), // weight 3, direct
|
||||
msigner(
|
||||
edgar, // weight 3, 2-level
|
||||
msigner(bogie),
|
||||
msigner(demon)),
|
||||
msigner(grace) // weight 3, direct
|
||||
}),
|
||||
fee(10 * baseFee)); // Total weight: 3+3+3+3+3 = 15 ✓
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
|
||||
// Test 4b: Just under quorum
|
||||
aliceSeq = env.seq(alice);
|
||||
env(noop(alice),
|
||||
msig(
|
||||
{msigner(
|
||||
becky, // weight 3
|
||||
msigner(demon)), // becky quorum not met!
|
||||
msigner(
|
||||
cheri, // weight 3
|
||||
msigner(haunt),
|
||||
msigner(jinni)),
|
||||
msigner(daria), // weight 3
|
||||
msigner(
|
||||
edgar, // weight 3
|
||||
msigner(bogie),
|
||||
msigner(demon)),
|
||||
msigner(
|
||||
henry, // weight 2
|
||||
msigner(
|
||||
jinni,
|
||||
msigner(phase, msigner(acc10), msigner(acc11))))}),
|
||||
fee(10 * baseFee),
|
||||
ter(tefBAD_QUORUM)); // becky's quorum not met
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq);
|
||||
}
|
||||
|
||||
// Test Case 5: Edge case - single signer with maximum nesting
|
||||
{
|
||||
// Alice needs just one signer, but that signer uses full 3-level
|
||||
// depth
|
||||
env(signers(alice, 1, {{becky, 1}}));
|
||||
env.close();
|
||||
|
||||
std::uint32_t aliceSeq = env.seq(alice);
|
||||
env(noop(alice),
|
||||
msig({msigner(becky, msigner(demon), msigner(ghost))}),
|
||||
fee(3 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
|
||||
// Now with 3-level depth through becky->cheri->jinni->phase
|
||||
env(signers(becky, 1, {{cheri, 1}}));
|
||||
env(signers(cheri, 1, {{jinni, 1}}));
|
||||
env.close();
|
||||
|
||||
aliceSeq = env.seq(alice);
|
||||
env(noop(alice),
|
||||
msig({msigner(
|
||||
becky,
|
||||
msigner(
|
||||
cheri,
|
||||
msigner(
|
||||
jinni,
|
||||
msigner(phase, msigner(acc10), msigner(acc11)))))}),
|
||||
fee(3 * baseFee));
|
||||
env.close();
|
||||
BEAST_EXPECT(env.seq(alice) == aliceSeq + 1);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
testAll(FeatureBitset features)
|
||||
{
|
||||
@@ -1680,6 +2028,7 @@ public:
|
||||
test_signForHash(features);
|
||||
test_signersWithTickets(features);
|
||||
test_signersWithTags(features);
|
||||
test_nestedMultiSign(features);
|
||||
}
|
||||
|
||||
void
|
||||
@@ -1692,8 +2041,11 @@ public:
|
||||
// featureMultiSignReserve. Limits on the number of signers
|
||||
// changes based on featureExpandedSignerList. Test both with and
|
||||
// without.
|
||||
testAll(all - featureMultiSignReserve - featureExpandedSignerList);
|
||||
testAll(all - featureExpandedSignerList);
|
||||
testAll(
|
||||
all - featureMultiSignReserve - featureExpandedSignerList -
|
||||
featureNestedMultiSign);
|
||||
testAll(all - featureExpandedSignerList - featureNestedMultiSign);
|
||||
testAll(all - featureNestedMultiSign);
|
||||
testAll(all);
|
||||
test_amendmentTransition();
|
||||
}
|
||||
|
||||
@@ -66,14 +66,30 @@ signers(Account const& account, none_t)
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
msig::msig(std::vector<msig::Reg> signers_) : signers(std::move(signers_))
|
||||
msig::msig(std::vector<msig::SignerPtr> signers_) : signers(std::move(signers_))
|
||||
{
|
||||
// Signatures must be applied in sorted order.
|
||||
std::sort(
|
||||
signers.begin(),
|
||||
signers.end(),
|
||||
[](msig::Reg const& lhs, msig::Reg const& rhs) {
|
||||
return lhs.acct.id() < rhs.acct.id();
|
||||
[](SignerPtr const& lhs, SignerPtr const& rhs) {
|
||||
return lhs->id() < rhs->id();
|
||||
});
|
||||
}
|
||||
|
||||
msig::msig(std::vector<msig::Reg> signers_)
|
||||
{
|
||||
// Convert Reg vector to SignerPtr vector for backward compatibility
|
||||
signers.reserve(signers_.size());
|
||||
for (auto const& s : signers_)
|
||||
signers.push_back(s.toSigner());
|
||||
|
||||
// Sort
|
||||
std::sort(
|
||||
signers.begin(),
|
||||
signers.end(),
|
||||
[](SignerPtr const& lhs, SignerPtr const& rhs) {
|
||||
return lhs->id() < rhs->id();
|
||||
});
|
||||
}
|
||||
|
||||
@@ -93,19 +109,53 @@ msig::operator()(Env& env, JTx& jt) const
|
||||
env.test.log << pretty(jtx.jv) << std::endl;
|
||||
Rethrow();
|
||||
}
|
||||
|
||||
// Recursive function to build signer JSON
|
||||
std::function<Json::Value(SignerPtr const&)> buildSignerJson;
|
||||
buildSignerJson = [&](SignerPtr const& signer) -> Json::Value {
|
||||
Json::Value jo;
|
||||
jo[jss::Account] = signer->acct.human();
|
||||
|
||||
if (signer->isNested())
|
||||
{
|
||||
// This is a nested signer - add subsigners
|
||||
auto sortedNested = signer->nested;
|
||||
std::sort(
|
||||
sortedNested.begin(),
|
||||
sortedNested.end(),
|
||||
[](SignerPtr const& lhs, SignerPtr const& rhs) {
|
||||
return lhs->id() < rhs->id();
|
||||
});
|
||||
|
||||
auto& subJs = jo[sfSigners.getJsonName()];
|
||||
for (std::size_t i = 0; i < sortedNested.size(); ++i)
|
||||
{
|
||||
auto& subJo = subJs[i][sfSigner.getJsonName()];
|
||||
subJo = buildSignerJson(sortedNested[i]);
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
// This is a leaf signer - add signature
|
||||
jo[jss::SigningPubKey] = strHex(signer->sig.pk().slice());
|
||||
|
||||
Serializer ss{buildMultiSigningData(*st, signer->acct.id())};
|
||||
auto const sig = ripple::sign(
|
||||
*publicKeyType(signer->sig.pk().slice()),
|
||||
signer->sig.sk(),
|
||||
ss.slice());
|
||||
jo[sfTxnSignature.getJsonName()] =
|
||||
strHex(Slice{sig.data(), sig.size()});
|
||||
}
|
||||
|
||||
return jo;
|
||||
};
|
||||
|
||||
auto& js = jtx[sfSigners.getJsonName()];
|
||||
for (std::size_t i = 0; i < mySigners.size(); ++i)
|
||||
{
|
||||
auto const& e = mySigners[i];
|
||||
auto& jo = js[i][sfSigner.getJsonName()];
|
||||
jo[jss::Account] = e.acct.human();
|
||||
jo[jss::SigningPubKey] = strHex(e.sig.pk().slice());
|
||||
|
||||
Serializer ss{buildMultiSigningData(*st, e.acct.id())};
|
||||
auto const sig = ripple::sign(
|
||||
*publicKeyType(e.sig.pk().slice()), e.sig.sk(), ss.slice());
|
||||
jo[sfTxnSignature.getJsonName()] =
|
||||
strHex(Slice{sig.data(), sig.size()});
|
||||
jo = buildSignerJson(mySigners[i]);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@@ -21,6 +21,7 @@
|
||||
#define RIPPLE_TEST_JTX_MULTISIGN_H_INCLUDED
|
||||
|
||||
#include <cstdint>
|
||||
#include <memory>
|
||||
#include <optional>
|
||||
#include <test/jtx/Account.h>
|
||||
#include <test/jtx/amount.h>
|
||||
@@ -65,6 +66,48 @@ signers(Account const& account, none_t);
|
||||
class msig
|
||||
{
|
||||
public:
|
||||
// Recursive signer structure
|
||||
struct Signer
|
||||
{
|
||||
Account acct;
|
||||
Account sig; // For leaf signers (same as acct for master key)
|
||||
std::vector<std::shared_ptr<Signer>> nested; // For nested signers
|
||||
|
||||
// Leaf signer constructor (regular signing)
|
||||
Signer(Account const& masterSig) : acct(masterSig), sig(masterSig)
|
||||
{
|
||||
}
|
||||
|
||||
// Leaf signer constructor (with different signing key)
|
||||
Signer(Account const& acct_, Account const& regularSig)
|
||||
: acct(acct_), sig(regularSig)
|
||||
{
|
||||
}
|
||||
|
||||
// Nested signer constructor
|
||||
Signer(
|
||||
Account const& acct_,
|
||||
std::vector<std::shared_ptr<Signer>> nested_)
|
||||
: acct(acct_), nested(std::move(nested_))
|
||||
{
|
||||
}
|
||||
|
||||
bool
|
||||
isNested() const
|
||||
{
|
||||
return !nested.empty();
|
||||
}
|
||||
|
||||
AccountID
|
||||
id() const
|
||||
{
|
||||
return acct.id();
|
||||
}
|
||||
};
|
||||
|
||||
using SignerPtr = std::shared_ptr<Signer>;
|
||||
|
||||
// For backward compatibility
|
||||
struct Reg
|
||||
{
|
||||
Account acct;
|
||||
@@ -73,16 +116,13 @@ public:
|
||||
Reg(Account const& masterSig) : acct(masterSig), sig(masterSig)
|
||||
{
|
||||
}
|
||||
|
||||
Reg(Account const& acct_, Account const& regularSig)
|
||||
: acct(acct_), sig(regularSig)
|
||||
{
|
||||
}
|
||||
|
||||
Reg(char const* masterSig) : acct(masterSig), sig(masterSig)
|
||||
{
|
||||
}
|
||||
|
||||
Reg(char const* acct_, char const* regularSig)
|
||||
: acct(acct_), sig(regularSig)
|
||||
{
|
||||
@@ -93,13 +133,25 @@ public:
|
||||
{
|
||||
return acct < rhs.acct;
|
||||
}
|
||||
|
||||
// Convert to Signer
|
||||
SignerPtr
|
||||
toSigner() const
|
||||
{
|
||||
return std::make_shared<Signer>(acct, sig);
|
||||
}
|
||||
};
|
||||
|
||||
std::vector<Reg> signers;
|
||||
std::vector<SignerPtr> signers;
|
||||
|
||||
public:
|
||||
// Direct constructor with SignerPtr vector
|
||||
msig(std::vector<SignerPtr> signers_);
|
||||
|
||||
// Backward compatibility constructor
|
||||
msig(std::vector<Reg> signers_);
|
||||
|
||||
// Variadic constructor for backward compatibility
|
||||
template <class AccountType, class... Accounts>
|
||||
explicit msig(AccountType&& a0, Accounts&&... aN)
|
||||
: msig{std::vector<Reg>{
|
||||
@@ -112,6 +164,30 @@ public:
|
||||
operator()(Env&, JTx& jt) const;
|
||||
};
|
||||
|
||||
// Helper functions to create signers - renamed to avoid conflict with sig()
|
||||
// transaction modifier
|
||||
inline msig::SignerPtr
|
||||
msigner(Account const& acct)
|
||||
{
|
||||
return std::make_shared<msig::Signer>(acct);
|
||||
}
|
||||
|
||||
inline msig::SignerPtr
|
||||
msigner(Account const& acct, Account const& signingKey)
|
||||
{
|
||||
return std::make_shared<msig::Signer>(acct, signingKey);
|
||||
}
|
||||
|
||||
// Create nested signer with initializer list
|
||||
template <typename... Args>
|
||||
inline msig::SignerPtr
|
||||
msigner(Account const& acct, Args&&... args)
|
||||
{
|
||||
std::vector<msig::SignerPtr> nested;
|
||||
(nested.push_back(std::forward<Args>(args)), ...);
|
||||
return std::make_shared<msig::Signer>(acct, std::move(nested));
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/** The number of signer lists matches. */
|
||||
|
||||
Reference in New Issue
Block a user