YieldFu is an innovative decentralized finance (DeFi) protocol that builds upon the foundations of Olympus DAO while introducing several unique features. The protocol incorporates advanced yield generation strategies, risk management techniques, and various user-centric functionalities to create a sustainable and rewarding ecosystem.
YieldFu is structured as a modular system utilizing separate smart contracts (referred to as modules) for various functions such as token debasement, staking rewards, bonding, treasury management, and more. These modules interact through a central Kernel contract, which manages execution permissions and dependencies between the modules and policies.
- Modular Architecture: YieldFu separates key functionalities into distinct modules, providing flexibility and extensibility.
- Debasing Mechanism: The protocol's native token, YieldFu, undergoes periodic debasement (devaluation) by 3% per day.
- Staking Rewards: Users can stake YieldFu tokens to earn rewards with a base APY of 1333%, with occasional boosts to 8888%.
- Bonding with Discounts: Users can purchase discounted bonds (ETH, USDT, or partner tokens) that mature in 3 days. The discount percentages are adjustable.
- Treasury Management: The protocol manages its treasury through the TRSRY module, handling withdrawals, debt, and fund allocations.
- Fully Permissioned: All module functionalities are gated by permissions managed via the Kernel contract, ensuring secure interaction between contracts.
The YieldFu protocol is broken down into the following key components:
The Kernel
is the core contract that manages all interactions between the modules and policies. It handles permissions, module installation, policy activation, and upgrading modules.
The native ERC20 token, YieldFu, has minting, burning, and permissioned functionality. It is fully integrated with the modular system, allowing other contracts (like MINTR
and DEBASE
) to control token supply dynamics.
This module handles the periodic debasement (devaluation) of the YieldFu token. It debases the token supply by 3% daily and is executed by the TokenPolicy
contract.
This module manages minting and burning of YieldFu tokens. The module supports:
- Minting tokens to specific addresses.
- Burning tokens by transferring them to a black hole (dead address).
- Emergency shutdown and reactivation of minting and burning functionality.
This module manages staking functionality. Users stake their YieldFu tokens to earn rewards at a base APY of 1333%, with the possibility of a temporary boost to 8888%. It keeps track of staking balances and calculates rewards based on the staking duration.
This module allows users to bond ETH, USDT, or partner tokens to receive YieldFu tokens at a discount (e.g., 15% for ETH/USDT and 25% for partner tokens). The bond matures in 3 days, and the discount percentages are configurable by the governance.
This module manages the protocol’s treasury. It allows for pre-approved withdrawals, manages debt, and keeps track of the protocol's reserves. This module is essential for handling the funds generated by bonding and other mechanisms.
Policies are external-facing contracts that interact with the modules to perform specific functions. The policies control user interactions such as staking, bonding, debasing, and treasury management. Each policy has specific permissions granted by the Kernel
contract.
Handles debasing and transferring of YieldFu tokens. Calls the debase function from the DEBASE
module and manages token transfers.
Allows users to stake and unstake YieldFu tokens through the STAKE
module. Also supports claiming staking rewards and boosting APY temporarily.
Manages bonding of ETH, USDT, and partner tokens through the BOND
module. Users can bond these tokens and claim discounted YieldFu tokens after a 3-day maturity period.
Allows for interaction with the TRSRY
module. This policy handles treasury withdrawals, authorizes debt, and manages reserves.
Ensure that you have the following installed:
-
Clone the Repository:
git clone https://github.com/your-repo/yieldfu.git cd yieldfu
-
Install Dependencies:
npm install
-
Compile Contracts:
npx hardhat compile
-
Run Tests:
npx hardhat test
-
Deploy Contracts: Adjust deployment scripts under
scripts/deploy.js
and deploy to the network:npx hardhat run scripts/deploy.js --network <network-name>
The Kernel contract is the central hub of the protocol, coordinating the interactions between various Modules and Policies. It manages permissions, ensures modularity, and enforces security across the protocol by restricting actions to approved entities.
The Kernel contract defines the execution flow of protocol-wide actions, including module installation, upgrades, policy activation, and permission handling. Its core functionality includes enabling Policies to execute protocol-specific logic within Modules, which store the protocol's state and operations.
Defines a set of protocol-wide actions that can be executed by the Kernel or authorized entities:
- InstallModule: Install a new module into the Kernel.
- UpgradeModule: Upgrade an existing module.
- ActivatePolicy: Activate a new policy, granting it permissions.
- DeactivatePolicy: Deactivate a policy, removing its permissions.
- ChangeExecutor: Change the address of the Kernel executor.
- MigrateKernel: Migrate the protocol to a new Kernel.
- ExecuteAction: Execute an arbitrary function in a module.
Encapsulates actions and their associated target addresses, which are executed by the Kernel.
struct Instruction {
Actions action;
address target;
}
Defines a permission request for a policy, consisting of a Keycode
(identifying a module) and a function selector (which is the function signature).
struct Permissions {
Keycode keycode;
bytes4 funcSelector;
}
A Keycode
is a type-safe representation of a module identifier. It wraps around a bytes5
value, representing the key used to map a module to its specific functionality.
Converts a bytes5
value into a Keycode
.
function toKeycode(bytes5 keycode_) pure returns (Keycode) {
return Keycode.wrap(keycode_);
}
Unwraps a Keycode
to its underlying bytes5
value.
function fromKeycode(Keycode keycode_) pure returns (bytes5) {
return Keycode.unwrap(keycode_);
}
Ensures that the target address is a valid contract by checking if it has bytecode deployed.
function ensureContract(address target_) view {
if (target_.code.length == 0) revert TargetNotAContract(target_);
}
Validates that a Keycode
is a valid alphanumeric code, restricted to uppercase letters (A-Z).
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++;
}
}
}
The Kernel constructor initializes the contract by granting the deployer the DEFAULT_ADMIN_ROLE and setting the deployer as the protocol’s initial executor.
constructor() {
executor = msg.sender;
_grantRole(DEFAULT_ADMIN_ROLE, msg.sender); // Granting the deployer DEFAULT_ADMIN_ROLE
}
The executor
is the address that can install modules, activate policies, and execute protocol-level actions. Only the executor
has permission to perform sensitive operations such as upgrading modules, changing the executor, or migrating the Kernel.
Modules are installed into the Kernel through the executeAction()
function. The InstallModule
action installs a module by linking its Keycode with its contract address and initializing it.
function _installModule(Module newModule_) internal {
Keycode keycode = newModule_.KEYCODE();
require(Keycode.unwrap(keycode) != bytes5(0), "Kernel: invalid keycode");
if (address(getModuleForKeycode[keycode]) != address(0))
revert Kernel_ModuleAlreadyInstalled(keycode);
getModuleForKeycode[keycode] = newModule_;
getKeycodeForModule[newModule_] = keycode;
newModule_.INIT();
}
The UpgradeModule
action replaces an existing module with a new implementation while retaining the same Keycode. This ensures that dependent Policies will interact with the new module.
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);
}
Policies are activated and deactivated through the ActivatePolicy
and DeactivatePolicy
actions, respectively. Activation grants the policy permission to interact with specific modules, while deactivation revokes those permissions.
function _activatePolicy(Policy policy_) internal {
if (isPolicyActive(policy_)) revert Kernel_PolicyAlreadyActivated(address(policy_));
activePolicies.push(policy_);
getPolicyIndex[policy_] = activePolicies.length - 1;
Keycode[] memory dependencies = policy_.configureDependencies();
for (uint256 i = 0; i < dependencies.length; ++i) {
moduleDependents[dependencies[i]].push(policy_);
}
Permissions[] memory requests = policy_.requestPermissions();
_setPolicyPermissions(policy_, requests, true);
}
The Kernel uses a permission system to restrict the actions of Policies on Modules. Permissions are granted or revoked on a per-function basis, with function selectors specifying which functions the policy is allowed to execute.
function _setPolicyPermissions(
Policy policy_,
Permissions[] memory requests_,
bool grant_
) internal {
for (uint256 i = 0; i < requests_.length; ) {
Permissions memory request = requests_[i];
modulePermissions[request.keycode][policy_][request.funcSelector] = grant_;
emit PermissionsUpdated(request.keycode, policy_, request.funcSelector, grant_);
unchecked {
++i;
}
}
}
Modules store the protocol’s core logic and state. Modules do not execute business logic directly but instead respond to external calls from the Kernel, allowing Policies to interact with them based on granted permissions.
The permissioned
modifier ensures that only authorized Policies or the Kernel can execute certain functions.
modifier permissioned() {
if (msg.sender == address(kernel)) {
_;
return;
}
bool hasPermission = kernel.modulePermissions(KEYCODE(), Policy(msg.sender), msg.sig);
if (!hasPermission) {
revert Module_PolicyNotPermitted(msg.sender);
}
_;
}
Each Module has a unique Keycode and supports versioning. The KEYCODE()
function is used to identify the module, while the VERSION()
function allows for tracking updates.
Policies contain the business logic of the protocol and interact with Modules via the Kernel. Policies must request permission from the Kernel to interact with specific functions within Modules.
Policies declare their dependencies on Modules and the permissions they require to interact with those modules using the requestPermissions()
and configureDependencies()
functions.
function requestPermissions() external view virtual returns (Permissions[] memory requests) {}
function configureDependencies() external virtual returns (Keycode[] memory dependencies) {}
The BONDS module is a key component of the YieldFu protocol. It allows users to bond ETH or partner tokens in exchange for discounted YieldFu tokens, promoting liquidity for the treasury while offering discounted tokens to users. This module handles bonding, calculating payouts, and managing bond claims. The contract is tightly integrated with the Kernel, ensuring that permissions are checked and enforced through the protocol's governance.
- ETH and Partner Token Bonding: Users can bond ETH or partner tokens to receive discounted payouts in YieldFu tokens.
- Payout Calculations: Discount rates are configurable for both ETH and partner tokens.
- Bond Maturity: Bonds mature after a fixed period, and users can claim their payouts after the bond matures.
- Treasury Interaction: The contract withdraws payouts from the protocol's treasury to fulfill bond claims.
- Bond Management: Allows for pausing/unpausing bonding, adjusting discount rates, and setting maximum bond sizes and cooldown periods.
YieldFuToken public token
: The YieldFu token associated with the bond. Users receive this token as a payout for bonding.address public treasury
: The address of the treasury where ETH or partner tokens are sent upon bonding.uint256 public ethDiscount
: The discount applied to ETH bonding, represented in basis points (e.g.,150
represents a 15% discount).uint256 public partnerDiscount
: The discount applied to partner token bonding.uint256 public constant BOND_MATURITY
: The duration after which a bond matures (3 days).uint256 public maxBondSize
: The maximum size of any single bond.uint256 public bondCooldown
: The cooldown period between consecutive bond creations by the same user.
The BondInfo
struct stores details about each bond:
payout
: The amount of YieldFu tokens the user is entitled to receive.maturity
: The timestamp when the bond matures.claimed
: Whether the bond has been claimed.
struct BondInfo {
uint256 payout;
uint256 maturity;
bool claimed;
}
The contract emits several events to notify external observers about key actions within the contract:
-
BondCreated
: Emitted when a bond is successfully created.- Parameters:
user
,amount
,payout
- Parameters:
-
BondClaimed
: Emitted when a bond is claimed.- Parameters:
user
,payout
- Parameters:
-
DiscountChanged
: Emitted when the discount rates are changed.- Parameters:
isEth
,newDiscount
- Parameters:
-
BondingPaused
: Emitted when bonding is paused.- Parameters:
by
- Parameters:
-
BondingUnpaused
: Emitted when bonding is unpaused.- Parameters:
by
- Parameters:
-
MaxBondSizeChanged
: Emitted when the maximum bond size is changed.- Parameters:
newSize
- Parameters:
-
BondCooldownChanged
: Emitted when the cooldown period is changed.- Parameters:
newCooldown
- Parameters:
-
TreasuryChanged
: Emitted when the treasury address is changed.- Parameters:
newTreasury
- Parameters:
Users can bond ETH through the bondEth
function, which checks the bond size, calculates the payout based on the ETH discount, and transfers the ETH to the treasury.
function bondEth(address sender, uint256 value) external payable nonReentrant permissioned whenNotPaused {
value.validateBond(maxBondSize, lastBondTime[sender], bondCooldown);
uint256 payout = value.calculatePayout(ethDiscount);
_createBond(sender, payout);
(bool success, ) = payable(treasury).call{value: value}("");
require(success, "ETH transfer to treasury failed");
lastBondTime[sender] = block.timestamp;
}
Users can bond partner tokens using the bondPartnerToken
function. It validates the bond size, calculates the payout using the partner token discount, and transfers the partner tokens to the treasury.
function bondPartnerToken(address sender, address tokenAddress, uint256 amount) external nonReentrant permissioned whenNotPaused {
amount.validateBond(maxBondSize, lastBondTime[sender], bondCooldown);
uint256 payout = amount.calculatePayout(partnerDiscount);
_createBond(sender, payout);
IERC20 partnerToken = IERC20(tokenAddress);
bool success = partnerToken.transferFrom(sender, treasury, amount);
require(success, "Token transfer to treasury failed");
lastBondTime[sender] = block.timestamp;
}
Once a bond has matured, the user can call claimBond
to receive their YieldFu token payout from the treasury.
function claimBond() external permissioned whenNotPaused {
BondInfo storage bond = bonds[msg.sender];
require(block.timestamp >= bond.maturity, "Bond not matured");
resetBond(msg.sender);
kernel.executeAction(
Actions.ExecuteAction,
address(treasury),
abi.encodeWithSelector(
bytes4(keccak256("withdrawReserves(address,address,uint256)")),
msg.sender,
address(token),
bond.payout
)
);
}
changeDiscount(bool isEth, uint256 newDiscount)
: Allows adjusting the discount rates for ETH and partner token bonding.pauseBonding()
: Pauses all bonding operations.unpauseBonding()
: Unpauses bonding operations.setMaxBondSize(uint256 newSize)
: Sets the maximum bond size allowed.setBondCooldown(uint256 newCooldown)
: Sets the cooldown period between bond creations for a user.setTreasury(address newTreasury)
: Changes the treasury address to a new one.
The BONDS module interacts with the Kernel through permissioned functions. Only authorized Policies can call key functions, ensuring that only approved actions are taken on the bond contracts.
The permissioned modifier checks that the caller is either the Kernel or a Policy with the necessary permissions to call the function.
The BONDS module is tightly integrated with the treasury, where all bonded ETH and partner tokens are stored. When a user claims their bond, the treasury releases the YieldFu token payout using the withdrawReserves
function.
The DEBASE module implements a periodic debasement mechanism for the YieldFu token, enabling supply reduction by adjusting a debase index. This is done using a rebasing mechanism that decreases the effective supply of tokens over time based on a configurable debasement rate and interval. The debasement is only executed if the total supply meets a specified threshold, ensuring the debasement process doesn't go below a critical minimum supply.
- Adjustable Debasement: The debasement rate, interval, and minimum debasement threshold can be configured by authorized policies.
- Rebasing Mechanism: The YieldFu token is debased by updating its internal index, which affects the effective balance of all token holders.
- Pausable Operations: Debasement can be paused and unpaused by authorized policies to safeguard the protocol during emergency situations.
- Time-based Execution: Debasement can only be triggered after a configurable interval has passed since the last debase event.
YieldFuToken public yieldFu
: Reference to the YieldFu token, which is debased by this module.uint256 public debaseRate
: The debasement rate in basis points (bps), where 1 bp = 0.01%. For example,100
would represent a 1% debasement.uint256 public debaseInterval
: The time interval between each debase event, specified in seconds.uint256 public lastDebaseTime
: The timestamp of the last debase event.uint256 public minDebaseThreshold
: Minimum token supply required to perform a debase event.uint256 public debaseIndex
: A scaling factor representing the debase effect, initialized at1e18
(1:1 ratio).
The module emits several events during key actions:
-
Debase(uint256 newIndex)
: Emitted when a debasement occurs.- Parameters:
newIndex
(the updated debase index)
- Parameters:
-
DebaseRateChanged(uint256 newRate)
: Emitted when the debase rate is changed.- Parameters:
newRate
(the new debase rate)
- Parameters:
-
DebaseIntervalChanged(uint256 newInterval)
: Emitted when the debase interval is changed.- Parameters:
newInterval
(the new interval between debase events)
- Parameters:
-
MinDebaseThresholdChanged(uint256 newThreshold)
: Emitted when the minimum debase threshold is changed.- Parameters:
newThreshold
(the new minimum token supply threshold for debasing)
- Parameters:
-
DebasePaused(address indexed by)
: Emitted when debasing is paused.- Parameters:
by
(the address that paused debasing)
- Parameters:
-
DebaseUnpaused(address indexed by)
: Emitted when debasing is unpaused.- Parameters:
by
(the address that unpaused debasing)
- Parameters:
The module defines several custom errors that are reverted upon failure:
DEBASE_TooSoon()
: Reverted if an attempt is made to debase before the debase interval has passed.DEBASE_InvalidRate()
: Reverted if an invalid debase rate is provided.DEBASE_InvalidInterval()
: Reverted if the debase interval is set outside the allowable range (1 hour to 30 days).DEBASE_InvalidThreshold()
: Reverted if the minimum debase threshold is set to 0.DEBASE_BelowMinThreshold()
: Reverted if the total supply of YieldFu is below the minimum threshold during a debase attempt.
The core function of the module is the debase()
function, which reduces the effective supply of YieldFu by adjusting the debase index. This function is permissioned, meaning only authorized policies can call it, and it checks that the debase interval has passed since the last debase event.
function debase() external permissioned onlyAfterInterval whenNotPaused {
uint256 totalSupply = yieldFu.totalSupply();
if (totalSupply < minDebaseThreshold) {
revert DEBASE_BelowMinThreshold();
}
uint256 newDebaseIndex = (yieldFu.debaseIndex() * (10000 - debaseRate)) / 10000;
yieldFu.updateDebaseIndex(newDebaseIndex);
lastDebaseTime = block.timestamp;
emit Debase(newDebaseIndex);
}
Authorized policies can modify the parameters that control the debasement process using the following functions:
changeDebaseRate(uint256 newRate)
: Changes the debasement rate, with a maximum allowed value of 10% (1000 basis points).
function changeDebaseRate(uint256 newRate) external permissioned {
if (newRate > 1000) revert DEBASE_InvalidRate(); // Max 10% debasement
debaseRate = newRate;
emit DebaseRateChanged(newRate);
}
changeDebaseInterval(uint256 newInterval)
: Adjusts the time interval between debasements. The interval must be between 1 hour and 30 days.
function changeDebaseInterval(uint256 newInterval) external permissioned {
if (newInterval < 1 hours || newInterval > 30 days) revert DEBASE_InvalidInterval();
debaseInterval = newInterval;
emit DebaseIntervalChanged(newInterval);
}
changeMinDebaseThreshold(uint256 newThreshold)
: Sets the minimum token supply required for debasement to occur.
function changeMinDebaseThreshold(uint256 newThreshold) external permissioned {
if (newThreshold == 0) revert DEBASE_InvalidThreshold();
minDebaseThreshold = newThreshold;
emit MinDebaseThresholdChanged(newThreshold);
}
Debasement can be paused or unpaused by authorized policies using the pauseDebase
and unpauseDebase
functions, respectively. This provides the protocol with a safeguard to pause debasement during emergencies or when necessary for protocol management.
function pauseDebase() external permissioned {
_pause();
emit DebasePaused(msg.sender);
}
function unpauseDebase() external permissioned {
_unpause();
emit DebaseUnpaused(msg.sender);
}
getDebaseInfo()
: Returns current debase settings and the status of the debase process, including the current rate, interval, and whether debasement is paused.
function getDebaseInfo() external view returns (
uint256 currentRate,
uint256 currentInterval,
uint256 lastDebase,
uint256 nextDebase,
bool isPaused,
uint256 currentIndex
) {
return (
debaseRate,
debaseInterval,
lastDebaseTime,
lastDebaseTime + debaseInterval,
paused(),
debaseIndex
);
}
getEffectiveBalance(address account)
: Calculates the effective token balance for an account based on the debase index.
function getEffectiveBalance(address account) public view returns (uint256) {
return (yieldFu.balanceOf(account) * debaseIndex) / 1e18;
}
The DEBASE module interacts with the Kernel to ensure that only authorized policies can call key functions. The permissioned modifier is used to restrict access to sensitive functions like debasement, changing rates, intervals, and thresholds, as well as pausing or unpausing the debase process.
The MINTR module is responsible for managing the minting and burning of YieldFu tokens. It includes mechanisms for enforcing minting limits on a daily basis as well as per-policy minting caps. Authorized policies can request token minting and burning actions, and the module ensures that these operations adhere to the defined minting limits.
- Daily Minting Cap: A maximum number of tokens that can be minted daily, enforced across all policies.
- Policy-Specific Mint Limits: Each policy has its own minting limit, restricting how much it can mint in a given day.
- Token Burning: Authorized policies can burn YieldFu tokens.
- Logging and Safety: Includes event logging for mint and burn operations and a pausable function to halt operations when necessary.
YieldFuToken public immutable yieldFu
: Reference to the YieldFu token that will be minted or burned.uint256 public dailyMintCap
: The total number of tokens that can be minted across all policies in a single day.uint256 public mintedToday
: Tracks how many tokens have been minted so far today.uint256 public lastMintDay
: The timestamp of the last day when minting occurred.mapping(address => uint256) public policyLimits
: Specifies the maximum number of tokens that each policy is allowed to mint.mapping(address => uint256) public policyMinted
: Tracks how many tokens each policy has minted so far today.
The module emits several events during key actions:
Minted(address indexed to, uint256 amount)
: Emitted when tokens are successfully minted.- Parameters:
to
(the recipient address),amount
(number of tokens minted)
- Parameters:
Burned(address indexed from, uint256 amount)
: Emitted when tokens are burned.- Parameters:
from
(the address whose tokens were burned),amount
(number of tokens burned)
- Parameters:
MintCapChanged(uint256 newDailyMintCap)
: Emitted when the daily minting cap is changed.- Parameters:
newDailyMintCap
(the updated minting cap)
- Parameters:
MintLimitChanged(address indexed policy, uint256 newLimit)
: Emitted when a policy's minting limit is changed.- Parameters:
policy
(the policy whose limit is changed),newLimit
(the updated mint limit)
- Parameters:
The module defines custom errors for various failure scenarios:
MINTR_Unauthorized()
: Reverted if an unauthorized entity attempts to mint or burn tokens.MINTR_DailyCapExceeded()
: Reverted if a mint request would exceed the daily minting cap.MINTR_PolicyLimitExceeded()
: Reverted if a mint request would exceed the policy's specific minting limit.MINTR_InvalidMintCap()
: Reverted if an attempt is made to set the daily mint cap to zero.MINTR_InvalidMintLimit()
: Reverted if an invalid mint limit (e.g., zero) is provided for a policy.
The core function of this module is mint()
, which allows authorized policies to mint new YieldFu tokens. This function checks that the requested mint amount is within the daily mint cap and the policy's specific limit before proceeding with the mint.
function mint(address policy_, address to_, uint256 amount_) external permissioned {
_updateDailyMint(); // Reset daily limits if it's a new day
_checkMintLimits(policy_, amount_); // Ensure minting stays within limits
yieldFu.mint(to_, amount_); // Proceed with minting tokens
policyMinted[policy_] += amount_; // Update policy minted amount
mintedToday += amount_; // Update total minted today
emit Minted(to_, amount_);
}
The burn()
function allows authorized policies to burn tokens from a specified address. This is useful for reducing the total token supply or correcting mistakes.
function burn(address from, uint256 amount) external permissioned whenNotPaused {
yieldFu.burnFrom(from, amount);
emit Burned(from, amount);
}
Each policy has a predefined limit that restricts how many tokens it can mint. These limits can be set or updated by authorized policies via the setPolicyLimit()
function.
function setPolicyLimit(address policy, uint256 limit) external permissioned {
if (limit == 0) revert MINTR_InvalidMintLimit();
policyLimits[policy] = limit;
emit MintLimitChanged(policy, limit);
}
The changeDailyMintCap()
function allows the daily minting cap to be changed. This cap defines the maximum number of tokens that can be minted by all policies combined in a single day.
function changeDailyMintCap(uint256 newCap) external permissioned {
if (newCap == 0) revert MINTR_InvalidMintCap();
dailyMintCap = newCap;
emit MintCapChanged(newCap);
}
_updateDailyMint()
: Resets the daily minting amount if the current day is different from the last recorded mint day.
function _updateDailyMint() internal {
uint256 currentDay = block.timestamp / 1 days;
if (currentDay > lastMintDay) {
mintedToday = 0; // Reset daily mint
lastMintDay = currentDay;
}
}
_checkMintLimits()
: Ensures that the requested minting amount is within the policy's limit and the daily mint cap.
function _checkMintLimits(address policy, uint256 amount) internal view {
if (mintedToday + amount > dailyMintCap) {
revert MINTR_DailyCapExceeded();
}
if (policyMinted[policy] + amount > policyLimits[policy]) {
revert MINTR_PolicyLimitExceeded();
}
}
getPolicyInfo()
: Returns the minting limit and the amount minted for a given policy.
function getPolicyInfo(address policy) external view returns (uint256 mintLimit, uint256 mintedAmount) {
return (policyLimits[policy], policyMinted[policy]);
}
getDailyMintInfo()
: Returns the daily mint cap, the number of tokens minted today, and the remaining amount that can still be minted.
function getDailyMintInfo() external view returns (uint256 cap, uint256 minted, uint256 remaining) {
return (dailyMintCap, mintedToday, dailyMintCap - mintedToday);
}
The MINTR module ensures that only authorized policies can call minting or burning functions using the permissioned modifier. The Kernel enforces permissions, ensuring that only policies with the necessary rights can request new tokens or burn existing ones.
Here is the detailed documentation for the STAKE contract:
The STAKE module is responsible for handling the staking of YieldFu tokens. Users can stake tokens to earn rewards at a base Annual Percentage Yield (APY), with the possibility of temporary APY boosts. The contract also includes mechanisms for penalizing early unstaking, as well as imposing maximum staking caps and cooldown periods to control the flow of tokens.
- Staking and Unstaking: Users can stake YieldFu tokens and earn rewards over time, based on a configurable APY.
- Reward Calculation: Rewards are calculated per token staked and are distributed periodically. A user’s rewards are updated each time they interact with the staking module.
- Cooldowns: A cooldown period is enforced to prevent immediate withdrawal after staking, with penalties for early unstaking.
- APY Management: The base and boosted APYs can be adjusted, allowing for reward increases for specific durations.
- Max Stake Cap: Limits the total amount of tokens that can be staked in the system.
- Early Unstaking Penalty: If users attempt to unstake before the cooldown period, a penalty (slash rate) is applied to the withdrawn amount.
- Pausing: The contract can be paused in case of emergency, suspending staking and unstaking activities.
YieldFuToken public token
: The YieldFu token that users will stake to earn rewards.uint256 public baseAPY
: The base Annual Percentage Yield (APY) that users earn when they stake tokens.uint256 public boostedAPY
: The boosted APY, applied for a limited time to increase rewards.uint256 public boostEndTime
: The time when the boosted APY will expire and revert to the base APY.uint256 public cooldownPeriod
: The cooldown period (in seconds) after staking, during which users cannot unstake without penalty.uint256 public earlyUnstakeSlashRate
: The percentage of tokens slashed when a user unstakes before the cooldown period ends.uint256 public maxStakeCap
: The maximum amount of tokens that can be staked in total.uint256 public totalStaked
: The total number of tokens currently staked in the system.uint256 public rewardRate
: The reward rate, which determines how rewards accumulate over time.uint256 public lastUpdateTime
: The last time the reward calculation was updated.uint256 public rewardPerTokenStored
: Tracks the cumulative rewards per staked token.
uint256 amount
: The number of tokens the user has staked.uint256 lastStakeTime
: The last time the user staked tokens, used to calculate cooldowns.uint256 userRewardPerTokenPaid
: Tracks the reward per token that the user has already been paid.uint256 rewards
: The total rewards the user has earned but not yet claimed.
mapping(address => StakeInfo) public stakes
: Stores the staking information for each user.
Stake(address indexed user, uint256 amount)
: Emitted when a user stakes tokens.- Parameters:
user
(the address staking tokens),amount
(the number of tokens staked)
- Parameters:
Unstake(address indexed user, uint256 amount)
: Emitted when a user unstakes tokens.- Parameters:
user
(the address unstaking tokens),amount
(the number of tokens unstaked)
- Parameters:
APYChanged(uint256 newBaseAPY, uint256 newBoostedAPY)
: Emitted when the APY is updated.- Parameters:
newBaseAPY
(the updated base APY),newBoostedAPY
(the updated boosted APY)
- Parameters:
APYBoosted(uint256 boostEndTime)
: Emitted when the APY is boosted.- Parameters:
boostEndTime
(the timestamp when the boost will expire)
- Parameters:
CooldownSet(uint256 newCooldown)
: Emitted when the cooldown period is updated.- Parameters:
newCooldown
(the updated cooldown period)
- Parameters:
EarlyUnstakeSlashSet(uint256 newSlashRate)
: Emitted when the early unstake penalty (slash rate) is updated.- Parameters:
newSlashRate
(the updated slash rate)
- Parameters:
MaxStakeCapSet(uint256 newMaxStakeCap)
: Emitted when the maximum staking cap is updated.- Parameters:
newMaxStakeCap
(the updated staking cap)
- Parameters:
The module defines several custom errors to handle failure scenarios:
STAKE_ZeroAmount()
: Reverted if the stake or unstake amount is zero.STAKE_InsufficientBalance()
: Reverted if a user tries to unstake more tokens than they have staked.STAKE_CooldownNotMet()
: Reverted if a user tries to unstake before the cooldown period has passed.STAKE_MaxStakeCapExceeded()
: Reverted if the total staked amount exceeds the maximum staking cap.STAKE_InvalidAPY()
: Reverted if an invalid APY (above 100%) is set.STAKE_InvalidCooldown()
: Reverted if an invalid cooldown period (above 30 days) is set.STAKE_InvalidSlashRate()
: Reverted if an invalid slash rate (above 50%) is set.
Users can stake tokens using the stake()
function. This function transfers tokens from the user to the contract and updates their staking balance. It also checks that the stake amount does not exceed the maximum stake cap.
function stake(address from, uint256 amount) external whenNotPaused updateReward(from) {
if (amount == 0) revert STAKE_ZeroAmount();
if (totalStaked + amount > maxStakeCap) revert STAKE_MaxStakeCapExceeded();
// Transfer and update balances
...
}
The unstake()
function allows users to withdraw staked tokens, with or without an early unstake penalty depending on whether the cooldown period has passed.
function unstake(address from, uint256 amount) external nonReentrant whenNotPaused permissioned updateReward(from) {
StakeInfo storage userStake = stakes[from];
if (block.timestamp < userStake.lastStakeTime + cooldownPeriod) {
slashAmount = (amount * earlyUnstakeSlashRate) / 10000;
}
// Transfer and burn slashed tokens
...
}
Rewards are calculated based on the APY and the amount of time that has passed since the user staked their tokens.
rewardPerToken()
: Calculates the reward per token since the last update.earned()
: Calculates the total rewards earned by a user based on the staked amount and the reward per token.
function rewardPerToken() public view returns (uint256) {
if (totalStaked == 0) {
return rewardPerTokenStored;
}
uint256 timeElapsed = block.timestamp - lastUpdateTime;
return rewardPerTokenStored + (timeElapsed * rewardRate * 1e18 / totalStaked);
}
The base and boosted APYs can be adjusted via the setAPY()
function. Additionally, the APY can be boosted for a specific duration using boostAPY()
.
function setAPY(uint256 newBaseAPY, uint256 newBoostedAPY) external permissioned updateReward(address(0)) {
if (newBaseAPY > 10000 || newBoostedAPY > 10000) revert STAKE_InvalidAPY();
baseAPY = newBaseAPY;
boostedAPY = newBoostedAPY;
rewardRate = getCurrentAPY() * 1e18 / (365 days * 1e4);
...
}
Users must wait until the cooldown period has passed before unstaking without penalty. If they unstake early, a portion of their tokens is slashed (burned).
function setCooldownPeriod(uint256 newCooldown) external permissioned {
if (newCooldown > 30 days) revert STAKE_InvalidCooldown();
cooldownPeriod = newCooldown;
}
function setEarlyUnstakeSlashRate(uint256 newSlashRate) external permissioned {
if (newSlashRate > 5000) revert STAKE_InvalidSlashRate();
earlyUnstakeSlashRate = newSlashRate;
}
getStakeInfo()
: Returns the staking information for a user, including the staked amount, pending rewards, and whether they are in the cooldown period.
function getStakeInfo(address user) external view returns (uint256 stakedAmount, uint256 pendingRewards, uint256 lastStakeTime, bool inCooldown) {
...
}
- **`getPending
Reward()`**: Returns the pending rewards for a user based on their staked tokens and the reward rate.
function getPendingReward(address user) external view returns (uint256) {
uint256 reward = earned(user);
return reward;
}
The contract includes functions to pause and unpause the staking module. When paused, no staking or unstaking actions can be performed.
function pause() external permissioned {
_pause();
}
function unpause() external permissioned {
_unpause();
}