sFRAX/sfrxUSD
93 lines
// SPDX-License-Identifier: AGPL-3.0-only
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.21;
pragma solidity ^0.8.21;
// ====================================================================
// ====================================================================
// | ______ _______ |
// | ______ _______ |
// | / _____________ __ __ / ____(_____ ____ _____ ________ |
// | / _____________ __ __ / ____(_____ ____ _____ ________ |
// | / /_ / ___/ __ `| |/_/ / /_ / / __ \/ __ `/ __ \/ ___/ _ \ |
// | / /_ / ___/ __ `| |/_/ / /_ / / __ \/ __ `/ __ \/ ___/ _ \ |
// | / __/ / / / /_/ _> < / __/ / / / / / /_/ / / / / /__/ __/ |
// | / __/ / / / /_/ _> < / __/ / / / / / /_/ / / / / /__/ __/ |
// | /_/ /_/ \__,_/_/|_| /_/ /_/_/ /_/\__,_/_/ /_/\___/\___/ |
// | /_/ /_/ \__,_/_/|_| /_/ /_/_/ /_/\__,_/_/ /_/\___/\___/ |
// | |
// | |
// ====================================================================
// ====================================================================
// ============================ StakedFrax ============================
// =========================== StakedFrxUSD ===========================
// ====================================================================
// ====================================================================
// Frax Finance: https://github.com/FraxFinance
// Frax Finance: https://github.com/FraxFinance
import { Timelock2Step } from "frax-std/access-control/v2/Timelock2Step.sol";
import { Timelock2Step } from "frax-std/access-control/v2/Timelock2Step.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { IERC20 } from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import { SafeCastLib } from "solmate/utils/SafeCastLib.sol";
import { SafeCastLib } from "solmate/utils/SafeCastLib.sol";
import { LinearRewardsErc4626, ERC20 } from "./LinearRewardsErc4626.sol";
import { LinearRewardsErc4626, ERC20 } from "./LinearRewardsErc4626.sol";
/// @title Staked Frax
/// @title Staked frxUSD
/// @notice A ERC4626 Vault implementation with linear rewards, rewards can be capped
/// @notice A ERC4626 Vault implementation with linear rewards, rewards can be capped
contract StakedFrax is LinearRewardsErc4626, Timelock2Step {
contract StakedFrxUSD is LinearRewardsErc4626, Timelock2Step {
using SafeCastLib for *;
using SafeCastLib for *;
/// @notice The maximum amount of rewards that can be distributed per second per 1e18 asset
/// @notice The maximum amount of rewards that can be distributed per second per 1e18 asset
uint256 public maxDistributionPerSecondPerAsset;
uint256 public maxDistributionPerSecondPerAsset;
uint256 private initializeStage=2;
/// @param _underlying The erc20 asset deposited
/// @param _underlying The erc20 asset deposited
/// @param _name The name of the vault
/// @param _name The name of the vault
/// @param _symbol The symbol of the vault
/// @param _symbol The symbol of the vault
/// @param _rewardsCycleLength The length of the rewards cycle in seconds
/// @param _rewardsCycleLength The length of the rewards cycle in seconds
/// @param _maxDistributionPerSecondPerAsset The maximum amount of rewards that can be distributed per second per 1e18 asset
/// @param _maxDistributionPerSecondPerAsset The maximum amount of rewards that can be distributed per second per 1e18 asset
/// @param _timelockAddress The address of the timelock/owner contract
/// @param _timelockAddress The address of the timelock/owner contract
constructor(
constructor(
IERC20 _underlying,
IERC20 _underlying,
string memory _name,
string memory _name,
string memory _symbol,
string memory _symbol,
uint32 _rewardsCycleLength,
uint32 _rewardsCycleLength,
uint256 _maxDistributionPerSecondPerAsset,
uint256 _maxDistributionPerSecondPerAsset,
address _timelockAddress
address _timelockAddress
)
)
LinearRewardsErc4626(ERC20(address(_underlying)), _name, _symbol, _rewardsCycleLength)
LinearRewardsErc4626(ERC20(address(_underlying)), _name, _symbol, _rewardsCycleLength)
Timelock2Step(_timelockAddress)
Timelock2Step(_timelockAddress)
{
{
maxDistributionPerSecondPerAsset = _maxDistributionPerSecondPerAsset;
maxDistributionPerSecondPerAsset = _maxDistributionPerSecondPerAsset;
}
}
/// @notice The ```SetMaxDistributionPerSecondPerAsset``` event is emitted when the maxDistributionPerSecondPerAsset is set
/// @notice The ```SetMaxDistributionPerSecondPerAsset``` event is emitted when the maxDistributionPerSecondPerAsset is set
/// @param oldMax The old maxDistributionPerSecondPerAsset value
/// @param oldMax The old maxDistributionPerSecondPerAsset value
/// @param newMax The new maxDistributionPerSecondPerAsset value
/// @param newMax The new maxDistributionPerSecondPerAsset value
event SetMaxDistributionPerSecondPerAsset(uint256 oldMax, uint256 newMax);
event SetMaxDistributionPerSecondPerAsset(uint256 oldMax, uint256 newMax);
error AlreadyInitialized();
function initialize(string memory _name,
string memory _symbol,
uint256 _maxDistributionPerSecondPerAsset,
address _timelockAddress) external {
if (initializeStage!=0) revert AlreadyInitialized();
initializeStage++;
name = _name;
symbol = _symbol;
maxDistributionPerSecondPerAsset = _maxDistributionPerSecondPerAsset;
timelockAddress = _timelockAddress;
// initialize rewardsCycleEnd value
// NOTE: normally distribution of rewards should be done prior to _syncRewards but in this case we know there are no users or rewards yet.
_syncRewards();
// initialize lastRewardsDistribution value
_distributeRewards();
}
/// @notice The ```initializeRewardsCycleData``` function initializes the rewards cycle data
/// @dev This function can only be called once
/// @param _pricePerShare The price per share
/// @param _maxDistributionPerSecondPerAsset The maximum amount of rewards that can be distributed per second per 1e18 asset
/// @param _cycleEnd The end of the rewards cycle
/// @param _lastSync The last sync time
/// @param _rewardCycleAmount The reward cycle amount
function initializeRewardsCycleData(
uint256 _pricePerShare,
uint256 _maxDistributionPerSecondPerAsset,
uint40 _cycleEnd,
uint40 _lastSync,
uint216 _rewardCycleAmount
) external {
if (initializeStage!=1) revert AlreadyInitialized();
initializeStage++;
storedTotalAssets = _pricePerShare*totalSupply/PRECISION;
maxDistributionPerSecondPerAsset = _maxDistributionPerSecondPerAsset;
rewardsCycleData.cycleEnd = _cycleEnd;
rewardsCycleData.lastSync = _lastSync;
rewardsCycleData.rewardCycleAmount = _rewardCycleAmount;
}
/// @notice The ```setMaxDistributionPerSecondPerAsset``` function sets the maxDistributionPerSecondPerAsset
/// @notice The ```setMaxDistributionPerSecondPerAsset``` function sets the maxDistributionPerSecondPerAsset
/// @dev This function can only be called by the timelock, caps the value to type(uint64).max
/// @dev This function can only be called by the timelock, caps the value to type(uint64).max
/// @param _maxDistributionPerSecondPerAsset The maximum amount of rewards that can be distributed per second per 1e18 asset
/// @param _maxDistributionPerSecondPerAsset The maximum amount of rewards that can be distributed per second per 1e18 asset
function setMaxDistributionPerSecondPerAsset(uint256 _maxDistributionPerSecondPerAsset) external {
function setMaxDistributionPerSecondPerAsset(uint256 _maxDistributionPerSecondPerAsset) external {
_requireSenderIsTimelock();
_requireSenderIsTimelock();
syncRewardsAndDistribution();
syncRewardsAndDistribution();
// NOTE: prevents bricking the contract via overflow
// NOTE: prevents bricking the contract via overflow
if (_maxDistributionPerSecondPerAsset > type(uint64).max) {
if (_maxDistributionPerSecondPerAsset > type(uint64).max) {
_maxDistributionPerSecondPerAsset = type(uint64).max;
_maxDistributionPerSecondPerAsset = type(uint64).max;
}
}
emit SetMaxDistributionPerSecondPerAsset({
emit SetMaxDistributionPerSecondPerAsset({
oldMax: maxDistributionPerSecondPerAsset,
oldMax: maxDistributionPerSecondPerAsset,
newMax: _maxDistributionPerSecondPerAsset
newMax: _maxDistributionPerSecondPerAsset
});
});
maxDistributionPerSecondPerAsset = _maxDistributionPerSecondPerAsset;
maxDistributionPerSecondPerAsset = _maxDistributionPerSecondPerAsset;
}
}
/// @notice The ```calculateRewardsToDistribute``` function calculates the amount of rewards to distribute based on the rewards cycle data and the time passed
/// @notice The ```calculateRewardsToDistribute``` function calculates the amount of rewards to distribute based on the rewards cycle data and the time passed
/// @param _rewardsCycleData The rewards cycle data
/// @param _rewardsCycleData The rewards cycle data
/// @param _deltaTime The time passed since the last rewards distribution
/// @param _deltaTime The time passed since the last rewards distribution
/// @return _rewardToDistribute The amount of rewards to distribute
/// @return _rewardToDistribute The amount of rewards to distribute
function calculateRewardsToDistribute(
function calculateRewardsToDistribute(
RewardsCycleData memory _rewardsCycleData,
RewardsCycleData memory _rewardsCycleData,
uint256 _deltaTime
uint256 _deltaTime
) public view override returns (uint256 _rewardToDistribute) {
) public view override returns (uint256 _rewardToDistribute) {
_rewardToDistribute = super.calculateRewardsToDistribute({
_rewardToDistribute = super.calculateRewardsToDistribute({
_rewardsCycleData: _rewardsCycleData,
_rewardsCycleData: _rewardsCycleData,
_deltaTime: _deltaTime
_deltaTime: _deltaTime
});
});
// Cap rewards
// Cap rewards
uint256 _maxDistribution = (maxDistributionPerSecondPerAsset * _deltaTime * storedTotalAssets) / PRECISION;
uint256 _maxDistribution = (maxDistributionPerSecondPerAsset * _deltaTime * storedTotalAssets) / PRECISION;
if (_rewardToDistribute > _maxDistribution) {
if (_rewardToDistribute > _maxDistribution) {
_rewardToDistribute = _maxDistribution;
_rewardToDistribute = _maxDistribution;
}
}
}
}
}
}