@@ -24,6 +24,7 @@ import {
2424 getSigners ,
2525 findSignerLeaf ,
2626 getWeight ,
27+ minimiseSignedTopology ,
2728 hashConfiguration ,
2829 flatLeavesToTopology ,
2930 configToJson ,
@@ -91,6 +92,37 @@ describe('Config', () => {
9192 checkpointer : testAddress1 ,
9293 }
9394
95+ function signedAddresses ( topology : Topology ) : string [ ] {
96+ if ( isNode ( topology ) ) {
97+ return [ ...signedAddresses ( topology [ 0 ] ) , ...signedAddresses ( topology [ 1 ] ) ]
98+ }
99+
100+ if ( isNestedLeaf ( topology ) ) {
101+ return signedAddresses ( topology . tree )
102+ }
103+
104+ if ( ( isSignerLeaf ( topology ) || isSapientSignerLeaf ( topology ) ) && topology . signature ) {
105+ return [ topology . address ]
106+ }
107+
108+ return [ ]
109+ }
110+
111+ function signSigner ( address : string , weight : bigint , nonce : bigint ) : SignerLeaf {
112+ return {
113+ type : 'signer' ,
114+ address,
115+ weight,
116+ signed : true ,
117+ signature : {
118+ type : 'hash' ,
119+ r : nonce ,
120+ s : nonce + 1n ,
121+ yParity : 0 ,
122+ } ,
123+ }
124+ }
125+
94126 describe ( 'Type Guards' , ( ) => {
95127 describe ( 'isSignerLeaf' , ( ) => {
96128 it ( 'should return true for valid signer leaf' , ( ) => {
@@ -417,6 +449,67 @@ describe('Config', () => {
417449 } )
418450 } )
419451
452+ describe ( 'minimiseSignedTopology' , ( ) => {
453+ it ( 'should prefer the smallest signature count that still meets threshold' , ( ) => {
454+ const topology = flatLeavesToTopology ( [
455+ signSigner ( '0x1000000000000000000000000000000000000001' , 4n , 1n ) ,
456+ signSigner ( '0x1000000000000000000000000000000000000002' , 4n , 3n ) ,
457+ signSigner ( '0x1000000000000000000000000000000000000003' , 4n , 5n ) ,
458+ signSigner ( '0x1000000000000000000000000000000000000004' , 6n , 7n ) ,
459+ signSigner ( '0x1000000000000000000000000000000000000005' , 6n , 9n ) ,
460+ ] )
461+
462+ const result = minimiseSignedTopology ( topology , 12n )
463+
464+ expect ( signedAddresses ( result ) ) . toEqual ( [
465+ '0x1000000000000000000000000000000000000004' ,
466+ '0x1000000000000000000000000000000000000005' ,
467+ ] )
468+ expect ( getWeight ( result , ( ) => false ) . weight ) . toBe ( 12n )
469+ } )
470+
471+ it ( 'should keep earlier signers when equal-count solutions tie' , ( ) => {
472+ const topology = flatLeavesToTopology ( [
473+ signSigner ( '0x2000000000000000000000000000000000000001' , 1n , 11n ) ,
474+ signSigner ( '0x2000000000000000000000000000000000000002' , 1n , 13n ) ,
475+ signSigner ( '0x2000000000000000000000000000000000000003' , 1n , 15n ) ,
476+ ] )
477+
478+ const result = minimiseSignedTopology ( topology , 2n )
479+
480+ expect ( signedAddresses ( result ) ) . toEqual ( [
481+ '0x2000000000000000000000000000000000000001' ,
482+ '0x2000000000000000000000000000000000000002' ,
483+ ] )
484+ } )
485+
486+ it ( 'should minimise nested signers while preserving nested thresholds' , ( ) => {
487+ const nested : NestedLeaf = {
488+ type : 'nested' ,
489+ weight : 4n ,
490+ threshold : 2n ,
491+ tree : flatLeavesToTopology ( [
492+ signSigner ( '0x3000000000000000000000000000000000000001' , 1n , 21n ) ,
493+ signSigner ( '0x3000000000000000000000000000000000000002' , 1n , 23n ) ,
494+ signSigner ( '0x3000000000000000000000000000000000000003' , 1n , 25n ) ,
495+ ] ) ,
496+ }
497+ const topology : Topology = [
498+ nested ,
499+ signSigner ( '0x3000000000000000000000000000000000000004' , 3n , 27n ) ,
500+ ]
501+
502+ const result = minimiseSignedTopology ( topology , 5n )
503+
504+ expect ( signedAddresses ( result ) ) . toEqual ( [
505+ '0x3000000000000000000000000000000000000001' ,
506+ '0x3000000000000000000000000000000000000002' ,
507+ '0x3000000000000000000000000000000000000004' ,
508+ ] )
509+ expect ( getWeight ( result , ( ) => false ) . weight ) . toBe ( 7n )
510+ } )
511+ } )
512+
420513 describe ( 'hashConfiguration' , ( ) => {
421514 it ( 'should hash signer leaf correctly' , ( ) => {
422515 const hash = hashConfiguration ( sampleSignerLeaf )
0 commit comments