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/wrappers/MorphoStrategyWrapper.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;
import {StrategyWrapper, IERC20Metadata} from "src/wrappers/StrategyWrapper.sol";
import {IMorpho, Id} from "@shared/src/interfaces/IMorpho.sol";
import {IRewardVault} from "src/interfaces/IRewardVault.sol";
contract MorphoStrategyWrapper is StrategyWrapper {
constructor(IRewardVault rewardVault, address lendingProtocol, address _owner)
StrategyWrapper(rewardVault, lendingProtocol, _owner)
{}
function initialize(bytes32 marketId) public override onlyOwner {
super.initialize(marketId);
require(
IMorpho(LENDING_PROTOCOL).idToMarketParams(Id.wrap(marketId)).collateralToken == address(this),
InvalidMarket()
);
}
///////////////////////////////////////////////////////////////
// --- INTERNAL FUNCTIONS
///////////////////////////////////////////////////////////////
function _supplyLendingProtocol(address account, uint256 amount) internal override {
IMorpho(LENDING_PROTOCOL)
.supplyCollateral(IMorpho(LENDING_PROTOCOL).idToMarketParams(Id.wrap(lendingMarketId)), amount, account, "");
}
function _withdrawLendingProtocol(address account, uint256 amount) internal override {
IMorpho(LENDING_PROTOCOL)
.withdrawCollateral(
IMorpho(LENDING_PROTOCOL).idToMarketParams(Id.wrap(lendingMarketId)), amount, account, account
);
}
function _getWorkingBalance(address account) internal view override returns (uint256) {
return IMorpho(LENDING_PROTOCOL).position(Id.wrap(lendingMarketId), account).collateral;
}
///////////////////////////////////////////////////////////////
// --- GETTERS FUNCTIONS
///////////////////////////////////////////////////////////////
function name() public view override onlyInitialized returns (string memory) {
return string.concat("Stake DAO ", IERC20Metadata(REWARD_VAULT.asset()).name());
}
function symbol() public view override onlyInitialized returns (string memory) {
return string.concat("stakedao-", IERC20Metadata(REWARD_VAULT.asset()).symbol());
}
}
"
},
"src/wrappers/StrategyWrapper.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;
import {Ownable} from "@openzeppelin/contracts/access/Ownable.sol";
import {ERC20} from "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import {IERC20} from "@openzeppelin/contracts/interfaces/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/interfaces/IERC20Metadata.sol";
import {SafeERC20} from "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
import {Math} from "@openzeppelin/contracts/utils/math/Math.sol";
import {IRewardVault} from "src/interfaces/IRewardVault.sol";
import {ReentrancyGuardTransient} from "@openzeppelin/contracts/utils/ReentrancyGuardTransient.sol";
import {IStrategyWrapper} from "src/interfaces/IStrategyWrapper.sol";
interface IAccountant {
/// @notice Tracks reward and supply data for each vault.
/// @dev Packed struct to minimize storage costs (3 storage slots).
/// This structure maintains the core accounting state for reward distribution.
struct VaultData {
uint256 integral; // Cumulative reward per token (scaled by SCALING_FACTOR)
uint128 supply; // Total supply of vault tokens
uint128 feeSubjectAmount; // Amount of rewards subject to fees
uint128 totalAmount; // Total reward amount including fee-exempt rewards
uint128 netCredited; // Net rewards already credited to users (after fees)
uint128 reservedHarvestFee; // Harvest fees reserved but not yet paid out
uint128 reservedProtocolFee; // Protocol fees reserved but not yet accrued
}
/// @notice Tracks individual user positions within a vault.
/// @dev Integral tracking enables O(1) reward calculations by storing the last known integral
/// value for each user, allowing efficient computation of rewards earned since last update.
struct AccountData {
uint128 balance; // User's token balance in the vault
uint256 integral; // Last integral value when user's rewards were updated
uint256 pendingRewards; // Rewards earned but not yet claimed
}
function claim(address[] calldata _gauges, bytes[] calldata harvestData) external;
function vaults(address vault) external view returns (VaultData memory);
function accounts(address vault, address account) external view returns (AccountData memory);
function REWARD_TOKEN() external view returns (address);
function SCALING_FACTOR() external view returns (uint128);
}
/// @title Stake DAO Strategy Wrapper
/// @notice Non-transferable ERC20 wrapper for Stake DAO RewardVault shares. It is designed for use as collateral in lending markets.
/// @dev - Allows users to deposit RewardVault shares or underlying LP tokens, and receive non-transferable tokens (1:1 ratio)
/// - While the ERC20 tokens are held (even as collateral in lending markets), users can claim both main protocol rewards and extra rewards
/// - Handles the edge case where the main reward token is also listed as an extra reward
/// - Integrates with Stake DAO's reward and checkpointing logic to ensure users always receive the correct rewards, regardless of the custody
/// @dev CRITICAL SECURITY ASSUMPTION: This contract assumes that no external
/// contract can claim rewards on behalf of this contract. The RewardVault
/// and Accountant must never implement claim-on-behalf-of functionality
/// that could target this contract, as it would break internal accounting.
/// @author Stake DAO
/// @custom:contact contact@stakedao.org
/// @custom:github https://github.com/stake-dao/contracts-monorepo
contract StrategyWrapper is ERC20, IStrategyWrapper, Ownable, ReentrancyGuardTransient {
using SafeERC20 for IERC20;
///////////////////////////////////////////////////////////////
// --- IMMUTABLES/CONSTANTS
///////////////////////////////////////////////////////////////
/// @notice The slot of the main reward token in the user checkpoint
/// @dev In order to optimize the storage, the main reward token is tracked in the same
/// mapping as the extra reward tokens by using the `address(0)` slot.
/// This internal value that is never supposed to be exposed must be used to read/write
/// the main reward token state.
/// @dev `address(0)` has been chosen to avoid collisions with the extra reward tokens.
address internal constant MAIN_REWARD_TOKEN_SLOT = address(0);
/// @notice Precision factor for reward calculations (18 decimals)
uint256 internal constant PRECISION = 1e18;
/// @notice Gauge backing the wrapped RewardVault
address internal immutable GAUGE;
/// @notice The address of the accountant contract
IAccountant internal immutable ACCOUNTANT;
/// @dev Precision used by Accountant.integral (usually 1e27)
uint128 internal immutable ACCOUNTANT_SCALING_FACTOR;
/// @notice The address of the token that is being wrapped
IRewardVault public immutable REWARD_VAULT;
/// @notice The address of the lending protocol
address public immutable LENDING_PROTOCOL;
/// @notice Reward token emitted by the Accountant (e.g. CRV)
IERC20 public immutable MAIN_REWARD_TOKEN;
/// @notice Tracks user deposit data and reward checkpoints
/// @param balance User's wrapped-share balance
/// @param rewardPerTokenPaid Last per-token accumulator seen
struct UserCheckpoint {
uint256 balance;
mapping(address token => uint256 amount) rewardPerTokenPaid;
}
/// @notice The ID of the lending market this token is expected to interact with
bytes32 public lendingMarketId;
/// @notice User checkpoint data including his deposit balance and the amount of rewards he has claimed for each token
/// Both the main reward token and the extra reward tokens are tracked in the same mapping.
/// @dev The main reward token is tracked in `rewardPerTokenPaid[address(0)]`
mapping(address user => UserCheckpoint checkpoint) private userCheckpoints;
/// @notice Accumulated rewards per token. Track the extra reward tokens only.
/// @dev The main reward token is tracked by fetching the latest value from the Accountant.
mapping(address token => uint256 amount) public extraRewardPerToken;
/// @dev The purpose of this transient variable is to ensure that this token can only be
/// used as collateral in the predefined lending market. When this variable is set to
/// false (the default value), transfers to the lending protocol are unauthorized.
/// The only way to change this variable to true is by entering the flow of the deposit
/// functions in this contract.
bool private transient depositUnlocked;
///////////////////////////////////////////////////////////////
// --- EVENTS/ERRORS
///////////////////////////////////////////////////////////////
event Claimed(address indexed token, address indexed account, uint256 amount);
event Deposited(address indexed user, uint256 amount, bytes32 marketId);
event Withdrawn(address indexed user, uint256 amount);
event Liquidated(address indexed liquidator, address indexed victim, uint256 amount);
event MarketAdded(bytes32 marketId);
error ZeroAmount();
error ZeroAddress();
error InvalidTokens();
error InvalidMarket();
error MarketAlreadySet();
error WrapperNotInitialized();
error InvalidLiquidation();
/// @dev Only the lending protocol can transfer in/out the wrapped tokens
error TransferUnauthorized();
/// @dev Deposit collateral to the lending protocol is only allowed through the StrategyWrapperContract
/// Do not interact directly with the lending protocol to deposit collateral. Instead, call one of the `deposit`
/// functions defined in the StrategyWrapperContract instead.
error InvalidDepositFlow();
/// @dev The caller must call `setAuthorization` on the lending protocol to authorize this contract to withdraw on behalf of the caller
error NotAuthorizedToWithdrawCollateral();
/// @param rewardVault The reward vault contract
/// @param lendingProtocol The lending protocol contract
/// @param _owner The owner of the contract
/// @custom:reverts ZeroAddress if the reward vault or _owner address are the zero address
constructor(IRewardVault rewardVault, address lendingProtocol, address _owner) ERC20("", "") Ownable(_owner) {
require(lendingProtocol != address(0), ZeroAddress());
IAccountant accountant = IAccountant(address(rewardVault.ACCOUNTANT()));
// Store the immutable variables
GAUGE = rewardVault.gauge();
ACCOUNTANT = accountant;
REWARD_VAULT = rewardVault;
MAIN_REWARD_TOKEN = IERC20(accountant.REWARD_TOKEN());
ACCOUNTANT_SCALING_FACTOR = accountant.SCALING_FACTOR();
LENDING_PROTOCOL = lendingProtocol;
// Approve the asset token to the reward vault and the wrapped token to the lending protocol
IERC20(rewardVault.asset()).approve(address(rewardVault), type(uint256).max);
_approve(address(this), lendingProtocol, type(uint256).max, true);
}
///////////////////////////////////////////////////////////////
// --- INITIALIZATION
///////////////////////////////////////////////////////////////
/// @notice Set the market ID for the wrapper
/// @param marketId The ID of the market to set
/// @custom:reverts MarketAlreadySet if the market ID is already set
/// @custom:reverts InvalidMarket if the market ID is the invalid
function initialize(bytes32 marketId) public virtual onlyOwner {
require(lendingMarketId == bytes32(0), MarketAlreadySet());
require(marketId != bytes32(0), InvalidMarket());
lendingMarketId = marketId;
emit MarketAdded(marketId);
}
modifier onlyInitialized() {
require(lendingMarketId != bytes32(0), WrapperNotInitialized());
_;
}
///////////////////////////////////////////////////////////////
// --- DEPOSIT
///////////////////////////////////////////////////////////////
/// @notice Wrap **all** RewardVault shares owned by the caller into wrapper tokens (1:1 ratio)
/// @dev Use this when you ALREADY hold RewardVault shares.
/// If you already have wrapped tokens, any pending main and extra rewards will be
/// automatically claimed before the new deposit to prevent reward loss.
/// @custom:reverts ZeroAmount If the caller's share balance is zero
function depositShares() external {
depositShares(REWARD_VAULT.balanceOf(msg.sender));
}
/// @notice Wrap `amount` RewardVault shares into wrapper tokens (1:1 ratio)
/// @param amount Number of RewardVault shares the caller wants to wrap
/// @dev Use this when you ALREADY hold RewardVault shares and wish to wrap a specific portion of them.
/// If you already have wrapped tokens, any pending main and extra rewards will be
/// automatically claimed before the new deposit to prevent reward loss.
/// @custom:reverts ZeroAmount If `amount` is zero
function depositShares(uint256 amount) public nonReentrant onlyInitialized {
require(amount > 0, ZeroAmount());
// 1. Transfer caller's shares to this contract (checkpoint)
IERC20(address(REWARD_VAULT)).safeTransferFrom(msg.sender, address(this), amount);
_deposit(msg.sender, amount);
}
/// @notice Convert **all** underlying LP tokens owned by the caller into RewardVault
/// shares and immediately wrap those shares into wrapper tokens (LP → share → wrapper)
/// @dev Use this when you DO NOT own RewardVault shares yet, only the raw LP tokens.
/// If you already have wrapped tokens, any pending main and extra rewards will be
/// automatically claimed before the new deposit to prevent reward loss.
/// @custom:reverts ZeroAmount If the caller's LP balance is zero
function depositAssets() external {
depositAssets(IERC20(REWARD_VAULT.asset()).balanceOf(msg.sender));
}
/// @notice Convert `amount` underlying LP tokens into RewardVault shares and
/// immediately wrap those shares into wrapper tokens (LP → share → wrapper)
/// @param amount Amount of underlying LP tokens provided by the caller
/// @dev Use this when you DO NOT own RewardVault shares yet, only the raw LP tokens,
/// and wish to wrap a specific portion of them.
/// If you already have wrapped tokens, any pending main and extra rewards will be
/// automatically claimed before the new deposit to prevent reward loss.
/// @custom:reverts ZeroAmount If `amount` is zero
function depositAssets(uint256 amount) public nonReentrant onlyInitialized {
require(amount > 0, ZeroAmount());
// 1. Get RewardVault's shares by depositing the underlying protocol LP tokens (checkpoint)
IERC20(REWARD_VAULT.asset()).safeTransferFrom(msg.sender, address(this), amount);
uint256 shares = REWARD_VAULT.deposit(amount, address(this), address(this));
_deposit(msg.sender, shares);
}
function _deposit(address account, uint256 amount) internal {
// 1. Update the user checkpoint
_updateUserCheckpoint(account, amount);
// 2. Mint wrapped tokens (1:1)
_update(address(0), address(this), amount);
// 3. Supply the minted tokens as collateral to the expected lending market
depositUnlocked = true;
_supplyLendingProtocol(account, amount);
depositUnlocked = false;
emit Deposited(account, amount, lendingMarketId);
}
///////////////////////////////////////////////////////////////
// --- WITHDRAW
///////////////////////////////////////////////////////////////
/// @notice Withdraws the collateral directly from the lending protocol for the caller
/// @param amount Amount of wrapped tokens put in collateral to withdraw
/// @custom:reverts NotAuthorizedToWithdrawCollateral if this contract is not authorized to withdraw
/// from the lending protocol on behalf of the caller
/// The caller must call `setAuthorization` on the lending protocol to authorize this
/// contract to withdraw on behalf of the caller
function withdrawCollateral(uint256 amount) external virtual {
// Withdraw the collateral from the lending protocol for the caller
_withdrawLendingProtocol(msg.sender, amount);
withdraw(amount);
}
/// @notice Withdraws all the assets and claims pending rewards for the caller
function withdraw() external {
withdraw(balanceOf(msg.sender));
}
/// @notice Withdraws the given amount of assets and claims main/extra pending rewards
/// @param amount Amount of wrapped tokens to burn
/// @custom:reverts ZeroAmount if the given amount is zero
function withdraw(uint256 amount) public nonReentrant {
require(amount > 0, ZeroAmount());
// 1. Claim all the pending rewards (main + extra)
_claimAllRewards(msg.sender);
// 2. Update the internal user checkpoint
UserCheckpoint storage checkpoint = userCheckpoints[msg.sender];
checkpoint.balance -= amount;
// 4. Burn caller's wrapped tokens
_burn(msg.sender, amount);
// 5. Transfer the underlying RewardVault shares back to the caller
IERC20(address(REWARD_VAULT)).safeTransfer(msg.sender, amount);
emit Withdrawn(msg.sender, amount);
}
///////////////////////////////////////////////////////////////
// --- CLAIM
///////////////////////////////////////////////////////////////
/// @notice Claims the caller's main reward token for himself
/// @return amount Amount of main reward tokens claimed
function claim() external nonReentrant returns (uint256 amount) {
return _claim(msg.sender);
}
function _claim(address account) internal returns (uint256 amount) {
require(account != address(0), ZeroAddress());
// 1. Pull the latest data from the Accountant and the total supply of the wrapper
IAccountant.AccountData memory wrapper = ACCOUNTANT.accounts(address(REWARD_VAULT), address(this));
uint256 globalIntegral = _getGlobalIntegral();
uint256 supply = totalSupply();
// 2. Calculate the pending rewards for the wrapper
uint256 claimable =
wrapper.pendingRewards + Math.mulDiv(supply, globalIntegral - wrapper.integral, ACCOUNTANT_SCALING_FACTOR);
// 3. If there are some pending rewards, claim them
if (claimable > 0) {
address[] memory gauges = new address[](1);
gauges[0] = GAUGE;
ACCOUNTANT.claim(gauges, new bytes[](0));
}
// 4. Transfer the part of the pending rewards that belongs to the account
UserCheckpoint storage userCheckpoint = userCheckpoints[account];
amount = _calculatePendingRewards(userCheckpoint);
if (amount != 0) {
userCheckpoint.rewardPerTokenPaid[MAIN_REWARD_TOKEN_SLOT] = globalIntegral;
// ! safeTransfer would allow the account to block liquidation
MAIN_REWARD_TOKEN.transfer(account, amount);
emit Claimed(address(MAIN_REWARD_TOKEN), account, amount);
}
}
/// @notice Claims all the caller's extra reward tokens for himself
/// @return amounts Array of claimed amounts
function claimExtraRewards() external nonReentrant returns (uint256[] memory amounts) {
return _claimExtraRewards(REWARD_VAULT.getRewardTokens(), msg.sender);
}
/// @notice Claims the rewards for the given extra reward tokens for the caller
/// @param tokens Array of extra reward tokens to claim
/// @return amounts Array of claimed amounts
function claimExtraRewards(address[] calldata tokens) external nonReentrant returns (uint256[] memory amounts) {
return _claimExtraRewards(tokens, msg.sender);
}
function _claimExtraRewards(address[] memory tokens, address account) internal returns (uint256[] memory amounts) {
require(tokens.length > 0, InvalidTokens());
require(account != address(0), ZeroAddress());
// 1. Update the reward state for all the extra reward tokens
_updateExtraRewardState(tokens);
amounts = new uint256[](tokens.length);
UserCheckpoint storage checkpoint = userCheckpoints[account];
// 2. Calculate the pending rewards for each extra reward token
for (uint256 i; i < tokens.length; i++) {
address token = tokens[i];
uint256 reward = _calculatePendingExtraRewards(checkpoint, token);
// 3. If there are pending rewards, update the user's checkpoint and transfer the rewards to the account
if (reward > 0) {
checkpoint.rewardPerTokenPaid[token] = extraRewardPerToken[token];
amounts[i] = reward; // Store the claimed amount
// ! safeTransfer would allow the account to block liquidation
ERC20(token).transfer(account, reward);
emit Claimed(token, account, reward);
}
}
return amounts;
}
/// @notice Claims pending main + extra rewards for the account
function _claimAllRewards(address account) internal {
_claim(account);
_claimExtraRewards(REWARD_VAULT.getRewardTokens(), account);
}
///////////////////////////////////////////////////////////////
// --- INTERNAL FUNCTIONS
///////////////////////////////////////////////////////////////
/// @notice Updates the user checkpoint with the new deposit amount
function _updateUserCheckpoint(address account, uint256 amount) internal {
// 1. Update the internal account checkpoint
UserCheckpoint storage checkpoint = userCheckpoints[account];
bool hasExistingBalance = checkpoint.balance != 0;
// 2. For existing users, claim rewards first (this already updates checkpoints)
if (hasExistingBalance) _claimAllRewards(account);
checkpoint.balance += amount;
// 3. Set initial checkpoints for new users
if (!hasExistingBalance) {
// Keep track of the Main reward token checkpoint
checkpoint.rewardPerTokenPaid[MAIN_REWARD_TOKEN_SLOT] = _getGlobalIntegral();
// Keep track of the Extra reward tokens checkpoints at deposit time
address[] memory rewardTokens = REWARD_VAULT.getRewardTokens();
_updateExtraRewardState(rewardTokens);
for (uint256 i; i < rewardTokens.length; i++) {
checkpoint.rewardPerTokenPaid[rewardTokens[i]] = extraRewardPerToken[rewardTokens[i]];
}
}
}
/// @notice Calculates the pending amount of main reward token for a user
/// @param checkpoint The user checkpoint
/// @return The amount of pending main reward token
function _calculatePendingRewards(UserCheckpoint storage checkpoint) internal view returns (uint256) {
uint256 globalIntegral = _getGlobalIntegral();
uint256 userRewardPerTokenPaid = checkpoint.rewardPerTokenPaid[MAIN_REWARD_TOKEN_SLOT];
return Math.mulDiv(checkpoint.balance, globalIntegral - userRewardPerTokenPaid, ACCOUNTANT_SCALING_FACTOR);
}
/// @notice Calculates the pending amount of a specific extra reward token for a user
/// @param checkpoint The user checkpoint
/// @param token The address of the extra reward token
/// @return The amount of pending extra reward token
function _calculatePendingExtraRewards(UserCheckpoint storage checkpoint, address token)
internal
view
returns (uint256)
{
uint256 supply = totalSupply();
if (supply == 0) return 0;
uint256 currentRewardPerToken = extraRewardPerToken[token];
uint256 newRewards = uint256(REWARD_VAULT.earned(address(this), token));
if (newRewards > 0) currentRewardPerToken += Math.mulDiv(newRewards, PRECISION, supply);
uint256 userRewardPerTokenPaid = checkpoint.rewardPerTokenPaid[token];
return Math.mulDiv(checkpoint.balance, currentRewardPerToken - userRewardPerTokenPaid, PRECISION);
}
/// @notice Updates the reward state for all the extra reward tokens
/// @dev Sweeping hypothetical sleeping rewards would require supply to be non-zero due to the safe early return
/// @param tokens Array of extra reward tokens to update
function _updateExtraRewardState(address[] memory tokens) internal {
// 1. Get the total supply of the wrapped tokens (shares)
uint256 supply = totalSupply();
if (supply == 0) return;
// 2. Claim the rewards from the RewardVault
uint256[] memory amounts = REWARD_VAULT.claim(tokens, address(this));
// 3. Update the stored rewards for each extra reward token
for (uint256 i; i < tokens.length; i++) {
uint256 amount = amounts[i];
if (amount > 0) extraRewardPerToken[tokens[i]] += Math.mulDiv(amount, PRECISION, supply);
}
}
/// @notice Get the global integral from the Accountant
/// @return integral The global integral for the RewardVault as stored in the Accountant
function _getGlobalIntegral() internal view returns (uint256 integral) {
integral = ACCOUNTANT.vaults(address(REWARD_VAULT)).integral;
}
///////////////////////////////////////////////////////////////
// --- TRANSFER
///////////////////////////////////////////////////////////////
/// @dev Only the lending protocol can transfer in/out the wrapped tokens
function transfer(address to, uint256 value) public virtual override(IERC20, ERC20) returns (bool) {
require(msg.sender == LENDING_PROTOCOL, TransferUnauthorized());
return super.transfer(to, value);
}
/// @dev Only the lending protocol can transfer in/out the wrapped tokens
function transferFrom(address from, address to, uint256 value)
public
virtual
override(IERC20, ERC20)
returns (bool)
{
require(msg.sender == LENDING_PROTOCOL, TransferUnauthorized());
// Only authorize deposits to the lending protocol when the deposit is made through this contract
if (to == LENDING_PROTOCOL) require(depositUnlocked, InvalidDepositFlow());
return super.transferFrom(from, to, value);
}
///////////////////////////////////////////////////////////////
// --- LIQUIDATION
///////////////////////////////////////////////////////////////
/// @notice Claims liquidation rights after receiving tokens from a liquidation event on the lending protocol
/// @param liquidator The address that received the liquidated tokens from Morpho
/// @param victim The address whose position was liquidated
/// @param liquidatedAmount The exact amount of tokens that were seized during liquidation
///
/// @dev This function MUST be called by liquidators after receiving wrapped tokens from a liquidation to receive
/// the liquidated amount of underlying RewardVault shares.
/// Without calling this function, liquidators will:
/// - Be unable to withdraw tokens (will revert due to insufficient internal balance)
/// - Leave the victim earning rewards on tokens they no longer own
/// Example:
/// 1. Alice has 1000 wrapped tokens used as collateral in Morpho
/// 2. Bob liquidates Alice's position, receiving 300 tokens from Morpho
/// 3. Bob's state: ERC20 balance = 300, internal balance = 0 (off-track!)
/// 4. Bob calls: claimLiquidation(bob, alice, 300)
/// 5. Result: Bob received 300 underlying RewardVault shares, Alice stops earning on liquidated amount
///
/// @dev This function must also be called if you withdraw your collateral on the lending protocol to a
/// different receiver. We advise against this scenario and recommend withdrawing your collateral to the
/// same receiver who deposited it. However, if you choose to proceed, you must call this function to
/// re-synchronize the internal accountability. In this case, the liquidator will be the receiver of
/// the collateral, while the victim will be the one who deposited it. From the perspective of this
/// contract, withdrawing to a different receiver is equivalent to liquidation.
/// Example:
/// 1. Alice has 1000 wrapped tokens used as collateral in Morpho
/// 2. Alice withdraws her collateral to Bob, receiving 1000 tokens from Morpho
/// 3. Bob's state: ERC20 balance = 1000, internal balance = 0 (off-track!)
/// 4. Bob calls: claimLiquidation(bob, alice, 1000)
/// 5. Result: Bob received 1000 underlying RewardVault shares, Alice stops earning on liquidated amount
function claimLiquidation(address liquidator, address victim, uint256 liquidatedAmount) external nonReentrant {
require(liquidator != address(0) && victim != address(0), ZeroAddress());
require(liquidatedAmount > 0, ZeroAmount());
// 1. Check that the liquidator own the claimed amount and that it's off-track
// Because token is untransferable (except by the lending protocol), the only way
// to have an off-track balance is to get it from liquidation executed by the lending protocol.
// We have to be sure the claimed amount is off-track, otherwise any holders will be able to liquidate any positions.
require(balanceOf(liquidator) >= userCheckpoints[liquidator].balance + liquidatedAmount, InvalidLiquidation());
// 2. Check that the victim has enough tokens registered internally
uint256 internalVictimBalance = userCheckpoints[victim].balance;
require(internalVictimBalance >= liquidatedAmount, InvalidLiquidation());
// 3. Check that the victim as a real holding hole of at least the claimed amount
// This is done by summing up the collateral supplied by the victim with his balance.
// Because token is untransferable (except by the lending protocol), the only way
// to have an off-track balance is to get liquidated.
uint256 victimBalance = balanceOf(victim) + _getWorkingBalance(victim);
require(internalVictimBalance - victimBalance >= liquidatedAmount, InvalidLiquidation());
// 4. Claim the accrued rewards for the victim and reduce his internal balance
_claimAllRewards(victim);
userCheckpoints[victim].balance -= liquidatedAmount;
// 5. Burn the liquidated amount and transfer the underlying RewardVault shares back to the liquidator
_burn(liquidator, liquidatedAmount);
IERC20(address(REWARD_VAULT)).safeTransfer(liquidator, liquidatedAmount);
emit Liquidated(liquidator, victim, liquidatedAmount);
}
///////////////////////////////////////////////////////////////
// --- GETTERS
///////////////////////////////////////////////////////////////
/// @notice Calculate the pending amount of main reward token for a user
/// @param user The address of the user
/// @return rewards The amount of pending main reward token
function getPendingRewards(address user) external view returns (uint256 rewards) {
UserCheckpoint storage checkpoint = userCheckpoints[user];
rewards = _calculatePendingRewards(checkpoint);
}
/// @notice Calculate the pending amount of all the extra reward tokens for a user.
/// @param user The address of the user
/// @return rewards The amount of pending rewards
function getPendingExtraRewards(address user) external view returns (uint256[] memory rewards) {
address[] memory extraRewardTokens = REWARD_VAULT.getRewardTokens();
UserCheckpoint storage checkpoint = userCheckpoints[user];
rewards = new uint256[](extraRewardTokens.length);
for (uint256 i; i < extraRewardTokens.length; i++) {
rewards[i] = _calculatePendingExtraRewards(checkpoint, extraRewardTokens[i]);
}
}
/// @notice Calculate the pending amount of a specific extra reward token for a user.
/// If you're looking for the pending amount of the main reward token, call `getPendingRewards` instead.
/// @dev The only reason to pass the address of the main reward token to this function is if
/// the main reward token is also listed as an extra reward token. It can happen in some cases.
/// @param user The address of the user
/// @param token The address of the extra reward token
/// @return rewards The amount of pending rewards
/// @custom:reverts ZeroAddress if the token address is the main reward token address (address(0))
function getPendingExtraRewards(address user, address token) external view returns (uint256 rewards) {
require(token != MAIN_REWARD_TOKEN_SLOT, ZeroAddress());
UserCheckpoint storage checkpoint = userCheckpoints[user];
rewards = _calculatePendingExtraRewards(checkpoint, token);
}
/// @notice Get the decimals of the wrapped token
/// @return The decimals of the wrapped token
function decimals() public view override(IERC20Metadata, ERC20) returns (uint8) {
return REWARD_VAULT.decimals();
}
///////////////////////////////////////////////////////////////
// --- VIRTUAL FUNCTIONS
///////////////////////////////////////////////////////////////
function _supplyLendingProtocol(address account, uint256 amount) internal virtual {}
function _withdrawLendingProtocol(address account, uint256 amount) internal virtual {}
function _getWorkingBalance(address account) internal view virtual returns (uint256) {}
function name() public view virtual override(IERC20Metadata, ERC20) onlyInitialized returns (string memory) {}
function symbol() public view virtual override(IERC20Metadata, ERC20) onlyInitialized returns (string memory) {}
}
"
},
"node_modules/@stake-dao/shared/src/interfaces/IMorpho.sol": {
"content": "// SPDX-License-Identifier: GPL-2.0-or-later
pragma solidity >=0.8.0;
type Id is bytes32;
struct MarketParams {
address loanToken;
address collateralToken;
address oracle;
address irm;
uint256 lltv;
}
/// @dev Warning: For `feeRecipient`, `supplyShares` does not contain the accrued shares since the last interest
/// accrual.
struct Position {
uint256 supplyShares;
uint128 borrowShares;
uint128 collateral;
}
/// @dev Warning: `totalSupplyAssets` does not contain the accrued interest since the last interest accrual.
/// @dev Warning: `totalBorrowAssets` does not contain the accrued interest since the last interest accrual.
/// @dev Warning: `totalSupplyShares` does not contain the additional shares accrued by `feeRecipient` since the last
/// interest accrual.
struct Market {
uint128 totalSupplyAssets;
uint128 totalSupplyShares;
uint128 totalBorrowAssets;
uint128 totalBorrowShares;
uint128 lastUpdate;
uint128 fee;
}
struct Authorization {
address authorizer;
address authorized;
bool isAuthorized;
uint256 nonce;
uint256 deadline;
}
struct Signature {
uint8 v;
bytes32 r;
bytes32 s;
}
/// @dev This interface is used for factorizing IMorphoStaticTyping and IMorpho.
/// @dev Consider using the IMorpho interface instead of this one.
interface IMorphoBase {
/// @notice The EIP-712 domain separator.
/// @dev Warning: Every EIP-712 signed message based on this domain separator can be reused on chains sharing the
/// same chain id and on forks because the domain separator would be the same.
function DOMAIN_SEPARATOR() external view returns (bytes32);
/// @notice The owner of the contract.
/// @dev It has the power to change the owner.
/// @dev It has the power to set fees on markets and set the fee recipient.
/// @dev It has the power to enable but not disable IRMs and LLTVs.
function owner() external view returns (address);
/// @notice The fee recipient of all markets.
/// @dev The recipient receives the fees of a given market through a supply position on that market.
function feeRecipient() external view returns (address);
/// @notice Whether the `irm` is enabled.
function isIrmEnabled(address irm) external view returns (bool);
/// @notice Whether the `lltv` is enabled.
function isLltvEnabled(uint256 lltv) external view returns (bool);
/// @notice Whether `authorized` is authorized to modify `authorizer`'s position on all markets.
/// @dev Anyone is authorized to modify their own positions, regardless of this variable.
function isAuthorized(address authorizer, address authorized) external view returns (bool);
/// @notice The `authorizer`'s current nonce. Used to prevent replay attacks with EIP-712 signatures.
function nonce(address authorizer) external view returns (uint256);
/// @notice Sets `newOwner` as `owner` of the contract.
/// @dev Warning: No two-step transfer ownership.
/// @dev Warning: The owner can be set to the zero address.
function setOwner(address newOwner) external;
/// @notice Enables `irm` as a possible IRM for market creation.
/// @dev Warning: It is not possible to disable an IRM.
function enableIrm(address irm) external;
/// @notice Enables `lltv` as a possible LLTV for market creation.
/// @dev Warning: It is not possible to disable a LLTV.
function enableLltv(uint256 lltv) external;
/// @notice Sets the `newFee` for the given market `marketParams`.
/// @param newFee The new fee, scaled by WAD.
/// @dev Warning: The recipient can be the zero address.
function setFee(MarketParams memory marketParams, uint256 newFee) external;
/// @notice Sets `newFeeRecipient` as `feeRecipient` of the fee.
/// @dev Warning: If the fee recipient is set to the zero address, fees will accrue there and will be lost.
/// @dev Modifying the fee recipient will allow the new recipient to claim any pending fees not yet accrued. To
/// ensure that the current recipient receives all due fees, accrue interest manually prior to making any changes.
function setFeeRecipient(address newFeeRecipient) external;
/// @notice Creates the market `marketParams`.
/// @dev Here is the list of assumptions on the market's dependencies (tokens, IRM and oracle) that guarantees
/// Morpho behaves as expected:
/// - The token should be ERC-20 compliant, except that it can omit return values on `transfer` and `transferFrom`.
/// - The token balance of Morpho should only decrease on `transfer` and `transferFrom`. In particular, tokens with
/// burn functions are not supported.
/// - The token should not re-enter Morpho on `transfer` nor `transferFrom`.
/// - The token balance of the sender (resp. receiver) should decrease (resp. increase) by exactly the given amount
/// on `transfer` and `transferFrom`. In particular, tokens with fees on transfer are not supported.
/// - The IRM should not re-enter Morpho.
/// - The oracle should return a price with the correct scaling.
/// - The oracle price should not be able to change instantly such that the new price is less than the old price
/// multiplied by LLTV*LIF. In particular, if the loan asset is a vault that can receive donations, the oracle
/// should not price its shares using the AUM.
/// @dev Here is a list of assumptions on the market's dependencies which, if broken, could break Morpho's liveness
/// properties (funds could get stuck):
/// - The token should not revert on `transfer` and `transferFrom` if balances and approvals are right.
/// - The amount of assets supplied and borrowed should not be too high (max ~1e32), otherwise the number of shares
/// might not fit within 128 bits.
/// - The IRM should not revert on `borrowRate`.
/// - The IRM should not return a very high borrow rate (otherwise the computation of `interest` in
/// `_accrueInterest` can overflow).
/// - The oracle should not revert `price`.
/// - The oracle should not return a very high price (otherwise the computation of `maxBorrow` in `_isHealthy` or of
/// `assetsRepaid` in `liquidate` can overflow).
/// @dev The borrow share price of a market with less than 1e4 assets borrowed can be decreased by manipulations, to
/// the point where `totalBorrowShares` is very large and borrowing overflows.
function createMarket(MarketParams memory marketParams) external;
/// @notice Supplies `assets` or `shares` on behalf of `onBehalf`, optionally calling back the caller's
/// `onMorphoSupply` function with the given `data`.
/// @dev Either `assets` or `shares` should be zero. Most use cases should rely on `assets` as an input so the
/// caller is guaranteed to have `assets` tokens pulled from their balance, but the possibility to mint a specific
/// amount of shares is given for full compatibility and precision.
/// @dev Supplying a large amount can revert for overflow.
/// @dev Supplying an amount of shares may lead to supply more or fewer assets than expected due to slippage.
/// Consider using the `assets` parameter to avoid this.
/// @param marketParams The market to supply assets to.
/// @param assets The amount of assets to supply.
/// @param shares The amount of shares to mint.
/// @param onBehalf The address that will own the increased supply position.
/// @param data Arbitrary data to pass to the `onMorphoSupply` callback. Pass empty data if not needed.
/// @return assetsSupplied The amount of assets supplied.
/// @return sharesSupplied The amount of shares minted.
function supply(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
bytes memory data
) external returns (uint256 assetsSupplied, uint256 sharesSupplied);
/// @notice Withdraws `assets` or `shares` on behalf of `onBehalf` and sends the assets to `receiver`.
/// @dev Either `assets` or `shares` should be zero. To withdraw max, pass the `shares`'s balance of `onBehalf`.
/// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions.
/// @dev Withdrawing an amount corresponding to more shares than supplied will revert for underflow.
/// @dev It is advised to use the `shares` input when withdrawing the full position to avoid reverts due to
/// conversion roundings between shares and assets.
/// @param marketParams The market to withdraw assets from.
/// @param assets The amount of assets to withdraw.
/// @param shares The amount of shares to burn.
/// @param onBehalf The address of the owner of the supply position.
/// @param receiver The address that will receive the withdrawn assets.
/// @return assetsWithdrawn The amount of assets withdrawn.
/// @return sharesWithdrawn The amount of shares burned.
function withdraw(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
address receiver
) external returns (uint256 assetsWithdrawn, uint256 sharesWithdrawn);
/// @notice Borrows `assets` or `shares` on behalf of `onBehalf` and sends the assets to `receiver`.
/// @dev Either `assets` or `shares` should be zero. Most use cases should rely on `assets` as an input so the
/// caller is guaranteed to borrow `assets` of tokens, but the possibility to mint a specific amount of shares is
/// given for full compatibility and precision.
/// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions.
/// @dev Borrowing a large amount can revert for overflow.
/// @dev Borrowing an amount of shares may lead to borrow fewer assets than expected due to slippage.
/// Consider using the `assets` parameter to avoid this.
/// @param marketParams The market to borrow assets from.
/// @param assets The amount of assets to borrow.
/// @param shares The amount of shares to mint.
/// @param onBehalf The address that will own the increased borrow position.
/// @param receiver The address that will receive the borrowed assets.
/// @return assetsBorrowed The amount of assets borrowed.
/// @return sharesBorrowed The amount of shares minted.
function borrow(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
address receiver
) external returns (uint256 assetsBorrowed, uint256 sharesBorrowed);
/// @notice Repays `assets` or `shares` on behalf of `onBehalf`, optionally calling back the caller's
/// `onMorphoRepay` function with the given `data`.
/// @dev Either `assets` or `shares` should be zero. To repay max, pass the `shares`'s balance of `onBehalf`.
/// @dev Repaying an amount corresponding to more shares than borrowed will revert for underflow.
/// @dev It is advised to use the `shares` input when repaying the full position to avoid reverts due to conversion
/// roundings between shares and assets.
/// @dev An attacker can front-run a repay with a small repay making the transaction revert for underflow.
/// @param marketParams The market to repay assets to.
/// @param assets The amount of assets to repay.
/// @param shares The amount of shares to burn.
/// @param onBehalf The address of the owner of the debt position.
/// @param data Arbitrary data to pass to the `onMorphoRepay` callback. Pass empty data if not needed.
/// @return assetsRepaid The amount of assets repaid.
/// @return sharesRepaid The amount of shares burned.
function repay(
MarketParams memory marketParams,
uint256 assets,
uint256 shares,
address onBehalf,
bytes memory data
) external returns (uint256 assetsRepaid, uint256 sharesRepaid);
/// @notice Supplies `assets` of collateral on behalf of `onBehalf`, optionally calling back the caller's
/// `onMorphoSupplyCollateral` function with the given `data`.
/// @dev Interest are not accrued since it's not required and it saves gas.
/// @dev Supplying a large amount can revert for overflow.
/// @param marketParams The market to supply collateral to.
/// @param assets The amount of collateral to supply.
/// @param onBehalf The address that will own the increased collateral position.
/// @param data Arbitrary data to pass to the `onMorphoSupplyCollateral` callback. Pass empty data if not needed.
function supplyCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, bytes memory data)
external;
/// @notice Withdraws `assets` of collateral on behalf of `onBehalf` and sends the assets to `receiver`.
/// @dev `msg.sender` must be authorized to manage `onBehalf`'s positions.
/// @dev Withdrawing an amount corresponding to more collateral than supplied will revert for underflow.
/// @param marketParams The market to withdraw collateral from.
/// @param assets The amount of collateral to withdraw.
/// @param onBehalf The address of the owner of the collateral position.
/// @param receiver The address that will receive the collateral assets.
function withdrawCollateral(MarketParams memory marketParams, uint256 assets, address onBehalf, address receiver)
external;
/// @notice Liquidates the given `repaidShares` of debt asset or seize the given `seizedAssets` of collateral on the
/// given market `marketParams` of the given `borrower`'s position, optionally calling back the caller's
/// `onMorphoLiquidate` function with the given `data`.
/// @dev Either `seizedAssets` or `repaidShares` should be zero.
/// @dev Seizing more than the collateral balance will underflow and revert without any error message.
/// @dev Repaying more than the borrow balance will underflow and revert without any error message.
/// @dev An attacker can front-run a liquidation with a small repay making the transaction revert for underflow.
/// @param marketParams The market of the position.
/// @param borrower The owner of the position.
/// @param seizedAssets The amount of collateral to seize.
/// @param repaidShares The amount of shares to repay.
/// @param data Arbitrary data to pass to the `onMorphoLiquidate` callback. Pass empty data if not needed.
/// @return The amount of assets seized.
/// @return The amount of assets repaid.
function liquidate(
MarketParams memory marketParams,
address borrower,
uint256 seizedAssets,
uint256 repaidShares,
bytes memory data
) external returns (uint256, uint256);
/// @notice Executes a flash loan.
/// @dev Flash loans have access to the whole balance of the contract (the liquidity and deposited collateral of all
/// markets combined, plus donations).
/// @dev Warning: Not ERC-3156 compliant but compatibility is easily reached:
/// - `flashFee` is zero.
/// - `maxFlashLoan` is the token's balance of this contract.
/// - The receiver of `assets` is the caller.
/// @param token The token to flash loan.
/// @param assets The amount of assets to flash loan.
/// @param data Arbitrary data to pass to the `onMorphoFlashLoan` callback.
function flashLoan(address token, uint256 assets, bytes calldata data) external;
/// @notice Sets the authorization for `authorized` to manage `msg.sender`'s positions.
/// @param authorized The authorized address.
/// @param newIsAuthorized The new authorization status.
function setAuthorization(address authorized, bool newIsAuthorized) external;
/// @notice Sets the authorization for `authorization.authorized` to manage `authorization.authorizer`'s positions.
/// @dev Warning: Reverts if the signature has already been submitted.
/// @dev The signature is malleable, but it has no impact on the security here.
/// @dev The nonce is passed as argument to be able to revert with a different error message.
/// @param authorization The `Authorization` struct.
/// @param signature The signature.
function setAuthorizationWithSig(Authorization calldata authorization, Signature calldata signature) external;
/// @notice Accrues interest for the given market `marketParams`.
function accrueInterest(MarketParams memory marketParams) external;
/// @notice Returns the data stored on the different `slots`.
function extSloads(bytes32[] memory slots) external view returns (bytes32[] memory);
}
/// @dev This interface is inherited by Morpho so that function signatures are checked by the compiler.
/// @dev Consider using the IMorpho interface instead of this one.
interface IMorphoStaticTyping is IMorphoBase {
/// @notice The state of the position of `user` on the market corresponding to `id`.
/// @dev Warning: For `feeRecipient`, `supplyShares` does not contain the accrued shares since the last interest
/// accrual.
function position(Id id, address user)
external
view
returns (uint256 supplyShares, uint128 borrowShares, uint128 collateral);
/// @notice The state of the market corresponding to `id`.
/// @dev Warning: `totalSupplyAssets` does not contain the accrued interest since the last interest accrual.
/// @dev Warning: `totalBorrowAssets` does not contain the accrued interest since the last interest accrual.
/// @dev Warning: `totalSupplyShares` does not contain the accrued shares by `feeRecipient` since the last interest
/// accrual.
function market(Id id)
external
view
returns (
uint128 totalSupplyAssets,
uint128 totalSupplyShares,
uint128 totalBorrowAssets,
uint128 totalBorrowShares,
uint128 lastUpdate,
uint128 fee
);
/// @notice The market params corresponding to `id`.
/// @dev This mapping is not used in Morpho. It is there to enable reducing the cost associated to calldata on layer
/// 2s by creating a wrapper contract with functions that take `id` as input instead of `marketParams`.
function idToMarketParams(Id id)
external
view
returns (address loanToken, address collateralToken, address oracle, address irm, uint256 lltv);
}
/// @title IMorpho
/// @author Morpho Labs
/// @custom:contact security@morpho.org
/// @dev Use this interface for Morpho to have access to all the functions with the appropriate function signatures.
interface IMorpho is IMorphoBase {
/// @notice The state of the position of `user` on the market corresponding to `id`.
/// @dev Warning: For `feeRecipient`, `p.supplyShares` does not contain the accrued shares since the last interest
/// accrual.
function position(Id id, address user) external view returns (Position memory p);
/// @notice The state of the market corresponding to `id`.
/// @dev Warning: `m.totalSupplyAssets` does not contain the accrued interest since the last interest accrual.
/// @dev Warning: `m.totalBorrowAssets` does not contain the accrued interest since the last interest accrual.
/// @dev Warning: `m.totalSupplyShares` does not contain the accrued shares by `feeRecipient` since the last
/// interest accrual.
function market(Id id) external view returns (Market memory m);
/// @notice The market params corresponding to `id`.
/// @dev This mapping is not used in Morpho. It is there to enable reducing the cost associated to calldata on layer
/// 2s by creating a wrapper contract with functions that take `id` as input instead of `marketParams`.
function idToMarketParams(Id id) external view returns (MarketParams memory);
}
/// @title MarketParamsLib
/// @author Morpho Labs
/// @custom:contact security@morpho.org
/// @notice Library to convert a market to its id.
library MarketParamsLib {
/// @notice The length of the data used to compute the id of a market.
/// @dev The length is 5 * 32 because `MarketParams` has 5 variables of 32 bytes each.
uint256 internal constant MARKET_PARAMS_BYTES_LENGTH = 5 * 32;
/// @notice Returns the id of the market `marketParams`.
function id(MarketParams memory marketParams) internal pure returns (Id marketParamsId) {
assembly ("memory-safe") {
marketParamsId := keccak256(marketParams, MARKET_PARAMS_BYTES_LENGTH)
}
}
}
"
},
"src/interfaces/IRewardVault.sol": {
"content": "// SPDX-License-Identifier: BUSL-1.1
pragma solidity 0.8.28;
import {IERC4626} from "@openzeppelin/contracts/interfaces/IERC4626.sol";
import {IAccountant} from "src/interfaces/IAccountant.sol";
/// @title IRewardVault
/// @notice Interface for the RewardVault contract
interface IRewardVault is IERC4626 {
function addRewardToken(address rewardsToken, address distributor) external;
function depositRewards(address _rewardsToken, uint128 _amount) external;
function deposit(uint256 assets, address receiver, address referrer) external returns (uint256 shares);
function deposit(address account, address receiver, uint256 assets, address referrer)
external
returns (uint256 shares);
function claim(address[] calldata tokens, address receiver) external returns (uint256[] memory amounts);
function claim(address account, address[] calldata tokens, address receiver)
external
returns (uint256[] memory amounts);
function getRewardsDistributor(address token) external view returns (address);
function getLastUpdateTime(address token) external view returns (uint32);
function getPeriodFinish(address token) external view returns (uint32);
function getRewardRate(address token) external view returns (uint128);
function getRewardPerTokenStored(address token) external view returns (uint128);
function getRewardPerTokenPaid(address token, address account) external view returns (uint128);
function getClaimable(address token, address account) external view returns (uint128);
function getRewardTokens() external view returns (address[] memory);
function lastTimeRewardApplicable(address token) external view returns (uint256);
function rewardPerToken(address token) external view returns (uint128);
function earned(address account, address token) external view returns (uint128);
function isRewardToken(address rewardToken) external view returns (bool);
function resumeVault() external;
function gauge() external view returns (address);
function ACCOUNTANT() external view returns (IAccountant);
function checkpoint(address account) external;
function PROTOCOL_ID() external view returns (bytes4);
}
"
},
"node_modules/@openzeppelin/contracts/access/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {Context} from "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* The initial owner is set to the address provided by the deployer. This can
* later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
/**
* @dev The caller account is not authorized to perform an operation.
*/
error OwnableUnauthorizedAccount(address account);
/**
* @dev The owner is not a valid owner account. (eg. `address(0)`)
*/
error OwnableInvalidOwner(address owner);
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the address provided by the deployer as the initial owner.
*/
constructor(address initialOwner) {
if (initialOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(initialOwner);
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
if (owner() != _msgSender()) {
revert OwnableUnauthorizedAccount(_msgSender());
}
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
if (newOwner == address(0)) {
revert OwnableInvalidOwner(address(0));
}
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
"
},
"node_modules/@openzeppelin/contracts/token/ERC20/ERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.2.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "./IERC20.sol";
import {IERC20Metadata} from "./extensions/IERC20Metadata.sol";
import {Context} from "../../utils/Context.sol";
import {IERC20Errors} from "../../interfaces/draft-IERC6093.sol";
/**
* @dev Implementation of the {IERC20} interface.
*
* This implementation is agnostic to the way tokens are created. This means
* that a supply mechanism has to be added in a derived contract using {_mint}.
*
* TIP: For a detailed writeup see our guide
* https://forum.openzeppelin.com/t/how-to-implement-erc20-supply-mechanisms/226[How
* to implement supply mechanisms].
*
* The default value of {decimals} is 18. To change this, you should override
* this function so it returns a different value.
*
* We have followed general OpenZeppelin Contracts guidelines: functions revert
* instead returning `false` on failure. This behavior is nonetheless
* conventional and does not conflict with the expectations of ERC-20
* applications.
*/
abstract contract ERC20 is Context, IERC20, IERC20Metadata, IERC20Errors {
mapping(address account => uint256) private _balances;
mapping(address account => mapping(address spender => uint256)) private _allowances;
uint256 private _totalSupply;
string private _name;
string private _symbol;
/**
* @dev Sets the values for {name} and {symbol}.
*
* All two of these values are immutable: they can only be set once during
* construction.
*/
constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
return _name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
return _symbol;
}
/**
* @dev Returns the number of decimals used to get its user representation.
* For example, if `decimals` equals `2`, a balance of `505` tokens should
* be displayed to a user as `5.05` (`505 / 10 ** 2`).
*
* Tokens usually opt for a value of 18, imitating the relationship between
* Ether and Wei. This is the default value returned by this function, unless
* it's overridden.
*
* NOTE: This information is only used for _display_ purposes: it in
* no way affects any of the arithmetic of the contract, including
* {IERC20-balanceOf} and {IERC20-transfer}.
*/
function decimals() public view virtual returns (uint8) {
return 18;
}
/**
* @dev See {IERC20-totalSupply}.
*/
function totalSupply() public view virtual returns (uint256) {
return _totalSupply;
}
/**
* @dev See {IERC20-balanceOf}.
*/
function balanceOf(address account) public view virtual returns (uint256) {
return _balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
Submitted on: 2025-11-03 14:42:46
Comments
Log in to comment.
No comments yet.