Skip to content

Commit

Permalink
AuthorizedCallers shared contract (#13625)
Browse files Browse the repository at this point in the history
* feat: AuthorizedCallers shared contract

* chore: update geth wrappers

* chore: update imported oz version
  • Loading branch information
RayXpub authored Jun 21, 2024
1 parent d79c921 commit c462111
Show file tree
Hide file tree
Showing 3 changed files with 277 additions and 0 deletions.
9 changes: 9 additions & 0 deletions contracts/gas-snapshots/shared.gas-snapshot
Original file line number Diff line number Diff line change
@@ -1,3 +1,12 @@
AuthorizedCallers_applyAuthorizedCallerUpdates:test_AddAndRemove_Success() (gas: 125205)
AuthorizedCallers_applyAuthorizedCallerUpdates:test_OnlyAdd_Success() (gas: 133100)
AuthorizedCallers_applyAuthorizedCallerUpdates:test_OnlyCallableByOwner_Revert() (gas: 12350)
AuthorizedCallers_applyAuthorizedCallerUpdates:test_OnlyRemove_Success() (gas: 45064)
AuthorizedCallers_applyAuthorizedCallerUpdates:test_RemoveThenAdd_Success() (gas: 57241)
AuthorizedCallers_applyAuthorizedCallerUpdates:test_SkipRemove_Success() (gas: 32121)
AuthorizedCallers_applyAuthorizedCallerUpdates:test_ZeroAddressNotAllowed_Revert() (gas: 64473)
AuthorizedCallers_constructor:test_ZeroAddressNotAllowed_Revert() (gas: 64473)
AuthorizedCallers_constructor:test_constructor_Success() (gas: 720513)
BurnMintERC677_approve:testApproveSuccess() (gas: 55512)
BurnMintERC677_approve:testInvalidAddressReverts() (gas: 10663)
BurnMintERC677_burn:testBasicBurnSuccess() (gas: 173939)
Expand Down
82 changes: 82 additions & 0 deletions contracts/src/v0.8/shared/access/AuthorizedCallers.sol
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 contracts/src/v0.8/shared/test/access/AuthorizedCallers.t.sol
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);
}
}

0 comments on commit c462111

Please sign in to comment.