-
Notifications
You must be signed in to change notification settings - Fork 3
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #21 from ferrumnet/develop
Release
- Loading branch information
Showing
80 changed files
with
4,996 additions
and
6,194 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
268
contracts/contracts-upgradeable/common/FerrumAdminUpgradeable.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
43
contracts/contracts-upgradeable/common/FreezableUpgradeable.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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
36
contracts/contracts-upgradeable/common/SweepableUpgradeable.sol
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | ||
} | ||
} |
Oops, something went wrong.