Skip to content

Commit

Permalink
Merge pull request #21 from ferrumnet/develop
Browse files Browse the repository at this point in the history
Release
  • Loading branch information
naiemk authored Sep 24, 2024
2 parents 39fa792 + 20ec242 commit 0164fa8
Show file tree
Hide file tree
Showing 80 changed files with 4,996 additions and 6,194 deletions.
6 changes: 6 additions & 0 deletions contracts/contracts-upgradeable/Proxies.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {ERC1967Proxy} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Proxy.sol";

// Import so hardhat will compile the above contract
268 changes: 268 additions & 0 deletions contracts/contracts-upgradeable/common/FerrumAdminUpgradeable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {MultiSigCheckableUpgradeable} from "..//signature/MultiSigCheckableUpgradeable.sol";
import {IUUPS} from "../../contracts/common/IUUPS.sol";


/**
* @title FerrumAdmin
* @notice Intended to be inherited by Quantum Portal Gateway contract
*/
abstract contract FerrumAdminUpgradeable is MultiSigCheckableUpgradeable {
// All other lower level (more leniet) quorums are less in value
address public constant BETA_QUORUMID = address(1111);
address public constant PROD_QUORUMID = address(2222);
address public constant TIMELOCKED_PROD_QUORUMID = address(3333);

uint256 public constant EXPIRY_7_DAYS = 7 days;
bytes32 constant PERMIT_CALL_TYPEHASH = keccak256("PermitCall(address target,bytes data,address quorumId,bytes32 salt,uint64 expiry)");

/// @custom:storage-location erc7201:ferrum.storage.ferrumadmin.001
struct FerrumAdminStorageV001 {
uint256 timelockPeriod;
mapping(address => bool) devAccounts;
mapping(bytes32 => FerrumAdminUpgradeable.PermittedCall) permittedCalls;
mapping(bytes32 => address) minRequiredAuth;
}

// keccak256(abi.encode(uint256(keccak256("ferrum.storage.ferrumadmin.001")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant FerrumAdminStorageV001Location = 0x090b67f069e2ef623d1b1ff39d73da6c91ee47bd12c4a1d2d8651f5faf006200;

struct MinAuthSetting {
address quorumId;
address target;
bytes4 funcSelector;
}

struct PermittedCall {
bool isPermitted;
uint64 executableFrom;
uint64 expiry;
}

struct InitQuorum {
uint16 minSignatures;
address[] addresses;
}

event DevAccountAdded(address indexed account);
event DevAccountRemoved(address indexed account);
event CallPermitted(bytes32 indexed callHash, address indexed target, bytes data);
event CallExecuted(bytes32 indexed callHash, address indexed target, bytes data);

modifier onlyDevs() {
require(_getFerrumAdminStorageV001().devAccounts[msg.sender], "Only devs can execute");
_;
}

modifier onlyDevsOrAdmin() {
require(_getFerrumAdminStorageV001().devAccounts[msg.sender] || msg.sender == owner(), "Only devs or admin can add/remove dev accounts");
_;
}

function __FerrumAdmin_init(uint256 _timelockPeriod,
address initialOwner,
address initialAdmin,
string memory name,
string memory version
) internal {
__WithAdmin_init(initialOwner, initialAdmin);
__FerrumAdmin_init_unchained(_timelockPeriod);
__EIP712_init(name, version);
}

function __FerrumAdmin_init_unchained(
uint256 _timelockPeriod
) internal {
FerrumAdminStorageV001 storage $ = _getFerrumAdminStorageV001();
$.timelockPeriod = _timelockPeriod;
$.devAccounts[tx.origin] = true;
}

function permitAndExecuteCall(
address target,
bytes calldata data,
address quorumId,
bytes32 salt,
uint64 expiry,
bytes memory multiSignature
) public {
permitCall(target, data, quorumId, salt, expiry, multiSignature);
executePermittedCall(target, data, quorumId, salt, expiry);
}

function permitCall(
address target,
bytes calldata data,
address quorumId,
bytes32 salt,
uint64 expiry,
bytes memory multiSignature
) public {
FerrumAdminStorageV001 storage $ = _getFerrumAdminStorageV001();
_isValidQourumId(quorumId);
bytes32 structHash = keccak256(
abi.encode(
PERMIT_CALL_TYPEHASH,
target,
keccak256(data),
quorumId,
salt,
expiry
)
);

// Verify
require(expiry < block.timestamp + EXPIRY_7_DAYS, "Expiry too far");
_validateAuthLevel(target, data, quorumId);
verifyUniqueSaltWithQuorumId(structHash, quorumId, salt, 0, multiSignature);

// Store the permitted call
uint64 excecutableFrom = quorumId == TIMELOCKED_PROD_QUORUMID ?
uint64(block.timestamp + $.timelockPeriod) :
uint64(block.timestamp);

$.permittedCalls[structHash] = PermittedCall(true, excecutableFrom, expiry);

emit CallPermitted(structHash, target, data);
}

function executePermittedCall(
address target,
bytes calldata data,
address quorumId,
bytes32 salt,
uint64 expiry
) public onlyDevs {
FerrumAdminStorageV001 storage $ = _getFerrumAdminStorageV001();
// Hash the call details
bytes32 structHash = keccak256(
abi.encode(
PERMIT_CALL_TYPEHASH,
target,
keccak256(data),
quorumId,
salt,
expiry
)
);

// Ensure the call is permitted
require($.permittedCalls[structHash].isPermitted, "FA: not permitted");
require(block.timestamp < $.permittedCalls[structHash].expiry, "FA: expired");
require(block.timestamp >= $.permittedCalls[structHash].executableFrom, "FA: not executable yet");

// Execute the call
(bool success, ) = target.call(data);
require(success, "FA: fail");

// Clear the permitted call
delete $.permittedCalls[structHash];

emit CallExecuted(structHash, target, data);
}

function addDevAccounts(address[] memory account) public onlyDevsOrAdmin {
FerrumAdminStorageV001 storage $ = _getFerrumAdminStorageV001();
for (uint i = 0; i < account.length; i++) {
if($.devAccounts[account[i]]) continue; // Prevents redundant event emission
$.devAccounts[account[i]] = true;
emit DevAccountAdded(account[i]);
}
}

function removeDevAccounts(address[] memory account) external onlyDevsOrAdmin {
FerrumAdminStorageV001 storage $ = _getFerrumAdminStorageV001();
for (uint i = 0; i < account.length; i++) {
if(!$.devAccounts[account[i]]) continue; // As above
emit DevAccountRemoved(account[i]);
}
}

function initializeQuorum(
address quorumId,
uint64 groupId,
uint16 minSignatures,
uint8 ownerGroupId,
address[] calldata addresses
) public override onlyAdmin {
_isValidQourumId(quorumId);
_initializeQuorum(quorumId, groupId, minSignatures, ownerGroupId, addresses);
}

function setCallAuthLevels(
MinAuthSetting[] memory settings
) external onlyAdmin {
_setCallAuthLevels(settings);
}

function updateTimelockPeriod(
uint256 newPeriod
) external onlyAdmin {
FerrumAdminStorageV001 storage $ = _getFerrumAdminStorageV001();
$.timelockPeriod = newPeriod;
}

function timelockPeriod() external view returns (uint256) {
return _getFerrumAdminStorageV001().timelockPeriod;
}

function devAccounts(address account) external view returns (bool) {
return _getFerrumAdminStorageV001().devAccounts[account];
}

function permittedCalls(bytes32 structHash) external view returns (PermittedCall memory) {
return _getFerrumAdminStorageV001().permittedCalls[structHash];
}

function minRequiredAuth(address target, bytes4 funcSelector) external view returns (address) {
FerrumAdminStorageV001 storage $ = _getFerrumAdminStorageV001();
bytes32 key = _getKey(target, funcSelector);
return $.minRequiredAuth[key];
}

function _setCallAuthLevels(MinAuthSetting[] memory settings) internal {
FerrumAdminStorageV001 storage $ = _getFerrumAdminStorageV001();
for (uint i = 0; i < settings.length; i++) {
bytes32 key = _getKey(settings[i].target, settings[i].funcSelector);
$.minRequiredAuth[key] = settings[i].quorumId;
}
}

function _getKey(address qpContract, bytes4 funcSelector) private pure returns (bytes32 key) {
// Takes the shape of 0x{4byteFuncSelector}00..00{20byteQPContractAddress}
assembly {
key := or(funcSelector, qpContract)
}
}

function _validateAuthLevel(address target, bytes calldata data, address quorumId) private view {
FerrumAdminStorageV001 storage $ = _getFerrumAdminStorageV001();
bytes32 key = _getKey(target, bytes4(data));
require(uint160($.minRequiredAuth[key]) != 0, "FA: call auth not set");
require(uint160(quorumId) >= uint160($.minRequiredAuth[key]), "FA: auth");
}

function _isValidQourumId(address quorumId) private pure {
require(quorumId == BETA_QUORUMID || quorumId == PROD_QUORUMID || quorumId == TIMELOCKED_PROD_QUORUMID, "FA: invalid quorum");
}

function _getFerrumAdminStorageV001() private pure returns (FerrumAdminStorageV001 storage $) {
assembly {
$.slot := FerrumAdminStorageV001Location
}
}

function forceRemoveFromQuorum(address _address) external pure override {
revert("Not implemented");
}

function upgradeQpComponentAndCall(
address target,
address newImplementation,
bytes calldata data
) external onlyOwner {
IUUPS(target).upgradeToAndCall(newImplementation, data);
}
}
43 changes: 43 additions & 0 deletions contracts/contracts-upgradeable/common/FreezableUpgradeable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";


