Contract Diff Checker

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 {}
}

Please enter a contract address above to load the contract details and source code.

Context size (optional):