Skip to content

Commit

Permalink
feat: add ownable-with-guardian
Browse files Browse the repository at this point in the history
  • Loading branch information
sakulstra committed Jul 17, 2024
1 parent 7a7548c commit 20fd662
Show file tree
Hide file tree
Showing 6 changed files with 169 additions and 36 deletions.
3 changes: 3 additions & 0 deletions .gitmodules
Original file line number Diff line number Diff line change
@@ -1,3 +1,6 @@
[submodule "lib/forge-std"]
path = lib/forge-std
url = https://github.com/foundry-rs/forge-std
[submodule "lib/openzeppelin-contracts-upgradeable"]
path = lib/openzeppelin-contracts-upgradeable
url = https://github.com/OpenZeppelin/openzeppelin-contracts-upgradeable
68 changes: 33 additions & 35 deletions foundry.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,43 +3,41 @@ src = 'src'
tests = 'tests'
out = 'out'
libs = ['lib']
solc = "0.8.14"
remappings = [
]
remappings = []

# See more config options https://github.com/gakonst/foundry/tree/master/config
[rpc_endpoints]
ethereum="${RPC_MAINNET}"
ethereum-testnet="${RPC_MAINNET_TESTNET}"
polygon="${RPC_POLYGON}"
polygon-testnet="${RPC_POLYGON_TESTNET}"
arbitrum="${RPC_ARBITRUM}"
arbitrum-testnet="${RPC_ARBITRUM_TESTNET}"
metis="${RPC_METIS}"
metis-testnet="${RPC_METIS_TESTNET}"
avalanche="${RPC_AVALANCHE}"
avalanche-testnet="${RPC_AVALANCHE_TESTNET}"
optimism="${RPC_OPTIMISM}"
optimism-testnet="${RPC_OPTIMISM_TESTNET}"
fantom="${RPC_FANTOM}"
fantom-testnet="${RPC_FANTOM_TESTNET}"
binance="${RPC_BINANCE}"
binance-testnet="${RPC_BINANCE_TESTNET}"
ethereum = "${RPC_MAINNET}"
ethereum-testnet = "${RPC_MAINNET_TESTNET}"
polygon = "${RPC_POLYGON}"
polygon-testnet = "${RPC_POLYGON_TESTNET}"
arbitrum = "${RPC_ARBITRUM}"
arbitrum-testnet = "${RPC_ARBITRUM_TESTNET}"
metis = "${RPC_METIS}"
metis-testnet = "${RPC_METIS_TESTNET}"
avalanche = "${RPC_AVALANCHE}"
avalanche-testnet = "${RPC_AVALANCHE_TESTNET}"
optimism = "${RPC_OPTIMISM}"
optimism-testnet = "${RPC_OPTIMISM_TESTNET}"
fantom = "${RPC_FANTOM}"
fantom-testnet = "${RPC_FANTOM_TESTNET}"
binance = "${RPC_BINANCE}"
binance-testnet = "${RPC_BINANCE_TESTNET}"

