Skip to content

Commit

Permalink
build: granular roles
Browse files Browse the repository at this point in the history
  • Loading branch information
Schlagonia committed Jan 27, 2024
1 parent 3bcee4d commit 8ae044e
Show file tree
Hide file tree
Showing 5 changed files with 118 additions and 6 deletions.
15 changes: 12 additions & 3 deletions src/BaseStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,11 @@ abstract contract BaseStrategy {
* @dev Use to assure that the call is coming from the strategies management.
*/
modifier onlyManagement() {
require(TokenizedStrategy.isManagement(msg.sender), "!management");
require(
TokenizedStrategy.isManagement(msg.sender) ||
TokenizedStrategy.isAllowed(msg.sig, msg.sender),
"!management"
);
_;
}

Expand All @@ -61,7 +65,11 @@ abstract contract BaseStrategy {
* management or the keeper.
*/
modifier onlyKeepers() {
require(TokenizedStrategy.isKeeperOrManagement(msg.sender), "!keeper");
require(
TokenizedStrategy.isKeeperOrManagement(msg.sender) ||
TokenizedStrategy.isAllowed(msg.sig, msg.sender),
"!keeper"
);
_;
}

Expand All @@ -71,7 +79,8 @@ abstract contract BaseStrategy {
*/
modifier onlyEmergencyAuthorized() {
require(
TokenizedStrategy.isEmergencyAuthorized(msg.sender),
TokenizedStrategy.isEmergencyAuthorized(msg.sender) ||
TokenizedStrategy.isAllowed(msg.sig, msg.sender),
"!emergency authorized"
);
_;
Expand Down
57 changes: 54 additions & 3 deletions src/TokenizedStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,15 @@ contract TokenizedStrategy {
*/
event UpdateProfitMaxUnlockTime(uint256 newProfitMaxUnlockTime);

/**
* @notice Emitted when the a `sender`s status for a certain `selector` is updated.
*/
event UpdateAllowed(
bytes4 indexed selector,
address indexed sender,
bool indexed allowed
);

/**
* @notice Emitted when a strategy is shutdown.
*/
Expand Down Expand Up @@ -242,6 +251,9 @@ contract TokenizedStrategy {
address keeper; // Address given permission to call {report} and {tend}.
address pendingManagement; // Address that is pending to take over `management`.
address emergencyAdmin; // Address to act in emergencies as well as `management`.
mapping(bytes4 => mapping(address => bool)) allowed; // Mapping from a function selector to if an address can call it.

// Strategy status checks.
bool entered; // Bool to prevent reentrancy.
bool shutdown; // Bool that can be used to stop deposits into the strategy.
}
Expand All @@ -254,7 +266,10 @@ contract TokenizedStrategy {
* @dev Require that the call is coming from the strategies management.
*/
modifier onlyManagement() {
require(isManagement(msg.sender), "!management");
require(
isManagement(msg.sender) || isAllowed(msg.sig, msg.sender),
"!management"
);
_;
}

Expand All @@ -263,7 +278,10 @@ contract TokenizedStrategy {
* management or the keeper.
*/
modifier onlyKeepers() {
require(isKeeperOrManagement(msg.sender), "!keeper");
require(
isKeeperOrManagement(msg.sender) || isAllowed(msg.sig, msg.sender),
"!keeper"
);
_;
}

Expand All @@ -272,7 +290,10 @@ contract TokenizedStrategy {
* management or the emergency admin.
*/
modifier onlyEmergencyAuthorized() {
require(isEmergencyAuthorized(msg.sender), "!emergency authorized");
require(
isEmergencyAuthorized(msg.sender) || isAllowed(msg.sig, msg.sender),
"!emergency authorized"
);
_;
}

Expand Down Expand Up @@ -1481,6 +1502,26 @@ contract TokenizedStrategy {
return _convertToAssets(S, 10 ** S.decimals);
}

/**
* @notice To check if a sender was given the ability to call
* as specific function based on its selector.
* @dev Is left public so that it can be used by the Strategy.
*
* When the Strategy calls this the msg.sender would be the
* address of the strategy so we need to specify the sender.
*
* Will return `true` if the check passed.
*
* @param _selector The functions selector.
* @param _sender The original msg.sender.
*/
function isAllowed(
bytes4 _selector,
address _sender
) public view returns (bool) {
return _strategyStorage().allowed[_selector][_sender];
}

/**
* @notice To check if the strategy has been shutdown.
* @return . Whether or not the strategy is shutdown.
Expand Down Expand Up @@ -1619,6 +1660,16 @@ contract TokenizedStrategy {
emit UpdateProfitMaxUnlockTime(_profitMaxUnlockTime);
}

function setAllowed(
bytes4 _selector,
address _sender,
bool _allowed
) external onlyManagement {
_strategyStorage().allowed[_selector][_sender] = _allowed;

emit UpdateAllowed(_selector, _sender, _allowed);
}

/*//////////////////////////////////////////////////////////////
ERC20 METHODS
//////////////////////////////////////////////////////////////*/
Expand Down
6 changes: 6 additions & 0 deletions src/interfaces/IEvents.sol
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ interface IEvents {
*/
event UpdateProfitMaxUnlockTime(uint256 newProfitMaxUnlockTime);

event UpdateAllowed(
bytes4 indexed selector,
address indexed sender,
bool indexed allowed
);

/**
* @notice Emitted when a strategy is shutdown.
*/
Expand Down
11 changes: 11 additions & 0 deletions src/interfaces/ITokenizedStrategy.sol
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,11 @@ interface ITokenizedStrategy is IERC4626, IERC20Permit {
address _sender
) external view returns (bool);

function isAllowed(
bytes4 _selector,
address _sender
) external view returns (bool);

/*//////////////////////////////////////////////////////////////
KEEPERS FUNCTIONS
//////////////////////////////////////////////////////////////*/
Expand Down Expand Up @@ -148,6 +153,12 @@ interface ITokenizedStrategy is IERC4626, IERC20Permit {

function setProfitMaxUnlockTime(uint256 _profitMaxUnlockTime) external;

function setAllowed(
bytes4 _selector,
address _sender,
bool _allowed
) external;

function shutdownStrategy() external;

function emergencyWithdraw(uint256 _amount) external;
Expand Down
35 changes: 35 additions & 0 deletions src/test/AccessControl.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,41 @@ contract AccessControlTest is Setup {
assertEq(strategy.profitMaxUnlockTime(), amount);
}

function test_setAllowed(address _toAllow) public {
vm.assume(_toAllow != management && _toAllow != keeper);

bytes4 _report = tokenizedStrategy.report.selector;

assertTrue(!strategy.isAllowed(_report, _toAllow));

vm.expectRevert("!keeper");
vm.prank(_toAllow);
strategy.report();

vm.expectEmit(true, true, true, true, address(strategy));
emit UpdateAllowed(_report, _toAllow, true);

vm.prank(management);
strategy.setAllowed(_report, _toAllow, true);

assertTrue(strategy.isAllowed(_report, _toAllow));

vm.prank(_toAllow);
strategy.report();

vm.expectEmit(true, true, true, true, address(strategy));
emit UpdateAllowed(_report, _toAllow, false);

vm.prank(management);
strategy.setAllowed(_report, _toAllow, false);

assertTrue(!strategy.isAllowed(_report, _toAllow));

vm.expectRevert("!keeper");
vm.prank(_toAllow);
strategy.report();
}

function test_shutdown() public {
assertTrue(!strategy.isShutdown());

Expand Down

0 comments on commit 8ae044e

Please sign in to comment.