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/BitcoinStrategyUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.26;
import {ERC20Upgradeable} from "@openzeppelin/contracts-upgradeable/token/ERC20/ERC20Upgradeable.sol";
import {ReentrancyGuardUpgradeable} from "@openzeppelin/contracts-upgradeable/utils/ReentrancyGuardUpgradeable.sol";
import {OwnableUpgradeable} from "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import {UUPSUpgradeable} from "@openzeppelin/contracts-upgradeable/proxy/utils/UUPSUpgradeable.sol";
import {Initializable} from "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {IHooks} from "@uniswap/v4-core/src/interfaces/IHooks.sol";
import {Currency} from "@uniswap/v4-core/src/types/Currency.sol";
import {PoolKey} from "@uniswap/v4-core/src/types/PoolKey.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {IV4Router} from "@uniswap/v4-periphery/src/interfaces/IV4Router.sol";
import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import {Slot0} from "@uniswap/v4-core/src/types/Slot0.sol";
import {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
import {IERC20} from "forge-std/interfaces/IERC20.sol";
import {IUniversalRouter} from "./interfaces/IUniversalRouter.sol";
import {Commands} from "./libraries/Commands.sol";
/// @custom:security-contact security@example.com
/// @custom:oz-upgrades-from BitcoinStrategy
contract BitcoinStrategyUpgradeable is
Initializable,
ERC20Upgradeable,
ReentrancyGuardUpgradeable,
OwnableUpgradeable,
UUPSUpgradeable
{
using PoolIdLibrary for PoolKey;
using StateLibrary for IPoolManager;
using SafeTransferLib for address;
/* ═══════════════════════════════════════════════════════ */
/* CONFIGURATION STRUCTS */
/* ═══════════════════════════════════════════════════════ */
struct FeeConfig {
uint256 creatorFeePercent; // Percentage of ETH fees to creator
uint256 treasuryPercent; // Percentage of ETH fees to treasury
uint256 profitTargetPercent; // Profit target for token sales
}
struct PoolConfig {
uint24 lpFee; // Liquidity pool fee
int24 tickSpacing; // Tick spacing for pool
uint256 token0Amount; // Initial ETH amount
uint256 token1Amount; // Initial token amount
uint160 startingPrice; // Starting price for pool
int24 tickLower; // Lower tick boundary
int24 tickUpper; // Upper tick boundary
uint128 liquidity; // Initial liquidity amount
}
struct SwapPoolConfig {
uint24 fee; // Pool fee for swaps
int24 tickSpacing; // Tick spacing for swaps
}
/* ═══════════════════════════════════════════════════════ */
/* IMMUTABLE-LIKE VARIABLES */
/* ═══════════════════════════════════════════════════════ */
// Note: These cannot be truly immutable in upgradeable contracts
// They are set once in initialize() and never changed
IV4Router private _router;
IPositionManager private _POSM;
IAllowanceTransfer private _PERMIT2;
IUniversalRouter private _UNIVERSAL_ROUTER;
address public creator;
address public creatorFeeWallet;
address public treasury;
IERC20 public TRADING_TOKEN;
uint256 public MAX_SUPPLY;
uint256 public CREATOR_FEE_PERCENT;
uint256 public TREASURY_PERCENT;
uint256 public PROFIT_TARGET_PERCENT;
// Pool configuration storage
PoolConfig private poolConfig;
/* ═══════════════════════════════════════════════════════ */
/* STATE VARIABLES */
/* ═══════════════════════════════════════════════════════ */
// Constants
uint256 public constant MAX_ACTIVE_BATCHES = 100;
// Pause state
bool public paused;
// Configurable slippage tolerance (can be updated by owner)
uint256 public slippageTolerancePercent;
uint256 public buybackSlippagePercent;
// Uniswap V4 Pool variables
bool public poolInitialized;
PoolKey public poolKey;
PoolId public poolId;
int24 public tickLower;
int24 public tickUpper;
uint256 public positionTokenId;
// Liquidity tracking - separate fees from principal
uint128 public principalLiquidity; // Original liquidity amount
uint256 public lastFeeCollection; // Timestamp of last fee collection
// Token trading batches
struct TokenBatch {
uint256 amount; // Amount of tokens bought
uint256 purchasePrice; // Price per token in ETH (scaled by 1e18)
uint256 targetPrice; // Price to sell at (+20%)
bool sold; // Whether this batch has been sold
}
uint256 public batchCounter;
mapping(uint256 => TokenBatch) public tokenBatches;
uint256[] public activeBatchIds;
// Blacklist mapping
mapping(address => bool) public isBlacklisted;
// Pull payment pattern for fee distribution
mapping(address => uint256) public pendingWithdrawals;
// Approval cache for efficiency
mapping(address => bool) private _permit2Approved;
// Swap pool configurations for different pairs
SwapPoolConfig public tradingTokenSwapConfig; // For ETH <-> TRADING_TOKEN
SwapPoolConfig public strategySwapConfig; // For ETH <-> STRATEGY
/* ═══════════════════════════════════════════════════════ */
/* CUSTOM EVENTS */
/* ═══════════════════════════════════════════════════════ */
event FeesCollected(uint256 ethFees, uint256 strategyFees, uint256 timestamp);
event StrategyBurned(uint256 amount);
event CreatorFeePaid(uint256 amount);
event TokenBatchPurchased(uint256 indexed batchId, uint256 amount, uint256 pricePerToken);
event TokenBatchSold(uint256 indexed batchId, uint256 amount, uint256 pricePerToken, uint256 profit);
event StrategyBuyback(uint256 ethSpent, uint256 strategyBought);
event AddressBlacklisted(address indexed account);
event AddressUnblacklisted(address indexed account);
event PoolInitialized(uint256 positionTokenId, uint128 liquidity);
event BuybackFailed(string reason);
event FeeWithdrawn(address indexed recipient, uint256 amount);
event Paused(address indexed account);
event Unpaused(address indexed account);
event ContractUpgraded(address indexed implementation);
event TWAPWindowUpdated(uint32 oldWindow, uint32 newWindow);
/* ═══════════════════════════════════════════════════════ */
/* CUSTOM ERRORS */
/* ═══════════════════════════════════════════════════════ */
error InsufficientContractBalance();
error NotValidSwap();
error AddressIsBlacklisted();
error NotOwner();
error NotTreasury();
error InvalidAddress();
error NoFeesToCollect();
error BatchAlreadySold();
error PriceTargetNotMet();
error InvalidFeeConfiguration();
error InvalidPoolConfiguration();
error TooManyActiveBatches();
error BatchDoesNotExist();
error InvalidBatchAmount();
error AlreadyBlacklisted();
error NotBlacklisted();
error PoolDoesNotExist();
error ZeroTokensReceived();
error InvalidContract();
error OnlySelf();
error ContractPaused();
error NoFeesToWithdraw();
/* ═══════════════════════════════════════════════════════ */
/* MODIFIERS */
/* ═══════════════════════════════════════════════════════ */
/// @notice Modifier to restrict access to treasury address only
modifier onlyTreasury() {
if (msg.sender != treasury) {
revert NotTreasury();
}
_;
}
/// @notice Modifier to check if contract is not paused
modifier whenNotPaused() {
if (paused) revert ContractPaused();
_;
}
/// @custom:oz-upgrades-unsafe-allow constructor
constructor() {
_disableInitializers();
}
/// @notice Initializes the upgradeable contract (replaces constructor)
function initialize(
IV4Router _routerAddr,
IPositionManager _posm,
IAllowanceTransfer _permit2,
IUniversalRouter _universalRouter,
address _tradingToken,
address _creatorFeeWallet,
address _treasury,
uint256 _maxSupply,
FeeConfig memory _feeConfig,
PoolConfig memory _poolConfig,
string memory _tokenName,
string memory _tokenSymbol
) public initializer {
// Initialize parent contracts
__ERC20_init(_tokenName, _tokenSymbol);
__ReentrancyGuard_init();
__Ownable_init(msg.sender);
__UUPSUpgradeable_init();
// Validate addresses are not zero
if (_tradingToken == address(0) || _creatorFeeWallet == address(0) || _treasury == address(0)) {
revert InvalidAddress();
}
if (address(_routerAddr) == address(0) || address(_posm) == address(0) || address(_permit2) == address(0) || address(_universalRouter) == address(0)) {
revert InvalidAddress();
}
// Validate addresses are contracts
if (address(_routerAddr).code.length == 0) revert InvalidContract();
if (address(_posm).code.length == 0) revert InvalidContract();
if (address(_permit2).code.length == 0) revert InvalidContract();
if (address(_universalRouter).code.length == 0) revert InvalidContract();
if (_tradingToken.code.length == 0) revert InvalidContract();
// Validate fee configuration
if (_feeConfig.creatorFeePercent + _feeConfig.treasuryPercent != 100) {
revert InvalidFeeConfiguration();
}
if (_feeConfig.profitTargetPercent == 0 || _feeConfig.profitTargetPercent > 1000) {
revert InvalidFeeConfiguration();
}
// Validate pool configuration
if (_poolConfig.liquidity == 0 || _maxSupply == 0) {
revert InvalidPoolConfiguration();
}
creator = msg.sender;
_router = _routerAddr;
_POSM = _posm;
_PERMIT2 = _permit2;
_UNIVERSAL_ROUTER = _universalRouter;
TRADING_TOKEN = IERC20(_tradingToken);
creatorFeeWallet = _creatorFeeWallet;
treasury = _treasury;
MAX_SUPPLY = _maxSupply;
CREATOR_FEE_PERCENT = _feeConfig.creatorFeePercent;
TREASURY_PERCENT = _feeConfig.treasuryPercent;
PROFIT_TARGET_PERCENT = _feeConfig.profitTargetPercent;
poolConfig = _poolConfig;
// Set default slippage values
slippageTolerancePercent = 15; // 15% default (conservative for low liquidity)
buybackSlippagePercent = 20; // 20% for buyback (optional operation)
// Initialize TWAP parameters
twapWindow = 1800; // 30 minutes default
// Set default swap configs (can be updated via setSwapConfig)
tradingTokenSwapConfig = SwapPoolConfig({
fee: 3000, // 0.3% default
tickSpacing: 60
});
strategySwapConfig = SwapPoolConfig({
fee: 3000, // 0.3% default
tickSpacing: 60
});
// Mint tokens to this contract for liquidity
_mint(address(this), MAX_SUPPLY);
}
/// @notice Required by UUPS pattern - restricts who can upgrade
function _authorizeUpgrade(address newImplementation) internal override onlyOwner {
emit ContractUpgraded(newImplementation);
}
/// @notice Returns the implementation version
function version() public pure virtual returns (string memory) {
return "1.0.0";
}
/* ═══════════════════════════════════════════════════════ */
/* ADMIN FUNCTIONS */
/* ═══════════════════════════════════════════════════════ */
/// @notice Pause the contract in case of emergency
function pause() external onlyOwner {
paused = true;
emit Paused(msg.sender);
}
/// @notice Unpause the contract
function unpause() external onlyOwner {
paused = false;
emit Unpaused(msg.sender);
}
/// @notice Set slippage tolerance for sell operations
/// @param _slippagePercent Slippage tolerance percentage (e.g., 5 = 5%)
function setSlippageTolerance(uint256 _slippagePercent) external onlyOwner {
require(_slippagePercent > 0 && _slippagePercent <= 50, "Invalid slippage");
slippageTolerancePercent = _slippagePercent;
}
/// @notice Set slippage tolerance for buyback operations
/// @param _slippagePercent Slippage tolerance percentage (e.g., 20 = 20%)
function setBuybackSlippage(uint256 _slippagePercent) external onlyOwner {
require(_slippagePercent > 0 && _slippagePercent <= 50, "Invalid slippage");
buybackSlippagePercent = _slippagePercent;
}
/// @notice Set swap pool configuration for trading token pair
/// @param _fee Pool fee (e.g., 3000 = 0.3%)
/// @param _tickSpacing Tick spacing for the pool
function setTradingTokenSwapConfig(uint24 _fee, int24 _tickSpacing) external onlyOwner {
tradingTokenSwapConfig = SwapPoolConfig({
fee: _fee,
tickSpacing: _tickSpacing
});
}
/// @notice Set swap pool configuration for strategy token pair
/// @param _fee Pool fee (e.g., 3000 = 0.3%)
/// @param _tickSpacing Tick spacing for the pool
function setStrategySwapConfig(uint24 _fee, int24 _tickSpacing) external onlyOwner {
strategySwapConfig = SwapPoolConfig({
fee: _fee,
tickSpacing: _tickSpacing
});
}
/// @notice Blacklists an address, preventing them from transferring tokens
function blacklistAddress(address account) external onlyOwner {
if (isBlacklisted[account]) revert AlreadyBlacklisted();
isBlacklisted[account] = true;
emit AddressBlacklisted(account);
}
/// @notice Removes an address from the blacklist
function unblacklistAddress(address account) external onlyOwner {
if (!isBlacklisted[account]) revert NotBlacklisted();
isBlacklisted[account] = false;
emit AddressUnblacklisted(account);
}
/// @notice Renounces ownership of the contract
function renounceOwnership() public override onlyOwner {
_transferOwnership(address(0));
}
/* ═══════════════════════════════════════════════════════ */
/* LIQUIDITY FUNCTIONS */
/* ═══════════════════════════════════════════════════════ */
/// @notice Load initial liquidity into the pool
function loadLiquidity() external onlyOwner {
Currency currency0 = Currency.wrap(address(0)); // ETH
Currency currency1 = Currency.wrap(address(this)); // STRATEGY
tickLower = poolConfig.tickLower;
tickUpper = poolConfig.tickUpper;
PoolKey memory key = PoolKey(
currency0,
currency1,
poolConfig.lpFee,
poolConfig.tickSpacing,
IHooks(address(0))
);
poolKey = key;
poolId = key.toId();
bytes memory hookData = new bytes(0);
principalLiquidity = poolConfig.liquidity; // Store principal
(
bytes memory actions,
bytes[] memory mintParams
) = _mintLiquidityParams(
key,
tickLower,
tickUpper,
poolConfig.liquidity,
poolConfig.token0Amount,
poolConfig.token1Amount,
address(this),
hookData
);
bytes[] memory params = new bytes[](2);
params[0] = abi.encodeWithSelector(
_POSM.initializePool.selector,
key,
poolConfig.startingPrice,
hookData
);
params[1] = abi.encodeWithSelector(
_POSM.modifyLiquidities.selector,
abi.encode(actions, mintParams),
block.timestamp + 60
);
uint256 valueToPass = poolConfig.token0Amount;
// Approve for position manager (one-time operation, unlimited is acceptable here)
_approve(address(this), address(_PERMIT2), type(uint256).max);
_PERMIT2.approve(
address(this),
address(_POSM),
type(uint160).max,
type(uint48).max
);
positionTokenId = _POSM.nextTokenId();
_POSM.multicall{value: valueToPass}(params);
poolInitialized = true;
lastFeeCollection = block.timestamp;
// Initialize first price observation for TWAP
_updatePriceObservation();
// Emit event
emit PoolInitialized(positionTokenId, principalLiquidity);
}
/// @notice Creates parameters for minting liquidity
function _mintLiquidityParams(
PoolKey memory key,
int24 _tickLower,
int24 _tickUpper,
uint256 liquidity,
uint256 amount0Max,
uint256 amount1Max,
address recipient,
bytes memory hookData
) internal pure returns (bytes memory, bytes[] memory) {
bytes memory actions = abi.encodePacked(
uint8(Actions.MINT_POSITION),
uint8(Actions.SETTLE_PAIR)
);
bytes[] memory params = new bytes[](2);
params[0] = abi.encode(
key,
_tickLower,
_tickUpper,
liquidity,
amount0Max,
amount1Max,
recipient,
hookData
);
params[1] = abi.encode(key.currency0, key.currency1);
return (actions, params);
}
/* ═══════════════════════════════════════════════════════ */
/* FEE COLLECTION FUNCTIONS */
/* ═══════════════════════════════════════════════════════ */
/// @notice Get accumulated fees in the pool
function getAccumulatedFees(
PoolKey memory _poolKey
) public view returns (uint256 fees0, uint256 fees1) {
IPoolManager poolManager = _POSM.poolManager();
PoolId _poolId = _poolKey.toId();
(
uint128 liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128
) = poolManager.getPositionInfo(
_poolId,
address(_POSM),
tickLower,
tickUpper,
bytes32(positionTokenId)
);
(
uint256 feeGrowthInside0X128,
uint256 feeGrowthInside1X128
) = poolManager.getFeeGrowthInside(_poolId, tickLower, tickUpper);
// Handle potential overflow in fee growth
unchecked {
uint256 delta0 = feeGrowthInside0X128 - feeGrowthInside0LastX128;
uint256 delta1 = feeGrowthInside1X128 - feeGrowthInside1LastX128;
fees0 = (delta0 * liquidity) / (1 << 128);
fees1 = (delta1 * liquidity) / (1 << 128);
}
return (fees0, fees1);
}
/// @notice Collects fees from the position and processes them
/// @dev Uses pull payment pattern for safer fee distribution
function collectAndProcessFees() external nonReentrant returns (uint256 ethFees, uint256 strategyFees) {
if (!poolInitialized) revert NoFeesToCollect();
// Get accumulated fees
(uint256 fees0, uint256 fees1) = getAccumulatedFees(poolKey);
if (fees0 == 0 && fees1 == 0) revert NoFeesToCollect();
// Prepare to decrease liquidity by ZERO to collect fees without removing principal
bytes memory actions = abi.encodePacked(
uint8(Actions.DECREASE_LIQUIDITY),
uint8(Actions.TAKE_PAIR)
);
bytes[] memory params = new bytes[](2);
// Decrease by 0 liquidity to only collect fees
params[0] = abi.encode(positionTokenId, 0, 0, 0, "");
// TAKE_PAIR requires: currency0, currency1, recipient
params[1] = abi.encode(poolKey.currency0, poolKey.currency1, address(this));
_POSM.modifyLiquidities(abi.encode(actions, params), block.timestamp + 60);
ethFees = fees0;
strategyFees = fees1;
// Burn the $STRATEGY portion using proper burn
if (strategyFees > 0) {
_burn(address(this), strategyFees);
emit StrategyBurned(strategyFees);
}
// Update state BEFORE external calls (Checks-Effects-Interactions pattern)
lastFeeCollection = block.timestamp;
// Update price observation for TWAP
_updatePriceObservation();
emit FeesCollected(ethFees, strategyFees, block.timestamp);
// Split ETH fees using pull payment pattern
if (ethFees > 0) {
uint256 creatorFee = (ethFees * CREATOR_FEE_PERCENT) / 100;
uint256 treasuryAmount = ethFees - creatorFee;
if (creatorFee > 0) {
pendingWithdrawals[creatorFeeWallet] += creatorFee;
emit CreatorFeePaid(creatorFee);
}
if (treasuryAmount > 0) {
pendingWithdrawals[treasury] += treasuryAmount;
}
}
return (ethFees, strategyFees);
}
/// @notice Withdraw pending fees
function withdrawFees() external nonReentrant {
uint256 amount = pendingWithdrawals[msg.sender];
if (amount == 0) revert NoFeesToWithdraw();
pendingWithdrawals[msg.sender] = 0;
msg.sender.forceSafeTransferETH(amount);
emit FeeWithdrawn(msg.sender, amount);
}
/* ═══════════════════════════════════════════════════════ */
/* TRADING FUNCTIONS */
/* ═══════════════════════════════════════════════════════ */
/// @notice Buy trading token with treasury ETH
/// @param ethAmount Amount of ETH to spend on tokens
/// @param minTokenOut Minimum tokens expected
function buyToken(uint256 ethAmount, uint256 minTokenOut) external onlyTreasury nonReentrant whenNotPaused returns (uint256 batchId) {
// Check batch limit
if (activeBatchIds.length >= MAX_ACTIVE_BATCHES) revert TooManyActiveBatches();
if (ethAmount == 0) revert InsufficientContractBalance();
if (address(this).balance < ethAmount) revert InsufficientContractBalance();
uint256 tokenBalanceBefore = TRADING_TOKEN.balanceOf(address(this));
// Execute swap via Universal Router
_swapExactInputSingle(
address(0), // ETH
address(TRADING_TOKEN),
ethAmount,
minTokenOut,
tradingTokenSwapConfig.fee,
tradingTokenSwapConfig.tickSpacing
);
uint256 tokenBalanceAfter = TRADING_TOKEN.balanceOf(address(this));
uint256 tokenReceived = tokenBalanceAfter - tokenBalanceBefore;
// Validate token receipt
if (tokenReceived == 0) revert ZeroTokensReceived();
if (tokenReceived < minTokenOut) revert NotValidSwap();
// Calculate price per token (scaled by 1e18)
uint256 pricePerToken = (ethAmount * 1e18) / tokenReceived;
uint256 targetPrice = (pricePerToken * (100 + PROFIT_TARGET_PERCENT)) / 100;
// Store batch
batchId = batchCounter++;
tokenBatches[batchId] = TokenBatch({
amount: tokenReceived,
purchasePrice: pricePerToken,
targetPrice: targetPrice,
sold: false
});
activeBatchIds.push(batchId);
// Update price observation for TWAP after swap
_updatePriceObservation();
emit TokenBatchPurchased(batchId, tokenReceived, pricePerToken);
return batchId;
}
/// @notice Sell token batch when profit target is hit
/// @param batchId The batch to sell
/// @param currentPricePerToken Current token price (for validation)
function sellToken(uint256 batchId, uint256 currentPricePerToken) external onlyTreasury nonReentrant whenNotPaused {
// Validate batch exists
if (batchId >= batchCounter) revert BatchDoesNotExist();
TokenBatch storage batch = tokenBatches[batchId];
if (batch.amount == 0) revert InvalidBatchAmount();
if (batch.sold) revert BatchAlreadySold();
if (currentPricePerToken < batch.targetPrice) revert PriceTargetNotMet();
uint256 tokenAmount = batch.amount;
// Calculate minEthOut with configurable slippage tolerance
// Use purchasePrice as baseline for slippage protection (conservative approach)
uint256 expectedEthOut = (tokenAmount * batch.purchasePrice) / 1e18;
uint256 minEthOut = (expectedEthOut * (100 - slippageTolerancePercent)) / 100;
uint256 ethBalanceBefore = address(this).balance;
// Execute swap via Universal Router
_swapExactInputSingle(
address(TRADING_TOKEN),
address(0), // ETH
tokenAmount,
minEthOut,
tradingTokenSwapConfig.fee,
tradingTokenSwapConfig.tickSpacing
);
uint256 ethBalanceAfter = address(this).balance;
uint256 ethReceived = ethBalanceAfter - ethBalanceBefore;
uint256 profit = ethReceived > (tokenAmount * batch.purchasePrice / 1e18)
? ethReceived - (tokenAmount * batch.purchasePrice / 1e18)
: 0;
batch.sold = true;
// Remove from active batches
_removeActiveBatch(batchId);
// Update price observation for TWAP after swap
_updatePriceObservation();
emit TokenBatchSold(batchId, tokenAmount, currentPricePerToken, profit);
// Automatically trigger buyback and burn
_buybackAndBurn(ethReceived);
}
/// @notice Buy back $STRATEGY and burn it
function _buybackAndBurn(uint256 ethAmount) internal {
if (ethAmount == 0) return;
uint256 strategyBalanceBefore = balanceOf(address(this));
// Try to buy $STRATEGY with ETH (may fail if no pool exists)
try this.swapForBuyback(ethAmount) {
uint256 strategyBalanceAfter = balanceOf(address(this));
uint256 strategyBought = strategyBalanceAfter - strategyBalanceBefore;
// Burn the bought tokens using proper burn
if (strategyBought > 0) {
_burn(address(this), strategyBought);
emit StrategyBuyback(ethAmount, strategyBought);
emit StrategyBurned(strategyBought);
}
} catch Error(string memory reason) {
// Emit event for failed buyback
emit BuybackFailed(reason);
} catch (bytes memory) {
// Emit event for unknown error
emit BuybackFailed("Unknown error");
}
}
/// @notice External wrapper for buyback swap (needed for try-catch)
function swapForBuyback(uint256 ethAmount) external {
if (msg.sender != address(this)) revert OnlySelf();
// Calculate minimum with slippage protection
uint256 minStrategyOut = _calculateMinBuyback(ethAmount);
_swapExactInputSingle(
address(0), // ETH
address(this), // STRATEGY
ethAmount,
minStrategyOut,
strategySwapConfig.fee,
strategySwapConfig.tickSpacing
);
}
/// @notice Update price observation for TWAP
function _updatePriceObservation() internal {
if (!poolInitialized) return;
IPoolManager poolManager = _POSM.poolManager();
(uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId);
// Update circular buffer
currentObservationIndex = uint8((currentObservationIndex + 1) % 8);
priceObservations[currentObservationIndex] = PriceObservation({
timestamp: uint32(block.timestamp),
sqrtPriceX96: sqrtPriceX96
});
}
/// @notice Calculate TWAP from stored observations
/// @return twapSqrtPriceX96 The time-weighted average sqrt price
function _getTWAP() internal view returns (uint160 twapSqrtPriceX96) {
uint256 priceSum = 0;
uint256 totalWeight = 0;
uint32 currentTime = uint32(block.timestamp);
uint32 oldestAllowedTime = currentTime > twapWindow ? currentTime - twapWindow : 0;
// Iterate through observations in reverse chronological order
for (uint256 i = 0; i < 8; i++) {
uint8 index = uint8((currentObservationIndex + 8 - i) % 8);
PriceObservation memory obs = priceObservations[index];
// Skip empty observations or those outside the window
if (obs.timestamp == 0 || obs.timestamp < oldestAllowedTime) {
continue;
}
// Calculate time weight (more recent = higher weight)
uint256 timeWeight = currentTime - obs.timestamp + 1;
priceSum += uint256(obs.sqrtPriceX96) * timeWeight;
totalWeight += timeWeight;
}
// If we have observations, return weighted average
if (totalWeight > 0) {
return uint160(priceSum / totalWeight);
}
// Fallback to current price if no observations
if (poolInitialized) {
IPoolManager poolManager = _POSM.poolManager();
(uint160 sqrtPriceX96,,,) = poolManager.getSlot0(poolId);
return sqrtPriceX96;
}
return 0;
}
/// @notice Calculate minimum buyback amount using TWAP to prevent price manipulation
/// @param ethAmount The amount of ETH to spend on buyback
/// @return minTokenAmount Minimum amount of tokens expected based on TWAP
function _calculateMinBuyback(uint256 ethAmount) internal view returns (uint256 minTokenAmount) {
if (!poolInitialized) return 0;
// Get TWAP price
uint160 twapSqrtPriceX96 = _getTWAP();
if (twapSqrtPriceX96 == 0) return 0;
// Calculate expected tokens based on TWAP
// sqrtPriceX96 = sqrt(token1/token0) * 2^96
// For ETH -> Strategy token swap:
// If ETH is token0: expectedTokens = ethAmount * (sqrtPrice^2) / (2^192)
// If ETH is token1: expectedTokens = ethAmount / (sqrtPrice^2) * (2^192)
uint256 sqrtPriceSquared = uint256(twapSqrtPriceX96) * uint256(twapSqrtPriceX96);
// Determine if ETH is token0 or token1
bool ethIsToken0 = Currency.unwrap(poolKey.currency0) < Currency.unwrap(poolKey.currency1);
uint256 expectedTokens;
if (ethIsToken0) {
// ETH is token0, strategy is token1
expectedTokens = (ethAmount * sqrtPriceSquared) >> 192;
} else {
// Strategy is token0, ETH is token1
expectedTokens = (ethAmount << 192) / sqrtPriceSquared;
}
// Apply buyback slippage tolerance
minTokenAmount = (expectedTokens * (100 - buybackSlippagePercent)) / 100;
return minTokenAmount;
}
/// @notice Set TWAP window duration (owner only)
/// @param _windowSeconds Time window in seconds for TWAP calculation
function setTWAPWindow(uint32 _windowSeconds) external onlyOwner {
require(_windowSeconds >= 300 && _windowSeconds <= 7200, "Window must be 5min-2hr");
uint32 oldWindow = twapWindow;
twapWindow = _windowSeconds;
emit TWAPWindowUpdated(oldWindow, _windowSeconds);
}
/* ═══════════════════════════════════════════════════════ */
/* SWAP FUNCTIONS */
/* ═══════════════════════════════════════════════════════ */
/// @notice Internal swap function using Universal Router
function _swapExactInputSingle(
address tokenIn,
address tokenOut,
uint256 amountIn,
uint256 minAmountOut,
uint24 poolFee,
int24 poolTickSpacing
) internal {
// Approve token if not ETH (Cache approvals)
if (tokenIn != address(0)) {
// Check if we already have approval cached
if (!_permit2Approved[tokenIn]) {
IERC20(tokenIn).approve(address(_PERMIT2), type(uint256).max);
_permit2Approved[tokenIn] = true;
}
// Approve exact amount via Permit2
_PERMIT2.approve(tokenIn, address(_UNIVERSAL_ROUTER), uint160(amountIn), type(uint48).max);
}
// Encode swap command
bytes memory commands = abi.encodePacked(uint8(Commands.V4_SWAP));
bytes[] memory inputs = new bytes[](1);
// Create pool key for the swap with configurable fee and tick spacing
Currency currency0 = tokenIn < tokenOut ? Currency.wrap(tokenIn) : Currency.wrap(tokenOut);
Currency currency1 = tokenIn < tokenOut ? Currency.wrap(tokenOut) : Currency.wrap(tokenIn);
PoolKey memory swapKey = PoolKey({
currency0: currency0,
currency1: currency1,
fee: poolFee,
tickSpacing: poolTickSpacing,
hooks: IHooks(address(0))
});
// Validate pool exists
IPoolManager poolManager = _POSM.poolManager();
PoolId pId = swapKey.toId();
(uint160 sqrtPriceX96,,,) = poolManager.getSlot0(pId);
if (sqrtPriceX96 == 0) revert PoolDoesNotExist();
bytes memory actions = abi.encodePacked(
uint8(Actions.SWAP_EXACT_IN_SINGLE),
uint8(Actions.SETTLE_ALL),
uint8(Actions.TAKE_ALL)
);
bool zeroForOne = tokenIn < tokenOut;
bytes[] memory params = new bytes[](3);
params[0] = abi.encode(
IV4Router.ExactInputSingleParams({
poolKey: swapKey,
zeroForOne: zeroForOne,
amountIn: uint128(amountIn),
amountOutMinimum: uint128(minAmountOut),
hookData: ""
})
);
// SETTLE_ALL: settle the currency we're giving (input token)
params[1] = abi.encode(
zeroForOne ? currency0 : currency1,
amountIn
);
// TAKE_ALL: take the currency we're receiving (output token)
params[2] = abi.encode(
zeroForOne ? currency1 : currency0,
minAmountOut
);
inputs[0] = abi.encode(actions, params);
uint256 value = tokenIn == address(0) ? amountIn : 0;
_UNIVERSAL_ROUTER.execute{value: value}(commands, inputs, block.timestamp + 300);
}
/* ═══════════════════════════════════════════════════════ */
/* BATCH MANAGEMENT */
/* ═══════════════════════════════════════════════════════ */
/// @notice Remove a batch from active list
function _removeActiveBatch(uint256 batchId) internal {
for (uint256 i = 0; i < activeBatchIds.length; i++) {
if (activeBatchIds[i] == batchId) {
activeBatchIds[i] = activeBatchIds[activeBatchIds.length - 1];
activeBatchIds.pop();
break;
}
}
}
/// @notice Get all active batch IDs
function getActiveBatches() external view returns (uint256[] memory) {
return activeBatchIds;
}
/* ═══════════════════════════════════════════════════════ */
/* TOKEN FUNCTIONS */
/* ═══════════════════════════════════════════════════════ */
/// @notice Override transfer to check blacklist
function transfer(address to, uint256 amount) public override returns (bool) {
if (isBlacklisted[msg.sender] || isBlacklisted[to]) {
revert AddressIsBlacklisted();
}
return super.transfer(to, amount);
}
/// @notice Override transferFrom to check blacklist
function transferFrom(address from, address to, uint256 amount) public override returns (bool) {
if (isBlacklisted[from] || isBlacklisted[to]) {
revert AddressIsBlacklisted();
}
return super.transferFrom(from, to, amount);
}
/// @notice Allows the contract to receive ETH
receive() external payable {}
// TWAP Price Oracle State
struct PriceObservation {
uint32 timestamp;
uint160 sqrtPriceX96;
}
PriceObservation[8] public priceObservations; // Circular buffer for last 8 observations
uint8 public currentObservationIndex;
uint32 public twapWindow; // TWAP time window in seconds (default: 30 minutes)
/**
* @dev This empty reserved space is put in place to allow future versions to add new
* variables without shifting down storage in the inheritance chain.
* See https://docs.openzeppelin.com/contracts/4.x/upgradeable#storage_gaps
*/
uint256[47] private __gap; // Reduced from 50 to 47 (3 slots used by TWAP)
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/token/ERC20/ERC20Upgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.4.0) (token/ERC20/ERC20.sol)
pragma solidity ^0.8.20;
import {IERC20} from "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import {IERC20Metadata} from "@openzeppelin/contracts/token/ERC20/extensions/IERC20Metadata.sol";
import {ContextUpgradeable} from "../../utils/ContextUpgradeable.sol";
import {IERC20Errors} from "@openzeppelin/contracts/interfaces/draft-IERC6093.sol";
import {Initializable} from "../../proxy/utils/Initializable.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 ERC20Upgradeable is Initializable, ContextUpgradeable, IERC20, IERC20Metadata, IERC20Errors {
/// @custom:storage-location erc7201:openzeppelin.storage.ERC20
struct ERC20Storage {
mapping(address account => uint256) _balances;
mapping(address account => mapping(address spender => uint256)) _allowances;
uint256 _totalSupply;
string _name;
string _symbol;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ERC20")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ERC20StorageLocation = 0x52c63247e1f47db19d5ce0460030c497f067ca4cebf71ba98eeadabe20bace00;
function _getERC20Storage() private pure returns (ERC20Storage storage $) {
assembly {
$.slot := ERC20StorageLocation
}
}
/**
* @dev Sets the values for {name} and {symbol}.
*
* Both values are immutable: they can only be set once during construction.
*/
function __ERC20_init(string memory name_, string memory symbol_) internal onlyInitializing {
__ERC20_init_unchained(name_, symbol_);
}
function __ERC20_init_unchained(string memory name_, string memory symbol_) internal onlyInitializing {
ERC20Storage storage $ = _getERC20Storage();
$._name = name_;
$._symbol = symbol_;
}
/**
* @dev Returns the name of the token.
*/
function name() public view virtual returns (string memory) {
ERC20Storage storage $ = _getERC20Storage();
return $._name;
}
/**
* @dev Returns the symbol of the token, usually a shorter version of the
* name.
*/
function symbol() public view virtual returns (string memory) {
ERC20Storage storage $ = _getERC20Storage();
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;
}
/// @inheritdoc IERC20
function totalSupply() public view virtual returns (uint256) {
ERC20Storage storage $ = _getERC20Storage();
return $._totalSupply;
}
/// @inheritdoc IERC20
function balanceOf(address account) public view virtual returns (uint256) {
ERC20Storage storage $ = _getERC20Storage();
return $._balances[account];
}
/**
* @dev See {IERC20-transfer}.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - the caller must have a balance of at least `value`.
*/
function transfer(address to, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_transfer(owner, to, value);
return true;
}
/// @inheritdoc IERC20
function allowance(address owner, address spender) public view virtual returns (uint256) {
ERC20Storage storage $ = _getERC20Storage();
return $._allowances[owner][spender];
}
/**
* @dev See {IERC20-approve}.
*
* NOTE: If `value` is the maximum `uint256`, the allowance is not updated on
* `transferFrom`. This is semantically equivalent to an infinite approval.
*
* Requirements:
*
* - `spender` cannot be the zero address.
*/
function approve(address spender, uint256 value) public virtual returns (bool) {
address owner = _msgSender();
_approve(owner, spender, value);
return true;
}
/**
* @dev See {IERC20-transferFrom}.
*
* Skips emitting an {Approval} event indicating an allowance update. This is not
* required by the ERC. See {xref-ERC20-_approve-address-address-uint256-bool-}[_approve].
*
* NOTE: Does not update the allowance if the current allowance
* is the maximum `uint256`.
*
* Requirements:
*
* - `from` and `to` cannot be the zero address.
* - `from` must have a balance of at least `value`.
* - the caller must have allowance for ``from``'s tokens of at least
* `value`.
*/
function transferFrom(address from, address to, uint256 value) public virtual returns (bool) {
address spender = _msgSender();
_spendAllowance(from, spender, value);
_transfer(from, to, value);
return true;
}
/**
* @dev Moves a `value` amount of tokens from `from` to `to`.
*
* This internal function is equivalent to {transfer}, and can be used to
* e.g. implement automatic token fees, slashing mechanisms, etc.
*
* Emits a {Transfer} event.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _transfer(address from, address to, uint256 value) internal {
if (from == address(0)) {
revert ERC20InvalidSender(address(0));
}
if (to == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(from, to, value);
}
/**
* @dev Transfers a `value` amount of tokens from `from` to `to`, or alternatively mints (or burns) if `from`
* (or `to`) is the zero address. All customizations to transfers, mints, and burns should be done by overriding
* this function.
*
* Emits a {Transfer} event.
*/
function _update(address from, address to, uint256 value) internal virtual {
ERC20Storage storage $ = _getERC20Storage();
if (from == address(0)) {
// Overflow check required: The rest of the code assumes that totalSupply never overflows
$._totalSupply += value;
} else {
uint256 fromBalance = $._balances[from];
if (fromBalance < value) {
revert ERC20InsufficientBalance(from, fromBalance, value);
}
unchecked {
// Overflow not possible: value <= fromBalance <= totalSupply.
$._balances[from] = fromBalance - value;
}
}
if (to == address(0)) {
unchecked {
// Overflow not possible: value <= totalSupply or value <= fromBalance <= totalSupply.
$._totalSupply -= value;
}
} else {
unchecked {
// Overflow not possible: balance + value is at most totalSupply, which we know fits into a uint256.
$._balances[to] += value;
}
}
emit Transfer(from, to, value);
}
/**
* @dev Creates a `value` amount of tokens and assigns them to `account`, by transferring it from address(0).
* Relies on the `_update` mechanism
*
* Emits a {Transfer} event with `from` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead.
*/
function _mint(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidReceiver(address(0));
}
_update(address(0), account, value);
}
/**
* @dev Destroys a `value` amount of tokens from `account`, lowering the total supply.
* Relies on the `_update` mechanism.
*
* Emits a {Transfer} event with `to` set to the zero address.
*
* NOTE: This function is not virtual, {_update} should be overridden instead
*/
function _burn(address account, uint256 value) internal {
if (account == address(0)) {
revert ERC20InvalidSender(address(0));
}
_update(account, address(0), value);
}
/**
* @dev Sets `value` as the allowance of `spender` over the `owner`'s tokens.
*
* This internal function is equivalent to `approve`, and can be used to
* e.g. set automatic allowances for certain subsystems, etc.
*
* Emits an {Approval} event.
*
* Requirements:
*
* - `owner` cannot be the zero address.
* - `spender` cannot be the zero address.
*
* Overrides to this logic should be done to the variant with an additional `bool emitEvent` argument.
*/
function _approve(address owner, address spender, uint256 value) internal {
_approve(owner, spender, value, true);
}
/**
* @dev Variant of {_approve} with an optional flag to enable or disable the {Approval} event.
*
* By default (when calling {_approve}) the flag is set to true. On the other hand, approval changes made by
* `_spendAllowance` during the `transferFrom` operation set the flag to false. This saves gas by not emitting any
* `Approval` event during `transferFrom` operations.
*
* Anyone who wishes to continue emitting `Approval` events on the`transferFrom` operation can force the flag to
* true using the following override:
*
* ```solidity
* function _approve(address owner, address spender, uint256 value, bool) internal virtual override {
* super._approve(owner, spender, value, true);
* }
* ```
*
* Requirements are the same as {_approve}.
*/
function _approve(address owner, address spender, uint256 value, bool emitEvent) internal virtual {
ERC20Storage storage $ = _getERC20Storage();
if (owner == address(0)) {
revert ERC20InvalidApprover(address(0));
}
if (spender == address(0)) {
revert ERC20InvalidSpender(address(0));
}
$._allowances[owner][spender] = value;
if (emitEvent) {
emit Approval(owner, spender, value);
}
}
/**
* @dev Updates `owner`'s allowance for `spender` based on spent `value`.
*
* Does not update the allowance value in case of infinite allowance.
* Revert if not enough allowance is available.
*
* Does not emit an {Approval} event.
*/
function _spendAllowance(address owner, address spender, uint256 value) internal virtual {
uint256 currentAllowance = allowance(owner, spender);
if (currentAllowance < type(uint256).max) {
if (currentAllowance < value) {
revert ERC20InsufficientAllowance(spender, currentAllowance, value);
}
unchecked {
_approve(owner, spender, currentAllowance - value, false);
}
}
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/utils/ReentrancyGuardUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.1.0) (utils/ReentrancyGuard.sol)
pragma solidity ^0.8.20;
import {Initializable} from "../proxy/utils/Initializable.sol";
/**
* @dev Contract module that helps prevent reentrant calls to a function.
*
* Inheriting from `ReentrancyGuard` will make the {nonReentrant} modifier
* available, which can be applied to functions to make sure there are no nested
* (reentrant) calls to them.
*
* Note that because there is a single `nonReentrant` guard, functions marked as
* `nonReentrant` may not call one another. This can be worked around by making
* those functions `private`, and then adding `external` `nonReentrant` entry
* points to them.
*
* TIP: If EIP-1153 (transient storage) is available on the chain you're deploying at,
* consider using {ReentrancyGuardTransient} instead.
*
* TIP: If you would like to learn more about reentrancy and alternative ways
* to protect against it, check out our blog post
* https://blog.openzeppelin.com/reentrancy-after-istanbul/[Reentrancy After Istanbul].
*/
abstract contract ReentrancyGuardUpgradeable is Initializable {
// Booleans are more expensive than uint256 or any type that takes up a full
// word because each write operation emits an extra SLOAD to first read the
// slot's contents, replace the bits taken up by the boolean, and then write
// back. This is the compiler's defense against contract upgrades and
// pointer aliasing, and it cannot be disabled.
// The values being non-zero value makes deployment a bit more expensive,
// but in exchange the refund on every call to nonReentrant will be lower in
// amount. Since refunds are capped to a percentage of the total
// transaction's gas, it is best to keep them low in cases like this one, to
// increase the likelihood of the full refund coming into effect.
uint256 private constant NOT_ENTERED = 1;
uint256 private constant ENTERED = 2;
/// @custom:storage-location erc7201:openzeppelin.storage.ReentrancyGuard
struct ReentrancyGuardStorage {
uint256 _status;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.ReentrancyGuard")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant ReentrancyGuardStorageLocation = 0x9b779b17422d0df92223018b32b4d1fa46e071723d6817e2486d003becc55f00;
function _getReentrancyGuardStorage() private pure returns (ReentrancyGuardStorage storage $) {
assembly {
$.slot := ReentrancyGuardStorageLocation
}
}
/**
* @dev Unauthorized reentrant call.
*/
error ReentrancyGuardReentrantCall();
function __ReentrancyGuard_init() internal onlyInitializing {
__ReentrancyGuard_init_unchained();
}
function __ReentrancyGuard_init_unchained() internal onlyInitializing {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
$._status = NOT_ENTERED;
}
/**
* @dev Prevents a contract from calling itself, directly or indirectly.
* Calling a `nonReentrant` function from another `nonReentrant`
* function is not supported. It is possible to prevent this from happening
* by making the `nonReentrant` function external, and making it call a
* `private` function that does the actual work.
*/
modifier nonReentrant() {
_nonReentrantBefore();
_;
_nonReentrantAfter();
}
function _nonReentrantBefore() private {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
// On the first call to nonReentrant, _status will be NOT_ENTERED
if ($._status == ENTERED) {
revert ReentrancyGuardReentrantCall();
}
// Any calls to nonReentrant after this point will fail
$._status = ENTERED;
}
function _nonReentrantAfter() private {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
// By storing the original value once again, a refund is triggered (see
// https://eips.ethereum.org/EIPS/eip-2200)
$._status = NOT_ENTERED;
}
/**
* @dev Returns true if the reentrancy guard is currently set to "entered", which indicates there is a
* `nonReentrant` function in the call stack.
*/
function _reentrancyGuardEntered() internal view returns (bool) {
ReentrancyGuardStorage storage $ = _getReentrancyGuardStorage();
return $._status == ENTERED;
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/access/OwnableUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.0.0) (access/Ownable.sol)
pragma solidity ^0.8.20;
import {ContextUpgradeable} from "../utils/ContextUpgradeable.sol";
import {Initializable} from "../proxy/utils/Initializable.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 OwnableUpgradeable is Initializable, ContextUpgradeable {
/// @custom:storage-location erc7201:openzeppelin.storage.Ownable
struct OwnableStorage {
address _owner;
}
// keccak256(abi.encode(uint256(keccak256("openzeppelin.storage.Ownable")) - 1)) & ~bytes32(uint256(0xff))
bytes32 private constant OwnableStorageLocation = 0x9016d09d72d40fdae2fd8ceac6b6234c7706214fd39c1cd1e609a0528c199300;
function _getOwnableStorage() private pure returns (OwnableStorage storage $) {
assembly {
$.slot := OwnableStorageLocation
}
}
/**
* @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.
*/
function __Ownable_init(address initialOwner) internal onlyInitializing {
__Ownable_init_unchained(initialOwner);
}
function __Ownable_init_unchained(address initialOwner) internal onlyInitializing {
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) {
OwnableStorage storage $ = _getOwnableStorage();
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 {
OwnableStorage storage $ = _getOwnableStorage();
address oldOwner = $._owner;
$._owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
"
},
"lib/openzeppelin-contracts-upgradeable/contracts/proxy/utils/UUPSUpgradeable.sol": {
"content": "// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v5.3.0) (proxy/utils/UUPSUpgradeable.sol)
pragma solidity ^0.8.22;
import {IERC1822Proxiable} from "@openzeppelin/contracts/interfaces/draft-IERC1822.sol";
import {ERC1967Utils} from "@openzeppelin/contracts/proxy/ERC1967/ERC1967Utils.sol";
import {Initializable} from "./Initializable.sol";
/**
* @dev An upgradeability mechanism designed for UUPS proxies. The functions included here can perform an upgrade of an
* {ERC1967Proxy}, when this contract is set as the implementation behind such a proxy.
*
* A security mechanism ensures that an upgrade does not turn off upgradeability accidentally, although this risk is
* reinstated if the upgrade retains upgradeability but removes the security mechanism, e.g. by replacing
* `UUPSUpgradeable` with a custom implementation of upgrades.
*
* The {_authorizeUpgrade} function must be overridden to include access restriction to the upgrade mechanism.
*/
abstract contract UUPSUpgradeable is Initializable, IERC1822Proxiable {
/// @custom:oz-upgrades-unsafe-allow state-variable-immutable
address private immutable __self = address(this);
/**
* @dev The version of the upgrade interface of the contract. If this getter is missing, both `upgradeTo(address)`
* and `upgradeToAndCall(address,bytes)` are present, and `upgradeTo` must be used if no function should be called,
* while `upgradeToAndCall` will invoke the `receive` function if the second argument is the empty byte string.
* If the getter returns `"5.0.0"`, only `upgradeToAndCall(address,bytes)` is present, and the second argument must
* be the empty byte string if no function should be called, making it impossible to invoke the `receive` function
* during an upgrade.
*/
string public constant UPGRADE_INTERFACE_VERSION = "5.0.0";
/**
* @dev The call is from an unauthorized context.
*/
error UUPSUnauthorizedCallContext();
/**
* @dev The storage `slot` is unsupported as a UUID.
*/
error UUPSUnsupportedProxiableUUID(bytes32 slot);
/**
* @dev Check that the execution is being performed through a delegatecall call and that the execution context is
* a proxy contract with an implementation (as defined in ERC-1967) pointing to self. This should only be the case
* for UUPS and transparent proxies that are using the current contract as their implementation. E
Submitted on: 2025-10-05 10:50:49
Comments
Log in to comment.
No comments yet.