/**
* To freeze functionality. This is one way. Once you freeze there is no way back.
*/
abstract contract FreezableUpgradeable is OwnableUpgradeable {
/// @custom:storage-location erc7201:ferrum.storage.freezable.001
struct FreezeableStorageV001 {
bool isFrozen;
}

// keccak256(abi.encode(uint256(keccak256("ferrum.storage.freezable.001")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant FreezableStorageV001Location = 0x9c47f24f7d551dfe423cbc7cb827a9fb704975fc1181b78265684facb23b5500;

function _getFreezableStorageV001() private pure returns (FreezeableStorageV001 storage $) {
assembly {
$.slot := FreezableStorageV001Location
}
}

/**
* Freeze all the freezable functions.
* They cannot be called any more.
*/
function freeze() external onlyOwner() {
FreezeableStorageV001 storage $ = _getFreezableStorageV001();
$.isFrozen = true;
}

/**
* @dev Throws if frozen
* Only use on external functions.
*/
modifier freezable() {
FreezeableStorageV001 storage $ = _getFreezableStorageV001();
require(!$.isFrozen, "Freezable: method is frozen");
_;
}
}
36 changes: 36 additions & 0 deletions contracts/contracts-upgradeable/common/SweepableUpgradeable.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.24;

import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {WithAdminUpgradeable} from "./WithAdminUpgradeable.sol";


abstract contract SweepableUpgradeable is WithAdminUpgradeable {
using SafeERC20 for IERC20;

/// @custom:storage-location erc7201:ferrum.storage.sweepable.001
struct SweepableStorageV001 {
bool sweepFrozen;
}

// keccak256(abi.encode(uint256(keccak256("ferrum.storage.sweepable.001")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant SweepableStorageV001Location = 0x501be003b95fbbd97d429e38784e33e3e5cdfaf3ddfa33121625540b893e7d00;

function _getSweepableStorageV001() private pure returns (SweepableStorageV001 storage $) {
assembly {
$.slot := SweepableStorageV001Location
}
}

function freezeSweep() external onlyAdmin {
SweepableStorageV001 storage $ = _getSweepableStorageV001();
$.sweepFrozen = true;
}

function sweepToken(address token, address to, uint256 amount) external onlyAdmin {
SweepableStorageV001 storage $ = _getSweepableStorageV001();
require(!$.sweepFrozen, "S: Sweep is frozen");
IERC20(token).safeTransfer(to, amount);
}
}
Loading

0 comments on commit 0164fa8

Please sign in to comment.