Description:
Multi-signature wallet contract requiring multiple confirmations for transaction execution.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/policies/Heart.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0
/// forge-lint: disable-start(mixed-case-variable)
pragma solidity >=0.8.15;
// External libraries
import {ReentrancyGuard} from "@solmate-6.2.0/utils/ReentrancyGuard.sol";
import {ERC20} from "@solmate-6.2.0/tokens/ERC20.sol";
// Internal libraries
import {TransferHelper} from "src/libraries/TransferHelper.sol";
// Interfaces
import {IDistributor} from "src/policies/interfaces/IDistributor.sol";
import {IHeart} from "src/policies/interfaces/IHeart.sol";
// Modules
import {ROLESv1} from "src/modules/ROLES/ROLES.v1.sol";
import {PRICEv1} from "src/modules/PRICE/PRICE.v1.sol";
import {MINTRv1} from "src/modules/MINTR/MINTR.v1.sol";
// Base Contracts
import {BasePeriodicTaskManager} from "src/bases/BasePeriodicTaskManager.sol";
import {PolicyEnabler} from "src/policies/utils/PolicyEnabler.sol";
// Kernel
import {Kernel, Policy, Keycode, Permissions, toKeycode} from "src/Kernel.sol";
/// @title Olympus Heart
/// @notice Olympus Heart (Policy) Contract
/// @dev The Olympus Heart contract provides keeper rewards to call the heart beat function which fuels
/// Olympus market operations. The Heart orchestrates state updates in the correct order to ensure
/// market operations use up to date information.
/// This version implements an auction style reward system where the reward is linearly increasing up to a max reward.
/// Rewards are issued in OHM.
contract OlympusHeart is IHeart, Policy, PolicyEnabler, ReentrancyGuard, BasePeriodicTaskManager {
using TransferHelper for ERC20;
// ========= STATE ========= //
/// @notice Timestamp of the last beat (UTC, in seconds)
uint48 public lastBeat;
/// @notice Duration of the reward auction (in seconds)
uint48 public auctionDuration;
/// @notice Max reward for beating the Heart (in reward token decimals)
uint256 public maxReward;
// Modules
PRICEv1 internal PRICE;
MINTRv1 internal MINTR;
// Policies
IDistributor public distributor;
//============================================================================================//
// POLICY SETUP //
//============================================================================================//
/// @dev Auction duration must be less than or equal to frequency, but we cannot validate that in the constructor because PRICE is not yet set.
/// Therefore, manually ensure that the value is valid when deploying the contract.
constructor(
Kernel kernel_,
IDistributor distributor_,
uint256 maxReward_,
uint48 auctionDuration_
) Policy(kernel_) {
distributor = distributor_;
auctionDuration = auctionDuration_;
maxReward = maxReward_;
emit RewardUpdated(maxReward_, auctionDuration_);
// Disabled by default by PolicyEnabler
}
/// @inheritdoc Policy
function configureDependencies() external override returns (Keycode[] memory dependencies) {
dependencies = new Keycode[](3);
dependencies[0] = toKeycode("PRICE");
dependencies[1] = toKeycode("ROLES");
dependencies[2] = toKeycode("MINTR");
PRICE = PRICEv1(getModuleAddress(dependencies[0]));
ROLES = ROLESv1(getModuleAddress(dependencies[1]));
MINTR = MINTRv1(getModuleAddress(dependencies[2]));
(uint8 MINTR_MAJOR, ) = MINTR.VERSION();
(uint8 PRICE_MAJOR, ) = PRICE.VERSION();
(uint8 ROLES_MAJOR, ) = ROLES.VERSION();
// Ensure Modules are using the expected major version.
// Modules should be sorted in alphabetical order.
bytes memory expected = abi.encode([1, 1, 1]);
if (MINTR_MAJOR != 1 || PRICE_MAJOR != 1 || ROLES_MAJOR != 1)
revert Policy_WrongModuleVersion(expected);
// Sync beat with distributor if called from kernel
if (msg.sender == address(kernel)) {
_syncBeatWithDistributor();
}
}
/// @inheritdoc Policy
function requestPermissions()
external
view
override
returns (Permissions[] memory permissions)
{
Keycode MINTR_KEYCODE = MINTR.KEYCODE();
permissions = new Permissions[](3);
permissions[0] = Permissions({
keycode: PRICE.KEYCODE(),
funcSelector: PRICE.updateMovingAverage.selector
});
permissions[1] = Permissions({
keycode: MINTR_KEYCODE,
funcSelector: MINTR.mintOhm.selector
});
permissions[2] = Permissions({
keycode: MINTR_KEYCODE,
funcSelector: MINTR.increaseMintApproval.selector
});
}
/// @notice Returns the version of the policy.
///
/// @return major The major version of the policy.
/// @return minor The minor version of the policy.
function VERSION() external pure returns (uint8 major, uint8 minor) {
return (1, 7);
}
//============================================================================================//
// CORE FUNCTIONS //
//============================================================================================//
/// @inheritdoc IHeart
function beat() external nonReentrant {
if (!isEnabled) revert Heart_BeatStopped();
uint48 currentTime = uint48(block.timestamp);
if (currentTime < lastBeat + frequency()) revert Heart_OutOfCycle();
// Update the moving average on the Price module
// This cannot be a periodic task, because it requires a policy with permission to call the updateMovingAverage function
PRICE.updateMovingAverage();
// Trigger the rebase
distributor.triggerRebase();
// Execute periodic tasks
_executePeriodicTasks();
// Calculate the reward (0 <= reward <= maxReward) for the keeper
uint256 reward = currentReward();
// Update the last beat timestamp
// Ensure that update frequency doesn't change, but do not allow multiple beats if one is skipped
lastBeat = currentTime - ((currentTime - lastBeat) % frequency());
// Issue the reward
if (reward > 0) {
MINTR.increaseMintApproval(address(this), reward);
MINTR.mintOhm(msg.sender, reward);
emit RewardIssued(msg.sender, reward);
}
emit Beat(block.timestamp);
}
//============================================================================================//
// ADMIN FUNCTIONS //
//============================================================================================//
function _syncBeatWithDistributor() internal {
(uint256 epochLength, , uint256 epochEnd, ) = distributor.staking().epoch();
if (frequency() != epochLength) revert Heart_InvalidFrequency();
lastBeat = uint48(epochEnd - epochLength);
}
function _resetBeat() internal {
lastBeat = uint48(block.timestamp) - frequency();
}
/// @inheritdoc IHeart
/// @dev This function is gated to the ADMIN or MANAGER roles
function resetBeat() external onlyManagerOrAdminRole {
_resetBeat();
}
/// @inheritdoc PolicyEnabler
function _enable(bytes calldata) internal override {
_resetBeat();
}
/// @inheritdoc IHeart
/// @dev This function is gated to the ADMIN role
function setDistributor(address distributor_) external onlyAdminRole {
distributor = IDistributor(distributor_);
_syncBeatWithDistributor();
}
modifier notWhileBeatAvailable() {
// Prevent calling if a beat is available to avoid front-running a keeper
if (uint48(block.timestamp) >= lastBeat + frequency()) revert Heart_BeatAvailable();
_;
}
/// @inheritdoc IHeart
/// @dev This function is gated to the ADMIN role
function setRewardAuctionParams(
uint256 maxReward_,
uint48 auctionDuration_
) external onlyAdminRole notWhileBeatAvailable {
// auction duration should be less than or equal to frequency, otherwise frequency will be used
if (auctionDuration_ > frequency()) revert Heart_InvalidParams();
maxReward = maxReward_;
auctionDuration = auctionDuration_;
emit RewardUpdated(maxReward_, auctionDuration_);
}
//============================================================================================//
// VIEW FUNCTIONS //
//============================================================================================//
/// @inheritdoc IHeart
function frequency() public view returns (uint48) {
return uint48(PRICE.observationFrequency());
}
/// @inheritdoc IHeart
function currentReward() public view returns (uint256) {
// If beat not available, return 0
// Otherwise, calculate reward from linearly increasing auction bounded by maxReward and heart balance
uint48 beatFrequency = frequency();
uint48 nextBeat = lastBeat + beatFrequency;
uint48 currentTime = uint48(block.timestamp);
uint48 duration = auctionDuration > beatFrequency ? beatFrequency : auctionDuration;
if (currentTime <= nextBeat) {
return 0;
} else {
return
currentTime - nextBeat >= duration
? maxReward
: (uint256(currentTime - nextBeat) * maxReward) / duration;
}
}
}
/// forge-lint: disable-end(mixed-case-variable)
"
},
"dependencies/solmate-6.2.0/src/utils/ReentrancyGuard.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Gas optimized reentrancy protection for smart contracts.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/ReentrancyGuard.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/security/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
uint256 private locked = 1;
modifier nonReentrant() virtual {
require(locked == 1, "REENTRANCY");
locked = 2;
_;
locked = 1;
}
}
"
},
"dependencies/solmate-6.2.0/src/tokens/ERC20.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
/// @notice Modern and gas efficient ERC20 + EIP-2612 implementation.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Uniswap (https://github.com/Uniswap/uniswap-v2-core/blob/master/contracts/UniswapV2ERC20.sol)
/// @dev Do not manually set balances without updating totalSupply, as the sum of all user balances must not exceed it.
abstract contract ERC20 {
/*//////////////////////////////////////////////////////////////
EVENTS
//////////////////////////////////////////////////////////////*/
event Transfer(address indexed from, address indexed to, uint256 amount);
event Approval(address indexed owner, address indexed spender, uint256 amount);
/*//////////////////////////////////////////////////////////////
METADATA STORAGE
//////////////////////////////////////////////////////////////*/
string public name;
string public symbol;
uint8 public immutable decimals;
/*//////////////////////////////////////////////////////////////
ERC20 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 public totalSupply;
mapping(address => uint256) public balanceOf;
mapping(address => mapping(address => uint256)) public allowance;
/*//////////////////////////////////////////////////////////////
EIP-2612 STORAGE
//////////////////////////////////////////////////////////////*/
uint256 internal immutable INITIAL_CHAIN_ID;
bytes32 internal immutable INITIAL_DOMAIN_SEPARATOR;
mapping(address => uint256) public nonces;
/*//////////////////////////////////////////////////////////////
CONSTRUCTOR
//////////////////////////////////////////////////////////////*/
constructor(
string memory _name,
string memory _symbol,
uint8 _decimals
) {
name = _name;
symbol = _symbol;
decimals = _decimals;
INITIAL_CHAIN_ID = block.chainid;
INITIAL_DOMAIN_SEPARATOR = computeDomainSeparator();
}
/*//////////////////////////////////////////////////////////////
ERC20 LOGIC
//////////////////////////////////////////////////////////////*/
function approve(address spender, uint256 amount) public virtual returns (bool) {
allowance[msg.sender][spender] = amount;
emit Approval(msg.sender, spender, amount);
return true;
}
function transfer(address to, uint256 amount) public virtual returns (bool) {
balanceOf[msg.sender] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(msg.sender, to, amount);
return true;
}
function transferFrom(
address from,
address to,
uint256 amount
) public virtual returns (bool) {
uint256 allowed = allowance[from][msg.sender]; // Saves gas for limited approvals.
if (allowed != type(uint256).max) allowance[from][msg.sender] = allowed - amount;
balanceOf[from] -= amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(from, to, amount);
return true;
}
/*//////////////////////////////////////////////////////////////
EIP-2612 LOGIC
//////////////////////////////////////////////////////////////*/
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
require(deadline >= block.timestamp, "PERMIT_DEADLINE_EXPIRED");
// Unchecked because the only math done is incrementing
// the owner's nonce which cannot realistically overflow.
unchecked {
address recoveredAddress = ecrecover(
keccak256(
abi.encodePacked(
"\x19\x01",
DOMAIN_SEPARATOR(),
keccak256(
abi.encode(
keccak256(
"Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)"
),
owner,
spender,
value,
nonces[owner]++,
deadline
)
)
)
),
v,
r,
s
);
require(recoveredAddress != address(0) && recoveredAddress == owner, "INVALID_SIGNER");
allowance[recoveredAddress][spender] = value;
}
emit Approval(owner, spender, value);
}
function DOMAIN_SEPARATOR() public view virtual returns (bytes32) {
return block.chainid == INITIAL_CHAIN_ID ? INITIAL_DOMAIN_SEPARATOR : computeDomainSeparator();
}
function computeDomainSeparator() internal view virtual returns (bytes32) {
return
keccak256(
abi.encode(
keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"),
keccak256(bytes(name)),
keccak256("1"),
block.chainid,
address(this)
)
);
}
/*//////////////////////////////////////////////////////////////
INTERNAL MINT/BURN LOGIC
//////////////////////////////////////////////////////////////*/
function _mint(address to, uint256 amount) internal virtual {
totalSupply += amount;
// Cannot overflow because the sum of all user
// balances can't exceed the max uint256 value.
unchecked {
balanceOf[to] += amount;
}
emit Transfer(address(0), to, amount);
}
function _burn(address from, uint256 amount) internal virtual {
balanceOf[from] -= amount;
// Cannot underflow because a user's balance
// will never be larger than the total supply.
unchecked {
totalSupply -= amount;
}
emit Transfer(from, address(0), amount);
}
}
"
},
"src/libraries/TransferHelper.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.0;
import {ERC20} from "solmate/tokens/ERC20.sol";
/// @notice Safe ERC20 and ETH transfer library that safely handles missing return values.
/// @author Modified from Uniswap & old Solmate (https://github.com/Uniswap/uniswap-v3-periphery/blob/main/contracts/libraries/TransferHelper.sol)
library TransferHelper {
function safeTransferFrom(ERC20 token, address from, address to, uint256 amount) internal {
(bool success, bytes memory data) = address(token).call(
abi.encodeWithSelector(ERC20.transferFrom.selector, from, to, amount)
);
require(success && (data.length == 0 || abi.decode(data, (bool))), "TRANSFER_FROM_FAILED");
}
function safeTransfer(ERC20 token, address to, uint256 amount) internal {
(bool success, bytes memory data) = address(token).call(
abi.encodeWithSelector(ERC20.transfer.selector, to, amount)
);
require(success && (data.length == 0 || abi.decode(data, (bool))), "TRANSFER_FAILED");
}
function safeApprove(ERC20 token, address to, uint256 amount) internal {
(bool success, bytes memory data) = address(token).call(
abi.encodeWithSelector(ERC20.approve.selector, to, amount)
);
require(success && (data.length == 0 || abi.decode(data, (bool))), "APPROVE_FAILED");
}
}
"
},
"src/policies/interfaces/IDistributor.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity ^0.8.0;
import {IStaking} from "interfaces/IStaking.sol";
interface IDistributor {
// ========= ERRORS ========= //
error Distributor_NoRebaseOccurred();
error Distributor_OnlyStaking();
error Distributor_NotUnlocked();
// ========= CORE FUNCTIONS ========= //
/// @notice Trigger rebases via distributor. There is an error in Staking's `stake` function
/// which pulls forward part of the rebase for the next epoch. This path triggers a
/// rebase by calling `unstake` (which does not have the issue). The patch also
/// restricts `distribute` to only be able to be called from a tx originating in this
/// function.
function triggerRebase() external;
/// @notice Send the epoch's reward to the staking contract, and mint rewards to Uniswap V2 pools.
/// This removes opportunity cost for liquidity providers by sending rebase rewards
/// directly into the liquidity pool.
///
/// NOTE: This does not add additional emissions (user could be staked instead and get the
/// same tokens).
function distribute() external;
/// @notice Mints the bounty (if > 0) to the staking contract for distribution.
/// @return uint256 The amount of OHM minted as a bounty.
function retrieveBounty() external returns (uint256);
/// @notice Getter function for the staking contract address.
/// @return address The staking contract address.
function staking() external view returns (IStaking);
}
"
},
"src/policies/interfaces/IHeart.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.0;
/// @dev Interface for the Heart policy as of v1.7
interface IHeart {
// ========= EVENTS ========= //
event Beat(uint256 timestamp_);
event RewardIssued(address to_, uint256 rewardAmount_);
event RewardUpdated(uint256 maxRewardAmount_, uint48 auctionDuration_);
// ========= ERRORS ========= //
error Heart_OutOfCycle();
error Heart_BeatStopped();
error Heart_InvalidParams();
error Heart_BeatAvailable();
error Heart_InvalidFrequency();
// ========= CORE FUNCTIONS ========= //
/// @notice Beats the heart
/// @notice Only callable when enough time has passed since last beat (determined by frequency variable)
/// @notice This function is incentivized with a token reward (see rewardToken and reward variables).
/// @dev Triggers price oracle update and market operations
function beat() external;
// ========= ADMIN FUNCTIONS ========= //
/// @notice Unlocks the cycle if stuck on one side, eject function
/// @notice Access restricted
function resetBeat() external;
/// @notice Updates the Distributor contract address that the Heart calls on a beat
/// @notice Access restricted
/// @param distributor_ The address of the new Distributor contract
function setDistributor(address distributor_) external;
/// @notice Sets the max reward amount, and auction duration for the beat function
/// @notice Access restricted
/// @param maxReward_ - New max reward amount, in units of the reward token
/// @param auctionDuration_ - New auction duration, in seconds
function setRewardAuctionParams(uint256 maxReward_, uint48 auctionDuration_) external;
// ========= VIEW FUNCTIONS ========= //
/// @notice Heart beat frequency, in seconds
function frequency() external view returns (uint48);
/// @notice Current reward amount based on linear auction
function currentReward() external view returns (uint256);
}
"
},
"src/modules/ROLES/ROLES.v1.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.15;
import "src/Kernel.sol";
abstract contract ROLESv1 is Module {
// ========= EVENTS ========= //
event RoleGranted(bytes32 indexed role_, address indexed addr_);
event RoleRevoked(bytes32 indexed role_, address indexed addr_);
// ========= ERRORS ========= //
error ROLES_InvalidRole(bytes32 role_);
error ROLES_RequireRole(bytes32 role_);
error ROLES_AddressAlreadyHasRole(address addr_, bytes32 role_);
error ROLES_AddressDoesNotHaveRole(address addr_, bytes32 role_);
error ROLES_RoleDoesNotExist(bytes32 role_);
// ========= STATE ========= //
/// @notice Mapping for if an address has a policy-defined role.
mapping(address => mapping(bytes32 => bool)) public hasRole;
// ========= FUNCTIONS ========= //
/// @notice Function to grant policy-defined roles to some address. Can only be called by admin.
function saveRole(bytes32 role_, address addr_) external virtual;
/// @notice Function to revoke policy-defined roles from some address. Can only be called by admin.
function removeRole(bytes32 role_, address addr_) external virtual;
/// @notice "Modifier" to restrict policy function access to certain addresses with a role.
/// @dev Roles are defined in the policy and granted by the ROLES admin.
function requireRole(bytes32 role_, address caller_) external virtual;
/// @notice Function that checks if role is valid (all lower case)
function ensureValidRole(bytes32 role_) external pure virtual;
}
"
},
"src/modules/PRICE/PRICE.v1.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0
pragma solidity >=0.8.15;
import {AggregatorV2V3Interface} from "interfaces/AggregatorV2V3Interface.sol";
import "src/Kernel.sol";
/// @notice Price oracle data storage
/// @dev The Olympus Price Oracle contract provides a standard interface for OHM price data against a reserve asset.
/// It also implements a moving average price calculation (same as a TWAP) on the price feed data over a configured
/// duration and observation frequency. The data provided by this contract is used by the Olympus Range Operator to
/// perform market operations. The Olympus Price Oracle is updated each epoch by the Olympus Heart contract.
abstract contract PRICEv1 is Module {
// ========= EVENTS ========= //
event NewObservation(uint256 timestamp_, uint256 price_, uint256 movingAverage_);
event MovingAverageDurationChanged(uint48 movingAverageDuration_);
event ObservationFrequencyChanged(uint48 observationFrequency_);
event UpdateThresholdsChanged(uint48 ohmEthUpdateThreshold_, uint48 reserveEthUpdateThreshold_);
event MinimumTargetPriceChanged(uint256 minimumTargetPrice_);
// ========= ERRORS ========= //
error Price_InvalidParams();
error Price_NotInitialized();
error Price_AlreadyInitialized();
error Price_BadFeed(address priceFeed);
// ========= STATE ========= //
/// @dev Price feeds. Chainlink typically provides price feeds for an asset in ETH. Therefore, we use two price feeds against ETH, one for OHM and one for the Reserve asset, to calculate the relative price of OHM in the Reserve asset.
/// @dev Update thresholds are the maximum amount of time that can pass between price feed updates before the price oracle is considered stale. These should be set based on the parameters of the price feed.
/// @notice OHM/ETH price feed
AggregatorV2V3Interface public ohmEthPriceFeed;
/// @notice Maximum expected time between OHM/ETH price feed updates
uint48 public ohmEthUpdateThreshold;
/// @notice Reserve/ETH price feed
AggregatorV2V3Interface public reserveEthPriceFeed;
/// @notice Maximum expected time between OHM/ETH price feed updates
uint48 public reserveEthUpdateThreshold;
/// @notice Running sum of observations to calculate the moving average price from
/// @dev See getMovingAverage()
uint256 public cumulativeObs;
/// @notice Array of price observations. Check nextObsIndex to determine latest data point.
/// @dev Observations are stored in a ring buffer where the moving average is the sum of all observations divided by the number of observations.
/// Observations can be cleared by changing the movingAverageDuration or observationFrequency and must be re-initialized.
uint256[] public observations;
/// @notice Index of the next observation to make. The current value at this index is the oldest observation.
uint32 public nextObsIndex;
/// @notice Number of observations used in the moving average calculation. Computed from movingAverageDuration / observationFrequency.
uint32 public numObservations;
/// @notice Frequency (in seconds) that observations should be stored.
uint48 public observationFrequency;
/// @notice Duration (in seconds) over which the moving average is calculated.
uint48 public movingAverageDuration;
/// @notice Unix timestamp of last observation (in seconds).
uint48 public lastObservationTime;
/// @notice Whether the price module is initialized (and therefore active).
bool public initialized;
/// @notice Number of decimals in the price values provided by the contract.
uint8 public constant decimals = 18;
/// @notice Minimum target price for RBS system. Set manually to correspond to the liquid backing of OHM.
uint256 public minimumTargetPrice;
// ========= FUNCTIONS ========= //
/// @notice Trigger an update of the moving average. Permissioned.
/// @dev This function does not have a time-gating on the observationFrequency on this contract. It is set on the Heart policy contract.
/// The Heart beat frequency should be set to the same value as the observationFrequency.
function updateMovingAverage() external virtual;
/// @notice Initialize the price module
/// @notice Access restricted to activated policies
/// @param startObservations_ - Array of observations to initialize the moving average with. Must be of length numObservations.
/// @param lastObservationTime_ - Unix timestamp of last observation being provided (in seconds).
/// @dev This function must be called after the Price module is deployed to activate it and after updating the observationFrequency
/// or movingAverageDuration (in certain cases) in order for the Price module to function properly.
function initialize(
uint256[] memory startObservations_,
uint48 lastObservationTime_
) external virtual;
/// @notice Change the moving average window (duration)
/// @param movingAverageDuration_ - Moving average duration in seconds, must be a multiple of observation frequency
/// @dev Changing the moving average duration will erase the current observations array
/// and require the initialize function to be called again. Ensure that you have saved
/// the existing data and can re-populate before calling this function.
function changeMovingAverageDuration(uint48 movingAverageDuration_) external virtual;
/// @notice Change the observation frequency of the moving average (i.e. how often a new observation is taken)
/// @param observationFrequency_ - Observation frequency in seconds, must be a divisor of the moving average duration
/// @dev Changing the observation frequency clears existing observation data since it will not be taken at the right time intervals.
/// Ensure that you have saved the existing data and/or can re-populate before calling this function.
function changeObservationFrequency(uint48 observationFrequency_) external virtual;
/// @notice Change the update thresholds for the price feeds
/// @param ohmEthUpdateThreshold_ - Maximum allowed time between OHM/ETH price feed updates
/// @param reserveEthUpdateThreshold_ - Maximum allowed time between Reserve/ETH price feed updates
/// @dev The update thresholds should be set based on the update threshold of the chainlink oracles.
function changeUpdateThresholds(
uint48 ohmEthUpdateThreshold_,
uint48 reserveEthUpdateThreshold_
) external virtual;
/// @notice Change the minimum target price
/// @param minimumTargetPrice_ - Minimum target price for RBS system with 18 decimals, expressed as number of Reserve per OHM
/// @dev The minimum target price should be set based on the liquid backing of OHM.
function changeMinimumTargetPrice(uint256 minimumTargetPrice_) external virtual;
/// @notice Get the current price of OHM in the Reserve asset from the price feeds
function getCurrentPrice() external view virtual returns (uint256);
/// @notice Get the last stored price observation of OHM in the Reserve asset
function getLastPrice() external view virtual returns (uint256);
/// @notice Get the moving average of OHM in the Reserve asset over the defined window (see movingAverageDuration and observationFrequency).
function getMovingAverage() external view virtual returns (uint256);
/// @notice Get target price of OHM in the Reserve asset for the RBS system
/// @dev Returns the maximum of the moving average and the minimum target price
function getTargetPrice() external view virtual returns (uint256);
}
"
},
"src/modules/MINTR/MINTR.v1.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.15;
import {OlympusERC20Token as OHM} from "src/external/OlympusERC20.sol";
import "src/Kernel.sol";
/// @notice Wrapper for minting and burning functions of OHM token.
abstract contract MINTRv1 is Module {
// ========= EVENTS ========= //
event IncreaseMintApproval(address indexed policy_, uint256 newAmount_);
event DecreaseMintApproval(address indexed policy_, uint256 newAmount_);
event Mint(address indexed policy_, address indexed to_, uint256 amount_);
event Burn(address indexed policy_, address indexed from_, uint256 amount_);
// ========= ERRORS ========= //
error MINTR_NotApproved();
error MINTR_ZeroAmount();
error MINTR_NotActive();
// ========= STATE ========= //
OHM public ohm;
/// @notice Status of the minter. If false, minting and burning OHM is disabled.
bool public active;
/// @notice Mapping of who is approved for minting.
/// @dev minter -> amount. Infinite approval is max(uint256).
mapping(address => uint256) public mintApproval;
// ========= FUNCTIONS ========= //
modifier onlyWhileActive() {
if (!active) revert MINTR_NotActive();
_;
}
/// @notice Mint OHM to an address.
function mintOhm(address to_, uint256 amount_) external virtual;
/// @notice Burn OHM from an address. Must have approval.
function burnOhm(address from_, uint256 amount_) external virtual;
/// @notice Increase approval for specific withdrawer addresses
/// @dev Policies must explicity request how much they want approved before withdrawing.
function increaseMintApproval(address policy_, uint256 amount_) external virtual;
/// @notice Decrease approval for specific withdrawer addresses
function decreaseMintApproval(address policy_, uint256 amount_) external virtual;
/// @notice Emergency shutdown of minting and burning.
function deactivate() external virtual;
/// @notice Re-activate minting and burning after shutdown.
function activate() external virtual;
}
"
},
"src/bases/BasePeriodicTaskManager.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.15;
// Interfaces
import {IPeriodicTask} from "src/interfaces/IPeriodicTask.sol";
import {IPeriodicTaskManager} from "src/bases/interfaces/IPeriodicTaskManager.sol";
import {IERC165} from "@openzeppelin-4.8.0/interfaces/IERC165.sol";
// Libraries
import {AddressStorageArray} from "src/libraries/AddressStorageArray.sol";
// Bophades
import {PolicyEnabler} from "src/policies/utils/PolicyEnabler.sol";
abstract contract BasePeriodicTaskManager is IPeriodicTaskManager, PolicyEnabler {
using AddressStorageArray for address[];
// ========== STATE VARIABLES ========== //
/// @notice The periodic tasks
address[] internal _periodicTaskAddresses;
/// @notice An optional custom selector for each periodic task
/// @dev If the selector is set (non-zero), the task will be executed using the custom selector
/// instead of the `IPeriodicTask.execute` function
mapping(address => bytes4) internal _periodicTaskCustomSelectors;
// ========== TASK CONFIGURATION ========== //
function _addPeriodicTask(address task_, bytes4 customSelector_, uint256 index_) internal {
// Validate that the task is not already added
if (hasPeriodicTask(task_)) revert PeriodicTaskManager_TaskAlreadyExists(task_);
// Validate that the task is not a zero address
if (task_ == address(0)) revert PeriodicTaskManager_ZeroAddress();
// Validate that the task is a contract
if (task_.code.length == 0) revert PeriodicTaskManager_NotPeriodicTask(task_);
// If there is no custom selector, validate that the task implements the IPeriodicTask interface
if (customSelector_ == bytes4(0)) {
// Validate that the task implements the IPeriodicTask interface
(bool success, bytes memory data) = task_.staticcall(
abi.encodeWithSelector(
IERC165.supportsInterface.selector,
type(IPeriodicTask).interfaceId
)
);
if (!success || abi.decode(data, (bool)) == false)
revert PeriodicTaskManager_NotPeriodicTask(task_);
} else {
// Validation of the selector happens at the time of execution, as there is no way to validate it here
_periodicTaskCustomSelectors[task_] = customSelector_;
}
// Insert the task at the index
// This will also validate that the index is within bounds
_periodicTaskAddresses.insert(task_, index_);
// Emit the event
emit PeriodicTaskAdded(task_, customSelector_, index_);
}
/// @inheritdoc IPeriodicTaskManager
/// @dev This function reverts if:
/// - The caller is not the admin
/// - The task is already added
/// - The task is not a valid periodic task
///
function addPeriodicTask(address task_) external override onlyAdminRole {
_addPeriodicTask(task_, bytes4(0), _periodicTaskAddresses.length);
}
/// @inheritdoc IPeriodicTaskManager
/// @dev This function reverts if:
/// - The caller is not the admin
/// - The task is already added
/// - The task is not a valid periodic task
/// - The index is out of bounds
///
/// If a custom selector is provided, care must be taken to ensure that the selector exists on {task_}.
/// If the selector does not exist, all of the periodic tasks will revert.
function addPeriodicTaskAtIndex(
address task_,
bytes4 customSelector_,
uint256 index_
) external override onlyAdminRole {
_addPeriodicTask(task_, customSelector_, index_);
}
function _removePeriodicTask(uint256 index_) internal {
// Remove the task at the index
address removedTask = _periodicTaskAddresses.remove(index_);
// Clear the custom selector for the task
delete _periodicTaskCustomSelectors[removedTask];
// Emit the event
emit PeriodicTaskRemoved(removedTask, index_);
}
/// @inheritdoc IPeriodicTaskManager
/// @dev This function reverts if:
/// - The caller is not the admin
/// - The task is not added
function removePeriodicTask(address task_) external override onlyAdminRole {
// Get the index of the task
uint256 index = getPeriodicTaskIndex(task_);
// Validate that the task exists
if (index == type(uint256).max) revert PeriodicTaskManager_TaskNotFound(task_);
// Remove the task at the index
_removePeriodicTask(index);
}
/// @inheritdoc IPeriodicTaskManager
/// @dev This function reverts if:
/// - The caller is not the admin
/// - The index is out of bounds
function removePeriodicTaskAtIndex(uint256 index_) external override onlyAdminRole {
// Remove the task at the index
_removePeriodicTask(index_);
}
// ========== TASK EXECUTION ========== //
/// @dev This function does not implement any logic to catch errors from the periodic tasks.
/// @dev The logic is that if a periodic task fails, it should fail loudly and revert.
/// @dev Any tasks that are non-essential can include a try-catch block to handle the error internally.
function _executePeriodicTasks() internal {
for (uint256 i = 0; i < _periodicTaskAddresses.length; i++) {
// Get the custom selector for the task
bytes4 customSelector = _periodicTaskCustomSelectors[_periodicTaskAddresses[i]];
// If there is no custom selector, execute the task using the `IPeriodicTask.execute` function
if (customSelector == bytes4(0)) {
IPeriodicTask(_periodicTaskAddresses[i]).execute();
}
// Otherwise, execute the task using the custom selector
else {
// Call the custom selector
(bool success, bytes memory data) = _periodicTaskAddresses[i].call(
abi.encodeWithSelector(customSelector)
);
// If the call fails, revert
if (!success)
revert PeriodicTaskManager_CustomSelectorFailed(
_periodicTaskAddresses[i],
customSelector,
data
);
}
}
}
// ========== VIEW FUNCTIONS ========== //
/// @inheritdoc IPeriodicTaskManager
function getPeriodicTaskCount() external view override returns (uint256) {
return _periodicTaskAddresses.length;
}
/// @inheritdoc IPeriodicTaskManager
function getPeriodicTaskAtIndex(
uint256 index_
) external view override returns (address, bytes4) {
address task = _periodicTaskAddresses[index_];
return (task, _periodicTaskCustomSelectors[task]);
}
/// @inheritdoc IPeriodicTaskManager
function getPeriodicTasks() external view override returns (address[] memory, bytes4[] memory) {
address[] memory tasks = new address[](_periodicTaskAddresses.length);
bytes4[] memory customSelectors = new bytes4[](_periodicTaskAddresses.length);
for (uint256 i = 0; i < _periodicTaskAddresses.length; i++) {
tasks[i] = _periodicTaskAddresses[i];
customSelectors[i] = _periodicTaskCustomSelectors[_periodicTaskAddresses[i]];
}
return (tasks, customSelectors);
}
/// @inheritdoc IPeriodicTaskManager
///
/// @return _index The index of the task, or type(uint256).max if not found
function getPeriodicTaskIndex(address task_) public view override returns (uint256 _index) {
uint256 length = _periodicTaskAddresses.length;
_index = type(uint256).max;
for (uint256 i = 0; i < length; i++) {
if (_periodicTaskAddresses[i] == task_) {
_index = i;
break;
}
}
return _index;
}
/// @inheritdoc IPeriodicTaskManager
function hasPeriodicTask(address task_) public view override returns (bool) {
return getPeriodicTaskIndex(task_) != type(uint256).max;
}
}
"
},
"src/policies/utils/PolicyEnabler.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity >=0.8.15;
import {IEnabler} from "src/periphery/interfaces/IEnabler.sol";
import {PolicyAdmin} from "./PolicyAdmin.sol";
/// @title PolicyEnabler
/// @notice This contract is designed to be inherited by contracts that need to be enabled or disabled. It replaces the inconsistent usage of `active` and `locallyActive` state variables across the codebase.
/// @dev A contract that inherits from this contract should use the `onlyEnabled` and `onlyDisabled` modifiers to gate access to certain functions.
///
/// Inheriting contracts must do the following:
/// - In `configureDependencies()`, assign the module address to the `ROLES` state variable, e.g. `ROLES = ROLESv1(getModuleAddress(toKeycode("ROLES")));`
///
/// The following are optional:
/// - Override the `_enable()` and `_disable()` functions if custom logic and/or parameters are needed for the enable/disable functions.
/// - For example, `enable()` could be called with initialisation data that is decoded, validated and assigned in `_enable()`.
abstract contract PolicyEnabler is IEnabler, PolicyAdmin {
// ===== STATE VARIABLES ===== //
/// @notice Whether the policy functionality is enabled
bool public isEnabled;
// ===== MODIFIERS ===== //
function _onlyEnabled() internal view {
if (!isEnabled) revert NotEnabled();
}
/// @notice Modifier that reverts if the policy is not enabled
modifier onlyEnabled() {
_onlyEnabled();
_;
}
function _onlyDisabled() internal view {
if (isEnabled) revert NotDisabled();
}
/// @notice Modifier that reverts if the policy is enabled
modifier onlyDisabled() {
_onlyDisabled();
_;
}
// ===== ENABLEABLE FUNCTIONS ===== //
/// @notice Enable the contract
/// @dev This function performs the following steps:
/// 1. Validates that the caller has the admin role
/// 2. Validates that the contract is disabled
/// 3. Calls the implementation-specific `_enable()` function
/// 4. Changes the state of the contract to enabled
/// 5. Emits the `Enabled` event
///
/// @param enableData_ The data to pass to the implementation-specific `_enable()` function
function enable(bytes calldata enableData_) public onlyAdminRole onlyDisabled {
// Call the implementation-specific enable function
_enable(enableData_);
// Change the state
isEnabled = true;
// Emit the enabled event
emit Enabled();
}
/// @notice Implementation-specific enable function
/// @dev This function is called by the `enable()` function
///
/// The implementing contract can override this function and perform the following:
/// 1. Validate any parameters (if needed) or revert
/// 2. Validate state (if needed) or revert
/// 3. Perform any necessary actions, apart from modifying the `isEnabled` state variable
///
/// @param enableData_ Custom data that can be used by the implementation. The format of this data is
/// left to the discretion of the implementation.
function _enable(bytes calldata enableData_) internal virtual {}
/// @notice Disable the contract
/// @dev This function performs the following steps:
/// 1. Validates that the caller has the admin or emergency role
/// 2. Validates that the contract is enabled
/// 3. Calls the implementation-specific `_disable()` function
/// 4. Changes the state of the contract to disabled
/// 5. Emits the `Disabled` event
///
/// @param disableData_ The data to pass to the implementation-specific `_disable()` function
function disable(bytes calldata disableData_) public onlyEmergencyOrAdminRole onlyEnabled {
// Call the implementation-specific disable function
_disable(disableData_);
// Change the state
isEnabled = false;
// Emit the disabled event
emit Disabled();
}
/// @notice Implementation-specific disable function
/// @dev This function is called by the `disable()` function.
///
/// The implementing contract can override this function and perform the following:
/// 1. Validate any parameters (if needed) or revert
/// 2. Validate state (if needed) or revert
/// 3. Perform any necessary actions, apart from modifying the `isEnabled` state variable
///
/// @param disableData_ Custom data that can be used by the implementation. The format of this data is
/// left to the discretion of the implementation.
function _disable(bytes calldata disableData_) internal virtual {}
// ========== ERC165 ========== //
function supportsInterface(bytes4 interfaceId) public view virtual returns (bool) {
return
interfaceId == bytes4(0x01ffc9a7) || // ERC-165
interfaceId == type(IEnabler).interfaceId;
}
}
"
},
"src/Kernel.sol": {
"content": "// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.15;
// ███████ █████ █████ █████ ██████ ██████ ███████████ █████ █████ █████████
// ███░░░░░███ ░░███ ░░███ ░░███ ░░██████ ██████ ░░███░░░░░███░░███ ░░███ ███░░░░░███
// ███ ░░███ ░███ ░░███ ███ ░███░█████░███ ░███ ░███ ░███ ░███ ░███ ░░░
// ░███ ░███ ░███ ░░█████ ░███░░███ ░███ ░██████████ ░███ ░███ ░░█████████
// ░███ ░███ ░███ ░░███ ░███ ░░░ ░███ ░███░░░░░░ ░███ ░███ ░░░░░░░░███
// ░░███ ███ ░███ █ ░███ ░███ ░███ ░███ ░███ ░███ ███ ░███
// ░░░███████░ ███████████ █████ █████ █████ █████ ░░████████ ░░█████████
// ░░░░░░░ ░░░░░░░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░ ░░░░░░░░ ░░░░░░░░░
//============================================================================================//
// GLOBAL TYPES //
//============================================================================================//
/// @notice Actions to trigger state changes in the kernel. Passed by the executor
enum Actions {
InstallModule,
UpgradeModule,
ActivatePolicy,
DeactivatePolicy,
ChangeExecutor,
MigrateKernel
}
/// @notice Used by executor to select an action and a target contract for a kernel action
struct Instruction {
Actions action;
address target;
}
/// @notice Used to define which module functions a policy needs access to
struct Permissions {
Keycode keycode;
bytes4 funcSelector;
}
type Keycode is bytes5;
//============================================================================================//
// UTIL FUNCTIONS //
//============================================================================================//
error TargetNotAContract(address target_);
error InvalidKeycode(Keycode keycode_);
// solhint-disable-next-line func-visibility
function toKeycode(bytes5 keycode_) pure returns (Keycode) {
return Keycode.wrap(keycode_);
}
// solhint-disable-next-line func-visibility
function fromKeycode(Keycode keycode_) pure returns (bytes5) {
return Keycode.unwrap(keycode_);
}
// solhint-disable-next-line func-visibility
function ensureContract(address target_) view {
if (target_.code.length == 0) revert TargetNotAContract(target_);
}
// solhint-disable-next-line func-visibility
function ensureValidKeycode(Keycode keycode_) pure {
bytes5 unwrapped = Keycode.unwrap(keycode_);
for (uint256 i = 0; i < 5; ) {
bytes1 char = unwrapped[i];
if (char < 0x41 || char > 0x5A) revert InvalidKeycode(keycode_); // A-Z only
unchecked {
i++;
}
}
}
//============================================================================================//
// COMPONENTS //
//============================================================================================//
/// @notice Generic adapter interface for kernel access in modules and policies.
abstract contract KernelAdapter {
error KernelAdapter_OnlyKernel(address caller_);
Kernel public kernel;
constructor(Kernel kernel_) {
kernel = kernel_;
}
/// @notice Modifier to restrict functions to be called only by kernel.
modifier onlyKernel() {
if (msg.sender != address(kernel)) revert KernelAdapter_OnlyKernel(msg.sender);
_;
}
/// @notice Function used by kernel when migrating to a new kernel.
function changeKernel(Kernel newKernel_) external onlyKernel {
kernel = newKernel_;
}
}
/// @notice Base level extension of the kernel. Modules act as independent state components to be
/// interacted with and mutated through policies.
/// @dev Modules are installed and uninstalled via the executor.
abstract contract Module is KernelAdapter {
error Module_PolicyNotPermitted(address policy_);
constructor(Kernel kernel_) KernelAdapter(kernel_) {}
/// @notice Modifier to restrict which policies have access to module functions.
modifier permissioned() {
if (
msg.sender == address(kernel) ||
!kernel.modulePermissions(KEYCODE(), Policy(msg.sender), msg.sig)
) revert Module_PolicyNotPermitted(msg.sender);
_;
}
/// @notice 5 byte identifier for a module.
function KEYCODE() public pure virtual returns (Keycode) {}
/// @notice Returns which semantic version of a module is being implemented.
/// @return major - Major version upgrade indicates breaking change to the interface.
/// @return minor - Minor version change retains backward-compatible interface.
function VERSION() external pure virtual returns (uint8 major, uint8 minor) {}
/// @notice Initialization function for the module
/// @dev This function is called when the module is installed or upgraded by the kernel.
/// @dev MUST BE GATED BY onlyKernel. Used to encompass any initialization or upgrade logic.
function INIT() external virtual onlyKernel {}
}
/// @notice Policies are application logic and external interface for the kernel and installed modules.
/// @dev Policies are activated and deactivated in the kernel by the executor.
/// @dev Module dependencies and function permissions must be defined in appropriate functions.
abstract contract Policy is KernelAdapter {
error Policy_ModuleDoesNotExist(Keycode keycode_);
error Policy_WrongModuleVersion(bytes expected_);
constructor(Kernel kernel_) KernelAdapter(kernel_) {}
/// @notice Easily accessible indicator for if a policy is activated or not.
function isActive() external view returns (bool) {
return kernel.isPolicyActive(this);
}
/// @notice Function to grab module address from a given keycode.
function getModuleAddress(Keycode keycode_) internal view returns (address) {
address moduleForKeycode = address(kernel.getModuleForKeycode(keycode_));
if (moduleForKeycode == address(0)) revert Policy_ModuleDoesNotExist(keycode_);
return moduleForKeycode;
}
/// @notice Define module dependencies for this policy.
/// @return dependencies - Keycode array of module dependencies.
function configureDependencies() external virtual returns (Keycode[] memory dependencies) {}
/// @notice Function called by kernel to set module function permissions.
/// @return requests - Array of keycodes and function selectors for requested permissions.
function requestPermissions() external view virtual returns (Permissions[] memory requests) {}
}
/// @notice Main contract that acts as a central component registry for the protocol.
/// @dev The kernel manages modules and policies. The kernel is mutated via predefined Actions,
/// @dev which are input from any address assigned as the executor. The executor can be changed as needed.
contract Kernel {
// ========= EVENTS ========= //
event PermissionsUpdated(
Keycode indexed keycode_,
Policy indexed policy_,
bytes4 funcSelector_,
bool granted_
);
event ActionExecuted(Actions indexed action_, address indexed target_);
// ========= ERRORS ========= //
error Kernel_OnlyExecutor(address caller_);
error Kernel_ModuleAlreadyInstalled(Keycode module_);
error Kernel_InvalidModuleUpgrade(Keycode module_);
error Kernel_PolicyAlreadyActivated(address policy_);
error Kernel_PolicyNotActivated(address policy_);
// ========= PRIVILEGED ADDRESSES ========= //
/// @notice Address that is able to initiate Actions in the kernel. Can be assigned to a multisig or governance contract.
address public executor;
// ========= MODULE MANAGEMENT ========= //
/// @notice Array of all modules currently installed.
Keycode[] public allKeycodes;
/// @notice Mapping of module address to keycode.
mapping(Keycode => Module) public getModuleForKeycode;
/// @notice Mapping of keycode to module address.
mapping(Module => Keycode) public getKeycodeForModule;
/// @notice Mapping of a keycode to all of its policy dependents. Used to efficiently reconfigure policy dependencies.
mapping(Keycode => Policy[]) public moduleDependents;
/// @notice Helper for module dependent arrays. Prevents the need to loop through array.
mapping(Keycode => mapping(Policy => uint256)) public getDependentIndex;
/// @notice Module <> Policy Permissions.
/// @dev Keycode -> Policy -> Function Selector -> bool for permission
mapping(Keycode => mapping(Policy => mapping(bytes4 => bool))) public modulePermissions;
// ========= POLICY MANAGEMENT ========= //
/// @notice List of all active policies
Policy[] public activePolicies;
/// @notice Helper to get active policy quickly. Prevents need to loop through array.
mapping(Policy => uint256) public getPolicyIndex;
//============================================================================================//
// CORE FUNCTIONS //
//============================================================================================//
constructor() {
executor = msg.sender;
}
/// @notice Modifier to check if caller is the executor.
modifier onlyExecutor() {
if (msg.sender != executor) revert Kernel_OnlyExecutor(msg.sender);
_;
}
function isPolicyActive(Policy policy_) public view returns (bool) {
return activePolicies.length > 0 && activePolicies[getPolicyIndex[policy_]] == policy_;
}
/// @notice Main kernel function. Initiates state changes to kernel depending on Action passed in.
function executeAction(Actions action_, address target_) external onlyExecutor {
if (action_ == Actions.InstallModule) {
ensureContract(target_);
ensureValidKeycode(Module(target_).KEYCODE());
_installModule(Module(target_));
} else if (action_ == Actions.UpgradeModule) {
ensureContract(target_);
ensureValidKeycode(Module(target_).KEYCODE());
_upgradeModule(Module(target_));
} else if (action_ == Actions.ActivatePolicy) {
ensureContract(target_);
_activatePolicy(Policy(target_));
} else if (action_ == Actions.DeactivatePolicy) {
ensureContract(target_);
_deactivatePolicy(Policy(target_));
} else if (action_ == Actions.ChangeExecutor) {
executor = target_;
} else if (action_ == Actions.MigrateKernel) {
ensureContract(target_);
_migrateKernel(Kernel(target_));
}
emit ActionExecuted(action_, target_);
}
function _installModule(Module newModule_) internal {
Keycode keycode = newModule_.KEYCODE();
if (address(getModuleForKeycode[keycode]) != address(0))
revert Kernel_ModuleAlreadyInstalled(keycode);
getModuleForKeycode[keycode] = newModule_;
getKeycodeForModule[newModule_] = keycode;
allKeycodes.push(keycode);
newModule_.INIT();
}
function _upgradeModule(Module newModule_) internal {
Keycode keycode = newModule_.KEYCODE();
Module oldModule = getModuleForKeycode[keycode];
if (address(oldModule) == address(0) || oldModule == newModule_)
revert Kernel_InvalidModuleUpgrade(keycode);
getKeycodeForModule[oldModule] = Keycode.wrap(bytes5(0));
getKeycodeForModule[newModule_] = keycode;
getModuleForKeycode[keycode] = newModule_;
newModule_.INIT();
_reconfigurePolicies(keycode);
}
function _activatePolicy(Policy policy_) internal {
if (isPolicyActive(policy_)) revert Kernel_PolicyAlreadyActivated(address(policy_));
// Add policy to list of active policies
activePolicies.push(policy_);
getPolicyIndex[policy_] = activePolicies.length - 1;
// Record module dependencies
Keycode[] memory dependencies = policy_.configureDependencies();
uint256 depLength = dependencies.length;
for (uint256 i; i < depLength; ) {
Keycode keycode = dependencies[i];
moduleDependents[keycode].push(policy_);
getDependentIndex[keycode][policy_] = moduleDependents[keycode].length - 1;
unchecked {
++i;
}
}
// Grant permissions for policy to access restricted module functions
Permissions[] memory requests = policy_.requestPermissions();
_setPolicyPermissions(policy_, requests, true);
}
function _deactivatePolicy(Policy policy_) internal {
if (!isPolicyActive(policy_)) revert Kernel_PolicyNotActivated(address(policy_));
// Revoke permissions
Permissions[] memory requests = policy_.requestPermissions();
_setPolicyPermissions(policy_, requests, false);
// Remove policy from all policy data structures
uint256 idx = getPolicyIndex[policy_];
Policy lastPolicy = activePolicies[activePolicies.length - 1];
activePolicies[idx] = lastPolicy;
activePolicies.pop();
getPolicyIndex[lastPolicy] = idx;
delete getPolicyIndex[policy_];
// Remove policy from module dependents
_pruneFromDependents(policy_);
}
/// @notice All functionality will move to the new kernel. WARNING: ACTION WILL BRICK THIS KERNEL.
/// @dev New kernel must add in all of the modules and policies via executeAction.
/// @dev NOTE: Data does not get cleared from this kernel.
function _migrateKernel(Kernel newKernel_) internal {
uint256 keycodeLen = allKeycodes.length;
for (uint256 i; i < keycodeLen; ) {
Module module = Module(getModuleForKeycode[allKeycodes[i]]);
module.changeKernel(newKernel_);
unchecked {
++i;
}
}
uint256 policiesLen = activePolicies.length;
for (uint256 j; j < policiesLen; ) {
Policy policy = activePolicies[j];
// Deactivate before changing kernel
policy.changeKernel(newKernel_);
unchecked {
++j;
}
}
}
function _reconfigurePolicies(Keycode keycode_) internal {
Policy[] memory dependents = moduleDependents[keycode_];
uint256 depLength = dependents.len
Submitted on: 2025-11-07 16:50:07
Comments
Log in to comment.
No comments yet.