diff --git a/src/contracts/access-control/StatelessOwnable.sol b/src/contracts/access-control/StatelessOwnable.sol new file mode 100644 index 0000000..23a19b1 --- /dev/null +++ b/src/contracts/access-control/StatelessOwnable.sol @@ -0,0 +1,41 @@ +// SPDX-License-Identifier: MIT +// OpenZeppelin Contracts (last updated v4.7.0) (access/Ownable.sol) +// From commit https://github.com/OpenZeppelin/openzeppelin-contracts/commit/8b778fa20d6d76340c5fac1ed66c80273f05b95a +// @dev modified version without storage that is intended to be anchored to a ACL +pragma solidity ^0.8.0; + +import '../oz-common/Context.sol'; + +/** + * @dev Contract module which provides a basic access control mechanism, where + * there is an account (an owner) that can be granted exclusive access to + * specific functions. + * + * By default, the owner account will be the one that deploys the contract. This + * can later be changed with {transferOwnership}. + * + * This module is used through inheritance. It will make available the modifier + * `onlyOwner`, which can be applied to your functions to restrict their use to + * the owner. + */ +abstract contract StatelessOwnable is Context { + /** + * @dev Throws if called by any account other than the owner. + */ + modifier onlyOwner() { + _checkOwner(); + _; + } + + /** + * @dev Returns the address of the current owner. + */ + function owner() public view virtual returns (address); + + /** + * @dev Throws if the sender is not the owner. + */ + function _checkOwner() internal view virtual { + require(owner() == _msgSender(), 'Ownable: caller is not the owner'); + } +} diff --git a/src/contracts/access-control/StatelessOwnableWithGuardian.sol b/src/contracts/access-control/StatelessOwnableWithGuardian.sol new file mode 100644 index 0000000..5bc0db8 --- /dev/null +++ b/src/contracts/access-control/StatelessOwnableWithGuardian.sol @@ -0,0 +1,29 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0; + +import {IStatelessOwnableWithGuardian} from './interfaces/IStatelessOwnableWithGuardian.sol'; +import {StatelessOwnable} from './StatelessOwnable.sol'; + +abstract contract StatelessOwnableWithGuardian is StatelessOwnable, IStatelessOwnableWithGuardian { + modifier onlyGuardian() { + _checkGuardian(); + _; + } + + modifier onlyOwnerOrGuardian() { + _checkOwnerOrGuardian(); + _; + } + + /// @inheritdoc IStatelessOwnableWithGuardian + function guardian() public view virtual returns (address); + + function _checkGuardian() internal view { + if (guardian() != _msgSender()) revert OnlyGuardianInvalidCaller(_msgSender()); + } + + function _checkOwnerOrGuardian() internal view { + if (_msgSender() != owner() && _msgSender() != guardian()) + revert OnlyGuardianOrOwnerInvalidCaller(_msgSender()); + } +} diff --git a/src/contracts/access-control/UpgradableOwnableWithGuardian.sol b/src/contracts/access-control/UpgradeableOwnableWithGuardian.sol similarity index 96% rename from src/contracts/access-control/UpgradableOwnableWithGuardian.sol rename to src/contracts/access-control/UpgradeableOwnableWithGuardian.sol index 0b4eb1b..a9ae741 100644 --- a/src/contracts/access-control/UpgradableOwnableWithGuardian.sol +++ b/src/contracts/access-control/UpgradeableOwnableWithGuardian.sol @@ -8,7 +8,7 @@ import {IWithGuardian} from './interfaces/IWithGuardian.sol'; * Forked version of https://github.com/bgd-labs/solidity-utils/blob/main/src/contracts/access-control/OwnableWithGuardian.sol * Relying on UpgradableOwnable & moving the storage to 7201 */ -abstract contract UpgradableOwnableWithGuardian is OwnableUpgradeable, IWithGuardian { +abstract contract UpgradeableOwnableWithGuardian is OwnableUpgradeable, IWithGuardian { /// @custom:storage-location erc7201:aave.storage.OwnableWithGuardian struct OwnableWithGuardian { address _guardian; diff --git a/src/contracts/access-control/interfaces/IStatelessOwnableWithGuardian.sol b/src/contracts/access-control/interfaces/IStatelessOwnableWithGuardian.sol new file mode 100644 index 0000000..ec20d30 --- /dev/null +++ b/src/contracts/access-control/interfaces/IStatelessOwnableWithGuardian.sol @@ -0,0 +1,19 @@ +// SPDX-License-Identifier: MIT +pragma solidity >=0.7.0; + +interface IStatelessOwnableWithGuardian { + /** + * @dev The caller account is not authorized to perform an operation. + */ + error OnlyGuardianInvalidCaller(address account); + + /** + * @dev The caller account is not authorized to perform an operation. + */ + error OnlyGuardianOrOwnerInvalidCaller(address account); + + /** + * @dev get guardian address; + */ + function guardian() external view returns (address); +} diff --git a/test/StatelessOwnableWithGuardian.t.sol b/test/StatelessOwnableWithGuardian.t.sol new file mode 100644 index 0000000..9f6408b --- /dev/null +++ b/test/StatelessOwnableWithGuardian.t.sol @@ -0,0 +1,64 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.20; + +import 'forge-std/Test.sol'; +import {IStatelessOwnableWithGuardian} from '../src/contracts/access-control/interfaces/IStatelessOwnableWithGuardian.sol'; +import {StatelessOwnableWithGuardian} from '../src/contracts/access-control/StatelessOwnableWithGuardian.sol'; + +contract ImplStatlessOwnableWithGuardian is StatelessOwnableWithGuardian { + address private _owner; + address private _guardian; + + constructor(address owner, address guardian) { + _owner = owner; + _guardian = guardian; + } + + function owner() public view override returns (address) { + return _owner; + } + + function guardian() public view override returns (address) { + return _guardian; + } + + function mock_onlyGuardian() external onlyGuardian {} + + function mock_onlyOwnerOrGuardian() external onlyOwnerOrGuardian {} +} + +contract TestOfUpgradableOwnableWithGuardian is Test { + ImplStatlessOwnableWithGuardian public withGuardian; + + address owner = address(0x4); + address guardian = address(0x8); + + function setUp() public { + withGuardian = new ImplStatlessOwnableWithGuardian(owner, guardian); + } + + function test_getters() external { + assertEq(withGuardian.owner(), owner); + assertEq(withGuardian.guardian(), guardian); + } + + function test_onlyGuardian() external { + vm.expectRevert( + abi.encodeWithSelector( + IStatelessOwnableWithGuardian.OnlyGuardianInvalidCaller.selector, + address(this) + ) + ); + withGuardian.mock_onlyGuardian(); + } + + function test_onlyOwnerOrGuardian() external { + vm.expectRevert( + abi.encodeWithSelector( + IStatelessOwnableWithGuardian.OnlyGuardianOrOwnerInvalidCaller.selector, + address(this) + ) + ); + withGuardian.mock_onlyOwnerOrGuardian(); + } +} diff --git a/test/UpgradableOwnableWithGuardian.t copy.sol b/test/UpgradeableOwnableWithGuardian.sol similarity index 78% rename from test/UpgradableOwnableWithGuardian.t copy.sol rename to test/UpgradeableOwnableWithGuardian.sol index 83e1532..0c666b8 100644 --- a/test/UpgradableOwnableWithGuardian.t copy.sol +++ b/test/UpgradeableOwnableWithGuardian.sol @@ -2,9 +2,9 @@ pragma solidity ^0.8.20; import 'forge-std/Test.sol'; -import {UpgradableOwnableWithGuardian} from '../src/contracts/access-control/UpgradableOwnableWithGuardian.sol'; +import {UpgradeableOwnableWithGuardian} from '../src/contracts/access-control/UpgradeableOwnableWithGuardian.sol'; -contract ImplOwnableWithGuardian is UpgradableOwnableWithGuardian { +contract ImplOwnableWithGuardian is UpgradeableOwnableWithGuardian { function initialize(address owner, address guardian) public initializer { __Ownable_init(owner); __Ownable_With_Guardian_init(guardian); @@ -16,7 +16,7 @@ contract ImplOwnableWithGuardian is UpgradableOwnableWithGuardian { } contract TestOfUpgradableOwnableWithGuardian is Test { - UpgradableOwnableWithGuardian public withGuardian; + UpgradeableOwnableWithGuardian public withGuardian; address owner = address(0x4); address guardian = address(0x8); @@ -34,7 +34,7 @@ contract TestOfUpgradableOwnableWithGuardian is Test { function test_onlyGuardian() external { vm.expectRevert( abi.encodeWithSelector( - UpgradableOwnableWithGuardian.OnlyGuardianInvalidCaller.selector, + UpgradeableOwnableWithGuardian.OnlyGuardianInvalidCaller.selector, address(this) ) ); @@ -44,7 +44,7 @@ contract TestOfUpgradableOwnableWithGuardian is Test { function test_onlyOwnerOrGuardian() external { vm.expectRevert( abi.encodeWithSelector( - UpgradableOwnableWithGuardian.OnlyGuardianOrOwnerInvalidCaller.selector, + UpgradeableOwnableWithGuardian.OnlyGuardianOrOwnerInvalidCaller.selector, address(this) ) ); @@ -67,7 +67,7 @@ contract TestOfUpgradableOwnableWithGuardian is Test { vm.prank(eoa); vm.expectRevert( abi.encodeWithSelector( - UpgradableOwnableWithGuardian.OnlyGuardianOrOwnerInvalidCaller.selector, + UpgradeableOwnableWithGuardian.OnlyGuardianOrOwnerInvalidCaller.selector, eoa ) );