-
Notifications
You must be signed in to change notification settings - Fork 1.7k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
AuthorizedCallers
shared contract (#13625)
* feat: AuthorizedCallers shared contract * chore: update geth wrappers * chore: update imported oz version
- Loading branch information
Showing
3 changed files
with
277 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,82 @@ | ||
// SPDX-License-Identifier: BUSL-1.1 | ||
pragma solidity ^0.8.0; | ||
|
||
import {OwnerIsCreator} from "./OwnerIsCreator.sol"; | ||
import {EnumerableSet} from "../../vendor/openzeppelin-solidity/v4.8.3/contracts/utils/structs/EnumerableSet.sol"; | ||
|
||
/// @title The AuthorizedCallers contract | ||
/// @notice A contract that manages multiple authorized callers. Enables restricting access to certain functions to a set of addresses. | ||
contract AuthorizedCallers is OwnerIsCreator { | ||
using EnumerableSet for EnumerableSet.AddressSet; | ||
|
||
event AuthorizedCallerAdded(address caller); | ||
event AuthorizedCallerRemoved(address caller); | ||
|
||
error UnauthorizedCaller(address caller); | ||
error ZeroAddressNotAllowed(); | ||
|
||
/// @notice Update args for changing the authorized callers | ||
struct AuthorizedCallerArgs { | ||
address[] addedCallers; | ||
address[] removedCallers; | ||
} | ||
|
||
/// @dev Set of authorized callers | ||
EnumerableSet.AddressSet internal s_authorizedCallers; | ||
|
||
/// @param authorizedCallers the authorized callers to set | ||
constructor(address[] memory authorizedCallers) { | ||
_applyAuthorizedCallerUpdates( | ||
AuthorizedCallerArgs({addedCallers: authorizedCallers, removedCallers: new address[](0)}) | ||
); | ||
} | ||
|
||
/// @return authorizedCallers Returns all authorized callers | ||
function getAllAuthorizedCallers() external view returns (address[] memory) { | ||
return s_authorizedCallers.values(); | ||
} | ||
|
||
/// @notice Updates the list of authorized callers | ||
/// @param authorizedCallerArgs Callers to add and remove. Removals are performed first. | ||
function applyAuthorizedCallerUpdates(AuthorizedCallerArgs memory authorizedCallerArgs) external onlyOwner { | ||
_applyAuthorizedCallerUpdates(authorizedCallerArgs); | ||
} | ||
|
||
/// @notice Updates the list of authorized callers | ||
/// @param authorizedCallerArgs Callers to add and remove. Removals are performed first. | ||
function _applyAuthorizedCallerUpdates(AuthorizedCallerArgs memory authorizedCallerArgs) internal { | ||
address[] memory removedCallers = authorizedCallerArgs.removedCallers; | ||
for (uint256 i = 0; i < removedCallers.length; ++i) { | ||
address caller = removedCallers[i]; | ||
|
||
if (s_authorizedCallers.remove(caller)) { | ||
emit AuthorizedCallerRemoved(caller); | ||
} | ||
} | ||
|
||
address[] memory addedCallers = authorizedCallerArgs.addedCallers; | ||
for (uint256 i = 0; i < addedCallers.length; ++i) { | ||
address caller = addedCallers[i]; | ||
|
||
if (caller == address(0)) { | ||
revert ZeroAddressNotAllowed(); | ||
} | ||
|
||
s_authorizedCallers.add(caller); | ||
emit AuthorizedCallerAdded(caller); | ||
} | ||
} | ||
|
||
/// @notice Checks the sender and reverts if it is anyone other than a listed authorized caller. | ||
function _validateCaller() internal view { | ||
if (!s_authorizedCallers.contains(msg.sender)) { | ||
revert UnauthorizedCaller(msg.sender); | ||
} | ||
} | ||
|
||
/// @notice Checks the sender and reverts if it is anyone other than a listed authorized caller. | ||
modifier onlyAuthorizedCallers() { | ||
_validateCaller(); | ||
_; | ||
} | ||
} |
186 changes: 186 additions & 0 deletions
186
contracts/src/v0.8/shared/test/access/AuthorizedCallers.t.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,186 @@ | ||
// SPDX-License-Identifier: MIT | ||
pragma solidity 0.8.19; | ||
|
||
import {AuthorizedCallers} from "../../access/AuthorizedCallers.sol"; | ||
import {BaseTest} from "../BaseTest.t.sol"; | ||
|
||
contract AuthorizedCallers_setup is BaseTest { | ||
address[] s_callers; | ||
|
||
AuthorizedCallers s_authorizedCallers; | ||
|
||
function setUp() public override { | ||
super.setUp(); | ||
s_callers.push(makeAddr("caller1")); | ||
s_callers.push(makeAddr("caller2")); | ||
|
||
s_authorizedCallers = new AuthorizedCallers(s_callers); | ||
} | ||
} | ||
|
||
contract AuthorizedCallers_constructor is AuthorizedCallers_setup { | ||
event AuthorizedCallerAdded(address caller); | ||
|
||
function test_constructor_Success() public { | ||
for (uint256 i = 0; i < s_callers.length; ++i) { | ||
vm.expectEmit(); | ||
emit AuthorizedCallerAdded(s_callers[i]); | ||
} | ||
|
||
s_authorizedCallers = new AuthorizedCallers(s_callers); | ||
|
||
assertEq(s_callers, s_authorizedCallers.getAllAuthorizedCallers()); | ||
} | ||
|
||
function test_ZeroAddressNotAllowed_Revert() public { | ||
s_callers[0] = address(0); | ||
|
||
vm.expectRevert(AuthorizedCallers.ZeroAddressNotAllowed.selector); | ||
|
||
new AuthorizedCallers(s_callers); | ||
} | ||
} | ||
|
||
contract AuthorizedCallers_applyAuthorizedCallerUpdates is AuthorizedCallers_setup { | ||
event AuthorizedCallerAdded(address caller); | ||
event AuthorizedCallerRemoved(address caller); | ||
|
||
function test_OnlyAdd_Success() public { | ||
address[] memory addedCallers = new address[](2); | ||
addedCallers[0] = vm.addr(3); | ||
addedCallers[1] = vm.addr(4); | ||
|
||
address[] memory removedCallers = new address[](0); | ||
|
||
assertEq(s_authorizedCallers.getAllAuthorizedCallers(), s_callers); | ||
|
||
vm.expectEmit(); | ||
emit AuthorizedCallerAdded(addedCallers[0]); | ||
vm.expectEmit(); | ||
emit AuthorizedCallerAdded(addedCallers[1]); | ||
|
||
s_authorizedCallers.applyAuthorizedCallerUpdates( | ||
AuthorizedCallers.AuthorizedCallerArgs({addedCallers: addedCallers, removedCallers: removedCallers}) | ||
); | ||
|
||
address[] memory expectedCallers = new address[](4); | ||
expectedCallers[0] = s_callers[0]; | ||
expectedCallers[1] = s_callers[1]; | ||
expectedCallers[2] = addedCallers[0]; | ||
expectedCallers[3] = addedCallers[1]; | ||
|
||
assertEq(s_authorizedCallers.getAllAuthorizedCallers(), expectedCallers); | ||
} | ||
|
||
function test_OnlyRemove_Success() public { | ||
address[] memory addedCallers = new address[](0); | ||
address[] memory removedCallers = new address[](1); | ||
removedCallers[0] = s_callers[0]; | ||
|
||
assertEq(s_authorizedCallers.getAllAuthorizedCallers(), s_callers); | ||
|
||
vm.expectEmit(); | ||
emit AuthorizedCallerRemoved(removedCallers[0]); | ||
|
||
s_authorizedCallers.applyAuthorizedCallerUpdates( | ||
AuthorizedCallers.AuthorizedCallerArgs({addedCallers: addedCallers, removedCallers: removedCallers}) | ||
); | ||
|
||
address[] memory expectedCallers = new address[](1); | ||
expectedCallers[0] = s_callers[1]; | ||
|
||
assertEq(s_authorizedCallers.getAllAuthorizedCallers(), expectedCallers); | ||
} | ||
|
||
function test_AddAndRemove_Success() public { | ||
address[] memory addedCallers = new address[](2); | ||
addedCallers[0] = address(42); | ||
addedCallers[1] = address(43); | ||
|
||
address[] memory removedCallers = new address[](1); | ||
removedCallers[0] = s_callers[0]; | ||
|
||
assertEq(s_authorizedCallers.getAllAuthorizedCallers(), s_callers); | ||
|
||
vm.expectEmit(); | ||
emit AuthorizedCallerRemoved(removedCallers[0]); | ||
vm.expectEmit(); | ||
emit AuthorizedCallerAdded(addedCallers[0]); | ||
vm.expectEmit(); | ||
emit AuthorizedCallerAdded(addedCallers[1]); | ||
|
||
s_authorizedCallers.applyAuthorizedCallerUpdates( | ||
AuthorizedCallers.AuthorizedCallerArgs({addedCallers: addedCallers, removedCallers: removedCallers}) | ||
); | ||
|
||
// Order of the set changes on removal | ||
address[] memory expectedCallers = new address[](3); | ||
expectedCallers[0] = s_callers[1]; | ||
expectedCallers[1] = addedCallers[0]; | ||
expectedCallers[2] = addedCallers[1]; | ||
|
||
assertEq(s_authorizedCallers.getAllAuthorizedCallers(), expectedCallers); | ||
} | ||
|
||
function test_RemoveThenAdd_Success() public { | ||
address[] memory addedCallers = new address[](1); | ||
addedCallers[0] = s_callers[0]; | ||
|
||
address[] memory removedCallers = new address[](1); | ||
removedCallers[0] = s_callers[0]; | ||
|
||
assertEq(s_authorizedCallers.getAllAuthorizedCallers(), s_callers); | ||
|
||
vm.expectEmit(); | ||
emit AuthorizedCallerRemoved(removedCallers[0]); | ||
|
||
vm.expectEmit(); | ||
emit AuthorizedCallerAdded(addedCallers[0]); | ||
|
||
s_authorizedCallers.applyAuthorizedCallerUpdates( | ||
AuthorizedCallers.AuthorizedCallerArgs({addedCallers: addedCallers, removedCallers: removedCallers}) | ||
); | ||
|
||
address[] memory expectedCallers = new address[](2); | ||
expectedCallers[0] = s_callers[1]; | ||
expectedCallers[1] = s_callers[0]; | ||
|
||
assertEq(s_authorizedCallers.getAllAuthorizedCallers(), expectedCallers); | ||
} | ||
|
||
function test_SkipRemove_Success() public { | ||
address[] memory addedCallers = new address[](0); | ||
|
||
address[] memory removedCallers = new address[](1); | ||
removedCallers[0] = address(42); | ||
|
||
vm.recordLogs(); | ||
s_authorizedCallers.applyAuthorizedCallerUpdates( | ||
AuthorizedCallers.AuthorizedCallerArgs({addedCallers: addedCallers, removedCallers: removedCallers}) | ||
); | ||
|
||
assertEq(s_authorizedCallers.getAllAuthorizedCallers(), s_callers); | ||
assertEq(vm.getRecordedLogs().length, 0); | ||
} | ||
|
||
function test_OnlyCallableByOwner_Revert() public { | ||
vm.stopPrank(); | ||
|
||
AuthorizedCallers.AuthorizedCallerArgs memory authorizedCallerArgs = AuthorizedCallers.AuthorizedCallerArgs({ | ||
addedCallers: new address[](0), | ||
removedCallers: new address[](0) | ||
}); | ||
|
||
vm.expectRevert("Only callable by owner"); | ||
|
||
s_authorizedCallers.applyAuthorizedCallerUpdates(authorizedCallerArgs); | ||
} | ||
|
||
function test_ZeroAddressNotAllowed_Revert() public { | ||
s_callers[0] = address(0); | ||
|
||
vm.expectRevert(AuthorizedCallers.ZeroAddressNotAllowed.selector); | ||
|
||
new AuthorizedCallers(s_callers); | ||
} | ||
} |