# pragma version 0.4.3 # pragma evm-version cancun # SPDX-License-Identifier: MIT struct PoolKey: currency0: address currency1: address fee: uint24 tickSpacing: int24 hooks: address struct SwapParams: zeroForOne: bool amountSpecified: int256 sqrtPriceLimitX96: uint160 interface Manager: def take(currency: address, to: address, amount: uint256): nonpayable event TaxCollected: buy: bool amount: uint256 event TaxClaimed: recipient: indexed(address) amount: uint256 poolManager: public(immutable(address)) token: public(immutable(address)) recipient: public(immutable(address)) lpFee: public(immutable(uint24)) tickSpacing: public(immutable(int24)) totalCollected: public(uint256) totalClaimed: public(uint256) TAX_BPS: public(constant(uint256)) = 100 MAX_TX_BPS: public(constant(uint256)) = 100 MAX_WALLET_BPS: public(constant(uint256)) = 200 LIMIT_BLOCKS: public(constant(uint256)) = 10 launchBlock: public(uint256) # beforeInitialize, beforeSwap, afterSwap, both swap-return deltas. HOOK_FLAGS: public(constant(uint256)) = 8396 event Transfer: sender: indexed(address) receiver: indexed(address) value: uint256 event Approval: owner: indexed(address) spender: indexed(address) value: uint256 name: public(constant(String[5])) = "Vyper" symbol: public(constant(String[5])) = "Vyper" decimals: public(constant(uint8)) = 18 deployer: public(immutable(address)) totalSupply: public(immutable(uint256)) balanceOf: public(HashMap[address, uint256]) allowance: public(HashMap[address, HashMap[address, uint256]]) # Written once by the constructor. No metadata setter or owner override. metadata: public(String[256]) event MetadataPublished: uri: String[256] @deploy def __init__(manager: address, beneficiary: address, supply: uint256, fee: uint24, spacing: int24, metadata_uri: String[256]): assert manager.is_contract, "not a contract" assert convert(self, uint256) & 16383 == HOOK_FLAGS, "hook address flags" assert fee < 1000000 and spacing > 0 and spacing <= 32767, "pool settings" assert beneficiary != empty(address) and beneficiary != self, "recipient" assert supply > 0, "zero supply" assert len(metadata_uri) > 7, "metadata required" assert slice(metadata_uri, 0, 7) == "ipfs://", "IPFS metadata only" self.metadata = metadata_uri poolManager = manager token = self recipient = beneficiary deployer = beneficiary totalSupply = supply lpFee = fee tickSpacing = spacing self.balanceOf[beneficiary] = supply log Transfer(sender=empty(address), receiver=beneficiary, value=supply) log MetadataPublished(uri=metadata_uri) @external @view def tokenURI() -> String[256]: return self.metadata @external @view def contractURI() -> String[256]: return self.metadata @internal @view def _limits_active() -> bool: return self.launchBlock != 0 and block.number < self.launchBlock + LIMIT_BLOCKS @external @view def limitsActive() -> bool: return self._limits_active() @external @view def maxTxAmount() -> uint256: return totalSupply // 100 @external @view def maxWalletAmount() -> uint256: return totalSupply // 50 @internal @view def _check(key: PoolKey): assert msg.sender == poolManager, "only PoolManager" assert key.currency0 == empty(address) and key.currency1 == self, "ETH/token only" assert key.hooks == self and key.fee == lpFee and key.tickSpacing == tickSpacing, "wrong pool" @internal def _collect(amount: uint256, buy: bool): if amount > 0: self.totalCollected += amount extcall Manager(poolManager).take(empty(address), self, amount) log TaxCollected(buy=buy, amount=amount) @external def beforeInitialize(sender: address, key: PoolKey, sqrtPriceX96: uint160) -> bytes4: self._check(key) # Only the coin deployer can establish the initial pool price. assert sender == recipient, "only deployer initializes" assert self.launchBlock == 0, "already initialized" self.launchBlock = block.number return method_id("beforeInitialize(address,(address,address,uint24,int24,address),uint160)", output_type=bytes4) @external def beforeSwap(sender: address, key: PoolKey, params: SwapParams, hookData: Bytes[4096]) -> (bytes4, int256, uint24): self._check(key) assert params.amountSpecified != 0, "zero swap" assert params.amountSpecified > -convert(max_value(int128), int256) and params.amountSpecified < convert(max_value(int128), int256), "amount bound" tax: uint256 = 0 # ETH is the specified currency for an exact-input buy or exact-output sell. if (params.amountSpecified < 0) == params.zeroForOne: if params.zeroForOne: tax = convert(-params.amountSpecified, uint256) // 100 else: # Gross up the requested net ETH output, rounding to whole wei. tax = convert(params.amountSpecified, uint256) // 99 self._collect(tax, params.zeroForOne) packed: int256 = convert(tax, int256) << 128 return method_id("beforeSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),bytes)", output_type=bytes4), packed, 0 @external def afterSwap(sender: address, key: PoolKey, params: SwapParams, delta: int256, hookData: Bytes[4096]) -> (bytes4, int128): self._check(key) if self._limits_active(): # Check executed token volume, even if a router splits settlement transfers. token_delta: int256 = (delta << 128) >> 128 assert convert(abs(token_delta), uint256) <= totalSupply // 100, "max transaction" eth_delta: int256 = delta >> 128 tax: uint256 = 0 if (params.amountSpecified < 0) == params.zeroForOne: expected: int256 = params.amountSpecified if params.zeroForOne: expected += convert(convert(-expected, uint256) // 100, int256) else: expected += convert(convert(expected, uint256) // 99, int256) # v4 cannot adjust the specified-currency delta after the swap. # Revert partial fills atomically instead of overcharging requested ETH. assert eth_delta == expected, "partial ETH fill" else: if params.zeroForOne: assert eth_delta <= 0, "buy delta" tax = convert(-eth_delta, uint256) // 99 else: assert eth_delta >= 0, "sell delta" tax = convert(eth_delta, uint256) // 100 self._collect(tax, params.zeroForOne) return method_id("afterSwap(address,(address,address,uint24,int24,address),(bool,int256,uint160),int256,bytes)", output_type=bytes4), convert(tax, int128) @external @nonreentrant def claim(): # Anyone can trigger payment; the destination can never be changed. amount: uint256 = self.balance assert amount > 0, "nothing to claim" self.totalClaimed += amount raw_call(recipient, b"", value=amount) log TaxClaimed(recipient=recipient, amount=amount) @external @payable def __default__(): assert msg.sender == poolManager, "only PoolManager ETH" @internal def _transfer(sender: address, receiver: address, amount: uint256): assert receiver != empty(address), "zero receiver" if self._limits_active(): # Allow the original deployer to fund pool liquidity. Swap deltas remain capped. if not (sender == deployer and receiver == poolManager): assert amount <= totalSupply // 100, "max transaction" self.balanceOf[sender] -= amount self.balanceOf[receiver] += amount if self._limits_active() and receiver != deployer and receiver != poolManager and receiver != sender: assert self.balanceOf[receiver] <= totalSupply // 50, "max wallet" log Transfer(sender=sender, receiver=receiver, value=amount) @external def transfer(receiver: address, amount: uint256) -> bool: self._transfer(msg.sender, receiver, amount) return True @external def approve(spender: address, amount: uint256) -> bool: self.allowance[msg.sender][spender] = amount log Approval(owner=msg.sender, spender=spender, value=amount) return True @external def transferFrom(sender: address, receiver: address, amount: uint256) -> bool: approved: uint256 = self.allowance[sender][msg.sender] if approved != max_value(uint256): self.allowance[sender][msg.sender] = approved - amount self._transfer(sender, receiver, amount) return True