Contract Name:
SpookyLaunchpad
Contract Source Code:
pragma solidity 0.8.18;
import { LibUtils } from "./libraries/LibUtils.sol";
import { LibTokens } from "./libraries/LibTokens.sol";
import { IwETH } from "./interfaces/IwETH.sol";
import { Ownable } from "@openzeppelin/contracts/access/Ownable.sol";
import { Token } from "./libraries/Token.sol";
import { INonfungiblePositionManager } from "./interfaces/INonFungiblePositionManager.sol";
import { Vault } from './Vault.sol';
struct Config {
uint256 creationPrice;
address nfpManager; //20B
uint16 txFee; //2B
uint16 launchFee; //2B
uint16 farmRatioDenominator; //2B
uint256 fakePoolEthMCapThreshold; //32B
uint256 tokenSupply;
uint256 fakePoolBaseEther;
address weth;
address v3staker;
}
struct FakePool {
uint256 fakeEth;
uint256 ethReserve;
uint256 tokenReserve;
address token;
address pair;
uint16 sellPenalty;
bool locked;
}
struct IncentiveKey {
address rewardToken;
address pool;
uint256 startTime;
uint256 endTime;
uint256 vestingPeriod;
address refundee;
}
interface IUniV3Staker {
function createIncentive(IncentiveKey memory key, uint256 reward) external;
}
contract SpookyLaunchpad is Ownable {
event TokenCreated(address creator, address token, uint256 supply, string name, string symbol, string imageCid, string description, string[] links, uint256 price, LibTokens.LpStrategy lpStrategy);
event TokenLaunched(address creator, address token, address pair);
event Bought(address buyer, address token, uint256 ethIn, uint256 tokensOut, uint256 newPrice);
event Sold(address seller, address token, uint256 ethOut, uint256 tokensIn, uint256 newPrice);
event Swap(address indexed sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, address indexed to); // UNIV2 event for tracking
event FakePoolCreated(address token, uint16 sellPenalty, uint256 ethReserve, uint256 tokenReserve);
event FakePoolReserveChanged(address token, uint256 ethReserve, uint256 tokenReserve);
event FakePoolMCapReached(address token);
mapping(address => FakePool) public poolMap;
Config public config;
uint256 public proceeds;
constructor() Ownable() {
}
function create(string calldata name, string calldata symbol, string calldata imageCid, string calldata description, string[] calldata links, uint16 sellPenalty, LibTokens.LpStrategy lpStrategy, uint256 initialBuy) external payable {
require(
bytes(name).length <= 18 &&
bytes(symbol).length <= 18 &&
bytes(description).length <= 512,
"Invalid params"
);
require(links.length < 5, "5 links max");
for (uint8 i = 0; i < links.length; i++) {
require(bytes(links[i]).length <= 128, "link too long");
}
uint256 supply = config.tokenSupply;
Token token = new Token();
address tokenAddress = address(token);
token.emboss(name, symbol, supply, address(this));
uint256 creationPrice = _create_FakePool(tokenAddress, supply, sellPenalty);
LibTokens.Storage storage ts = LibTokens.store();
ts.creatorMap[tokenAddress] = msg.sender;
ts.lpStrategyMap[tokenAddress] = lpStrategy;
emit TokenCreated(msg.sender, tokenAddress, config.tokenSupply, name, symbol, imageCid, description, links, creationPrice, lpStrategy);
uint256 eth = msg.value;
uint256 creationEth = config.creationPrice;
require(eth >= creationEth, "Not enough eth to pay the creation price!");
eth -= creationEth;
gatherProceeds(creationEth);
if (initialBuy > 0) {
eth -= initialBuy;
(uint256 tokensOut, uint256 newPrice) = _buy_FakePool(tokenAddress, initialBuy);
Token(token).transfer(msg.sender, tokensOut); // Transfer tokens to buyer
emit Bought(msg.sender, tokenAddress, initialBuy, tokensOut, newPrice);
}
if (eth > 0) {
(bool sent,) = msg.sender.call{ value: eth }(""); // refund dust
require(sent, "not sent!");
}
}
function quote(address token, uint256 amount, bool ethOut) public view returns(uint256) {
return _quote_FakePool(token, amount, ethOut);
}
function buy(address token, uint256 min) external payable returns (uint256 tokensOut, uint256 newPrice) {
(tokensOut, newPrice) = _buy_FakePool(token, msg.value);
if (min != 0) require(tokensOut >= min, "amount out lower than min");
Token(token).transfer(msg.sender, tokensOut); // Transfer tokens to buyer
emit Bought(msg.sender, token, msg.value, tokensOut, newPrice);
emit Swap(address(this), msg.value, 0, 0, tokensOut, msg.sender);
}
function sell(address token, uint256 amount, uint256 min) external returns (uint256 ethOut, uint256 newPrice) {
(ethOut, newPrice) = _sell_FakePool(token, amount);
if (min != 0) require(ethOut >= min, "amount out lower than min");
Token(token).transferFrom(msg.sender, address(this), amount); // Transfer tokens from seller
(bool sent,) = msg.sender.call{ value: ethOut }(""); require(sent, "eth send failed"); // Transfer eth to seller
emit Sold(msg.sender, token, ethOut, amount, newPrice);
emit Swap(msg.sender, 0, amount, ethOut, 0, address(this));
}
function launch(address token, uint8 farmType) external onlyOwner payable returns (
uint256 tokenId, //0=no farm, 1=token farm 2=wnative farm
uint128 liquidity,
uint256 amount0,
uint256 amount1
){
(uint256 eth, uint256 tokens) = _launchstats_FakePool(token);
uint256 launchFee = LibUtils.calculatePercentage(config.launchFee, eth);
gatherProceeds(launchFee);
eth -= launchFee;
INonfungiblePositionManager nonfungiblePositionManager = INonfungiblePositionManager(config.nfpManager);
IUniV3Staker v3staker = IUniV3Staker(config.v3staker);
Token(token).approve(address(nonfungiblePositionManager), tokens);
Token(token).launch();
LibTokens.Storage storage ts = LibTokens.store();
//LibTokens.LpStrategy lpStrategy = ts.lpStrategyMap[token];
address wethAdr = config.weth;
IwETH(wethAdr).deposit{ value: eth }();
Token(wethAdr).approve(address(nonfungiblePositionManager), eth);
uint amountFarm;
if(farmType == 1) {
amountFarm = tokens / config.farmRatioDenominator;
tokens -= amountFarm;
Token(token).approve(address(v3staker), amountFarm);
} else if(farmType == 2) {
amountFarm = eth / config.farmRatioDenominator;
eth -= amountFarm;
Token(wethAdr).approve(address(v3staker), amountFarm);
}
(address _token0, address _token1) = token < wethAdr ? (token, wethAdr) : (wethAdr, token);
(uint _amount0, uint _amount1) = token < wethAdr ? (tokens, eth) : (eth, tokens);
address pair = nonfungiblePositionManager.createAndInitializePoolIfNecessary(_token0, _token1, 3000, calculateSqrtPriceX96(_amount1, _amount0));
Vault vault = new Vault();
INonfungiblePositionManager.MintParams memory params =
INonfungiblePositionManager.MintParams({
token0: _token0,
token1: _token1,
fee: 3000,
tickLower: -887272 + 52,
tickUpper: 887272 - 52,
amount0Desired: _amount0,
amount1Desired: _amount1,
amount0Min: _amount0 * 9 / 10,
amount1Min: _amount1 * 9 / 10,
recipient: address(vault),
deadline: block.timestamp
});
(tokenId, liquidity, amount0, amount1) = nonfungiblePositionManager.mint(params);
vault.setup(owner(), ts.creatorMap[token], tokenId);
// create farm
if(farmType != 0) {
IncentiveKey memory incentive = IncentiveKey({
rewardToken: farmType == 1 ? token : wethAdr,
pool: pair,
startTime: block.timestamp + 1,
endTime: (block.timestamp + 1 + 60 days),
vestingPeriod: 72 hours,
refundee: owner()
});
v3staker.createIncentive(incentive, amountFarm);
}
setPool(token, pair);
emit TokenLaunched(ts.creatorMap[token], token, pair);
}
function calculateSqrtPriceX96(uint256 amount0, uint256 amount1) public pure returns (uint160) {
return (uint160)(sqrt(amount0 * (2**192 / amount1)));
}
function sqrt(uint256 x) internal pure returns (uint256 result) {
if (x == 0) return 0;
uint256 z = (x + 1) / 2;
result = x;
while (z < result) {
result = z;
z = (x / z + z) / 2;
}
}
// // ==================================================================== // //
// // =========================== FAKE POOLS ============================= // //
// // ==================================================================== // //
function getConfig() external view returns (Config memory) {
return config;
}
function setConfig(Config memory _config) public onlyOwner {
config = _config;
}
function swapExactTokensForETH(FakePool storage pool, uint256 tokens) internal returns (uint256) {
uint256 out = getAmountOut(tokens, pool.tokenReserve, pool.ethReserve);
pool.tokenReserve += tokens;
pool.ethReserve -= out;
emit FakePoolReserveChanged(pool.token, pool.ethReserve, pool.tokenReserve);
return out;
}
function swapExactETHForTokens(FakePool storage pool, uint256 eth) internal returns (uint256) {
uint256 out = getAmountOut(eth, pool.ethReserve, pool.tokenReserve);
pool.tokenReserve -= out;
pool.ethReserve += eth;
emit FakePoolReserveChanged(pool.token, pool.ethReserve, pool.tokenReserve);
return out;
}
function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) internal pure returns (uint256) {
uint256 numerator = amountIn * reserveOut;
uint256 denominator = reserveIn + amountIn;
return numerator / denominator;
}
function price(FakePool storage pool, uint256 amount, bool ethOut) internal view returns (uint256) {
if (ethOut) {
return (amount * pool.ethReserve) / pool.tokenReserve;
} else {
return (amount * pool.tokenReserve) / pool.ethReserve;
}
}
function checkMarketCapThreshold(FakePool storage pool) internal {
uint256 p = price(pool, 1 ether, true);
uint256 ethMcap = (config.tokenSupply * p) / 1 ether;
if (ethMcap >= config.fakePoolEthMCapThreshold) {
pool.locked = true;
emit FakePoolMCapReached(pool.token);
}
}
function calculateSellPenalty(FakePool storage pool, uint256 eth) internal view returns (uint256) {
if (pool.sellPenalty == 0) return 0;
return LibUtils.calculatePercentage(pool.sellPenalty, eth);
}
function deductSellPenalty(FakePool storage pool, uint256 eth) internal returns (uint256) {
uint256 fee = calculateSellPenalty(pool, eth);
if (fee == 0) {
return eth;
} else {
pool.ethReserve += fee; // redistribute the penaltyFee back into the ether reserve
return eth - fee;
}
}
// PUBLIC
function _quote_FakePool(address token, uint256 amount, bool ethOut) public view returns (uint256) {
FakePool storage pool = poolMap[token];
if (ethOut) { // sell
uint256 eth = getAmountOut(amount, pool.tokenReserve, pool.ethReserve);
eth -= calculateTxFee(eth);
eth -= calculateSellPenalty(pool, eth);
return eth;
} else { // buy
uint256 txFee = calculateTxFee(amount);
return getAmountOut(amount - txFee, pool.ethReserve, pool.tokenReserve);
}
}
function _create_FakePool(address tokenAddress, uint256 supply, uint16 sellPenalty) internal returns (uint256) {
require(sellPenalty <= 700, "sell penalty invalid");
FakePool storage pool = poolMap[tokenAddress];
uint fakeEth = config.fakePoolBaseEther;
pool.token = tokenAddress;
pool.fakeEth = fakeEth;
pool.ethReserve = fakeEth;
pool.tokenReserve = supply;
pool.sellPenalty = sellPenalty;
emit FakePoolCreated(tokenAddress, sellPenalty, pool.ethReserve, pool.tokenReserve);
return price(pool, 1 ether, true);
}
function _launchstats_FakePool(address token) public view returns (uint256, uint256) {
FakePool storage pool = poolMap[token];
require(pool.token != address(0));
return (pool.ethReserve - pool.fakeEth, pool.tokenReserve);
}
function _buy_FakePool(address token, uint amount) internal returns (uint256, uint256) {
FakePool storage pool = poolMap[token];
require(pool.token != address(0) && !pool.locked, "pool is zero or locked!");
uint256 ethIn = deductTxFee(amount);
uint256 tokensOut = swapExactETHForTokens(pool, ethIn);
checkMarketCapThreshold(pool);
return (tokensOut, price(pool, 1 ether, true));
}
function _sell_FakePool(address token, uint256 amount) internal returns (uint256, uint256) {
FakePool storage pool = poolMap[token];
require(pool.token != address(0) && !pool.locked);
uint256 ethOut = swapExactTokensForETH(pool, amount);
ethOut = deductTxFee(ethOut);
ethOut = deductSellPenalty(pool, ethOut);
require(pool.ethReserve >= pool.fakeEth, "no eth left in pool");
checkMarketCapThreshold(pool);
return (ethOut, price(pool, 1 ether, true));
}
function setPool(address _token, address _pair) internal {
poolMap[_token].locked = true;
poolMap[_token].pair = _pair;
}
function calculateTxFee(uint256 eth) public view returns (uint256) {
return LibUtils.calculatePercentage(config.txFee, eth);
}
function gatherProceeds(uint256 amount) internal {
proceeds += amount;
}
function deductTxFee(uint256 eth) internal returns (uint256) {
uint256 fee = calculateTxFee(eth);
gatherProceeds(fee);
return eth - fee;
}
function reap() external onlyOwner {
uint256 eth = proceeds;
proceeds = 0;
(bool sent,) = payable(msg.sender).call{ value: eth }("");
require(sent);
}
function unlock(address[] calldata assets) external onlyOwner {
for (uint256 i = 0; i < assets.length; i++) {
Token(assets[i]).transfer(msg.sender, Token(assets[i]).balanceOf(address(this)));
}
}
function unlockEth(uint _value) external onlyOwner {
(bool sent,) = payable(msg.sender).call{ value: _value }("");
require(sent);
}
function manualLock(address _pool, bool _lock) external onlyOwner {
FakePool storage pool = poolMap[_pool];
pool.locked = _lock;
}
function manualLaunch(address token) external onlyOwner {
Token(token).launch();
}
}
// SPDX-License-Identifier: UNKNOWN
pragma solidity ^0.8.18;
library LibUtils {
function calculatePercentage(uint16 fee, uint256 amount) internal pure returns (uint256) {
return amount * fee / 1000;
}
function ethToUsd(uint256 usdcEthPrice, uint256 amountEth) internal pure returns (uint256) {
uint256 ethAmount = amountEth;
return ((ethAmount * usdcEthPrice) / (10**(8+18)) / (10**18));
}
function usdToEth(uint256 usdcEthPrice, uint256 usdAmount) internal pure returns (uint256) {
return ((10**18 * (10**8)) / usdcEthPrice) * usdAmount;
}
}
// SPDX-License-Identifier: UNKNOWN
pragma solidity ^0.8.18;
library LibTokens {
bytes32 constant STORAGE_POSITION = keccak256("diamond.tokens.storage");
struct TeamAllocation {
uint16 percentage;
uint8 beneficiary;
}
enum LpStrategy {
Burn,
Vest
}
struct Storage {
mapping(address => TeamAllocation) _unused_teamAllocMap;
mapping(address => address) creatorMap;
mapping(address => LpStrategy) lpStrategyMap;
}
function store() internal pure returns (Storage storage s) {
bytes32 position = STORAGE_POSITION;
assembly {
s.slot := position
}
}
}
// SPDX-License-Identifier: UNKNOWN
pragma solidity ^0.8.18;
interface IwETH {
function deposit() external payable;
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.0) (access/Ownable.sol)
pragma solidity ^0.8.0;
import "../utils/Context.sol";
/**
* @dev Contract module which provides a basic access control mechanism, where
* there is an account (an owner) that can be granted exclusive access to
* specific functions.
*
* By default, the owner account will be the one that deploys the contract. This
* can later be changed with {transferOwnership}.
*
* This module is used through inheritance. It will make available the modifier
* `onlyOwner`, which can be applied to your functions to restrict their use to
* the owner.
*/
abstract contract Ownable is Context {
address private _owner;
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
/**
* @dev Initializes the contract setting the deployer as the initial owner.
*/
constructor() {
_transferOwnership(_msgSender());
}
/**
* @dev Throws if called by any account other than the owner.
*/
modifier onlyOwner() {
_checkOwner();
_;
}
/**
* @dev Returns the address of the current owner.
*/
function owner() public view virtual returns (address) {
return _owner;
}
/**
* @dev Throws if the sender is not the owner.
*/
function _checkOwner() internal view virtual {
require(owner() == _msgSender(), "Ownable: caller is not the owner");
}
/**
* @dev Leaves the contract without owner. It will not be possible to call
* `onlyOwner` functions. Can only be called by the current owner.
*
* NOTE: Renouncing ownership will leave the contract without an owner,
* thereby disabling any functionality that is only available to the owner.
*/
function renounceOwnership() public virtual onlyOwner {
_transferOwnership(address(0));
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Can only be called by the current owner.
*/
function transferOwnership(address newOwner) public virtual onlyOwner {
require(newOwner != address(0), "Ownable: new owner is the zero address");
_transferOwnership(newOwner);
}
/**
* @dev Transfers ownership of the contract to a new account (`newOwner`).
* Internal function without access restriction.
*/
function _transferOwnership(address newOwner) internal virtual {
address oldOwner = _owner;
_owner = newOwner;
emit OwnershipTransferred(oldOwner, newOwner);
}
}
// SPDX-License-Identifier: UNKNOWN
pragma solidity ^0.8.18;
import { ERC20 } from "./ERC20.sol";
contract Token is ERC20 {
string private _name;
string private _symbol;
string public junk = "junkdata";
address internal owner;
bool internal launched = false;
function name() public view virtual override returns (string memory) {
return _name;
}
function symbol() public view virtual override returns (string memory) {
return _symbol;
}
function emboss(string calldata n, string calldata s, uint256 supply, address d) external {
require(totalSupply() == 0);
owner = d;
_name = n;
_symbol = s;
_mint(msg.sender, supply);
}
function launch() external {
require(msg.sender == owner && launched == false);
launched = true;
}
function _beforeTokenTransfer(address from, address to, uint256 /*amount*/) internal view override {
if (!launched) {
require(from == owner || to == owner, "transfer not allowed before launch");
}
}
}
interface INonfungiblePositionManager {
struct MintParams {
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
address recipient;
uint256 deadline;
}
function mint(MintParams calldata params)
external
payable
returns (
uint256 tokenId,
uint128 liquidity,
uint256 amount0,
uint256 amount1
);
struct IncreaseLiquidityParams {
uint256 tokenId;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
uint256 deadline;
}
function increaseLiquidity(IncreaseLiquidityParams calldata params)
external
payable
returns (uint128 liquidity, uint256 amount0, uint256 amount1);
struct DecreaseLiquidityParams {
uint256 tokenId;
uint128 liquidity;
uint256 amount0Min;
uint256 amount1Min;
uint256 deadline;
}
function decreaseLiquidity(DecreaseLiquidityParams calldata params)
external
payable
returns (uint256 amount0, uint256 amount1);
struct CollectParams {
uint256 tokenId;
address recipient;
uint128 amount0Max;
uint128 amount1Max;
}
function collect(CollectParams calldata params)
external
payable
returns (uint256 amount0, uint256 amount1);
function createAndInitializePoolIfNecessary(
address token0,
address token1,
uint24 fee,
uint160 sqrtPriceX96
) external payable returns (address pool);
function positions(uint256 tokenId)
external
view
returns (
uint96 nonce,
address operator,
address token0,
address token1,
uint24 fee,
int24 tickLower,
int24 tickUpper,
uint128 liquidity,
uint256 feeGrowthInside0LastX128,
uint256 feeGrowthInside1LastX128,
uint128 tokensOwed0,
uint128 tokensOwed1
);
function WETH9() external returns (address);
function factory() external returns (address);
}
// SPDX-License-Identifier: UNKNOWN
pragma solidity ^0.8.18;
interface IERC20 {
function balanceOf(address account) external view returns (uint256);
function transfer(address to, uint256 value) external returns (bool);
}
interface IERC721Receiver {
function onERC721Received(address operator, address from, uint256 tokenId, bytes calldata data) external returns (bytes4);
}
interface Manager {
function WETH9() external view returns (address);
struct MintParams {
address token0;
address token1;
uint24 fee;
int24 tickLower;
int24 tickUpper;
uint256 amount0Desired;
uint256 amount1Desired;
uint256 amount0Min;
uint256 amount1Min;
address recipient;
uint256 deadline;
}
struct CollectParams {
uint256 tokenId;
address recipient;
uint128 amount0Max;
uint128 amount1Max;
}
function mint(MintParams memory params) external returns (uint256 tokenId, uint128 liquidity, uint256 amount0, uint256 amount1);
function collect(CollectParams calldata params) external returns (uint256 amount0, uint256 amount1);
function safeTransferFrom(address from, address to, uint256 tokenId) external;
}
contract Vault is IERC721Receiver {
address public owner;
address public owner2;
uint256 public tokenId;
Manager public nfpm = Manager(0xf807Aca27B1550Fe778fD4E7013BB57480b17fAc);
uint public withdrawTime;
constructor() {
}
function setup(address _owner, address _owner2, uint _tokenId) external {
require(owner == address(0));
owner = _owner;
owner2 = _owner2;
tokenId = _tokenId;
withdrawTime = block.timestamp + 365 days;
}
function setNewOwner2(address newOwner) external {
require(msg.sender == owner2 || msg.sender == owner);
owner2 = newOwner;
}
function collectAndWithdraw(address token0, address token1) external {
collectFees();
withdraw(token0, token1);
}
function collectFees() public {
nfpm.collect(
Manager.CollectParams({
tokenId: tokenId,
recipient: address(this),
amount0Max: type(uint128).max,
amount1Max: type(uint128).max
})
);
}
function withdraw(address token0, address token1) public {
uint amount0 = IERC20(token0).balanceOf(address(this)) / 2;
uint amount1 = IERC20(token1).balanceOf(address(this)) / 2;
IERC20(token0).transfer(owner, amount0);
IERC20(token1).transfer(owner, amount1);
IERC20(token0).transfer(owner2, IERC20(token0).balanceOf(address(this)));
IERC20(token1).transfer(owner2, IERC20(token1).balanceOf(address(this)));
}
function withdraw2() external {
require(msg.sender == owner);
require(block.timestamp >= withdrawTime);
nfpm.safeTransferFrom(address(this), owner, tokenId);
}
function onERC721Received(
address operator,
address from,
uint256 id,
bytes calldata data
) external override returns (bytes4) {
require(tokenId == 0, "Vault: LOCKED");
tokenId = id;
return IERC721Receiver.onERC721Received.selector;
}
}
// SPDX-License-Identifier: MIT
// OpenZeppelin Contracts (last updated v4.9.4) (utils/Context.sol)
pragma solidity ^0.8.0;
/**
* @dev Provides information about the current execution context, including the
* sender of the transaction and its data. While these are generally available
* via msg.sender and msg.data, they should not be accessed in such a direct
* manner, since when dealing with meta-transactions the account sending and
* paying for execution may not be the actual sender (as far as an application
* is concerned).
*
* This contract is only required for intermediate, library-like contracts.
*/
abstract contract Context {
function _msgSender() internal view virtual returns (address) {
return msg.sender;
}
function _msgData() internal view virtual returns (bytes calldata) {
return msg.data;
}
function _contextSuffixLength() internal view virtual returns (uint256) {
return 0;
}
}
// 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();
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* 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")`.
bytes32 private constant _VERSION_HASH =
0xc89efdaa54c0f20c7adf612882df0950f5a951637e0307cdcb4c672f298b8bc6;
/// @dev `keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 deadline)")`.
bytes32 private constant _PERMIT_TYPEHASH =
0x6e71edae12b1b97f4d1f60370fef10105fa2faae0126114a169c64845d6126c9;
/*´:°•.°+.*•´.*:˚.°*.˚•´.°:°•.°•.*•´.*:˚.°*.˚•´.°:°•.°+.*•´.*:*/
/* 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)
{
/// @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) {
/// @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);
/// @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 add(allowance_, 1) {
// 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 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 {
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()));
/// @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), _VERSION_HASH)
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, 0, 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()));
/// @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), _VERSION_HASH)
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 {
/// @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 add(allowance_, 1) {
// 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 {
/// @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 {}
}