2222/// - Up to `MAX_SIGNER_COUNT` signers are supported to keep the account small and rent-cheap.
2323use anchor_lang:: prelude:: * ;
2424
25- use crate :: common:: MAX_SIGNER_COUNT ;
26-
2725#[ account]
28- #[ derive( Debug ) ]
26+ #[ derive( InitSpace ) ]
2927pub struct PartnerConfig {
30- /// Number of valid entries at the start of `signers` to consider.
31- pub signer_count : u8 ,
32- /// Fixed-capacity array of authorized EVM addresses (20-byte) for partner approvals.
33- /// Only the first `signer_count` elements should be treated as initialized.
34- pub signers : [ [ u8 ; 20 ] ; MAX_SIGNER_COUNT ] ,
28+ // Static list of partner signers, max_len 20 to facilitate max of 4 concurrent validator rotations
29+ // at regular operating capacity of 16 validators, while capping heap usage to 800b
30+ #[ max_len( 20 ) ]
31+ pub signers : Vec < PartnerSigner > ,
3532}
3633
37- #[ derive( Default ) ]
38- /// Internal helper that materializes the configured signer set in a structure
39- /// with fast membership checks.
40- struct PartnerSet {
41- signers : std:: collections:: BTreeSet < [ u8 ; 20 ] > ,
34+ #[ derive( Debug , Clone , AnchorSerialize , AnchorDeserialize , InitSpace ) ]
35+ pub struct PartnerSigner {
36+ // Regular active EVM address of the signer
37+ pub evm_address : [ u8 ; 20 ] ,
38+ // New candidate address that each signer will sign with in a blue/green key rotation setting
39+ // When this value is not empty, the signer will start exclusively signing with this key offchain
40+ // However, from an onchain perspective, signature could arrive with either the old or new address for a while
41+ //
42+ // Since Base enforces that nonce ranges never skip, when a signature with the new address arrives,
43+ // it should be safe to assume that because it's the latest nonce range, all previous nonce ranges that could've been
44+ // signed with the old address must have already been consumed, at that point it'd be safe for partner Owner to
45+ // move the new_address to address field
46+ pub new_evm_address : Option < [ u8 ; 20 ] > ,
4247}
4348
4449impl PartnerConfig {
@@ -49,28 +54,121 @@ impl PartnerConfig {
4954 /// double counting.
5055 /// - Returns the number of addresses present in this config's allowlist.
5156 pub fn count_approvals ( & self , signers : & [ [ u8 ; 20 ] ] ) -> u32 {
52- let mut partner_set = PartnerSet :: default ( ) ;
53- let n = self . signer_count as usize ;
54- let max = core:: cmp:: min ( n, MAX_SIGNER_COUNT ) ;
55- for i in 0 ..max {
56- partner_set. signers . insert ( self . signers [ i] ) ;
57+ let mut count: u32 = 0 ;
58+ // Track which indices of self.signers have already been matched so that
59+ // the same configured signer is not counted more than once (e.g. if
60+ // both its old and new addresses appear in `signers`).
61+ let mut matched_indices = vec ! [ false ; self . signers. len( ) ] ;
62+
63+ ' outer: for provided in signers. iter ( ) {
64+ for ( idx, configured) in self . signers . iter ( ) . enumerate ( ) {
65+ if matched_indices[ idx] {
66+ continue ;
67+ }
68+
69+ let is_match_old = configured. evm_address == * provided;
70+ let is_match_new = configured
71+ . new_evm_address
72+ . as_ref ( )
73+ . is_some_and ( |new_addr| new_addr == provided) ;
74+
75+ if is_match_old || is_match_new {
76+ matched_indices[ idx] = true ;
77+ count += 1 ;
78+ // Move to next provided signer. This ensures a single
79+ // configured signer index cannot be matched more than once.
80+ continue ' outer;
81+ }
82+ }
5783 }
58- partner_set. count_approvals ( signers)
84+
85+ count
5986 }
6087}
6188
62- impl PartnerSet {
63- /// Returns how many of the provided addresses exist in the configured set.
64- ///
65- /// Caller should pass a deduplicated list; duplicates would be counted more
66- /// than once by this function.
67- pub fn count_approvals ( & self , signers : & [ [ u8 ; 20 ] ] ) -> u32 {
68- let mut count: u32 = 0 ;
69- for signer in signers. iter ( ) {
70- if self . signers . contains ( signer) {
71- count += 1 ;
72- }
89+ #[ cfg( test) ]
90+ mod tests {
91+
92+ use super :: * ;
93+
94+ fn addr ( byte : u8 ) -> [ u8 ; 20 ] {
95+ [ byte; 20 ]
96+ }
97+
98+ fn signer ( old : u8 , new : Option < u8 > ) -> PartnerSigner {
99+ PartnerSigner {
100+ evm_address : addr ( old) ,
101+ new_evm_address : new. map ( addr) ,
73102 }
74- count
103+ }
104+
105+ #[ test]
106+ fn returns_zero_when_no_configured_signers ( ) {
107+ let cfg = PartnerConfig { signers : vec ! [ ] } ;
108+ let provided = [ addr ( 1 ) , addr ( 2 ) ] ;
109+ assert_eq ! ( cfg. count_approvals( & provided) , 0 ) ;
110+ }
111+
112+ #[ test]
113+ fn returns_zero_when_no_provided_addresses ( ) {
114+ let cfg = PartnerConfig {
115+ signers : vec ! [ signer( 1 , None ) ] ,
116+ } ;
117+ let provided: [ [ u8 ; 20 ] ; 0 ] = [ ] ;
118+ assert_eq ! ( cfg. count_approvals( & provided) , 0 ) ;
119+ }
120+
121+ #[ test]
122+ fn matches_old_address_counts_one ( ) {
123+ let cfg = PartnerConfig {
124+ signers : vec ! [ signer( 1 , None ) ] ,
125+ } ;
126+ let provided = [ addr ( 1 ) ] ;
127+ assert_eq ! ( cfg. count_approvals( & provided) , 1 ) ;
128+ }
129+
130+ #[ test]
131+ fn matches_new_address_counts_one ( ) {
132+ let cfg = PartnerConfig {
133+ signers : vec ! [ signer( 1 , Some ( 2 ) ) ] ,
134+ } ;
135+ let provided = [ addr ( 2 ) ] ;
136+ assert_eq ! ( cfg. count_approvals( & provided) , 1 ) ;
137+ }
138+
139+ #[ test]
140+ fn old_and_new_for_same_signer_counts_once ( ) {
141+ let cfg = PartnerConfig {
142+ signers : vec ! [ signer( 1 , Some ( 2 ) ) ] ,
143+ } ;
144+ let provided = [ addr ( 1 ) , addr ( 2 ) ] ;
145+ assert_eq ! ( cfg. count_approvals( & provided) , 1 ) ;
146+ }
147+
148+ #[ test]
149+ fn multiple_distinct_matches_count_correctly ( ) {
150+ let cfg = PartnerConfig {
151+ signers : vec ! [ signer( 1 , None ) , signer( 2 , None ) , signer( 3 , Some ( 4 ) ) ] ,
152+ } ;
153+ let provided = [ addr ( 1 ) , addr ( 4 ) ] ;
154+ assert_eq ! ( cfg. count_approvals( & provided) , 2 ) ;
155+ }
156+
157+ #[ test]
158+ fn non_matching_addresses_count_zero ( ) {
159+ let cfg = PartnerConfig {
160+ signers : vec ! [ signer( 1 , None ) ] ,
161+ } ;
162+ let provided = [ addr ( 9 ) ] ;
163+ assert_eq ! ( cfg. count_approvals( & provided) , 0 ) ;
164+ }
165+
166+ #[ test]
167+ fn duplicate_provided_addresses_do_not_increase_count ( ) {
168+ let cfg = PartnerConfig {
169+ signers : vec ! [ signer( 1 , None ) ] ,
170+ } ;
171+ let provided = [ addr ( 1 ) , addr ( 1 ) ] ;
172+ assert_eq ! ( cfg. count_approvals( & provided) , 1 ) ;
75173 }
76174}
0 commit comments