[etherscan]
ethereum={key="${ETHERSCAN_API_KEY_MAINNET}", chain=1 }
ethereum-testnet={key="${ETHERSCAN_API_KEY_MAINNET}",chain=1}
optimism={key="${ETHERSCAN_API_KEY_OPTIMISM}",chain=10}
optimism-testnet={key="${ETHERSCAN_API_KEY_OPTIMISM}",chain=10}
avalanche={key="${ETHERSCAN_API_KEY_AVALANCHE}",chain=43114}
avalanche-testnet={key="${ETHERSCAN_API_KEY_AVALANCHE}",chain=43114}
polygon={key="${ETHERSCAN_API_KEY_POLYGON}",chain=137}
polygon-testnet={key="${ETHERSCAN_API_KEY_POLYGON}",chain=137}
arbitrum={key="${ETHERSCAN_API_KEY_ARBITRUM}",chain=42161}
arbitrum-testnet={key="${ETHERSCAN_API_KEY_ARBITRUM}",chain=42161}
metis={ key="any", chain=1088, url='https://andromeda-explorer.metis.io/' }
metis-testnet={ key="any", chain=599, url='https://goerli.explorer.metisdevops.link/' }
fantom={key="${ETHERSCAN_API_KEY_FANTOM}",chain=250}
fantom-testnet={key="${ETHERSCAN_API_KEY_FANTOM}",chain=250}
binance={key="${ETHERSCAN_API_KEY_BINANCE}",chain=56}
binance-testnet={key="${ETHERSCAN_API_KEY_BINANCE}",chain=56}
ethereum = { key = "${ETHERSCAN_API_KEY_MAINNET}", chain = 1 }
ethereum-testnet = { key = "${ETHERSCAN_API_KEY_MAINNET}", chain = 1 }
optimism = { key = "${ETHERSCAN_API_KEY_OPTIMISM}", chain = 10 }
optimism-testnet = { key = "${ETHERSCAN_API_KEY_OPTIMISM}", chain = 10 }
avalanche = { key = "${ETHERSCAN_API_KEY_AVALANCHE}", chain = 43114 }
avalanche-testnet = { key = "${ETHERSCAN_API_KEY_AVALANCHE}", chain = 43114 }
polygon = { key = "${ETHERSCAN_API_KEY_POLYGON}", chain = 137 }
polygon-testnet = { key = "${ETHERSCAN_API_KEY_POLYGON}", chain = 137 }
arbitrum = { key = "${ETHERSCAN_API_KEY_ARBITRUM}", chain = 42161 }
arbitrum-testnet = { key = "${ETHERSCAN_API_KEY_ARBITRUM}", chain = 42161 }
metis = { key = "any", chain = 1088, url = 'https://andromeda-explorer.metis.io/' }
metis-testnet = { key = "any", chain = 599, url = 'https://goerli.explorer.metisdevops.link/' }
fantom = { key = "${ETHERSCAN_API_KEY_FANTOM}", chain = 250 }
fantom-testnet = { key = "${ETHERSCAN_API_KEY_FANTOM}", chain = 250 }
binance = { key = "${ETHERSCAN_API_KEY_BINANCE}", chain = 56 }
binance-testnet = { key = "${ETHERSCAN_API_KEY_BINANCE}", chain = 56 }
1 change: 1 addition & 0 deletions lib/openzeppelin-contracts-upgradeable
72 changes: 72 additions & 0 deletions src/contracts/access-control/UpgradableOwnableWithGuardian.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import {OwnableUpgradeable} from 'openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol';
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 {
/// @custom:storage-location erc7201:aave.storage.OwnableWithGuardian
struct OwnableWithGuardian {
address _guardian;
}

// keccak256(abi.encode(uint256(keccak256("aave.storage.OwnableWithGuardian")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant OwnableWithGuardianStorageLocation =
0xdc8016945fab92f4608d8f23802ef36d865b35bd839402e24dec05cd76049e00;

function _getOwnableWithGuardianStorage() private pure returns (OwnableWithGuardian storage $) {
assembly {
$.slot := OwnableWithGuardianStorageLocation
}
}

/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
function __Ownable_With_Guardian_init(address initialGuardian) internal onlyInitializing {
_updateGuardian(initialGuardian);
}

modifier onlyGuardian() {
_checkGuardian();
_;
}

modifier onlyOwnerOrGuardian() {
_checkOwnerOrGuardian();
_;
}

function guardian() public view override returns (address) {
OwnableWithGuardian storage $ = _getOwnableWithGuardianStorage();
return $._guardian;
}

/// @inheritdoc IWithGuardian
function updateGuardian(address newGuardian) external override onlyOwnerOrGuardian {
_updateGuardian(newGuardian);
}

/**
* @dev method to update the guardian
* @param newGuardian the new guardian address
*/
function _updateGuardian(address newGuardian) internal {
OwnableWithGuardian storage $ = _getOwnableWithGuardianStorage();
address oldGuardian = $._guardian;
$._guardian = newGuardian;
emit GuardianUpdated(oldGuardian, newGuardian);
}

function _checkGuardian() internal view {
require(guardian() == _msgSender(), 'ONLY_BY_GUARDIAN');
}

function _checkOwnerOrGuardian() internal view {
require(_msgSender() == owner() || _msgSender() == guardian(), 'ONLY_BY_OWNER_OR_GUARDIAN');
}
}
59 changes: 59 additions & 0 deletions test/UpgradableOwnableWithGuardian.t copy.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;

import 'forge-std/Test.sol';
import {UpgradableOwnableWithGuardian} from '../src/contracts/access-control/UpgradableOwnableWithGuardian.sol';

contract ImplOwnableWithGuardian is UpgradableOwnableWithGuardian {
function initialize(address owner, address guardian) public initializer {
__Ownable_init(owner);
__Ownable_With_Guardian_init(guardian);
}

function mock_onlyGuardian() external onlyGuardian {}

function mock_onlyOwnerOrGuardian() external onlyOwnerOrGuardian {}
}

contract TestOfUpgradableOwnableWithGuardian is Test {
UpgradableOwnableWithGuardian public withGuardian;

address owner = address(0x4);
address guardian = address(0x8);

function setUp() public {
withGuardian = new ImplOwnableWithGuardian();
ImplOwnableWithGuardian(address(withGuardian)).initialize(owner, guardian);
}

function test_initializer() external {
assertEq(withGuardian.owner(), owner);
assertEq(withGuardian.guardian(), guardian);
}

function test_onlyGuardian() external {
ImplOwnableWithGuardian(address(withGuardian)).mock_onlyGuardian();
}

function test_onlyOwnerOrGuardian() external {
ImplOwnableWithGuardian(address(withGuardian)).mock_onlyOwnerOrGuardian();
}

function test_updateGuardian_guardian(address newGuardian) external {
vm.prank(guardian);
withGuardian.updateGuardian(newGuardian);
}

function test_updateGuardian_owner(address newGuardian) external {
vm.prank(owner);
withGuardian.updateGuardian(newGuardian);
}

function test_updateGuardian_eoa(address newGuardian) external {
vm.assume(newGuardian != owner && newGuardian != guardian);

vm.prank(newGuardian);
vm.expectRevert('ONLY_BY_OWNER_OR_GUARDIAN');
withGuardian.updateGuardian(newGuardian);
}
}

0 comments on commit 20fd662

Please sign in to comment.