@@ -22,7 +22,7 @@ contract Escrow is Owned, IEscrow {
2222 uint16 public constant MAX_FEE = 1_000 ; // 10 %
2323
2424 bytes32 public constant SET_ADMIN_TYPEHASH =
25- keccak256 ("SetAdmin(uint repoId,uint accountId,address admin ,uint nonce,uint deadline) " );
25+ keccak256 ("SetAdmin(uint repoId,uint accountId,address[] admins ,uint nonce,uint deadline) " );
2626 bytes32 public constant CLAIM_TYPEHASH =
2727 keccak256 ("Claim(uint[] distributionIds,address recipient,uint nonce,uint deadline) " );
2828
@@ -32,8 +32,8 @@ contract Escrow is Owned, IEscrow {
3232 struct Account {
3333 mapping (address => uint ) balance; // token → balance
3434 bool hasDistributions; // whether any distributions have occurred
35- address admin;
36- mapping ( address => bool ) distributors; // distributor → authorized?
35+ EnumerableSet.AddressSet admins; // set of authorized admins
36+ EnumerableSet.AddressSet distributors; // set of authorized distributors
3737 bool exists;
3838 }
3939
@@ -75,12 +75,12 @@ contract Escrow is Owned, IEscrow {
7575 /* -------------------------------------------------------------------------- */
7676 /* STATE VARIABLES */
7777 /* -------------------------------------------------------------------------- */
78- mapping (uint => mapping (uint => Account)) public accounts; // repoId → accountId → Account
78+ mapping (uint => mapping (uint => Account)) accounts; // repoId → accountId → Account
7979
80- mapping (uint => Distribution) public distributions; // distributionId → Distribution
81- mapping (uint => RepoAccount) public distributionToRepo; // distributionId → RepoAccount (for repo distributions)
80+ mapping (uint => Distribution) public distributions; // distributionId → Distribution
81+ mapping (uint => RepoAccount) public distributionToRepo; // distributionId → RepoAccount (for repo distributions)
8282
83- mapping (address => uint ) public recipientNonce; // recipient → nonce
83+ mapping (address => uint ) public recipientNonce; // recipient → nonce
8484 uint public ownerNonce;
8585
8686 uint public fee;
@@ -104,7 +104,7 @@ contract Escrow is Owned, IEscrow {
104104 /* MODIFIERS */
105105 /* -------------------------------------------------------------------------- */
106106 modifier onlyRepoAdmin (uint repoId , uint accountId ) {
107- require (msg . sender == accounts[repoId][accountId].admin , Errors.NOT_REPO_ADMIN);
107+ require (accounts[repoId][accountId].admins. contains ( msg . sender ) , Errors.NOT_REPO_ADMIN);
108108 _;
109109 }
110110
@@ -137,19 +137,20 @@ contract Escrow is Owned, IEscrow {
137137 /* INIT REPO ADMIN */
138138 /* -------------------------------------------------------------------------- */
139139 function initRepo (
140- uint repoId ,
141- uint accountId ,
142- address admin ,
143- uint deadline ,
144- uint8 v ,
145- bytes32 r ,
146- bytes32 s
140+ uint repoId ,
141+ uint accountId ,
142+ address [] calldata admins ,
143+ uint deadline ,
144+ uint8 v ,
145+ bytes32 r ,
146+ bytes32 s
147147 ) external {
148148 Account storage account = accounts[repoId][accountId];
149149
150- require (! account.exists, Errors.REPO_ALREADY_INITIALIZED);
151- require (admin != address (0 ), Errors.INVALID_ADDRESS);
152- require (block .timestamp <= deadline, Errors.SIGNATURE_EXPIRED);
150+ require (! account.exists, Errors.REPO_ALREADY_INITIALIZED);
151+ require (admins.length > 0 , Errors.INVALID_AMOUNT);
152+ require (admins.length <= batchLimit, Errors.BATCH_LIMIT_EXCEEDED);
153+ require (block .timestamp <= deadline, Errors.SIGNATURE_EXPIRED);
153154
154155 bytes32 digest = keccak256 (
155156 abi.encodePacked (
@@ -159,7 +160,7 @@ contract Escrow is Owned, IEscrow {
159160 SET_ADMIN_TYPEHASH,
160161 repoId,
161162 accountId,
162- admin ,
163+ keccak256 ( abi.encode (admins)) ,
163164 ownerNonce,
164165 deadline
165166 ))
@@ -169,8 +170,12 @@ contract Escrow is Owned, IEscrow {
169170
170171 ownerNonce++ ;
171172 account.exists = true ;
172- account.admin = admin;
173- emit AdminSet (repoId, accountId, address (0 ), admin);
173+
174+ for (uint i; i < admins.length ; ++ i) {
175+ require (admins[i] != address (0 ), Errors.INVALID_ADDRESS);
176+ account.admins.add (admins[i]);
177+ emit AdminSet (repoId, accountId, address (0 ), admins[i]);
178+ }
174179 }
175180
176181 /* -------------------------------------------------------------------------- */
@@ -209,8 +214,8 @@ contract Escrow is Owned, IEscrow {
209214
210215 Account storage account = accounts[repoId][accountId];
211216
212- bool isAdmin = msg .sender == account.admin ;
213- bool isDistributor = account.distributors[ msg .sender ] ;
217+ bool isAdmin = account.admins. contains ( msg .sender ) ;
218+ bool isDistributor = account.distributors. contains ( msg .sender ) ;
214219 require (isAdmin || isDistributor, Errors.NOT_AUTHORIZED_DISTRIBUTOR);
215220
216221 distributionIds = new uint [](_distributions.length );
@@ -421,7 +426,7 @@ contract Escrow is Owned, IEscrow {
421426 require (distributionIds.length <= batchLimit, Errors.BATCH_LIMIT_EXCEEDED);
422427
423428 for (uint i; i < distributionIds.length ; ++ i) {
424- uint distributionId = distributionIds[i];
429+ uint distributionId = distributionIds[i];
425430 Distribution storage distribution = distributions[distributionId];
426431
427432 require (distribution.exists, Errors.INVALID_DISTRIBUTION_ID);
@@ -482,18 +487,44 @@ contract Escrow is Owned, IEscrow {
482487 /* -------------------------------------------------------------------------- */
483488 /* ONLY REPO ADMIN */
484489 /* -------------------------------------------------------------------------- */
485- function transferRepoAdmin (uint repoId , uint accountId , address newAdmin )
490+ function addAdmins (uint repoId , uint accountId , address [] calldata admins )
486491 external
487492 onlyRepoAdmin (repoId, accountId)
488493 {
489- require (newAdmin != address (0 ), Errors.INVALID_ADDRESS);
494+ require (admins.length > 0 , Errors.INVALID_AMOUNT);
495+ require (admins.length <= batchLimit, Errors.BATCH_LIMIT_EXCEEDED);
496+
497+ Account storage account = accounts[repoId][accountId];
498+ for (uint i; i < admins.length ; ++ i) {
499+ address admin = admins[i];
500+ require (admin != address (0 ), Errors.INVALID_ADDRESS);
501+ if (account.admins.add (admin)) {
502+ emit AdminSet (repoId, accountId, address (0 ), admin);
503+ }
504+ }
505+ }
490506
491- address oldAdmin = accounts[repoId][accountId].admin;
492- accounts[repoId][accountId].admin = newAdmin;
493- emit RepoAdminChanged (repoId, oldAdmin, newAdmin);
507+ function removeAdmins (uint repoId , uint accountId , address [] calldata admins )
508+ external
509+ onlyRepoAdmin (repoId, accountId)
510+ {
511+ require (admins.length > 0 , Errors.INVALID_AMOUNT);
512+ require (admins.length <= batchLimit, Errors.BATCH_LIMIT_EXCEEDED);
513+
514+ Account storage account = accounts[repoId][accountId];
515+
516+ // Ensure we don't remove all admins
517+ require (account.admins.length () > admins.length , Errors.CANNOT_REMOVE_ALL_ADMINS);
518+
519+ for (uint i; i < admins.length ; ++ i) {
520+ address admin = admins[i];
521+ if (account.admins.remove (admin)) {
522+ emit RepoAdminChanged (repoId, admin, address (0 ));
523+ }
524+ }
494525 }
495526
496- function addDistributor (uint repoId , uint accountId , address [] calldata distributors )
527+ function addDistributors (uint repoId , uint accountId , address [] calldata distributors )
497528 external
498529 onlyRepoAdmin (repoId, accountId)
499530 {
@@ -503,14 +534,14 @@ contract Escrow is Owned, IEscrow {
503534 for (uint i; i < distributors.length ; ++ i) {
504535 address distributor = distributors[i];
505536 require (distributor != address (0 ), Errors.INVALID_ADDRESS);
506- if (! account.distributors[ distributor] ) {
507- account.distributors[ distributor] = true ;
537+ if (! account.distributors. contains ( distributor) ) {
538+ account.distributors. add ( distributor) ;
508539 emit AddedDistributor (repoId, accountId, distributor);
509540 }
510541 }
511542 }
512543
513- function removeDistributor (uint repoId , uint accountId , address [] calldata distributors )
544+ function removeDistributors (uint repoId , uint accountId , address [] calldata distributors )
514545 external
515546 onlyRepoAdmin (repoId, accountId)
516547 {
@@ -519,8 +550,7 @@ contract Escrow is Owned, IEscrow {
519550 Account storage account = accounts[repoId][accountId];
520551 for (uint i; i < distributors.length ; ++ i) {
521552 address distributor = distributors[i];
522- if (account.distributors[distributor]) {
523- account.distributors[distributor] = false ;
553+ if (account.distributors.remove (distributor)) {
524554 emit RemovedDistributor (repoId, accountId, distributor);
525555 }
526556 }
@@ -547,28 +577,54 @@ contract Escrow is Owned, IEscrow {
547577 /* -------------------------------------------------------------------------- */
548578 /* GETTERS */
549579 /* -------------------------------------------------------------------------- */
550- function getAccountAdmin (uint repoId , uint accountId )
580+ function getAllAdmins (uint repoId , uint accountId )
551581 external
552582 view
553- returns (address )
583+ returns (address [] memory admins )
554584 {
555- return accounts[repoId][accountId].admin;
585+ EnumerableSet.AddressSet storage adminSet = accounts[repoId][accountId].admins;
586+ uint len = adminSet.length ();
587+ admins = new address [](len);
588+ for (uint i; i < len; ++ i) {
589+ admins[i] = adminSet.at (i);
590+ }
591+ }
592+
593+ function getIsAuthorizedAdmin (uint repoId , uint accountId , address admin )
594+ external
595+ view
596+ returns (bool )
597+ {
598+ return accounts[repoId][accountId].admins.contains (admin);
556599 }
557600
558601 function getIsAuthorizedDistributor (uint repoId , uint accountId , address distributor )
559602 external
560603 view
561604 returns (bool )
562605 {
563- return accounts[repoId][accountId].distributors[ distributor] ;
606+ return accounts[repoId][accountId].distributors. contains ( distributor) ;
564607 }
565608
566609 function canDistribute (uint repoId , uint accountId , address caller )
567610 external
568611 view
569612 returns (bool )
570613 {
571- return caller == accounts[repoId][accountId].admin || accounts[repoId][accountId].distributors[caller];
614+ return accounts[repoId][accountId].admins.contains (caller) || accounts[repoId][accountId].distributors.contains (caller);
615+ }
616+
617+ function getAllDistributors (uint repoId , uint accountId )
618+ external
619+ view
620+ returns (address [] memory distributors )
621+ {
622+ EnumerableSet.AddressSet storage distributorSet = accounts[repoId][accountId].distributors;
623+ uint len = distributorSet.length ();
624+ distributors = new address [](len);
625+ for (uint i; i < len; ++ i) {
626+ distributors[i] = distributorSet.at (i);
627+ }
572628 }
573629
574630 function getAccountBalance (uint repoId , uint accountId , address token )
@@ -587,6 +643,14 @@ contract Escrow is Owned, IEscrow {
587643 return accounts[repoId][accountId].hasDistributions;
588644 }
589645
646+ function getAccountExists (uint repoId , uint accountId )
647+ external
648+ view
649+ returns (bool )
650+ {
651+ return accounts[repoId][accountId].exists;
652+ }
653+
590654 function getDistribution (uint distributionId )
591655 external
592656 view
0 commit comments