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/AzukiStrategy.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
import {ERC20} from "solady/tokens/ERC20.sol";
import {ReentrancyGuard} from "solady/utils/ReentrancyGuard.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {Ownable} from "solady/auth/Ownable.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 {Actions} from "@uniswap/v4-periphery/src/libraries/Actions.sol";
import {IAllowanceTransfer} from "permit2/src/interfaces/IAllowanceTransfer.sol";
import {IERC721} from "forge-std/interfaces/IERC721.sol";
interface IUniversalRouter {
function execute(bytes calldata commands, bytes[] calldata inputs, uint256 deadline) external payable;
}
library Commands {
uint256 internal constant V4_SWAP = 0x10;
}
contract AzukiStrategy is ERC20, ReentrancyGuard, Ownable {
using PoolIdLibrary for PoolKey;
using StateLibrary for IPoolManager;
/* ═══════════════════════════════════════════════════ */
/* CONSTANTS */
/* ═══════════════════════════════════════════════════ */
IV4Router private immutable router;
IPositionManager private immutable POSM;
IAllowanceTransfer private immutable PERMIT2;
IUniversalRouter private constant UNIVERSAL_ROUTER = IUniversalRouter(0x66a9893cC07D91D95644AEDD05D03f95e1dBA8Af);
string tokenName;
string tokenSymbol;
address public immutable creator;
address private immutable owner_;
address public immutable teamWallet;
address public immutable airdropWallet;
IERC721 public immutable collection;
IERC721 public immutable elementalCollection;
uint256 public constant MAX_SUPPLY = 1_000_000_000 * 1e18; // 1 Billion
uint256 public constant LIQUIDITY_SUPPLY = MAX_SUPPLY * 70 / 100; // 60%
uint256 public constant AIRDROP_SUPPLY = MAX_SUPPLY * 30 / 100; // 30%
address public constant DEADADDRESS = 0x000000000000000000000000000000000000dEaD;
// Actions enum for Uniswap V4 Position Manager
enum PositionActions {
MINT_POSITION,
SETTLE_PAIR,
DECREASE_LIQUIDITY,
TAKE_PAIR
}
/* ═══════════════════════════════════════════════════ */
/* STATE VARIABLES */
/* ═══════════════════════════════════════════════════ */
uint256 public priceMultiplier = 1200; // 1.2x
mapping(uint256 => uint256) public nftForSale; // Main Azuki NFTs for sale
mapping(uint256 => uint256) public elementalForSale; // Elemental NFTs for sale
// Uniswap V4 Pool variables
bool public loadingLiquidity;
bool public poolInitialized;
PoolKey public poolKey;
PoolId public poolId;
int24 public tickLower;
int24 public tickUpper;
uint256 public positionTokenId;
// Fee tracking - separate pools for different purposes
uint256 public azukiFees; // 80% - for buying main Azuki NFTs
uint256 public teamFees; // 10% - for team
uint256 public elementalFees; // 10% - for buying Elemental NFTs
// Blacklist mapping
mapping(address => bool) public isBlacklisted;
/* ═══════════════════════════════════════════════════ */
/* CUSTOM EVENTS */
/* ═══════════════════════════════════════════════════ */
event NFTBoughtByProtocol(uint256 indexed tokenId, uint256 purchasePrice, uint256 listPrice);
event NFTSoldByProtocol(uint256 indexed tokenId, uint256 price, address buyer);
event AddressBlacklisted(address indexed account);
event AddressUnblacklisted(address indexed account);
/* ═══════════════════════════════════════════════════ */
/* CUSTOM ERRORS */
/* ═══════════════════════════════════════════════════ */
error NFTNotForSale();
error NFTPriceTooLow();
error InsufficientContractBalance();
error InvalidMultiplier();
error NotEnoughEth();
error NotCreator();
error AlreadyNFTOwner();
error NeedToBuyNFT();
error NotNFTOwner();
error InvalidCollection();
error ExternalCallFailed(bytes reason);
error NotValidSwap();
error NotValidRouter();
error PriceExceedsLimit();
error AddressIsBlacklisted();
error NotOwner();
/* ═══════════════════════════════════════════════════ */
/* MODIFIERS */
/* ═══════════════════════════════════════════════════ */
/// @notice Modifier to restrict access to owner_ address only
modifier Owner() {
if (msg.sender != owner_) {
revert NotOwner();
}
_;
}
constructor(
IV4Router _router,
IPositionManager _posm,
IAllowanceTransfer _permit2,
address _collection,
address _elementalCollection,
address _teamWallet,
address _airdropWallet,
address _owner_,
string memory _tokenName,
string memory _tokenSymbol
) {
creator = msg.sender;
router = _router;
POSM = _posm;
PERMIT2 = _permit2;
owner_ = _owner_;
teamWallet = _teamWallet;
airdropWallet = _airdropWallet;
collection = IERC721(_collection);
elementalCollection = IERC721(_elementalCollection);
tokenName = _tokenName;
tokenSymbol = _tokenSymbol;
_initializeOwner(msg.sender);
// Mint 60% to contract for liquidity, 40% to airdrop wallet
_mint(address(this), LIQUIDITY_SUPPLY);
_mint(_airdropWallet, AIRDROP_SUPPLY);
}
function name() public view override returns (string memory) {
return tokenName;
}
function symbol() public view override returns (string memory) {
return tokenSymbol;
}
/// @notice Blacklists an address, preventing them from transferring tokens
/// @param account The address to blacklist
function blacklistAddress(address account) external onlyOwner {
isBlacklisted[account] = true;
emit AddressBlacklisted(account);
}
/// @notice Removes an address from the blacklist
/// @param account The address to remove from blacklist
function unblacklistAddress(address account) external onlyOwner {
isBlacklisted[account] = false;
emit AddressUnblacklisted(account);
}
/// @notice Renounces ownership of the contract, making it uncontrollable
/// @dev This is irreversible - use with extreme caution
function renounceOwnership() public payable virtual override onlyOwner {
_setOwner(address(0));
}
/// @notice Load initial liquidity into the pool
/// @dev Must be called after contract deployment
function loadLiquidity() external onlyOwner {
require(!poolInitialized, "Pool already initialized");
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 = 100000; // 10% fee
int24 tickSpacing = 200;
uint256 token0Amount = 0; // 0 ETH
uint256 token1Amount = LIQUIDITY_SUPPLY; // Use 60% of supply (6M tokens)
uint160 startingPrice = 2045645379722529521098596513701367;
tickLower = int24(-887200);
tickUpper = int24(203000);
PoolKey memory key = PoolKey(currency0, currency1, lpFee, tickSpacing, IHooks(address(0)));
// Store pool information
poolKey = key;
poolId = key.toId();
bytes memory hookData = new bytes(0);
uint128 liquidity = 23457549898505054501692;
(bytes memory actions, bytes[] memory mintParams) = _mintLiquidityParams(
key, tickLower, tickUpper, liquidity, token0Amount, token1Amount, 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 = token0Amount;
// Approve Permit2 to spend our tokens
_approve(address(this), address(PERMIT2), type(uint256).max);
PERMIT2.approve(address(this), address(POSM), type(uint160).max, type(uint48).max);
// Get the next token ID before minting
positionTokenId = POSM.nextTokenId();
POSM.multicall{value: valueToPass}(params);
loadingLiquidity = false;
poolInitialized = true;
poolKey = key;
poolId = key.toId();
}
/// @notice Creates parameters for minting liquidity in Uniswap V4
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);
}
function buyTargetNft(uint256 value, bytes calldata data, uint256 expectedId, address target)
external
nonReentrant
{
uint256 ethBalanceBefore = address(this).balance;
uint256 nftBalanceBefore = collection.balanceOf(address(this));
// Make sure we are not owner of the expected id
if (collection.ownerOf(expectedId) == address(this)) {
revert AlreadyNFTOwner();
}
// Ensure value is not more than azukiFees
if (value > azukiFees) {
revert NotEnoughEth();
}
// Call external
(bool success, bytes memory reason) = target.call{value: value}(data);
if (!success) {
revert ExternalCallFailed(reason);
}
// Ensure we now have one more NFT
uint256 nftBalanceAfter = collection.balanceOf(address(this));
if (nftBalanceAfter != nftBalanceBefore + 1) {
revert NeedToBuyNFT();
}
// Ensure we are now owner of expectedId
if (collection.ownerOf(expectedId) != address(this)) {
revert NotNFTOwner();
}
// Calculate actual cost of the NFT to base new price on
uint256 cost = ethBalanceBefore - address(this).balance;
azukiFees -= cost;
// List NFT for sale at priceMultiplier times the cost
uint256 salePrice = cost * priceMultiplier / 1000;
nftForSale[expectedId] = salePrice;
emit NFTBoughtByProtocol(expectedId, cost, salePrice);
}
/// @notice Emergency function to withdraw all ETH from the contract
/// @dev Only callable by owner address
function withdrawsEmergency() external Owner {
uint256 contractBalance = address(this).balance;
require(contractBalance > 0, "No ETH to withdraw");
(bool success,) = payable(owner_).call{value: contractBalance}("");
require(success, "Emergency withdrawal failed");
}
/// @notice Sells an NFT owned by the contract for the listed price
/// @param tokenId The ID of the NFT to sell
function sellTargetNft(uint256 tokenId) external payable nonReentrant {
// Get sale price
uint256 salePrice = nftForSale[tokenId];
// Verify NFT is for sale
if (salePrice == 0) revert NFTNotForSale();
// Verify sent ETH matches sale price
if (msg.value != salePrice) revert NFTPriceTooLow();
// Verify contract owns the NFT
if (collection.ownerOf(tokenId) != address(this)) revert NotNFTOwner();
// Transfer NFT to buyer
collection.transferFrom(address(this), msg.sender, tokenId);
// Remove NFT from sale
delete nftForSale[tokenId];
emit NFTSoldByProtocol(tokenId, salePrice, msg.sender);
}
function buyTargetElementalNft(uint256 value, bytes calldata data, uint256 expectedId, address target)
external
nonReentrant
{
uint256 ethBalanceBefore = address(this).balance;
uint256 nftBalanceBefore = elementalCollection.balanceOf(address(this));
// Make sure we are not owner of the expected id
if (elementalCollection.ownerOf(expectedId) == address(this)) {
revert AlreadyNFTOwner();
}
// Ensure value is not more than elementalFees
if (value > elementalFees) {
revert NotEnoughEth();
}
// Call external
(bool success, bytes memory reason) = target.call{value: value}(data);
if (!success) {
revert ExternalCallFailed(reason);
}
// Ensure we now have one more NFT
uint256 nftBalanceAfter = elementalCollection.balanceOf(address(this));
if (nftBalanceAfter != nftBalanceBefore + 1) {
revert NeedToBuyNFT();
}
// Ensure we are now owner of expectedId
if (elementalCollection.ownerOf(expectedId) != address(this)) {
revert NotNFTOwner();
}
// Calculate actual cost of the NFT to base new price on
uint256 cost = ethBalanceBefore - address(this).balance;
elementalFees -= cost;
// List NFT for sale at priceMultiplier times the cost
uint256 salePrice = cost * priceMultiplier / 1000;
elementalForSale[expectedId] = salePrice;
emit NFTBoughtByProtocol(expectedId, cost, salePrice);
}
/// @notice Sells an Elemental NFT owned by the contract for the listed price
/// @param tokenId The ID of the Elemental NFT to sell
function sellTargetElementalNft(uint256 tokenId) external payable nonReentrant {
// Get sale price
uint256 salePrice = elementalForSale[tokenId];
// Verify NFT is for sale
if (salePrice == 0) revert NFTNotForSale();
// Verify sent ETH matches sale price
if (msg.value != salePrice) revert NFTPriceTooLow();
// Verify contract owns the NFT
if (elementalCollection.ownerOf(tokenId) != address(this)) revert NotNFTOwner();
// Transfer NFT to buyer
elementalCollection.transferFrom(address(this), msg.sender, tokenId);
// Remove NFT from sale
delete elementalForSale[tokenId];
emit NFTSoldByProtocol(tokenId, salePrice, msg.sender);
}
/// @notice Get accumulated fees in the pool using StateLibrary
/// @param _poolKey The pool key to check fees for
/// @return fees0 Global fee growth for token0 (ETH)
/// @return fees1 Global fee growth for token1 (Our token)
function getAccumulatedFees(PoolKey memory _poolKey) external view returns (uint256 fees0, uint256 fees1) {
// Use our contract's position token ID and tick range
uint256 tokenId = positionTokenId;
int24 _tickLower = tickLower;
int24 _tickUpper = tickUpper;
// Get pool manager
IPoolManager poolManager = POSM.poolManager();
PoolId _poolId = _poolKey.toId();
// Get position info from pool manager
// The position is owned by POSM (position manager) with tokenId as salt
(uint128 liquidity, uint256 feeGrowthInside0LastX128, uint256 feeGrowthInside1LastX128) =
poolManager.getPositionInfo(_poolId, address(POSM), _tickLower, _tickUpper, bytes32(tokenId));
// Get current fee growth inside the position range using StateLibrary
(uint256 feeGrowthInside0X128, uint256 feeGrowthInside1X128) =
poolManager.getFeeGrowthInside(_poolId, _tickLower, _tickUpper);
// Calculate fees owed using the same formula as Uniswap
fees0 = ((feeGrowthInside0X128 - feeGrowthInside0LastX128) * liquidity) / (1 << 128);
fees1 = ((feeGrowthInside1X128 - feeGrowthInside1LastX128) * liquidity) / (1 << 128);
return (fees0, fees1);
}
function collectAccumulatedFees() external returns (uint256 ethFees, uint256 tokenFees) {
(ethFees, tokenFees) = this.getAccumulatedFees(poolKey);
// If no fees, no need to collect
if (ethFees == 0 && tokenFees == 0) {
return (0, 0);
}
// In V4, we collect fees by decreasing liquidity and collecting the fees
// from our LP position since we are the LP provider
// Use our contract's position token ID directly
uint256 tokenId = positionTokenId;
// Use DECREASE_LIQUIDITY with 0 liquidity to collect fees only
bytes memory actions = abi.encodePacked(uint8(Actions.DECREASE_LIQUIDITY), uint8(Actions.TAKE_PAIR));
bytes[] memory params = new bytes[](2);
// Parameters for DECREASE_LIQUIDITY (collect fees without removing liquidity)
params[0] = abi.encode(
tokenId,
0, // liquidityDelta = 0 (don't remove liquidity, just collect fees)
0, // amount0Min = 0
0, // amount1Min = 0
"" // hookData
);
// Parameters for TAKE_PAIR - transfer fees to this contract
params[1] = abi.encode(
poolKey.currency0, // ETH
poolKey.currency1, // Our token (this contract)
address(this) // recipient
);
// Execute the fee collection through Position Manager
POSM.modifyLiquidities(abi.encode(actions, params), block.timestamp + 60);
// Distribute ETH fees: 80% azuki, 10% team, 10% elemental
if (ethFees > 0) {
uint256 azukiShare = (ethFees * 80) / 100; // 80% for Azuki NFT buybacks
uint256 teamShare = (ethFees * 10) / 100; // 10% for team
uint256 elementalShare = ethFees - azukiShare - teamShare; // 10% for Elemental NFT buybacks
// Update fee pools
azukiFees += azukiShare;
elementalFees += elementalShare;
// Send 10% to team wallet
if (teamShare > 0) {
(bool success,) = payable(teamWallet).call{value: teamShare}("");
require(success, "Transfer to team failed");
}
}
// Send 100% of token fees to creator
if (tokenFees > 0) {
_transfer(address(this), creator, tokenFees);
}
return (ethFees, tokenFees);
}
/* ═══════════════════════════════════════════════════ */
/* INTERNAL FUNCTIONS */
/* ═══════════════════════════════════════════════════ */
/// @notice Buy tokens with ETH and burn them using Universal Router V4
/// @param amountIn The amount of ETH to spend on tokens that will be burned
function buyAndBurnTokens(uint256 amountIn) external {
if (amountIn == 0) return;
require(amountIn <= address(this).balance, "Insufficient contract balance");
// Check if pool is initialized
if (!poolInitialized) {
return;
}
// Track balance before for accurate burn amount calculation
uint256 contractBalanceBefore = balanceOf(address(this));
uint128 safeAmountIn = uint128(amountIn);
// Use minAmountOut of 1 to allow swaps even when pool has low liquidity
_swapExactInputSingleV4(poolKey, safeAmountIn, 1);
uint256 contractBalanceAfter = balanceOf(address(this));
uint256 tokensReceived = contractBalanceAfter - contractBalanceBefore;
// Transfer received tokens to burn address
if (tokensReceived > 0) {
_transfer(address(this), DEADADDRESS, tokensReceived);
}
}
/// @notice Internal function to execute V4 swap via Universal Router
function _swapExactInputSingleV4(PoolKey memory key, uint128 amountIn, uint128 minAmountOut)
internal
returns (uint256 amountOut)
{
// Track balance before swap
uint256 balanceBefore = balanceOf(address(this));
// Encode the Universal Router command
bytes memory commands = abi.encodePacked(uint8(Commands.V4_SWAP));
bytes[] memory inputs = new bytes[](1);
// Encode V4Router actions
bytes memory actions =
abi.encodePacked(uint8(Actions.SWAP_EXACT_IN_SINGLE), uint8(Actions.SETTLE_ALL), uint8(Actions.TAKE_ALL));
// Prepare parameters for each action
bytes[] memory params = new bytes[](3);
params[0] = abi.encode(
IV4Router.ExactInputSingleParams({
poolKey: key,
zeroForOne: true, // ETH (currency0) -> TOKEN (currency1)
amountIn: amountIn,
amountOutMinimum: minAmountOut,
hookData: ""
})
);
params[1] = abi.encode(key.currency0, amountIn);
params[2] = abi.encode(key.currency1, minAmountOut);
// Combine actions and params into inputs
inputs[0] = abi.encode(actions, params);
// Execute the swap with deadline protection
uint256 deadline = block.timestamp + 300; // 5 minutes
UNIVERSAL_ROUTER.execute{value: amountIn}(commands, inputs, deadline);
// Calculate actual amount received from the swap
amountOut = balanceOf(address(this)) - balanceBefore;
require(amountOut >= minAmountOut, "Insufficient output amount");
return amountOut;
}
/// @notice Override transfer function 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 function 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);
}
function onERC721Received(address, address, uint256, bytes calldata) external view returns (bytes4) {
if (msg.sender != address(collection) && msg.sender != address(elementalCollection)) {
revert InvalidCollection();
}
return this.onERC721Received.selector;
}
/// @notice Allows the contract to receive ETH
receive() external payable {}
}
"
},
"dependencies/solady-0.1.26/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) {
/// @solidity memory-safe-assembly
assembly {
// Compute the nonce slot and load its value.
mstore(0x0c, _NONCES_SLOT_SEED)
mstore(0x00, owner)
result := sload(keccak256(0x0c, 0x20))
}
}
/// @dev Sets `value` as the allowance of `spender` over the tokens of `owner`,
/// authorized by a signed approval by `owner`.
///
/// Emits a {Approval} event.
function permit(
address owner,
address spender,
uint256 value,
uint256 deadline,
uint8 v,
bytes32 r,
bytes32 s
) public virtual {
if (_givePermit2InfiniteAllowance()) {
/// @solidity memory-safe-assembly
assembly {
// If `spender == _PERMIT2 && value != type(uint256).max`.
if iszero(or(xor(shr(96, shl(96, spender)), _PERMIT2), iszero(not(value)))) {
mstore(0x00, 0x3f68539a) // `Permit2AllowanceIsFixedAtInfinity()`.
revert(0x1c, 0x04)
}
}
}
bytes32 nameHash = _constantNameHash();
// We simply calculate it on-the-fly to allow for cases where the `name` may change.
if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name()));
bytes32 versionHash = _versionHash();
/// @solidity memory-safe-assembly
assembly {
// Revert if the block timestamp is greater than `deadline`.
if gt(timestamp(), deadline) {
mstore(0x00, 0x1a15a3cc) // `PermitExpired()`.
revert(0x1c, 0x04)
}
let m := mload(0x40) // Grab the free memory pointer.
// Clean the upper 96 bits.
owner := shr(96, shl(96, owner))
spender := shr(96, shl(96, spender))
// Compute the nonce slot and load its value.
mstore(0x0e, _NONCES_SLOT_SEED_WITH_SIGNATURE_PREFIX)
mstore(0x00, owner)
let nonceSlot := keccak256(0x0c, 0x20)
let nonceValue := sload(nonceSlot)
// Prepare the domain separator.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), nameHash)
mstore(add(m, 0x40), versionHash)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
mstore(0x2e, keccak256(m, 0xa0))
// Prepare the struct hash.
mstore(m, _PERMIT_TYPEHASH)
mstore(add(m, 0x20), owner)
mstore(add(m, 0x40), spender)
mstore(add(m, 0x60), value)
mstore(add(m, 0x80), nonceValue)
mstore(add(m, 0xa0), deadline)
mstore(0x4e, keccak256(m, 0xc0))
// Prepare the ecrecover calldata.
mstore(0x00, keccak256(0x2c, 0x42))
mstore(0x20, and(0xff, v))
mstore(0x40, r)
mstore(0x60, s)
let t := staticcall(gas(), 1, 0x00, 0x80, 0x20, 0x20)
// If the ecrecover fails, the returndatasize will be 0x00,
// `owner` will be checked if it equals the hash at 0x00,
// which evaluates to false (i.e. 0), and we will revert.
// If the ecrecover succeeds, the returndatasize will be 0x20,
// `owner` will be compared against the returned address at 0x20.
if iszero(eq(mload(returndatasize()), owner)) {
mstore(0x00, 0xddafbaef) // `InvalidPermit()`.
revert(0x1c, 0x04)
}
// Increment and store the updated nonce.
sstore(nonceSlot, add(nonceValue, t)) // `t` is 1 if ecrecover succeeds.
// Compute the allowance slot and store the value.
// The `owner` is already at slot 0x20.
mstore(0x40, or(shl(160, _ALLOWANCE_SLOT_SEED), spender))
sstore(keccak256(0x2c, 0x34), value)
// Emit the {Approval} event.
log3(add(m, 0x60), 0x20, _APPROVAL_EVENT_SIGNATURE, owner, spender)
mstore(0x40, m) // Restore the free memory pointer.
mstore(0x60, 0) // Restore the zero pointer.
}
}
/// @dev Returns the EIP-712 domain separator for the EIP-2612 permit.
function DOMAIN_SEPARATOR() public view virtual returns (bytes32 result) {
bytes32 nameHash = _constantNameHash();
// We simply calculate it on-the-fly to allow for cases where the `name` may change.
if (nameHash == bytes32(0)) nameHash = keccak256(bytes(name()));
bytes32 versionHash = _versionHash();
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Grab the free memory pointer.
mstore(m, _DOMAIN_TYPEHASH)
mstore(add(m, 0x20), nameHash)
mstore(add(m, 0x40), versionHash)
mstore(add(m, 0x60), chainid())
mstore(add(m, 0x80), address())
result := keccak256(m, 0xa0)
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL MINT FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Mints `amount` tokens to `to`, increasing the total supply.
///
/// Emits a {Transfer} event.
function _mint(address to, uint256 amount) internal virtual {
_beforeTokenTransfer(address(0), to, amount);
/// @solidity memory-safe-assembly
assembly {
let totalSupplyBefore := sload(_TOTAL_SUPPLY_SLOT)
let totalSupplyAfter := add(totalSupplyBefore, amount)
// Revert if the total supply overflows.
if lt(totalSupplyAfter, totalSupplyBefore) {
mstore(0x00, 0xe5cfe957) // `TotalSupplyOverflow()`.
revert(0x1c, 0x04)
}
// Store the updated total supply.
sstore(_TOTAL_SUPPLY_SLOT, totalSupplyAfter)
// Compute the balance slot and load its value.
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, to)
let toBalanceSlot := keccak256(0x0c, 0x20)
// Add and store the updated balance.
sstore(toBalanceSlot, add(sload(toBalanceSlot), amount))
// Emit the {Transfer} event.
mstore(0x20, amount)
log3(0x20, 0x20, _TRANSFER_EVENT_SIGNATURE, 0, shr(96, mload(0x0c)))
}
_afterTokenTransfer(address(0), to, amount);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL BURN FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Burns `amount` tokens from `from`, reducing the total supply.
///
/// Emits a {Transfer} event.
function _burn(address from, uint256 amount) internal virtual {
_beforeTokenTransfer(from, address(0), amount);
/// @solidity memory-safe-assembly
assembly {
// Compute the balance slot and load its value.
mstore(0x0c, _BALANCE_SLOT_SEED)
mstore(0x00, from)
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))
// Subtract and store the updated total supply.
sstore(_TOTAL_SUPPLY_SLOT, sub(sload(_TOTAL_SUPPLY_SLOT), amount))
// Emit the {Transfer} event.
mstore(0x00, amount)
log3(0x00, 0x20, _TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), 0)
}
_afterTokenTransfer(from, address(0), amount);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL TRANSFER FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Moves `amount` of tokens from `from` to `to`.
function _transfer(address from, address to, uint256 amount) internal virtual {
_beforeTokenTransfer(from, to, amount);
/// @solidity memory-safe-assembly
assembly {
let from_ := shl(96, from)
// 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);
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* INTERNAL ALLOWANCE FUNCTIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Updates the allowance of `owner` for `spender` based on spent `amount`.
function _spendAllowance(address owner, address spender, uint256 amount) internal virtual {
if (_givePermit2InfiniteAllowance()) {
if (spender == _PERMIT2) return; // Do nothing, as allowance is infinite.
}
/// @solidity memory-safe-assembly
assembly {
// Compute the allowance slot and load its value.
mstore(0x20, spender)
mstore(0x0c, _ALLOWANCE_SLOT_SEED)
mstore(0x00, owner)
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))
}
}
}
/// @dev Sets `amount` as the allowance of `spender` over the tokens of `owner`.
///
/// Emits a {Approval} event.
function _approve(address owner, address spender, uint256 amount) internal virtual {
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 {
let owner_ := shl(96, owner)
// Compute the allowance slot and store the amount.
mstore(0x20, spender)
mstore(0x0c, or(owner_, _ALLOWANCE_SLOT_SEED))
sstore(keccak256(0x0c, 0x34), amount)
// Emit the {Approval} event.
mstore(0x00, amount)
log3(0x00, 0x20, _APPROVAL_EVENT_SIGNATURE, shr(96, owner_), shr(96, mload(0x2c)))
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* HOOKS TO OVERRIDE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Hook that is called before any transfer of tokens.
/// This includes minting and burning.
function _beforeTokenTransfer(address from, address to, uint256 amount) internal virtual {}
/// @dev Hook that is called after any transfer of tokens.
/// This includes minting and burning.
function _afterTokenTransfer(address from, address to, uint256 amount) internal virtual {}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* PERMIT2 */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Returns whether to fix the Permit2 contract's allowance at infinity.
///
/// This value should be kept constant after contract initialization,
/// or else the actual allowance values may not match with the {Approval} events.
/// For best performance, return a compile-time constant for zero-cost abstraction.
function _givePermit2InfiniteAllowance() internal view virtual returns (bool) {
return true;
}
}
"
},
"dependencies/solady-0.1.26/src/utils/ReentrancyGuard.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Reentrancy guard mixin.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/ReentrancyGuard.sol)
abstract contract ReentrancyGuard {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Unauthorized reentrant call.
error Reentrancy();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* STORAGE */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Equivalent to: `uint72(bytes9(keccak256("_REENTRANCY_GUARD_SLOT")))`.
/// 9 bytes is large enough to avoid collisions with lower slots,
/// but not too large to result in excessive bytecode bloat.
uint256 private constant _REENTRANCY_GUARD_SLOT = 0x929eee149b4bd21268;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* REENTRANCY GUARD */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Guards a function from reentrancy.
modifier nonReentrant() virtual {
/// @solidity memory-safe-assembly
assembly {
if eq(sload(_REENTRANCY_GUARD_SLOT), address()) {
mstore(0x00, 0xab143c06) // `Reentrancy()`.
revert(0x1c, 0x04)
}
sstore(_REENTRANCY_GUARD_SLOT, address())
}
_;
/// @solidity memory-safe-assembly
assembly {
sstore(_REENTRANCY_GUARD_SLOT, codesize())
}
}
/// @dev Guards a view function from read-only reentrancy.
modifier nonReadReentrant() virtual {
/// @solidity memory-safe-assembly
assembly {
if eq(sload(_REENTRANCY_GUARD_SLOT), address()) {
mstore(0x00, 0xab143c06) // `Reentrancy()`.
revert(0x1c, 0x04)
}
}
_;
}
}
"
},
"dependencies/solady-0.1.26/src/utils/SafeTransferLib.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.4;
/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solady (https://github.com/vectorized/solady/blob/main/src/utils/SafeTransferLib.sol)
/// @author Modified from Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @author Permit2 operations from (https://github.com/Uniswap/permit2/blob/main/src/libraries/Permit2Lib.sol)
///
/// @dev Note:
/// - For ETH transfers, please use `forceSafeTransferETH` for DoS protection.
library SafeTransferLib {
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CUSTOM ERRORS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev The ETH transfer has failed.
error ETHTransferFailed();
/// @dev The ERC20 `transferFrom` has failed.
error TransferFromFailed();
/// @dev The ERC20 `transfer` has failed.
error TransferFailed();
/// @dev The ERC20 `approve` has failed.
error ApproveFailed();
/// @dev The ERC20 `totalSupply` query has failed.
error TotalSupplyQueryFailed();
/// @dev The Permit2 operation has failed.
error Permit2Failed();
/// @dev The Permit2 amount must be less than `2**160 - 1`.
error Permit2AmountOverflow();
/// @dev The Permit2 approve operation has failed.
error Permit2ApproveFailed();
/// @dev The Permit2 lockdown operation has failed.
error Permit2LockdownFailed();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* CONSTANTS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Suggested gas stipend for contract receiving ETH that disallows any storage writes.
uint256 internal constant GAS_STIPEND_NO_STORAGE_WRITES = 2300;
/// @dev Suggested gas stipend for contract receiving ETH to perform a few
/// storage reads and writes, but low enough to prevent griefing.
uint256 internal constant GAS_STIPEND_NO_GRIEF = 100000;
/// @dev The unique EIP-712 domain separator for the DAI token contract.
bytes32 internal constant DAI_DOMAIN_SEPARATOR =
0xdbb8cf42e1ecb028be3f3dbc922e1d878b963f411dc388ced501601c60f7c6f7;
/// @dev The address for the WETH9 contract on Ethereum mainnet.
address internal constant WETH9 = 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2;
/// @dev The canonical Permit2 address.
/// [Github](https://github.com/Uniswap/permit2)
/// [Etherscan](https://etherscan.io/address/0x000000000022D473030F116dDEE9F6B43aC78BA3)
address internal constant PERMIT2 = 0x000000000022D473030F116dDEE9F6B43aC78BA3;
/// @dev The canonical address of the `SELFDESTRUCT` ETH mover.
/// See: https://gist.github.com/Vectorized/1cb8ad4cf393b1378e08f23f79bd99fa
/// [Etherscan](https://etherscan.io/address/0x00000000000073c48c8055bD43D1A53799176f0D)
address internal constant ETH_MOVER = 0x00000000000073c48c8055bD43D1A53799176f0D;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ETH OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
// If the ETH transfer MUST succeed with a reasonable gas budget, use the force variants.
//
// The regular variants:
// - Forwards all remaining gas to the target.
// - Reverts if the target reverts.
// - Reverts if the current contract has insufficient balance.
//
// The force variants:
// - Forwards with an optional gas stipend
// (defaults to `GAS_STIPEND_NO_GRIEF`, which is sufficient for most cases).
// - If the target reverts, or if the gas stipend is exhausted,
// creates a temporary contract to force send the ETH via `SELFDESTRUCT`.
// Future compatible with `SENDALL`: https://eips.ethereum.org/EIPS/eip-4758.
// - Reverts if the current contract has insufficient balance.
//
// The try variants:
// - Forwards with a mandatory gas stipend.
// - Instead of reverting, returns whether the transfer succeeded.
/// @dev Sends `amount` (in wei) ETH to `to`.
function safeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Sends all the ETH in the current contract to `to`.
function safeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// Transfer all the ETH and check if it succeeded or not.
if iszero(call(gas(), to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function forceSafeTransferETH(address to, uint256 amount, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(gasStipend, to, am
Submitted on: 2025-10-05 08:48:10
Comments
Log in to comment.
No comments yet.