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/punkbuy/StrategyPunk.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {Ownable} from "solady/auth/Ownable.sol";
import {ERC20} from "solady/tokens/ERC20.sol";
import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {IPositionManager} from "@uniswap/v4-periphery/src/interfaces/IPositionManager.sol";
import {BalanceDelta} from "@uniswap/v4-core/src/types/BalanceDelta.sol";
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.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 {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
import {PositionInfo, PositionInfoLibrary} from "@uniswap/v4-periphery/src/libraries/PositionInfoLibrary.sol";
import {TickMath} from "@uniswap/v4-core/src/libraries/TickMath.sol";
import {IPoolManager} from "@uniswap/v4-core/src/interfaces/IPoolManager.sol";
import {PoolId, PoolIdLibrary} from "@uniswap/v4-core/src/types/PoolId.sol";
import {StateLibrary} from "@uniswap/v4-core/src/libraries/StateLibrary.sol";
import {SafeCast} from "@uniswap/v4-core/src/libraries/SafeCast.sol";
import {FullMath} from "@uniswap/v4-core/src/libraries/FullMath.sol";
import {FixedPoint128} from "@uniswap/v4-core/src/libraries/FixedPoint128.sol";
import {IUniswapV4Router04} from "v4-router/interfaces/IUniswapV4Router04.sol";
import "./Interfaces.sol";
/*
____ __ _____ __ __ ___
/ __ \__ ______ / /__ / ___// /__________ _/ /____ ____ ___ __|_ |
/ /_/ / / / / __ \/ //_/ \__ \/ __/ ___/ __ `/ __/ _ \/ __ `/ / / / __/
/ ____/ /_/ / / / / ,< ___/ / /_/ / / /_/ / /_/ __/ /_/ / /_/ /____/
/_/ \__,_/_/ /_/_/|_| /____/\__/_/ \__,_/\__/\___/\__, /\__, /
/____//____/
*/
contract PunkStrategyStrategy is ERC20, Ownable, ReentrancyGuard {
using PoolIdLibrary for PoolKey;
using PositionInfoLibrary for PositionInfo;
/* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
/* CONSTANTS */
/* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
IPositionManager private immutable posm;
IAllowanceTransfer private immutable permit2;
IUniswapV4Router04 private immutable router;
address private immutable poolManager;
uint256 public constant MAX_SUPPLY = 1_000_000_000 * 1e18;
address public constant PNKSTR_ADDRESS = 0xc50673EDb3A7b94E8CAD8a7d4E0cD68864E33eDF;
address public constant PNKSTR_HOOK_ADDRESS = 0xfAaad5B731F52cDc9746F2414c823eca9B06E844;
address public constant DEADADDRESS = 0x000000000000000000000000000000000000dEaD;
int24 private constant PRIMARY_TOKEN_TICK_SPACING = 60;
uint24 private constant PRIMARY_TOKEN_POOL_FEE = 0;
/* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
/* VARIABLES */
/* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
address public hookAddress;
bool public routerRestrict = true;
mapping(address => bool) public listOfRouters;
mapping(address => bool) internal theList;
bool public midSwap;
mapping(bytes32 => uint256) public pnkstrLiquidityTokenIds;
mapping(uint256 => PoolKey) private registeredPnkstrPoolKeys;
mapping(uint256 => bool) private registeredPnkstrPoolKeyExists;
// Hook & accounting
bool public loadingLiquidity;
uint256 public currentFees; // accum ETH forwarded by hook into contract (for fee accounting)
uint256 public totalEthSpentOnPurchases;
uint256 public totalEthSpentOnLiquidity;
uint256 public totalPnkPurchased;
uint256 public totalTokenBurned;
/* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
/* EVENTS */
/* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
event HookFeesReceived(uint256 amount);
event PNKSTRPurchased(uint256 ethSpent, uint256 tokenReceived);
event LiquidityLoaded(address hook);
event PnkstrPoolKeyRegistered(uint256 indexed listId, bytes32 indexed poolId);
event PnkstrPositionInitialized(uint256 indexed listId, bytes32 indexed poolId, uint256 tokenId);
event FeesConvertedAndBurned(uint256 ethInput, uint256 pnkInput, uint256 primaryTokenBurned);
event PoolInitialized(address posm, address poolm, address token, address hook);
event PoolInitFailed(bytes errorData);
event SwapExecuted(uint256 tokenIn, uint256 tokenOut);
/* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
/* ERRORS */
/* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
error OnlyHook();
error InvalidMultiplier();
error InsufficientAmount();
error PurchaseNotFound();
error NotValidRouter();
error PositionAlreadyInitialized();
error PositionNotInitialized();
error LiquidityTooLow();
error AmountTooLarge();
error InvalidPoolKey();
error InsufficientContractEth();
error PoolKeyNotRegistered();
error PoolKeyAlreadyRegistered();
error PnkstrBalanceTooLow();
/* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
/* CONSTRUCTOR */
/* ™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™™ */
constructor(
address _posm,
address _permit2,
address _poolManager,
address _universalRouter,
address payable _router
) {
router = IUniswapV4Router04(_router);
posm = IPositionManager(_posm);
permit2 = IAllowanceTransfer(_permit2);
poolManager = _poolManager;
listOfRouters[address(this)] = true;
listOfRouters[_posm] = true;
listOfRouters[_permit2] = true;
listOfRouters[_router] = true;
listOfRouters[_universalRouter] = true;
listOfRouters[DEADADDRESS] = true;
routerRestrict = true;
_initializeOwner(msg.sender);
_mint(address(this), MAX_SUPPLY);
}
/* ========================= ERC20 basics ========================= */
function name() public pure override returns (string memory) { return "PunkStrategyStrategy"; }
function symbol() public pure override returns (string memory) { return "PSS"; }
/* ========================= Hook & Owner ========================= */
function addFees() external payable {
if (msg.sender != hookAddress && msg.sender != owner()) revert OnlyHook();
currentFees += msg.value;
emit HookFeesReceived(msg.value);
}
function setMidSwap(bool value) external {
if (msg.sender != hookAddress) revert OnlyHook();
midSwap = value;
}
function transferEther(
address _to,
uint256 _amount
) external payable onlyOwner {
SafeTransferLib.forceSafeTransferETH(_to, _amount);
}
function setRouterRestriction(bool _restrict) external onlyOwner {
routerRestrict = _restrict;
}
function addRouter(address _router) external onlyOwner {
listOfRouters[_router] = true;
}
function setlist(address user, bool isEvil) external onlyOwner {
theList[user] = isEvil;
}
function addPoolKey(uint256 listId, uint24 lpFee, int24 tickSpacing, address hooks) external {
if (registeredPnkstrPoolKeyExists[listId]) revert PoolKeyAlreadyRegistered();
PoolKey memory poolKey = PoolKey({
currency0: Currency.wrap(address(0)),
currency1: Currency.wrap(PNKSTR_ADDRESS),
fee: lpFee,
tickSpacing: tickSpacing,
hooks: IHooks(hooks)
});
registeredPnkstrPoolKeys[listId] = poolKey;
registeredPnkstrPoolKeyExists[listId] = true;
bytes32 poolId = PoolId.unwrap(poolKey.toId());
emit PnkstrPoolKeyRegistered(listId, poolId);
}
function getPoolInfo(uint256 listId)
external
view
returns (PoolKey memory poolKey, uint256 tokenId)
{
bytes32 poolId;
(poolKey, poolId) = _getRegisteredPoolKey(listId);
tokenId = pnkstrLiquidityTokenIds[poolId];
}
/* ========================= PNKSTR Purchase ========================= */
function _pnkstrPurchase(uint256 ethAmount) internal returns (uint256, uint256) {
require(address(this).balance >= ethAmount, "Not enough ETH in contract");
PoolKey memory key = PoolKey(
Currency.wrap(address(0)),
Currency.wrap(PNKSTR_ADDRESS),
10000,
200,
IHooks(0x0000000000000000000000000000000000000000)
);
BalanceDelta delta = router.swapExactTokensForTokens{value: ethAmount}(
ethAmount,
0,
true,
key,
"",
address(this),
block.timestamp
);
uint256 ethSpent = _abs(delta.amount0());
uint256 pnkReceived = _abs(delta.amount1());
totalEthSpentOnPurchases += ethSpent;
emit PNKSTRPurchased(ethSpent, pnkReceived);
return (ethSpent, pnkReceived);
}
/* ========================= Pool init & liquidity functions ========================= */
function loadLiquidity(address _hook) external payable onlyOwner {
require(msg.value > 0, "need some native to fund init");
hookAddress = _hook;
_loadLiquidity(_hook);
emit LiquidityLoaded(_hook);
}
function _loadLiquidity(address _hook) internal {
loadingLiquidity = true;
// Create the pool with ETH (currency0) and TOKEN (currency1)
Currency currency0 = Currency.wrap(address(0)); // ETH
Currency currency1 = Currency.wrap(address(this)); // TOKEN
uint24 lpFee = 0;
int24 tickSpacing = 60;
uint256 token0Amount = 1; // 1 wei
uint256 token1Amount = 1_000_000_000 * 10**18; // 1B TOKEN
// 12.5e18 ETH = 1_000_000_000e18 TOKEN
uint160 startingPrice = 708638228457182841184406864642904;
int24 tickLower = TickMath.minUsableTick(tickSpacing);
int24 tickUpper = int24(181980);
PoolKey memory key = PoolKey(currency0, currency1, lpFee, tickSpacing, IHooks(_hook));
bytes memory hookData = new bytes(0);
// Hardcoded from LiquidityAmounts.getLiquidityForAmounts
uint128 liquidity = 111828391515548962972817;
uint256 amount0Max = token0Amount + 1 wei;
uint256 amount1Max = token1Amount + 1 wei;
(bytes memory actions, bytes[] memory mintParams) =
_mintLiquidityParams(key, tickLower, tickUpper, liquidity, amount0Max, amount1Max, address(this), hookData);
bytes[] memory params = new bytes[](2);
params[0] = abi.encodeWithSelector(posm.initializePool.selector, key, startingPrice, hookData);
params[1] = abi.encodeWithSelector(
posm.modifyLiquidities.selector, abi.encode(actions, mintParams), block.timestamp + 60
);
uint256 valueToPass = amount0Max;
permit2.approve(address(this), address(posm), type(uint160).max, type(uint48).max);
posm.multicall{value: valueToPass}(params);
loadingLiquidity = false;
}
/// @notice Creates parameters for minting liquidity in Uniswap V4
function _mintLiquidityParams(
PoolKey memory poolKey,
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(poolKey, _tickLower, _tickUpper, liquidity, amount0Max, amount1Max, recipient, hookData);
params[1] = abi.encode(poolKey.currency0, poolKey.currency1);
return (actions, params);
}
/* ========================= PNKSTR liquidity management ========================= */
/// @notice Mints a new Uniswap V4 position for a registered PNKSTR pool using contract-held liquidity.
/// @param listId Identifier of the registered pool key configuration.
/// @param nativeAmount Amount of native ETH (token0) to deploy from the contract balance.
/// @param minLiquidity Minimum liquidity the mint should produce (0 to skip the check).
function mintLiquidity(
uint256 listId,
uint256 nativeAmount,
uint256 minLiquidity
)
external
nonReentrant
returns (uint256)
{
(PoolKey memory poolKey, bytes32 poolId) = _getRegisteredPoolKey(listId);
if (pnkstrLiquidityTokenIds[poolId] != 0) revert PositionAlreadyInitialized();
uint256 ethPurchase = nativeAmount / 2;
uint256 pnkAmount;
(ethPurchase, pnkAmount) = _pnkstrPurchase(ethPurchase);
uint ethLeft = nativeAmount - ethPurchase;
if (ethLeft == 0 || pnkAmount == 0) revert InsufficientAmount();
if (IERC20(PNKSTR_ADDRESS).balanceOf(address(this)) < pnkAmount) revert InsufficientAmount();
uint256 contractBalanceBefore = address(this).balance;
if (contractBalanceBefore < ethLeft) revert InsufficientContractEth();
(uint160 sqrtPriceX96,) = _getCurrentPoolState(poolKey);
(int24 tickLower, int24 tickUpper) = _globalTicks(poolKey.tickSpacing);
uint128 liquidity = _computeLiquidity(sqrtPriceX96, tickLower, tickUpper, ethLeft, pnkAmount);
if (liquidity == 0 || (minLiquidity > 0 && liquidity < minLiquidity)) revert LiquidityTooLow();
if (pnkAmount > type(uint160).max) revert AmountTooLarge();
_ensurePnkstrApprovals(pnkAmount);
uint128 amount0Max = _toUint128(ethLeft);
uint128 amount1Max = _toUint128(pnkAmount);
bytes memory hookData = new bytes(0);
(bytes memory actions, bytes[] memory params) = _mintLiquidityParams(
poolKey,
tickLower,
tickUpper,
uint256(liquidity),
uint256(amount0Max),
uint256(amount1Max),
address(this),
hookData
);
uint256 tokenId = posm.nextTokenId();
posm.modifyLiquidities{value: ethLeft}(abi.encode(actions, params), block.timestamp + 60);
pnkstrLiquidityTokenIds[poolId] = tokenId;
totalEthSpentOnLiquidity += ethLeft;
totalPnkPurchased += pnkAmount;
emit PnkstrPositionInitialized(listId, poolId, tokenId);
return tokenId;
}
/// @notice Adds liquidity to the existing PNKSTR position tracked by this contract using available balances.
/// @param listId Identifier of the registered pool key configuration.
function addLiquidity(uint256 listId) external nonReentrant returns (uint128) {
(PoolKey memory poolKey, bytes32 poolId) = _getRegisteredPoolKey(listId);
uint256 tokenId = pnkstrLiquidityTokenIds[poolId];
uint256 availableEth = address(this).balance;
uint256 ethPurchase = availableEth / 2;
uint256 pnkAmount;
(ethPurchase, pnkAmount) = _pnkstrPurchase(ethPurchase);
uint ethLeft = availableEth - ethPurchase;
if (tokenId == 0) revert PositionNotInitialized();
if (ethLeft == 0 || pnkAmount == 0) revert InsufficientAmount();
if (IERC20(PNKSTR_ADDRESS).balanceOf(address(this)) < pnkAmount) revert InsufficientAmount();
if (address(this).balance < ethLeft) revert InsufficientContractEth();
PositionInfo info;
(poolKey, info) = posm.getPoolAndPositionInfo(tokenId);
(uint160 sqrtPriceX96,) = _getCurrentPoolState(poolKey);
int24 tickLower = info.tickLower();
int24 tickUpper = info.tickUpper();
uint160 sqrtLower = TickMath.getSqrtPriceAtTick(tickLower);
uint160 sqrtUpper = TickMath.getSqrtPriceAtTick(tickUpper);
uint128 liquidityDelta = LiquidityAmounts.getLiquidityForAmounts(
sqrtPriceX96,
sqrtLower,
sqrtUpper,
ethLeft,
pnkAmount
);
if (liquidityDelta == 0) revert LiquidityTooLow();
_ensurePnkstrApprovals(pnkAmount);
uint256 ethBefore = address(this).balance;
uint256 pnkBefore = IERC20(PNKSTR_ADDRESS).balanceOf(address(this));
uint128 amount0Max = _toUint128(ethLeft);
uint128 amount1Max = _toUint128(pnkAmount);
bytes memory hookData = new bytes(0);
bytes memory actions = abi.encodePacked(uint8(Actions.INCREASE_LIQUIDITY), uint8(Actions.SETTLE_PAIR));
bytes[] memory params = new bytes[](2);
params[0] = abi.encode(tokenId, uint256(liquidityDelta), amount0Max, amount1Max, hookData);
params[1] = abi.encode(poolKey.currency0, poolKey.currency1);
posm.modifyLiquidities{value: ethLeft}(abi.encode(actions, params), block.timestamp + 60);
uint256 ethAfter = address(this).balance;
uint256 pnkAfter = IERC20(PNKSTR_ADDRESS).balanceOf(address(this));
uint256 feeEth = ethAfter + ethLeft > ethBefore ? ethAfter + ethLeft - ethBefore : 0;
uint256 feePnk = pnkAfter + pnkAmount > pnkBefore ? pnkAfter + pnkAmount - pnkBefore : 0;
if (feeEth > 0 || feePnk > 0) {
uint256 burnedPrimaryToken = _convertFeesAndBurn(poolKey, feeEth, feePnk);
totalTokenBurned += burnedPrimaryToken;
emit FeesConvertedAndBurned(feeEth, feePnk, burnedPrimaryToken);
}
totalEthSpentOnLiquidity += ethLeft;
totalPnkPurchased += pnkAmount;
return liquidityDelta;
}
function addLiquidityStatus(uint256 listId)
public
view
returns (uint256 requiredEth, uint256 requiredPnk)
{
bytes32 poolId;
PoolKey memory poolKey;
(poolKey, poolId) = _getRegisteredPoolKey(listId);
uint256 availableEth = address(this).balance;
requiredEth = availableEth / 2;
(uint160 sqrtPriceX96,) = _getCurrentPoolState(poolKey);
uint256 priceX192 = uint256(sqrtPriceX96) * uint256(sqrtPriceX96);
requiredPnk = FullMath.mulDiv(requiredEth, priceX192, uint256(1) << 192);
return (requiredEth, requiredPnk);
}
function collectLPFees(uint256 listId)
external
nonReentrant
returns (uint256 amountEth, uint256 amountPnk)
{
(PoolKey memory poolKey, bytes32 poolId) = _getRegisteredPoolKey(listId);
uint256 tokenId = pnkstrLiquidityTokenIds[poolId];
if (tokenId == 0) revert PositionNotInitialized();
(PoolKey memory storedPoolKey,) = posm.getPoolAndPositionInfo(tokenId);
if (!_poolIdsMatch(poolKey, storedPoolKey)) revert InvalidPoolKey();
uint256 ethBefore = address(this).balance;
uint256 pnkBefore = IERC20(PNKSTR_ADDRESS).balanceOf(address(this));
bytes memory actions = abi.encodePacked(
uint8(Actions.DECREASE_LIQUIDITY),
uint8(Actions.TAKE_PAIR)
);
bytes[] memory params = new bytes[](2);
params[0] = abi.encode(tokenId, uint256(0), uint128(0), uint128(0), bytes(""));
params[1] = abi.encode(storedPoolKey.currency0, storedPoolKey.currency1, address(this));
posm.modifyLiquidities(abi.encode(actions, params), block.timestamp + 60);
amountEth = address(this).balance - ethBefore;
amountPnk = IERC20(PNKSTR_ADDRESS).balanceOf(address(this)) - pnkBefore;
uint256 burnedPrimaryToken = _convertFeesAndBurn(storedPoolKey, amountEth, amountPnk);
totalTokenBurned += burnedPrimaryToken;
emit FeesConvertedAndBurned(amountEth, amountPnk, burnedPrimaryToken);
}
function getPositionDetails(uint256 listId)
external
view
returns (
uint256 tokenId,
int24 tickLower,
int24 tickUpper,
uint128 liquidity,
uint160 sqrtPriceX96,
uint256 positionEth,
uint256 positionPnk
)
{
PoolKey memory poolKey;
bytes32 poolIdKey;
(poolKey, poolIdKey) = _getRegisteredPoolKey(listId);
(sqrtPriceX96,) = _getCurrentPoolState(poolKey);
tokenId = pnkstrLiquidityTokenIds[poolIdKey];
if (tokenId == 0) {
return (0, 0, 0, 0, sqrtPriceX96, 0, 0);
}
PositionInfo info;
(poolKey, info) = posm.getPoolAndPositionInfo(tokenId);
tickLower = info.tickLower();
tickUpper = info.tickUpper();
liquidity = posm.getPositionLiquidity(tokenId);
uint160 sqrtLower = TickMath.getSqrtPriceAtTick(tickLower);
uint160 sqrtUpper = TickMath.getSqrtPriceAtTick(tickUpper);
(positionEth, positionPnk) =
LiquidityAmounts.getAmountsForLiquidity(sqrtPriceX96, sqrtLower, sqrtUpper, liquidity);
}
function getPendingFees(uint256 listId) external view returns (uint256 pendingEth, uint256 pendingPnk) {
if (!registeredPnkstrPoolKeyExists[listId]) revert PoolKeyNotRegistered();
(PoolKey memory poolKey, bytes32 poolId) = _getRegisteredPoolKey(listId);
uint256 tokenId = pnkstrLiquidityTokenIds[poolId];
if (tokenId == 0) revert PositionNotInitialized();
(PoolKey memory activePoolKey, PositionInfo info) = posm.getPoolAndPositionInfo(tokenId);
if (!_poolIdsMatch(poolKey, activePoolKey)) poolKey = activePoolKey;
uint128 liquidity = posm.getPositionLiquidity(tokenId);
if (liquidity == 0) return (0, 0);
int24 tickLower = info.tickLower();
int24 tickUpper = info.tickUpper();
IPoolManager manager = IPoolManager(poolManager);
(uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) =
StateLibrary.getFeeGrowthInside(manager, poolKey.toId(), tickLower, tickUpper);
bytes32 positionKey = _calculatePositionKey(address(posm), tickLower, tickUpper, bytes32(tokenId));
(uint128 storedLiquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128) =
StateLibrary.getPositionInfo(manager, poolKey.toId(), positionKey);
if (storedLiquidity == 0 && feeGrowthInside0LastX128 == 0 && feeGrowthInside1LastX128 == 0) {
positionKey = _calculatePositionKey(address(this), tickLower, tickUpper, bytes32(0));
(storedLiquidity, feeGrowthInside0LastX128, feeGrowthInside1LastX128) =
StateLibrary.getPositionInfo(manager, poolKey.toId(), positionKey);
}
if (storedLiquidity == 0) storedLiquidity = liquidity;
if (feeGrowthInside0X128 > feeGrowthInside0LastX128) {
pendingEth = FullMath.mulDiv(
feeGrowthInside0X128 - feeGrowthInside0LastX128, storedLiquidity, FixedPoint128.Q128
);
}
if (feeGrowthInside1X128 > feeGrowthInside1LastX128) {
pendingPnk = FullMath.mulDiv(
feeGrowthInside1X128 - feeGrowthInside1LastX128, storedLiquidity, FixedPoint128.Q128
);
}
}
/* ========================= PNKSTR liquidity helpers ========================= */
function _getRegisteredPoolKey(uint256 listId) internal view returns (PoolKey memory poolKey, bytes32 poolId) {
if (!registeredPnkstrPoolKeyExists[listId]) revert PoolKeyNotRegistered();
poolKey = registeredPnkstrPoolKeys[listId];
poolId = PoolId.unwrap(poolKey.toId());
}
function _poolIdsMatch(PoolKey memory a, PoolKey memory b) internal pure returns (bool) {
return PoolId.unwrap(a.toId()) == PoolId.unwrap(b.toId());
}
function _getCurrentPoolState(PoolKey memory poolKey)
internal
view
returns (uint160 sqrtPriceX96, int24 currentTick)
{
PoolId poolId = poolKey.toId();
(sqrtPriceX96, currentTick,,) = StateLibrary.getSlot0(IPoolManager(poolManager), poolId);
}
function _calculatePositionKey(address owner, int24 tickLower, int24 tickUpper, bytes32 salt)
internal
pure
returns (bytes32)
{
return keccak256(abi.encodePacked(owner, tickLower, tickUpper, salt));
}
function _globalTicks(int24 tickSpacing) internal pure returns (int24 tickLower, int24 tickUpper) {
tickLower = TickMath.minUsableTick(tickSpacing);
tickUpper = TickMath.maxUsableTick(tickSpacing);
}
function _computeLiquidity(
uint160 sqrtPriceX96,
int24 tickLower,
int24 tickUpper,
uint256 amount0,
uint256 amount1
) internal pure returns (uint128) {
uint160 sqrtLower = TickMath.getSqrtPriceAtTick(tickLower);
uint160 sqrtUpper = TickMath.getSqrtPriceAtTick(tickUpper);
return LiquidityAmounts.getLiquidityForAmounts(
sqrtPriceX96,
sqrtLower,
sqrtUpper,
amount0,
amount1
);
}
function _convertFeesAndBurn(PoolKey memory poolKey, uint256 ethAmount, uint256 pnkAmount)
internal
returns (uint256 burnedPrimaryToken)
{
uint256 ethForBurn = ethAmount;
if (pnkAmount > 0) {
uint256 ethFromPnk = _swapPnkForEth(poolKey, pnkAmount);
ethForBurn += ethFromPnk;
}
if (ethForBurn > 0) {
burnedPrimaryToken = _buyAndBurnPrimaryToken(ethForBurn);
}
}
function _swapPnkForEth(PoolKey memory poolKey, uint256 amountPnk) internal returns (uint256 ethOut) {
if (amountPnk == 0) return 0;
_ensureRouterApproval(PNKSTR_ADDRESS, amountPnk);
BalanceDelta delta = router.swapExactTokensForTokens(
amountPnk,
0,
false,
poolKey,
"",
address(this),
block.timestamp
);
ethOut = _abs(delta.amount0());
}
function _buyAndBurnPrimaryToken(uint256 ethAmount) internal returns (uint256 primaryTokenBurned) {
if (ethAmount == 0) return 0;
PoolKey memory key = PoolKey(
Currency.wrap(address(0)),
Currency.wrap(address(this)),
PRIMARY_TOKEN_POOL_FEE,
PRIMARY_TOKEN_TICK_SPACING,
IHooks(hookAddress)
);
BalanceDelta delta = router.swapExactTokensForTokens{value: ethAmount}(
ethAmount,
0,
true,
key,
"",
DEADADDRESS,
block.timestamp
);
primaryTokenBurned = _abs(delta.amount1());
}
function _ensureRouterApproval(address token, uint256 amount) internal {
if (IERC20(token).allowance(address(this), address(router)) < amount) {
IERC20(token).approve(address(router), type(uint256).max);
}
}
function _ensurePnkstrApprovals(uint256 tokenAmount) internal {
IERC20 token = IERC20(PNKSTR_ADDRESS);
if (token.allowance(address(this), address(permit2)) < tokenAmount) {
token.approve(address(permit2), type(uint256).max);
}
(uint160 permitted,,) = permit2.allowance(address(this), PNKSTR_ADDRESS, address(posm));
if (permitted < uint160(tokenAmount)) {
permit2.approve(PNKSTR_ADDRESS, address(posm), type(uint160).max, type(uint48).max);
}
}
function _toUint128(uint256 value) internal pure returns (uint128) {
if (value > type(uint128).max) revert AmountTooLarge();
return uint128(value);
}
/// @notice Provides aggregated ETH accounting for monitoring net LP fees.
function getEthAccounting()
external
view
returns (
uint256 contractBalance,
uint256 feesReceived,
uint256 ethSpentOnPurchases_,
uint256 ethSpentOnLiquidity_,
uint256 pnkPurchased_,
int256 netEarnedFees
)
{
contractBalance = address(this).balance;
feesReceived = currentFees;
ethSpentOnPurchases_ = totalEthSpentOnPurchases;
ethSpentOnLiquidity_ = totalEthSpentOnLiquidity;
pnkPurchased_ = totalPnkPurchased;
uint256 totalSpent = ethSpentOnPurchases_ + ethSpentOnLiquidity_;
uint256 gross = contractBalance + totalSpent;
if (gross >= feesReceived) {
netEarnedFees = int256(gross - feesReceived);
} else {
netEarnedFees = -int256(feesReceived - gross);
}
}
/* ========================= Utilities ========================= */
function _abs(int128 x) internal pure returns (uint256) {
return x < 0 ? uint256(int256(-x)) : uint256(int256(x));
}
function tranferERC20(address token, address to, uint256 amount) external onlyOwner {
IERC20(token).transfer(to, amount);
}
function tranferERC721(address token, address to, uint256 id) external onlyOwner {
IERC721(token).safeTransferFrom(address(this), to, id);
}
receive() external payable {}
}
library LiquidityAmounts {
using SafeCast for uint256;
uint256 internal constant Q96 = 0x1000000000000000000000000;
function getLiquidityForAmount0(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount0)
internal
pure
returns (uint128 liquidity)
{
if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
uint256 intermediate = FullMath.mulDiv(sqrtPriceAX96, sqrtPriceBX96, Q96);
return FullMath.mulDiv(amount0, intermediate, sqrtPriceBX96 - sqrtPriceAX96).toUint128();
}
function getLiquidityForAmount1(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint256 amount1)
internal
pure
returns (uint128 liquidity)
{
if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
return FullMath.mulDiv(amount1, Q96, sqrtPriceBX96 - sqrtPriceAX96).toUint128();
}
function getLiquidityForAmounts(
uint160 sqrtPriceX96,
uint160 sqrtPriceAX96,
uint160 sqrtPriceBX96,
uint256 amount0,
uint256 amount1
) internal pure returns (uint128 liquidity) {
if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
if (sqrtPriceX96 <= sqrtPriceAX96) {
liquidity = getLiquidityForAmount0(sqrtPriceAX96, sqrtPriceBX96, amount0);
} else if (sqrtPriceX96 < sqrtPriceBX96) {
uint128 liquidity0 = getLiquidityForAmount0(sqrtPriceX96, sqrtPriceBX96, amount0);
uint128 liquidity1 = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceX96, amount1);
liquidity = liquidity0 < liquidity1 ? liquidity0 : liquidity1;
} else {
liquidity = getLiquidityForAmount1(sqrtPriceAX96, sqrtPriceBX96, amount1);
}
}
function getAmount0ForLiquidity(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint128 liquidity)
internal
pure
returns (uint256 amount0)
{
if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
return FullMath.mulDiv(
uint256(liquidity) << 96, sqrtPriceBX96 - sqrtPriceAX96, sqrtPriceBX96
) / sqrtPriceAX96;
}
function getAmount1ForLiquidity(uint160 sqrtPriceAX96, uint160 sqrtPriceBX96, uint128 liquidity)
internal
pure
returns (uint256 amount1)
{
if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
return FullMath.mulDiv(liquidity, sqrtPriceBX96 - sqrtPriceAX96, Q96);
}
function getAmountsForLiquidity(
uint160 sqrtPriceX96,
uint160 sqrtPriceAX96,
uint160 sqrtPriceBX96,
uint128 liquidity
) internal pure returns (uint256 amount0, uint256 amount1) {
if (sqrtPriceAX96 > sqrtPriceBX96) (sqrtPriceAX96, sqrtPriceBX96) = (sqrtPriceBX96, sqrtPriceAX96);
if (sqrtPriceX96 <= sqrtPriceAX96) {
amount0 = getAmount0ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity);
} else if (sqrtPriceX96 < sqrtPriceBX96) {
amount0 = getAmount0ForLiquidity(sqrtPriceX96, sqrtPriceBX96, liquidity);
amount1 = getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceX96, liquidity);
} else {
amount1 = getAmount1ForLiquidity(sqrtPriceAX96, sqrtPriceBX96, liquidity);
}
}
}"
},
"lib/solady/src/auth/Ownable.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple single owner authorization mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/auth/Ownable.sol)
///
/// @dev Note:
/// This implementation does NOT auto-initialize the owner to `msg.sender`.
/// You MUST call the `_initializeOwner` in the constructor / initializer.
///
/// While the ownable portion follows
/// [EIP-173](https://eips.ethereum.org/EIPS/eip-173) for compatibility,
/// the nomenclature for the 2-step ownership handover may be unique to this codebase.
abstract contract Ownable {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The caller is not authorized to call the function.
error Unauthorized();
/// @dev The `newOwner` cannot be the zero address.
error NewOwnerIsZeroAddress();
/// @dev The `pendingOwner` does not have a valid handover request.
error NoHandoverRequest();
/// @dev Cannot double-initialize.
error AlreadyInitialized();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ownership is transferred from `oldOwner` to `newOwner`.
/// This event is intentionally kept the same as OpenZeppelin's Ownable to be
/// compatible with indexers and [EIP-173](https://eips.ethereum.org/EIPS/eip-173),
/// despite it not being as lightweight as a single argument event.
event OwnershipTransferred(address indexed oldOwner, address indexed newOwner);
/// @dev An ownership handover to `pendingOwner` has been requested.
event OwnershipHandoverRequested(address indexed pendingOwner);
/// @dev The ownership handover to `pendingOwner` has been canceled.
event OwnershipHandoverCanceled(address indexed pendingOwner);
/// @dev `keccak256(bytes("OwnershipTransferred(address,address)"))`.
uint256 private constant _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE =
0x8be0079c531659141344cd1fd0a4f28419497f9722a3daafe3b4186f6b6457e0;
/// @dev `keccak256(bytes("OwnershipHandoverRequested(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE =
0xdbf36a107da19e49527a7176a1babf963b4b0ff8cde35ee35d6cd8f1f9ac7e1d;
/// @dev `keccak256(bytes("OwnershipHandoverCanceled(address)"))`.
uint256 private constant _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE =
0xfa7b8eab7da67f412cc9575ed43464468f9bfbae89d1675917346ca6d8fe3c92;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The owner slot is given by:
/// `bytes32(~uint256(uint32(bytes4(keccak256("_OWNER_SLOT_NOT")))))`.
/// It is intentionally chosen to be a high value
/// to avoid collision with lower slots.
/// The choice of manual storage layout is to enable compatibility
/// with both regular and upgradeable contracts.
bytes32 internal constant _OWNER_SLOT =
0xffffffffffffffffffffffffffffffffffffffffffffffffffffffff74873927;
/// The ownership handover slot of `newOwner` is given by:
/// ```
/// mstore(0x00, or(shl(96, user), _HANDOVER_SLOT_SEED))
/// let handoverSlot := keccak256(0x00, 0x20)
/// ```
/// It stores the expiry timestamp of the two-step ownership handover.
uint256 private constant _HANDOVER_SLOT_SEED = 0x389a75e1;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Override to return true to make `_initializeOwner` prevent double-initialization.
function _guardInitializeOwner() internal pure virtual returns (bool guard) {}
/// @dev Initializes the owner directly without authorization guard.
/// This function must be called upon initialization,
/// regardless of whether the contract is upgradeable or not.
/// This is to enable generalization to both regular and upgradeable contracts,
/// and to save gas in case the initial owner is not the caller.
/// For performance reasons, this function will not check if there
/// is an existing owner.
function _initializeOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
if sload(ownerSlot) {
mstore(0x00, 0x0dc149f0) // `AlreadyInitialized()`.
revert(0x1c, 0x04)
}
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
} else {
/// @solidity memory-safe-assembly
assembly {
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Store the new value.
sstore(_OWNER_SLOT, newOwner)
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, 0, newOwner)
}
}
}
/// @dev Sets the owner directly without authorization guard.
function _setOwner(address newOwner) internal virtual {
if (_guardInitializeOwner()) {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, or(newOwner, shl(255, iszero(newOwner))))
}
} else {
/// @solidity memory-safe-assembly
assembly {
let ownerSlot := _OWNER_SLOT
// Clean the upper 96 bits.
newOwner := shr(96, shl(96, newOwner))
// Emit the {OwnershipTransferred} event.
log3(0, 0, _OWNERSHIP_TRANSFERRED_EVENT_SIGNATURE, sload(ownerSlot), newOwner)
// Store the new value.
sstore(ownerSlot, newOwner)
}
}
}
/// @dev Throws if the sender is not the owner.
function _checkOwner() internal view virtual {
/// @solidity memory-safe-assembly
assembly {
// If the caller is not the stored owner, revert.
if iszero(eq(caller(), sload(_OWNER_SLOT))) {
mstore(0x00, 0x82b42900) // `Unauthorized()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Returns how long a two-step ownership handover is valid for in seconds.
/// Override to return a different value if needed.
/// Made internal to conserve bytecode. Wrap it in a public function if needed.
function _ownershipHandoverValidFor() internal view virtual returns (uint64) {
return 48 * 3600;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC UPDATE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Allows the owner to transfer the ownership to `newOwner`.
function transferOwnership(address newOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
if iszero(shl(96, newOwner)) {
mstore(0x00, 0x7448fbae) // `NewOwnerIsZeroAddress()`.
revert(0x1c, 0x04)
}
}
_setOwner(newOwner);
}
/// @dev Allows the owner to renounce their ownership.
function renounceOwnership() public payable virtual onlyOwner {
_setOwner(address(0));
}
/// @dev Request a two-step ownership handover to the caller.
/// The request will automatically expire in 48 hours (172800 seconds) by default.
function requestOwnershipHandover() public payable virtual {
unchecked {
uint256 expires = block.timestamp + _ownershipHandoverValidFor();
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to `expires`.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), expires)
// Emit the {OwnershipHandoverRequested} event.
log2(0, 0, _OWNERSHIP_HANDOVER_REQUESTED_EVENT_SIGNATURE, caller())
}
}
}
/// @dev Cancels the two-step ownership handover to the caller, if any.
function cancelOwnershipHandover() public payable virtual {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x20), 0)
// Emit the {OwnershipHandoverCanceled} event.
log2(0, 0, _OWNERSHIP_HANDOVER_CANCELED_EVENT_SIGNATURE, caller())
}
}
/// @dev Allows the owner to complete the two-step ownership handover to `pendingOwner`.
/// Reverts if there is no existing ownership handover requested by `pendingOwner`.
function completeOwnershipHandover(address pendingOwner) public payable virtual onlyOwner {
/// @solidity memory-safe-assembly
assembly {
// Compute and set the handover slot to 0.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
let handoverSlot := keccak256(0x0c, 0x20)
// If the handover does not exist, or has expired.
if gt(timestamp(), sload(handoverSlot)) {
mstore(0x00, 0x6f5e8818) // `NoHandoverRequest()`.
revert(0x1c, 0x04)
}
// Set the handover slot to 0.
sstore(handoverSlot, 0)
}
_setOwner(pendingOwner);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PUBLIC READ FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the owner of the contract.
function owner() public view virtual returns (address result) {
/// @solidity memory-safe-assembly
assembly {
result := sload(_OWNER_SLOT)
}
}
/// @dev Returns the expiry timestamp for the two-step ownership handover to `pendingOwner`.
function ownershipHandoverExpiresAt(address pendingOwner)
public
view
virtual
returns (uint256 result)
{
/// @solidity memory-safe-assembly
assembly {
// Compute the handover slot.
mstore(0x0c, _HANDOVER_SLOT_SEED)
mstore(0x00, pendingOwner)
// Load the handover slot.
result := sload(keccak256(0x0c, 0x20))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* MODIFIERS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Marks a function as only callable by the owner.
modifier onlyOwner() virtual {
_checkOwner();
_;
}
}"
},
"lib/solady/src/tokens/ERC20.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Simple ERC20 + EIP-2612 implementation.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC20.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/tokens/ERC20.sol)
/// @author Modified from OpenZeppelin (https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol)
///
/// @dev Note:
/// - The ERC20 standard allows minting and transferring to and from the zero address,
/// minting and transferring zero tokens, as well as self-approvals.
/// For performance, this implementation WILL NOT revert for such actions.
/// Please add any checks with overrides if desired.
/// - The `permit` function uses the ecrecover precompile (0x1).
///
/// If you are overriding:
/// - NEVER violate the ERC20 invariant:
/// the total sum of all balances must be equal to `totalSupply()`.
/// - Check that the overridden function is actually used in the function you want to
/// change the behavior of. Much of the code has been manually inlined for performance.
abstract contract ERC20 {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The total supply has overflowed.
error TotalSupplyOverflow();
/// @dev The allowance has overflowed.
error AllowanceOverflow();
/// @dev The allowance has underflowed.
error AllowanceUnderflow();
/// @dev Insufficient balance.
error InsufficientBalance();
/// @dev Insufficient allowance.
error InsufficientAllowance();
/// @dev The permit is invalid.
error InvalidPermit();
/// @dev The permit has expired.
error PermitExpired();
/// @dev The allowance of Permit2 is fixed at infinity.
error Permit2AllowanceIsFixedAtInfinity();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EVENTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Emitted when `amount` tokens is transferred from `from` to `to`.
event Transfer(address indexed from, address indexed to, uint256 amount);
/// @dev Emitted when `amount` tokens is approved by `owner` to be used by `spender`.
event Approval(address indexed owner, address indexed spender, uint256 amount);
/// @dev `keccak256(bytes("Transfer(address,address,uint256)"))`.
uint256 private constant _TRANSFER_EVENT_SIGNATURE =
0xddf252ad1be2c89b69c2b068fc378daa952ba7f163c4a11628f55a4df523b3ef;
/// @dev `keccak256(bytes("Approval(address,address,uint256)"))`.
uint256 private constant _APPROVAL_EVENT_SIGNATURE =
0x8c5be1e5ebec7d5bd14f71427d1e84f3dd0314c0f7b2291e5b200ac8c7c3b925;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The storage slot for the total supply.
uint256 private constant _TOTAL_SUPPLY_SLOT = 0x05345cdf77eb68f44c;
/// @dev The balance slot of `owner` is given by:
/// ```
/// mstore(0x0c, _BALANCE_SLOT_SEED)
/// mstore(0x00, owner)
/// let balanceSlot := keccak256(0x0c, 0x20)
/// ```
uint256 private constant _BALANCE_SLOT_SEED = 0x87a211a2;
/// @dev The allowance slot of (`owner`, `spender`) is given by:
/// ```
/// mstore(0x20, spender)
/// mstore(0x0c, _ALLOWANCE_SLOT_SEED)
/// mstore(0x00, owner)
/// let allowanceSlot := keccak256(0x0c, 0x34)
/// ```
uint256 private constant _ALLOWANCE_SLOT_SEED = 0x7f5e9f20;
/// @dev The nonce slot of `owner` is given by:
/// ```
/// mstore(0x0c, _NONCES_SLOT_SEED)
/// mstore(0x00, owner)
/// let nonceSlot := keccak256(0x0c, 0x20)
/// ```
uint256 private constant _NONCES_SLOT_SEED = 0x38377508;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev `(_NONCES_SLOT_SEED << 16) | 0x1901`.
uint256 private constant _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX = 0x383775081901;
/// @dev `keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)")`.
bytes32 private constant _DOMAIN_TYPEHASH =
0x8b73c3c69bb8fe3d512ecc4cf759cc79239f7b179b0ffacaa9a75d522b39400f;
/// @dev `keccak256("1")`.
/// If you need to use a different version, override `_versionHash`.
bytes32 private constant _DEFAULT_VERSION_HASH =
0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6;
/// @dev `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`.
bytes32 private constant _PERMIT_TYPEHASH =
0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
/// @dev The canonical Permit2 address.
/// For signature-based allowance granting for single transaction ERC20 `transferFrom`.
/// Enabled by default. To disable, override `_givePermit2InfiniteAllowance()`.
/// [Github](https://github.com/Uniswap/permit2)
/// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
address internal constant _PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 METADATA */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the name of the token.
function name() public view virtual returns (string memory);
/// @dev Returns the symbol of the token.
function symbol() public view virtual returns (string memory);
/// @dev Returns the decimals places of the token.
function decimals() public view virtual returns (uint8) {
return 18;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns the amount of tokens in existence.
function totalSupply() public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
result := sload(_TOTAL_SUPPLY_SLOT)
}
}
/// @dev Returns the amount of tokens owned by `owner`.
function balanceOf(address owner) public view virtual returns (uint256 result) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x20))
}
}
/// @dev Returns the amount of tokens that `spender` can spend on behalf of `owner`.
function allowance(address owner, address spender)
public
view
virtual
returns (uint256 result)
{
if (_givePermit2InfiniteAllowance()) {
if (spender == _PERMIT2) return type(uint256).max;
}
/// @solidity memory-safe-assembly
assembly {
mstore(0x20, spender)
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x34))
}
}
/// @dev Sets `amount` as the allowance of `spender` over the caller's tokens.
///
/// Emits a {Approval} event.
function approve(address spender, uint256 amount) public virtual returns (bool) {
if (_givePermit2InfiniteAllowance()) {
/// @solidity memory-safe-assembly
assembly {
// If `spender == _PERMIT2 && amount != type(uint256).max`.
if iszero(or(xor(shr(96, shl(96, spender)), _PERMIT2), iszero(not(amount)))) {
mstore(0x00, 0x3f68539a) // `Permit2AllowanceIsFixedAtInfinity()`.
revert(0x1c, 0x04)
}
}
}
/// @solidity memory-safe-assembly
assembly {
// Compute the allowance slot and store the amount.
mstore(0x20, spender)
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, caller())
sstore(keccak256(0x0c, 0x34), amount)
// Emit the {Approval} event.
mstore(0x00, amount)
log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x2c)))
}
return true;
}
/// @dev Transfer `amount` tokens from the caller to `to`.
///
/// Requirements:
/// - `from` must at least have `amount`.
///
/// Emits a {Transfer} event.
function transfer(address to, uint256 amount) public virtual returns (bool) {
_beforeTokenTransfer(msg.sender, to, amount);
/// @solidity memory-safe-assembly
assembly {
// Compute the balance slot and load its value.
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, caller())
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Compute the balance slot of `to`.
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance of `to`.
// Will not overflow because the sum of all user balances
// cannot exceed the maximum uint256 value.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c)))
}
_afterTokenTransfer(msg.sender, to, amount);
return true;
}
/// @dev Transfers `amount` tokens from `from` to `to`.
///
/// Note: Does not update the allowance if it is the maximum uint256 value.
///
/// Requirements:
/// - `from` must at least have `amount`.
/// - The caller must have at least `amount` of allowance to transfer the tokens of `from`.
///
/// Emits a {Transfer} event.
function transferFrom(address from, address to, uint256 amount) public virtual returns (bool) {
_beforeTokenTransfer(from, to, amount);
// Code duplication is for zero-cost abstraction if possible.
if (_givePermit2InfiniteAllowance()) {
/// @solidity memory-safe-assembly
assembly {
let from_ := shl(96, from)
if iszero(eq(caller(), _PERMIT2)) {
// Compute the allowance slot and load its value.
mstore(0x20, caller())
mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED))
let allowanceSlot := keccak256(0x0c, 0x34)
let allowance_ := sload(allowanceSlot)
// If the allowance is not the maximum uint256 value.
if not(allowance_) {
// Revert if the amount to be transferred exceeds the allowance.
if gt(amount, allowance_) {
mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated allowance.
sstore(allowanceSlot, sub(allowance_, amount))
}
}
// Compute the balance slot and load its value.
mstore(0x0c, or(from_, _BALANCE_SLOT_SEED))
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Compute the balance slot of `to`.
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance of `to`.
// Will not overflow because the sum of all user balances
// cannot exceed the maximum uint256 value.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c)))
}
} else {
/// @solidity memory-safe-assembly
assembly {
let from_ := shl(96, from)
// Compute the allowance slot and load its value.
mstore(0x20, caller())
mstore(0x0c, or(from_, _ALLOWANCE_SLOT_SEED))
let allowanceSlot := keccak256(0x0c, 0x34)
let allowance_ := sload(allowanceSlot)
// If the allowance is not the maximum uint256 value.
if not(allowance_) {
// Revert if the amount to be transferred exceeds the allowance.
if gt(amount, allowance_) {
mstore(0x00, 0x13be252b) // `InsufficientAllowance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated allowance.
sstore(allowanceSlot, sub(allowance_, amount))
}
// Compute the balance slot and load its value.
mstore(0x0c, or(from_, _BALANCE_SLOT_SEED))
let fromBalanceSlot := keccak256(0x0c, 0x20)
let fromBalance := sload(fromBalanceSlot)
// Revert if insufficient balance.
if gt(amount, fromBalance) {
mstore(0x00, 0xf4d678b8) // `InsufficientBalance()`.
revert(0x1c, 0x04)
}
// Subtract and store the updated balance.
sstore(fromBalanceSlot, sub(fromBalance, amount))
// Compute the balance slot of `to`.
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance of `to`.
// Will not overflow because the sum of all user balances
// cannot exceed the maximum uint256 value.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, from_), shr(96, mload(0x0c)))
}
}
_afterTokenTransfer(from, to, amount);
return true;
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* EIP-2612 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev For more performance, override to return the constant value
/// of `keccak256(bytes(name()))` if `name()` will never change.
function _constantNameHash() internal view virtual returns (bytes32 result) {}
/// @dev If you need a different value, override this function.
function _versionHash() internal view virtual returns (bytes32 result) {
result = _DEFAULT_VERSION_HASH;
}
/// @dev For inheriting contracts to increment the nonce.
function _incrementNonce(address owner) internal virtual {
/// @solidity memory-safe-assembly
assembly {
mstore(0x0c, _NONCES_SLOT_SEED)
mstore(0x00, owner)
let nonceSlot := keccak256(0x0c, 0x20)
sstore(nonceSlot, add(1, sload(nonceSlot)))
}
}
/// @dev Returns the current nonce for `owner`.
/// This value is used to compute the signature for EIP-2612 permit.
function nonces(address owner) public view virtual returns (uint256 result) {
/// @solidit
Submitted on: 2025-10-16 14:12:32
Comments
Log in to comment.
No comments yet.