Skip to content

Commit 153c20d

Browse files
authored
feat(paymaster): add separate whitelistAdmin param to BondTreasuryPaymaster (#106)
The BondTreasuryPaymaster constructor previously coupled DEFAULT_ADMIN_ROLE and WHITELIST_ADMIN_ROLE to the same 'admin' address. This made it impossible to assign whitelist management to an operational key (e.g. FLEET_OPERATOR) while keeping DEFAULT_ADMIN_ROLE on a multisig. Changes: - Add 'whitelistAdmin' as the second constructor parameter - When whitelistAdmin != admin, grant WHITELIST_ADMIN_ROLE to whitelistAdmin - Update deploy script to pass fleetOperator as whitelistAdmin - Update all test instantiations and add test_separateWhitelistAdmin - Update spec doc deployment example
1 parent 242af5f commit 153c20d

4 files changed

Lines changed: 44 additions & 10 deletions

File tree

script/DeploySwarmUpgradeableZkSync.s.sol

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ contract DeploySwarmUpgradeableZkSync is Script {
107107
whitelistedUsers[0] = fleetOperator;
108108
bondTreasuryPaymaster = address(
109109
new BondTreasuryPaymaster(
110-
owner, withdrawer, whitelistedContracts, whitelistedUsers, bondToken, bondQuota, bondPeriod
110+
owner, fleetOperator, withdrawer, whitelistedContracts, whitelistedUsers, bondToken, bondQuota, bondPeriod
111111
)
112112
);
113113
console.log(" Address:", bondTreasuryPaymaster);

src/paymasters/BondTreasuryPaymaster.sol

Lines changed: 13 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -23,35 +23,41 @@ contract BondTreasuryPaymaster is WhitelistPaymaster, QuotaControl {
2323

2424
constructor(
2525
address admin,
26+
address whitelistAdmin,
2627
address withdrawer,
2728
address[] memory initialWhitelistedContracts,
2829
address[] memory initialWhitelistedUsers,
2930
address bondToken_,
3031
uint256 initialQuota,
3132
uint256 initialPeriod
3233
) WhitelistPaymaster(admin, withdrawer) QuotaControl(initialQuota, initialPeriod, admin) {
34+
if (whitelistAdmin != admin) {
35+
_grantRole(WHITELIST_ADMIN_ROLE, whitelistAdmin);
36+
}
3337
bondToken = IERC20(bondToken_);
38+
3439
uint256 n = initialWhitelistedContracts.length;
3540
for (uint256 i = 0; i < n; ++i) {
3641
isWhitelistedContract[initialWhitelistedContracts[i]] = true;
3742
}
3843
if (n > 0) {
3944
emit WhitelistedContractsAdded(initialWhitelistedContracts);
4045
}
41-
uint256 m = initialWhitelistedUsers.length;
42-
for (uint256 j = 0; j < m; ++j) {
43-
isWhitelistedUser[initialWhitelistedUsers[j]] = true;
44-
}
45-
if (m > 0) {
46-
emit WhitelistedUsersAdded(initialWhitelistedUsers);
47-
}
4846

4947
if (!isWhitelistedContract[address(this)]) {
5048
isWhitelistedContract[address(this)] = true;
5149
address[] memory selfDest = new address[](1);
5250
selfDest[0] = address(this);
5351
emit WhitelistedContractsAdded(selfDest);
5452
}
53+
54+
uint256 m = initialWhitelistedUsers.length;
55+
for (uint256 j = 0; j < m; ++j) {
56+
isWhitelistedUser[initialWhitelistedUsers[j]] = true;
57+
}
58+
if (m > 0) {
59+
emit WhitelistedUsersAdded(initialWhitelistedUsers);
60+
}
5561
}
5662

5763
/// @notice Validate whitelist + consume quota for a sponsored bond.

src/swarms/doc/spec/swarm-specification.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,7 @@ address[] memory initialUsers = new address[](1);
660660
initialUsers[0] = nodleSwarmOperator;
661661
new BondTreasuryPaymaster(
662662
admin,
663+
whitelistAdmin,
663664
withdrawer,
664665
initialContracts,
665666
initialUsers,

test/paymasters/BondTreasuryPaymaster.t.sol

Lines changed: 29 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,7 @@ contract SponsoredBondPuller {
4242
contract MockBondTreasuryPaymaster is BondTreasuryPaymaster {
4343
constructor(
4444
address admin,
45+
address whitelistAdmin,
4546
address withdrawer,
4647
address[] memory initialWhitelistedContracts,
4748
address[] memory initialWhitelistedUsers,
@@ -51,6 +52,7 @@ contract MockBondTreasuryPaymaster is BondTreasuryPaymaster {
5152
)
5253
BondTreasuryPaymaster(
5354
admin,
55+
whitelistAdmin,
5456
withdrawer,
5557
initialWhitelistedContracts,
5658
initialWhitelistedUsers,
@@ -130,6 +132,7 @@ contract BondTreasuryPaymasterTest is Test {
130132
initialUsers[1] = admin;
131133

132134
paymaster = new MockBondTreasuryPaymaster(
135+
admin,
133136
admin,
134137
withdrawer,
135138
_initialContractWhitelist(address(fleet)),
@@ -154,6 +157,25 @@ contract BondTreasuryPaymasterTest is Test {
154157
assertTrue(paymaster.hasRole(paymaster.WITHDRAWER_ROLE(), withdrawer));
155158
}
156159

160+
function test_separateWhitelistAdmin() public {
161+
address whitelistAdmin = address(0x3333);
162+
MockBondTreasuryPaymaster pm = new MockBondTreasuryPaymaster(
163+
admin,
164+
whitelistAdmin,
165+
withdrawer,
166+
_initialContractWhitelist(address(fleet)),
167+
_emptyAddresses(),
168+
address(bondToken),
169+
QUOTA,
170+
PERIOD
171+
);
172+
assertTrue(pm.hasRole(pm.DEFAULT_ADMIN_ROLE(), admin));
173+
assertTrue(pm.hasRole(pm.WHITELIST_ADMIN_ROLE(), admin));
174+
assertTrue(pm.hasRole(pm.WHITELIST_ADMIN_ROLE(), whitelistAdmin));
175+
assertTrue(pm.hasRole(pm.WITHDRAWER_ROLE(), withdrawer));
176+
assertFalse(pm.hasRole(pm.DEFAULT_ADMIN_ROLE(), whitelistAdmin));
177+
}
178+
157179
function test_immutables() public view {
158180
assertEq(address(paymaster.bondToken()), address(bondToken));
159181
}
@@ -173,6 +195,7 @@ contract BondTreasuryPaymasterTest is Test {
173195

174196
function test_constructorWithEmptyWhitelistedUsers() public {
175197
MockBondTreasuryPaymaster pm = new MockBondTreasuryPaymaster(
198+
admin,
176199
admin,
177200
withdrawer,
178201
_initialContractWhitelist(address(fleet)),
@@ -193,7 +216,7 @@ contract BondTreasuryPaymasterTest is Test {
193216
users[2] = charlie;
194217

195218
MockBondTreasuryPaymaster pm = new MockBondTreasuryPaymaster(
196-
admin, withdrawer, _initialContractWhitelist(address(fleet)), users, address(bondToken), QUOTA, PERIOD
219+
admin, admin, withdrawer, _initialContractWhitelist(address(fleet)), users, address(bondToken), QUOTA, PERIOD
197220
);
198221
assertTrue(pm.isWhitelistedUser(alice));
199222
assertTrue(pm.isWhitelistedUser(bob));
@@ -208,13 +231,14 @@ contract BondTreasuryPaymasterTest is Test {
208231
vm.expectEmit();
209232
emit WhitelistPaymaster.WhitelistedUsersAdded(users);
210233
new MockBondTreasuryPaymaster(
211-
admin, withdrawer, _initialContractWhitelist(address(fleet)), users, address(bondToken), QUOTA, PERIOD
234+
admin, admin, withdrawer, _initialContractWhitelist(address(fleet)), users, address(bondToken), QUOTA, PERIOD
212235
);
213236
}
214237

215238
function test_constructorEmptyUsersDoesNotEmitEvent() public {
216239
vm.recordLogs();
217240
new MockBondTreasuryPaymaster(
241+
admin,
218242
admin,
219243
withdrawer,
220244
_initialContractWhitelist(address(fleet)),
@@ -501,6 +525,7 @@ contract BondTreasuryPaymasterTest is Test {
501525

502526
function test_quotaTracksBaseBondNotClaimCount() public {
503527
MockBondTreasuryPaymaster tightPaymaster = new MockBondTreasuryPaymaster(
528+
admin,
504529
admin,
505530
withdrawer,
506531
_initialContractWhitelist(address(fleet)),
@@ -581,6 +606,7 @@ contract BondTreasuryPaymasterTest is Test {
581606
function test_RevertIf_constructorZeroPeriod() public {
582607
vm.expectRevert(QuotaControl.ZeroPeriod.selector);
583608
new MockBondTreasuryPaymaster(
609+
admin,
584610
admin,
585611
withdrawer,
586612
_initialContractWhitelist(address(fleet)),
@@ -594,6 +620,7 @@ contract BondTreasuryPaymasterTest is Test {
594620
function test_RevertIf_constructorTooLongPeriod() public {
595621
vm.expectRevert(QuotaControl.TooLongPeriod.selector);
596622
new MockBondTreasuryPaymaster(
623+
admin,
597624
admin,
598625
withdrawer,
599626
_initialContractWhitelist(address(fleet)),

0 commit comments

Comments
 (0)