Description:
Proxy contract enabling upgradeable smart contract patterns. Delegates calls to an implementation contract.
Blockchain: Ethereum
Source Code: View Code On The Blockchain
Solidity Source Code:
{{
"language": "Solidity",
"sources": {
"src/PenguStrategyToken.sol": {
"content": "// SPDX-License-Identifier: MIT
pragma solidity ^0.8.30;
import {ERC20} from "solady/tokens/ERC20.sol";
import {SafeTransferLib} from "solady/utils/SafeTransferLib.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/utils/ReentrancyGuard.sol";
import {QuadraticCurve} from "./lib/QuadraticCurve.sol";
import {QuadraticCurveSpread} from "./lib/QuadraticCurveSpread.sol";
contract PenguStrategyToken is ERC20, ReentrancyGuard {
using QuadraticCurve for QuadraticCurve.Params;
using QuadraticCurveSpread for QuadraticCurveSpread.Params;
QuadraticCurveSpread.Params public curve;
uint256 public constant MAX_SUPPLY = 888_888_888 * 1e18;
address public constant DEAD_ADDRESS = 0x000000000000000000000000000000000000dEaD;
uint128 public constant BUY_FEE_RATE = 1e17; // 10%
uint128 public constant SELL_FEE_RATE = 15e16; // 15%
uint256 public constant PROTOCOL_FEE_BPS = 2000; // 20% of fee rate (of the 10% total fee)
uint256 public constant VILLAGE_FEE_BPS = 1000; // 10% of fee rate (of the 10% total fee)
address public protocolFeeRecipient;
address public deployer;
address public transferController;
bool public transfersEnabled;
bool public live;
address public strategy;
address public villageNft; // Set by strategy after deployment
// Village fee system
uint256 public villageFeePool; // Accumulated fees awaiting distribution
uint256 public totalVillageWeight; // Sum of pudgies across active villages
uint256 public villageRewardsIndex; // Accumulated rewards per pudgy (scaled by ACC_PRECISION)
mapping(uint256 => uint256) public villageRewardDebt; // villageId => reward debt
mapping(uint256 => uint256) public villageWeight; // villageId => pudgy count
uint256 public totalVillageRewardDebt; // Sum of reward debt across all villages
uint256 private constant ACC_PRECISION = 1e18;
/// @dev Emitted during a mint call
event Mint(address indexed by, address indexed to, uint256 assets, uint256 tokens);
/// @dev Emitted during a redeem call.
event Redeem(address indexed by, address indexed from, address indexed to, uint256 assets, uint256 tokens);
/// @dev Emitted during a lock call
event Lock(address indexed by, address indexed from, uint256 tokens);
/// @dev Emitted when strategy is updated
event StrategyUpdated(address indexed newStrategy);
/// @dev Emitted when surplus is used by strategy
event SurplusUsed(address indexed strategy, uint256 amount);
/// @dev Emitted when village NFT is configured
event VillageNftSet(address indexed villageNft);
/// @dev Emitted when protocol fee recipient is updated
event ProtocolFeeRecipientUpdated(address indexed newRecipient);
/// @dev Emitted when village fees are claimed by a village owner
event VillageFeeClaimed(address indexed recipient, uint256 villageId, uint256 amount);
/// @dev Emitted when transfer status changes
event TransfersStatusUpdated(bool enabled);
error TransfersDisabled();
error NotLive();
/// @dev Marks a function as only callable by the strategy.
modifier onlyStrategy() {
_onlyStrategy();
_;
}
modifier onlyDeployer() {
require(msg.sender == deployer, "Only deployer");
_;
}
modifier onlyVillageNft() {
_onlyVillageNft();
_;
}
function _onlyStrategy() internal view {
require(msg.sender == strategy, "Only strategy");
}
function _onlyVillageNft() internal view {
require(msg.sender == villageNft, "Only village NFT");
}
constructor(address _feeRecipient, address _transferController) {
require(_feeRecipient != address(0), "Invalid fee recipient");
require(_transferController != address(0), "Invalid transfer controller");
deployer = msg.sender;
protocolFeeRecipient = _feeRecipient;
transferController = _transferController;
transfersEnabled = false;
curve = QuadraticCurveSpread.Params({
p: QuadraticCurve.Params({p0: 0.000015 ether, k: 1}), buySpread: BUY_FEE_RATE, sellSpread: SELL_FEE_RATE
});
}
function name() public pure override returns (string memory) {
return "Pengu Strategy Token";
}
function symbol() public pure override returns (string memory) {
return "PENGUST";
}
/// @dev Returns the token amount that can be minted for the given ETH amount.
function previewMint(uint256 ethAmount) external view returns (uint256) {
return _findTokenAmountForEth(totalSupply(), ethAmount);
}
function quoteMint(uint256 amount) external view returns (uint256 ethRequired) {
return _mintPrice(totalSupply(), amount);
}
/// @dev Mints tokens using the ETH sent, subject to minimum token output.
function mint(
address receiver,
uint256 minTokensOut // minimum tokens to receive, needed for slippage protection
)
external
payable
nonReentrant
{
require(msg.value > 0, "Must send ETH");
uint256 supply = totalSupply();
// Find maximum tokens we can buy with msg.value
uint256 tokenAmount = _findTokenAmountForEth(supply, msg.value);
require(tokenAmount >= minTokensOut, "Insufficient output amount");
require(supply + tokenAmount <= MAX_SUPPLY, "Max supply reached");
uint256 actualPrice = _mintPrice(supply, tokenAmount);
uint256 protocolFee = _protocolFee(supply, tokenAmount);
uint256 villageFee = _villageFee(supply, tokenAmount);
_mint(receiver, tokenAmount);
emit Mint(msg.sender, receiver, actualPrice, tokenAmount);
// Split fees: 20% to devs, 10% to village pool, 70% stays in treasury
SafeTransferLib.safeTransferETH(protocolFeeRecipient, protocolFee);
villageFeePool += villageFee;
// Refund excess ETH (difference between msg.value and actual price)
uint256 excess = msg.value - actualPrice;
if (excess > 0) {
SafeTransferLib.safeTransferETH(msg.sender, excess);
}
}
/// @dev Returns the amount of collateral returned for the given token amount.
function previewRedeem(uint256 amount) external view returns (uint256) {
return _redeemPrice(totalSupply(), amount);
}
/// @dev Redeems the given token amount from the owner.
function redeem(
uint256 amount,
address from,
address receiver,
uint256 minAmountOut // minimum collateral returned, needed for slippage protection
)
external
nonReentrant
{
require(amount > 0, "Amount must be > 0");
uint256 supply = totalSupply();
uint256 ethOut = _redeemPrice(supply, amount);
require(ethOut >= minAmountOut, "Insufficient output amount");
if (msg.sender != from) {
_spendAllowance(from, msg.sender, amount);
}
_burn(from, amount);
emit Redeem(msg.sender, from, receiver, ethOut, amount);
SafeTransferLib.safeTransferETH(receiver, ethOut);
}
/// @dev Lock the given amount of tokens to the dead address.
function lock(uint256 amount, address from) external {
require(amount > 0, "Amount must be > 0");
require(msg.sender == from || msg.sender == strategy, "Unauthorized locker");
if (msg.sender != from) {
_spendAllowance(from, msg.sender, amount);
}
_transfer(from, DEAD_ADDRESS, amount);
emit Lock(msg.sender, from, amount);
}
/// @dev Returns the amount of tokens locked (sent to dead address)
function lockedSupply() public view returns (uint256) {
return balanceOf(DEAD_ADDRESS);
}
/// @dev Returns the effective supply (total supply minus locked tokens)
function effectiveSupply() public view returns (uint256) {
return totalSupply() - lockedSupply();
}
/// @dev Returns the ETH reserve required to back all redeemable tokens
function reserve() public view returns (uint256) {
return _redeemPrice(totalSupply(), effectiveSupply());
}
/// @dev Returns the surplus ETH available for strategy
function surplus() public view returns (uint256) {
uint256 balance = address(this).balance;
uint256 supply = totalSupply();
uint256 amount = effectiveSupply();
// Net ETH required to honour all redemptions after applying the sell spread.
uint256 reserve_ = _redeemPrice(supply, amount);
// Gross ETH (before spreads) that would come out of the pure bonding curve.
// The difference between the two is the deferred fee wedge the treasury accrues over time.
uint256 base_ = curve.p.reserveOutForTokenIn(supply, amount);
uint256 deferred_;
if (base_ > reserve_) {
// Only the treasury’s share (currently 70%) should be treated as unavailable until redemption.
uint256 treasuryShareBps = 10000 - PROTOCOL_FEE_BPS - VILLAGE_FEE_BPS;
deferred_ = (base_ - reserve_) * treasuryShareBps / 10000;
}
uint256 villageLiability = villageFeePool;
if (totalVillageWeight > 0) {
uint256 accrued = totalVillageWeight * villageRewardsIndex / ACC_PRECISION;
if (accrued > totalVillageRewardDebt) {
villageLiability += accrued - totalVillageRewardDebt;
}
}
uint256 required = reserve_ + deferred_ + villageLiability;
// If assets are exhausted or exactly match liabilities, no surplus is available.
if (balance <= required) {
return 0;
}
return balance - required;
}
/// @dev One-time configuration hook invoked by deployer to wire system contracts.
function configure(address newStrategy, address newVillageNft, address newProtocolFeeRecipient) external {
require(deployer != address(0), "Already configured");
require(msg.sender == deployer, "Only deployer");
require(newStrategy != address(0), "Strategy required");
require(newVillageNft != address(0), "Village NFT required");
require(newProtocolFeeRecipient != address(0), "Fee recipient required");
strategy = newStrategy;
emit StrategyUpdated(newStrategy);
villageNft = newVillageNft;
emit VillageNftSet(newVillageNft);
protocolFeeRecipient = newProtocolFeeRecipient;
emit ProtocolFeeRecipientUpdated(newProtocolFeeRecipient);
}
/// @dev Allows
function start() external {
require(deployer != address(0), "Already started");
require(msg.sender == deployer, "Only deployer");
live = true;
deployer = address(0);
}
function enableTransfers() external {
require(msg.sender == transferController, "Only transfer controller");
require(!transfersEnabled, "Transfers already enabled");
transfersEnabled = true;
emit TransfersStatusUpdated(true);
transferController = address(0);
}
/// @dev Allows strategy to use surplus funds.
/// @param amount The amount of surplus to use.
function useSurplus(uint256 amount) external onlyStrategy nonReentrant {
require(amount <= surplus(), "Insufficient surplus");
require(address(this).balance >= amount, "Insufficient ETH");
SafeTransferLib.safeTransferETH(strategy, amount);
emit SurplusUsed(strategy, amount);
}
receive() external payable {} // can receive ETH
/// @dev Max token amount whose total mint price is <= ethAmount.
/// Assumes _mintPrice(supply, n) is non-decreasing in n.
function _findTokenAmountForEth(uint256 supply, uint256 ethAmount) internal view returns (uint256) {
uint256 high = MAX_SUPPLY - supply;
if (high == 0 || ethAmount == 0) return 0;
uint256 low = 0; // allow 0 results
while (low < high) {
// Upper mid without overflow: low + (high - low + 1)/2
uint256 mid = low + (high - low + 1) / 2;
uint256 price = _mintPrice(supply, mid);
if (price <= ethAmount) {
low = mid; // mid is feasible, search right (upper bound)
} else {
high = mid - 1; // mid too expensive, search left
}
}
return low; // maximum n with price <= ethAmount
}
function _mintPrice(uint256 supply, uint256 amount) internal view returns (uint256) {
require(supply + amount <= MAX_SUPPLY, "Max supply reached");
return curve.reserveInForTokenOut(supply, amount);
}
function _redeemPrice(uint256 supply, uint256 amount) internal view returns (uint256) {
require(supply >= amount, "Insufficient supply");
return curve.reserveOutForTokenIn(supply, amount);
}
function _protocolFee(uint256 supply, uint256 amount) internal view returns (uint256) {
uint256 mintPrice = _mintPrice(supply, amount);
uint256 redeemPrice = _redeemPrice(supply + amount, amount);
return (mintPrice - redeemPrice) * PROTOCOL_FEE_BPS / 10000;
}
function _villageFee(uint256 supply, uint256 amount) internal view returns (uint256) {
uint256 mintPrice = _mintPrice(supply, amount);
uint256 redeemPrice = _redeemPrice(supply + amount, amount);
return (mintPrice - redeemPrice) * VILLAGE_FEE_BPS / 10000;
}
function _beforeTokenTransfer(address from, address to, uint256) internal view override {
if (!live) {
revert NotLive();
}
if (!transfersEnabled) {
bool isMint = from == address(0);
bool isBurn = to == address(0);
bool isInternal = msg.sender == address(this) || msg.sender == strategy;
bool isLocking = to == DEAD_ADDRESS;
if (!(isMint || isBurn || isInternal || isLocking)) {
revert TransfersDisabled();
}
}
}
function pendingVillageFees(uint256 villageId) public view returns (uint256) {
uint256 weight = villageWeight[villageId];
if (weight == 0) return 0;
uint256 accrued = weight * villageRewardsIndex / ACC_PRECISION;
uint256 debt = villageRewardDebt[villageId];
return accrued > debt ? accrued - debt : 0;
}
function onVillageCreated(uint256 villageId, uint256 pudgyCount, address owner) external onlyStrategy nonReentrant {
require(villageWeight[villageId] == 0, "Village exists");
require(pudgyCount > 0, "Pudgy count must be > 0");
uint256 pending = villageFeePool;
uint256 oldIndex = villageRewardsIndex;
uint256 newTotalWeight = totalVillageWeight + pudgyCount;
if (pending > 0) {
require(newTotalWeight > 0, "No villages");
villageRewardsIndex = oldIndex + pending * ACC_PRECISION / newTotalWeight;
villageFeePool = 0;
}
totalVillageWeight = newTotalWeight;
villageWeight[villageId] = pudgyCount;
uint256 initialDebt = pudgyCount * oldIndex / ACC_PRECISION;
villageRewardDebt[villageId] = initialDebt;
totalVillageRewardDebt += initialDebt;
_harvestVillage(villageId, owner);
}
function claimVillageFees(uint256 villageId, address recipient)
external
onlyVillageNft
nonReentrant
returns (uint256 amount)
{
require(recipient != address(0), "Invalid recipient");
amount = _harvestVillage(villageId, recipient);
}
function removeVillage(uint256 villageId, address recipient) external onlyVillageNft nonReentrant {
_harvestVillage(villageId, recipient);
uint256 weight = villageWeight[villageId];
if (weight > 0) {
totalVillageWeight -= weight;
uint256 debt = villageRewardDebt[villageId];
if (debt > 0) {
totalVillageRewardDebt -= debt;
}
delete villageWeight[villageId];
delete villageRewardDebt[villageId];
}
}
function _harvestVillage(uint256 villageId, address recipient) internal returns (uint256 amount) {
uint256 weight = villageWeight[villageId];
if (weight == 0) {
return 0;
}
uint256 accrued = weight * villageRewardsIndex / ACC_PRECISION;
uint256 debt = villageRewardDebt[villageId];
if (accrued <= debt) {
if (accrued != debt) {
totalVillageRewardDebt = totalVillageRewardDebt - debt + accrued;
villageRewardDebt[villageId] = accrued;
}
return 0;
}
amount = accrued - debt;
totalVillageRewardDebt = totalVillageRewardDebt - debt + accrued;
villageRewardDebt[villageId] = accrued;
if (amount > 0 && recipient != address(0)) {
SafeTransferLib.safeTransferETH(recipient, amount);
emit VillageFeeClaimed(recipient, villageId, amount);
}
}
}
"
},
"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) {
/// @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;
}
}
"
},
"lib/solady/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, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with a `gasStipend`.
function forceSafeTransferAllETH(address to, uint256 gasStipend) internal {
/// @solidity memory-safe-assembly
assembly {
if iszero(call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends `amount` (in wei) ETH to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferETH(address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
if lt(selfbalance(), amount) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if iszero(call(GAS_STIPEND_NO_GRIEF, to, amount, codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(amount, 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Force sends all the ETH in the current contract to `to`, with `GAS_STIPEND_NO_GRIEF`.
function forceSafeTransferAllETH(address to) internal {
/// @solidity memory-safe-assembly
assembly {
// forgefmt: disable-next-item
if iszero(call(GAS_STIPEND_NO_GRIEF, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)) {
mstore(0x00, to) // Store the address in scratch space.
mstore8(0x0b, 0x73) // Opcode `PUSH20`.
mstore8(0x20, 0xff) // Opcode `SELFDESTRUCT`.
if iszero(create(selfbalance(), 0x0b, 0x16)) { revert(codesize(), codesize()) } // For gas estimation.
}
}
}
/// @dev Sends `amount` (in wei) ETH to `to`, with a `gasStipend`.
function trySafeTransferETH(address to, uint256 amount, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, amount, codesize(), 0x00, codesize(), 0x00)
}
}
/// @dev Sends all the ETH in the current contract to `to`, with a `gasStipend`.
function trySafeTransferAllETH(address to, uint256 gasStipend)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
success := call(gasStipend, to, selfbalance(), codesize(), 0x00, codesize(), 0x00)
}
}
/// @dev Force transfers ETH to `to`, without triggering the fallback (if any).
/// This method attempts to use a separate contract to send via `SELFDESTRUCT`,
/// and upon failure, deploys a minimal vault to accrue the ETH.
function safeMoveETH(address to, uint256 amount) internal returns (address vault) {
/// @solidity memory-safe-assembly
assembly {
to := shr(96, shl(96, to)) // Clean upper 96 bits.
for { let mover := ETH_MOVER } iszero(eq(to, address())) {} {
let selfBalanceBefore := selfbalance()
if or(lt(selfBalanceBefore, amount), eq(to, mover)) {
mstore(0x00, 0xb12d13eb) // `ETHTransferFailed()`.
revert(0x1c, 0x04)
}
if extcodesize(mover) {
let balanceBefore := balance(to) // Check via delta, in case `SELFDESTRUCT` is bricked.
mstore(0x00, to)
pop(call(gas(), mover, amount, 0x00, 0x20, codesize(), 0x00))
// If `address(to).balance >= amount + balanceBefore`, skip vault workflow.
if iszero(lt(balance(to), add(amount, balanceBefore))) { break }
// Just in case `SELFDESTRUCT` is changed to not revert and do nothing.
if lt(selfBalanceBefore, selfbalance()) { invalid() }
}
let m := mload(0x40)
// If the mover is missing or bricked, deploy a minimal vault
// that withdraws all ETH to `to` when being called only by `to`.
// forgefmt: disable-next-item
mstore(add(m, 0x20), 0x33146025575b600160005260206000f35b3d3d3d3d47335af1601a5760003dfd)
mstore(m, or(to, shl(160, 0x6035600b3d3960353df3fe73)))
// Compute and store the bytecode hash.
mstore8(0x00, 0xff) // Write the prefix.
mstore(0x35, keccak256(m, 0x40))
mstore(0x01, shl(96, address())) // Deployer.
mstore(0x15, 0) // Salt.
vault := keccak256(0x00, 0x55)
pop(call(gas(), vault, amount, codesize(), 0x00, codesize(), 0x00))
// The vault returns a single word on success. Failure reverts with empty data.
if iszero(returndatasize()) {
if iszero(create2(0, m, 0x40, 0)) { revert(codesize(), codesize()) } // For gas estimation.
}
mstore(0x40, m) // Restore the free memory pointer.
break
}
}
}
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* ERC20 OPERATIONS */
/*.•°:°.´+˚.*°.˚:*.´•*.+°.•°:´*.´•*.•°.•°:°.´:•˚°.*°.˚:*.´+°.•*/
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have at least `amount` approved for
/// the current contract to manage.
function safeTransferFrom(address token, address from, address to, uint256 amount) internal {
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends `amount` of ERC20 `token` from `from` to `to`.
///
/// The `from` account must have at least `amount` approved for the current contract to manage.
function trySafeTransferFrom(address token, address from, address to, uint256 amount)
internal
returns (bool success)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x60, amount) // Store the `amount` argument.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x23b872dd000000000000000000000000) // `transferFrom(address,address,uint256)`.
success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20)
if iszero(and(eq(mload(0x00), 1), success)) {
success := lt(or(iszero(extcodesize(token)), returndatasize()), success)
}
mstore(0x60, 0) // Restore the zero slot to zero.
mstore(0x40, m) // Restore the free memory pointer.
}
}
/// @dev Sends all of ERC20 `token` from `from` to `to`.
/// Reverts upon failure.
///
/// The `from` account must have their entire balance approved for the current contract to manage.
function safeTransferAllFrom(address token, address from, address to)
internal
returns (uint256 amount)
{
/// @solidity memory-safe-assembly
assembly {
let m := mload(0x40) // Cache the free memory pointer.
mstore(0x40, to) // Store the `to` argument.
mstore(0x2c, shl(96, from)) // Store the `from` argument.
mstore(0x0c, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
// Read the balance, reverting upon failure.
if iszero(
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x1c, 0x24, 0x60, 0x20)
)
) {
mstore(0x00, 0x7939f424) // `TransferFromFailed()`.
revert(0x1c, 0x04)
}
mstore(0x00, 0x23b872dd) // `
Submitted on: 2025-10-19 19:53:06
Comments
Log in to comment.
No comments yet.