Transaction Hash:
Block:
22818653 at Jun-30-2025 05:13:23 PM +UTC
Transaction Fee:
0.000541391831621126 ETH
$1.36
Gas Used:
111,547 Gas / 4.853486258 Gwei
Emitted Events:
239 |
Coins.OperatorSet( [Receiver] 0x9c63762fd8789f99e4a8bdaa92d7364be1c56727, ZAMM, True )
|
240 |
Coins.Transfer( ZAMM, [Receiver] 0x9c63762fd8789f99e4a8bdaa92d7364be1c56727, ZAMM, 754922417686844852297650605102897991409579903666, 7074678456927887571706560 )
|
241 |
ZAMM.Sync( poolId=86747398623582161866936615244950034759064619359605100395857260863866475167514, reserve0=24957376236064797, reserve1=21000000000000000000000000 )
|
242 |
ZAMM.Swap( poolId=86747398623582161866936615244950034759064619359605100395857260863866475167514, sender=[Receiver] 0x9c63762fd8789f99e4a8bdaa92d7364be1c56727, amount0In=0, amount1In=7074678456927887571706560, amount0Out=12552655056335679, amount1Out=0, to=[Receiver] 0x9c63762fd8789f99e4a8bdaa92d7364be1c56727 )
|
Account State Difference:
Address | Before | After | State Difference | ||
---|---|---|---|---|---|
0x00000000...40b24C860 | (NANI: AMM ZAMM) | 79.150807587453221887 Eth | 79.138254932396886208 Eth | 0.012552655056335679 | |
0x00000000...029651eE8 | |||||
0x9C63762f...be1c56727 |
0.112231344138052923 Eth
Nonce: 6
|
0.124242607362767476 Eth
Nonce: 8
| 0.012011263224714553 | ||
0xdadB0d80...24f783711
Miner
| (BuilderNet) | 29.859628482769433783 Eth | 29.859851576769433783 Eth | 0.000223094 |
Execution Trace
0x9c63762fd8789f99e4a8bdaa92d7364be1c56727.e9ae5c53( )
-
Coins.setOperator( operator=0x00000000000008882D72EfA6cCE4B6a40b24C860, approved=True ) => ( True )
ZAMM.swapExactIn( poolKey=[{name:id0, type:uint256, order:1, indexed:false, value:0, valueString:0}, {name:id1, type:uint256, order:2, indexed:false, value:754922417686844852297650605102897991409579903666, valueString:754922417686844852297650605102897991409579903666}, {name:token0, type:address, order:3, indexed:false, value:0x0000000000000000000000000000000000000000, valueString:0x0000000000000000000000000000000000000000}, {name:token1, type:address, order:4, indexed:false, value:0x0000000000009710cd229bF635c4500029651eE8, valueString:0x0000000000009710cd229bF635c4500029651eE8}, {name:swapFee, type:uint96, order:5, indexed:false, value:100, valueString:100}], amountIn=7074678456927887571706560, amountOutMin=12301601955208965, zeroForOne=False, to=0x9C63762fD8789f99E4a8bdaA92d7364be1c56727, deadline=1751304788 ) => ( amountOut=12552655056335679 )
-
Coins.transferFrom( from=0x9C63762fD8789f99E4a8bdaA92d7364be1c56727, to=0x00000000000008882D72EfA6cCE4B6a40b24C860, id=754922417686844852297650605102897991409579903666, amount=7074678456927887571706560 ) => ( True )
- ETH 0.012552655056335679
0x9c63762fd8789f99e4a8bdaa92d7364be1c56727.CALL( )
-
File 1 of 2: Coins
File 2 of 2: ZAMM
// SPDX-License-Identifier: MIT pragma solidity 0.8.29; error OnlyExternal(); error Unauthorized(); error InvalidMetadata(); error DeploymentFailed(); /// @title Coins /// @notice Singleton for ERC6909 & ERC20s /// @author z0r0z & 0xc0de4c0ffee & kobuta23 contract Coins { event MetadataSet(uint256 indexed); event OwnershipTransferred(uint256 indexed); event OperatorSet(address indexed, address indexed, bool); event Approval(address indexed, address indexed, uint256 indexed, uint256); event Transfer(address, address indexed, address indexed, uint256 indexed, uint256); Token immutable implementation = new Token{salt: keccak256("")}(); mapping(uint256 id => Metadata) _metadata; mapping(uint256 id => uint256) public totalSupply; mapping(uint256 id => address owner) public ownerOf; mapping(address owner => mapping(uint256 id => uint256)) public balanceOf; mapping(address owner => mapping(address operator => bool)) public isOperator; mapping(address owner => mapping(address spender => mapping(uint256 id => uint256))) public allowance; modifier onlyOwnerOf(uint256 id) { require(msg.sender == ownerOf[id], Unauthorized()); _; } constructor() payable {} // METADATA struct Metadata { string name; string symbol; string tokenURI; } function name(uint256 id) public view returns (string memory) { Metadata storage meta = _metadata[id]; return bytes(meta.tokenURI).length != 0 ? meta.name : Token(address(uint160(id))).name(); } function symbol(uint256 id) public view returns (string memory) { Metadata storage meta = _metadata[id]; return bytes(meta.tokenURI).length != 0 ? meta.symbol : Token(address(uint160(id))).symbol(); } function decimals(uint256 id) public view returns (uint8) { return bytes(_metadata[id].tokenURI).length != 0 ? 18 : uint8(Token(address(uint160(id))).decimals()); } function tokenURI(uint256 id) public view returns (string memory) { return _metadata[id].tokenURI; } // CREATION function create( string calldata _name, string calldata _symbol, string calldata _tokenURI, address owner, uint256 supply ) public { require(bytes(_tokenURI).length != 0, InvalidMetadata()); uint256 id; Token _implementation = implementation; bytes32 salt = keccak256(abi.encodePacked(_name, address(this), _symbol)); assembly ("memory-safe") { mstore(0x21, 0x5af43d3d93803e602a57fd5bf3) mstore(0x14, _implementation) mstore(0x00, 0x602c3d8160093d39f33d3d3d3d363d3d37363d73) id := create2(0, 0x0c, 0x35, salt) if iszero(id) { mstore(0x00, 0x30116425) // `DeploymentFailed()` revert(0x1c, 0x04) } mstore(0x21, 0) } _metadata[id] = Metadata(_name, _symbol, _tokenURI); emit Transfer( msg.sender, address(0), ownerOf[id] = owner, id, balanceOf[owner][id] = totalSupply[id] = supply ); } // WRAPPING function wrap(Token token, uint256 amount) public { uint256 id = uint160(address(token)); require(bytes(_metadata[id].tokenURI).length == 0, OnlyExternal()); token.transferFrom(msg.sender, address(this), amount); _mint(msg.sender, id, amount); } function unwrap(Token token, uint256 amount) public { _burn(msg.sender, uint256(uint160(address(token))), amount); token.transfer(msg.sender, amount); } // MINT/BURN function mint(address to, uint256 id, uint256 amount) public onlyOwnerOf(id) { _mint(to, id, amount); } function burn(uint256 id, uint256 amount) public { _burn(msg.sender, id, amount); } // GOVERNANCE function setMetadata(uint256 id, string calldata _tokenURI) public onlyOwnerOf(id) { require(bytes(_tokenURI).length != 0, InvalidMetadata()); _metadata[id].tokenURI = _tokenURI; emit MetadataSet(id); } function transferOwnership(uint256 id, address newOwner) public onlyOwnerOf(id) { ownerOf[id] = newOwner; emit OwnershipTransferred(id); } // ERC6909 function transfer(address to, uint256 id, uint256 amount) public returns (bool) { balanceOf[msg.sender][id] -= amount; unchecked { balanceOf[to][id] += amount; } emit Transfer(msg.sender, msg.sender, to, id, amount); return true; } function transferFrom(address from, address to, uint256 id, uint256 amount) public returns (bool) { if (msg.sender != address(uint160(id))) { if (!isOperator[from][msg.sender]) { if (allowance[from][msg.sender][id] != type(uint256).max) { allowance[from][msg.sender][id] -= amount; } } } balanceOf[from][id] -= amount; unchecked { balanceOf[to][id] += amount; } emit Transfer(msg.sender, from, to, id, amount); return true; } function approve(address spender, uint256 id, uint256 amount) public returns (bool) { allowance[msg.sender][spender][id] = amount; emit Approval(msg.sender, spender, id, amount); return true; } function setOperator(address operator, bool approved) public returns (bool) { isOperator[msg.sender][operator] = approved; emit OperatorSet(msg.sender, operator, approved); return true; } // ERC20 APPROVAL function setAllowance(address owner, address spender, uint256 id, uint256 amount) public payable returns (bool) { require(msg.sender == address(uint160(id)), Unauthorized()); allowance[owner][spender][id] = amount; emit Approval(owner, spender, id, amount); return true; } // ERC165 function supportsInterface(bytes4 interfaceId) public pure returns (bool) { return interfaceId == 0x01ffc9a7 // ERC165 || interfaceId == 0x0f632fb3; // ERC6909 } // INTERNAL MINT/BURN function _mint(address to, uint256 id, uint256 amount) internal { totalSupply[id] += amount; unchecked { balanceOf[to][id] += amount; } emit Transfer(msg.sender, address(0), to, id, amount); } function _burn(address from, uint256 id, uint256 amount) internal { balanceOf[from][id] -= amount; unchecked { totalSupply[id] -= amount; } emit Transfer(msg.sender, from, address(0), id, amount); } } contract Token { event Approval(address indexed, address indexed, uint256); event Transfer(address indexed, address indexed, uint256); uint256 public constant decimals = 18; address immutable coins = msg.sender; constructor() payable {} function name() public view returns (string memory) { return Coins(coins).name(uint160(address(this))); } function symbol() public view returns (string memory) { return Coins(coins).symbol(uint160(address(this))); } function totalSupply() public view returns (uint256) { return Coins(coins).totalSupply(uint160(address(this))); } function balanceOf(address owner) public view returns (uint256) { return Coins(coins).balanceOf(owner, uint160(address(this))); } function allowance(address owner, address spender) public view returns (uint256) { if (Coins(coins).isOperator(owner, spender)) return type(uint256).max; return Coins(coins).allowance(owner, spender, uint160(address(this))); } function approve(address spender, uint256 amount) public returns (bool) { emit Approval(msg.sender, spender, amount); return Coins(coins).setAllowance(msg.sender, spender, uint160(address(this)), amount); } function transfer(address to, uint256 amount) public returns (bool) { emit Transfer(msg.sender, to, amount); return Coins(coins).transferFrom(msg.sender, to, uint160(address(this)), amount); } function transferFrom(address from, address to, uint256 amount) public returns (bool) { require(allowance(from, msg.sender) >= amount, Unauthorized()); emit Transfer(from, to, amount); return Coins(coins).transferFrom(from, to, uint160(address(this)), amount); } }
File 2 of 2: ZAMM
// SPDX-License-Identifier: MIT pragma solidity ^0.8.29; import "./ZERC6909.sol"; import "./utils/Math.sol"; import "./utils/TransferHelper.sol"; // maximally simple constant product AMM singleton // minted by z0r0z as concentric liquidity backend contract ZAMM is ZERC6909 { uint256 constant MINIMUM_LIQUIDITY = 1000; uint256 constant MAX_FEE = 10000; // 100% mapping(uint256 poolId => Pool) public pools; struct PoolKey { uint256 id0; uint256 id1; address token0; address token1; uint96 swapFee; } struct Pool { uint112 reserve0; uint112 reserve1; uint32 blockTimestampLast; uint256 price0CumulativeLast; uint256 price1CumulativeLast; uint256 kLast; // `reserve0` * `reserve1`, as of immediately after the most recent liquidity event uint256 supply; } // Solady (https://github.com/Vectorized/soledge/blob/main/src/utils/ReentrancyGuard.sol) error Reentrancy(); modifier lock() { assembly ("memory-safe") { if tload(0x929eee149b4bd21268) { mstore(0x00, 0xab143c06) // `Reentrancy()` revert(0x1c, 0x04) } tstore(0x929eee149b4bd21268, address()) } _; assembly ("memory-safe") { tstore(0x929eee149b4bd21268, 0) } } // ** TRANSFER function _safeTransfer(address token, address to, uint256 id, uint256 amount) internal { if (to == address(this)) { assembly ("memory-safe") { let m := mload(0x40) mstore(0x00, caller()) mstore(0x20, token) mstore(0x40, id) let slot := keccak256(0x00, 0x60) tstore(slot, add(tload(slot), amount)) mstore(0x40, m) } } else if (token == address(this)) { _mint(to, id, amount); } else if (token == address(0)) { safeTransferETH(to, amount); } else if (id == 0) { safeTransfer(token, to, amount); } else { ZERC6909(token).transfer(to, id, amount); } } function _safeTransferFrom(address token, uint256 id, uint256 amount) internal { if (token == address(this)) { _burn(id, amount); } else if (id == 0) { safeTransferFrom(token, amount); } else { ZERC6909(token).transferFrom(msg.sender, address(this), id, amount); } } event Mint(uint256 indexed poolId, address indexed sender, uint256 amount0, uint256 amount1); event Burn( uint256 indexed poolId, address indexed sender, uint256 amount0, uint256 amount1, address indexed to ); event Swap( uint256 indexed poolId, address indexed sender, uint256 amount0In, uint256 amount1In, uint256 amount0Out, uint256 amount1Out, address indexed to ); event Sync(uint256 indexed poolId, uint112 reserve0, uint112 reserve1); constructor() payable { assembly ("memory-safe") { sstore(0x00, origin()) } } // ** INTERNAL error Overflow(); // update reserves and, on the first call per block, price accumulators for the given pool `poolId` function _update( Pool storage pool, uint256 poolId, uint256 balance0, uint256 balance1, uint112 reserve0, uint112 reserve1 ) internal { unchecked { require(balance0 <= type(uint112).max, Overflow()); require(balance1 <= type(uint112).max, Overflow()); uint32 blockTimestamp = uint32(block.timestamp % 2 ** 32); uint32 timeElapsed = blockTimestamp - pool.blockTimestampLast; // overflow is desired if (timeElapsed > 0 && reserve0 != 0 && reserve1 != 0) { // * never overflows, and + overflow is desired pool.price0CumulativeLast += uint256(uqdiv(encode(reserve1), reserve0)) * timeElapsed; pool.price1CumulativeLast += uint256(uqdiv(encode(reserve0), reserve1)) * timeElapsed; } pool.blockTimestampLast = blockTimestamp; emit Sync(poolId, pool.reserve0 = uint112(balance0), pool.reserve1 = uint112(balance1)); } } // if fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k) function _mintFee(Pool storage pool, uint256 poolId, uint112 reserve0, uint112 reserve1) internal returns (bool feeOn) { address feeTo; assembly ("memory-safe") { feeTo := sload(0x20) feeOn := iszero(iszero(feeTo)) } if (feeOn) { if (pool.kLast != 0) { uint256 rootK = sqrt(uint256(reserve0) * reserve1); uint256 rootKLast = sqrt(pool.kLast); if (rootK > rootKLast) { uint256 numerator = pool.supply * (rootK - rootKLast); uint256 denominator = rootK * 5 + rootKLast; uint256 liquidity; unchecked { liquidity = numerator / denominator; if (liquidity > 0) { _mint(feeTo, poolId, liquidity); } pool.supply += liquidity; } } } } else if (pool.kLast != 0) { delete pool.kLast; } } // ** SWAPPERS error Expired(); error InvalidMsgVal(); error InsufficientLiquidity(); error InsufficientInputAmount(); error InsufficientOutputAmount(); function swapExactIn( PoolKey calldata poolKey, uint256 amountIn, uint256 amountOutMin, bool zeroForOne, address to, uint256 deadline ) public payable lock returns (uint256 amountOut) { require(deadline >= block.timestamp, Expired()); require(amountIn != 0, InsufficientInputAmount()); uint256 poolId = _getPoolId(poolKey); Pool storage pool = pools[poolId]; (uint112 reserve0, uint112 reserve1) = (pool.reserve0, pool.reserve1); bool credited; if (zeroForOne) { credited = _useTransientBalance(poolKey.token0, poolKey.id0, amountIn); if (credited) require(msg.value == 0, InvalidMsgVal()); } else { credited = _useTransientBalance(poolKey.token1, poolKey.id1, amountIn); } if (!credited) { if (zeroForOne) { if (poolKey.token0 == address(0)) { require(msg.value == amountIn, InvalidMsgVal()); } else { require(msg.value == 0, InvalidMsgVal()); _safeTransferFrom(poolKey.token0, poolKey.id0, amountIn); } } else { require(msg.value == 0, InvalidMsgVal()); _safeTransferFrom(poolKey.token1, poolKey.id1, amountIn); } } if (zeroForOne) { amountOut = _getAmountOut(amountIn, reserve0, reserve1, poolKey.swapFee); require(amountOut != 0, InsufficientOutputAmount()); require(amountOut >= amountOutMin, InsufficientOutputAmount()); require(amountOut < reserve1, InsufficientLiquidity()); _safeTransfer(poolKey.token1, to, poolKey.id1, amountOut); _update(pool, poolId, reserve0 + amountIn, reserve1 - amountOut, reserve0, reserve1); emit Swap(poolId, msg.sender, amountIn, 0, 0, amountOut, to); } else { amountOut = _getAmountOut(amountIn, reserve1, reserve0, poolKey.swapFee); require(amountOut != 0, InsufficientOutputAmount()); require(amountOut >= amountOutMin, InsufficientOutputAmount()); require(amountOut < reserve0, InsufficientLiquidity()); _safeTransfer(poolKey.token0, to, poolKey.id0, amountOut); _update(pool, poolId, reserve0 - amountOut, reserve1 + amountIn, reserve0, reserve1); emit Swap(poolId, msg.sender, 0, amountIn, amountOut, 0, to); } } function swapExactOut( PoolKey calldata poolKey, uint256 amountOut, uint256 amountInMax, bool zeroForOne, address to, uint256 deadline ) public payable lock returns (uint256 amountIn) { require(deadline >= block.timestamp, Expired()); require(amountOut != 0, InsufficientOutputAmount()); uint256 poolId = _getPoolId(poolKey); Pool storage pool = pools[poolId]; (uint112 reserve0, uint112 reserve1) = (pool.reserve0, pool.reserve1); bool credited; if (zeroForOne) { require(amountOut < reserve1, InsufficientLiquidity()); amountIn = _getAmountIn(amountOut, reserve0, reserve1, poolKey.swapFee); require(amountIn <= amountInMax, InsufficientInputAmount()); credited = _useTransientBalance(poolKey.token0, poolKey.id0, amountIn); if (credited) require(msg.value == 0, InvalidMsgVal()); if (!credited) { if (poolKey.token0 == address(0)) { require(msg.value >= amountIn, InvalidMsgVal()); if (msg.value > amountIn) { unchecked { safeTransferETH(msg.sender, msg.value - amountIn); } } } else { require(msg.value == 0, InvalidMsgVal()); _safeTransferFrom(poolKey.token0, poolKey.id0, amountIn); } } _safeTransfer(poolKey.token1, to, poolKey.id1, amountOut); _update(pool, poolId, reserve0 + amountIn, reserve1 - amountOut, reserve0, reserve1); emit Swap(poolId, msg.sender, amountIn, 0, 0, amountOut, to); } else { require(amountOut < reserve0, InsufficientLiquidity()); amountIn = _getAmountIn(amountOut, reserve1, reserve0, poolKey.swapFee); require(amountIn <= amountInMax, InsufficientInputAmount()); credited = _useTransientBalance(poolKey.token1, poolKey.id1, amountIn); if (!credited) { require(msg.value == 0, InvalidMsgVal()); _safeTransferFrom(poolKey.token1, poolKey.id1, amountIn); } _safeTransfer(poolKey.token0, to, poolKey.id0, amountOut); _update(pool, poolId, reserve0 - amountOut, reserve1 + amountIn, reserve0, reserve1); emit Swap(poolId, msg.sender, 0, amountIn, amountOut, 0, to); } } error K(); // this low-level function should be called from a contract which performs important safety checks function swap( PoolKey calldata poolKey, uint256 amount0Out, uint256 amount1Out, address to, bytes calldata data ) public lock { require(amount0Out > 0 || amount1Out > 0, InsufficientOutputAmount()); uint256 poolId = _getPoolId(poolKey); Pool storage pool = pools[poolId]; (uint112 reserve0, uint112 reserve1) = (pool.reserve0, pool.reserve1); require(amount0Out < reserve0, InsufficientLiquidity()); require(amount1Out < reserve1, InsufficientLiquidity()); // optimistically transfer tokens - if `to` is `this`, tstore for multihop if (amount0Out > 0) _safeTransfer(poolKey.token0, to, poolKey.id0, amount0Out); if (amount1Out > 0) _safeTransfer(poolKey.token1, to, poolKey.id1, amount1Out); if (data.length > 0) { IZAMMCallee(to).zammCall(poolId, msg.sender, amount0Out, amount1Out, data); } uint256 balance0 = reserve0 + _getTransientBalance(poolKey.token0, poolKey.id0) - amount0Out; uint256 balance1 = reserve1 + _getTransientBalance(poolKey.token1, poolKey.id1) - amount1Out; uint256 amount0In; uint256 amount1In; unchecked { amount0In = balance0 > reserve0 - amount0Out ? balance0 - (reserve0 - amount0Out) : 0; amount1In = balance1 > reserve1 - amount1Out ? balance1 - (reserve1 - amount1Out) : 0; } require(amount0In > 0 || amount1In > 0, InsufficientInputAmount()); uint256 balance0Adjusted = (balance0 * 10000) - (amount0In * poolKey.swapFee); uint256 balance1Adjusted = (balance1 * 10000) - (amount1In * poolKey.swapFee); require( balance0Adjusted * balance1Adjusted >= (uint256(reserve0) * reserve1) * 10000 ** 2, K() ); _update(pool, poolId, balance0, balance1, reserve0, reserve1); emit Swap(poolId, msg.sender, amount0In, amount1In, amount0Out, amount1Out, to); } // ** LIQ MGMT error InvalidSwapFee(); error InvalidPoolTokens(); error InsufficientLiquidityMinted(); function addLiquidity( PoolKey calldata poolKey, uint256 amount0Desired, uint256 amount1Desired, uint256 amount0Min, uint256 amount1Min, address to, uint256 deadline ) public payable lock returns (uint256 amount0, uint256 amount1, uint256 liquidity) { require(deadline >= block.timestamp, Expired()); uint256 poolId = _getPoolId(poolKey); Pool storage pool = pools[poolId]; (uint112 reserve0, uint112 reserve1, uint256 supply) = (pool.reserve0, pool.reserve1, pool.supply); bool feeOn; if (supply != 0) { feeOn = _mintFee(pool, poolId, reserve0, reserve1); supply = pool.supply; } else { assembly ("memory-safe") { feeOn := iszero(iszero(sload(0x20))) } } if (supply == 0) { (amount0, amount1) = (amount0Desired, amount1Desired); } else { uint256 amount1Optimal = mulDiv(amount0Desired, reserve1, reserve0); if (amount1Optimal <= amount1Desired) { require(amount1Optimal >= amount1Min, InsufficientOutputAmount()); (amount0, amount1) = (amount0Desired, amount1Optimal); } else { uint256 amount0Optimal = mulDiv(amount1Desired, reserve0, reserve1); assert(amount0Optimal <= amount0Desired); require(amount0Optimal >= amount0Min, InsufficientOutputAmount()); (amount0, amount1) = (amount0Optimal, amount1Desired); } } bool credited = _useTransientBalance(poolKey.token0, poolKey.id0, amount0); if (credited) require(msg.value == 0, InvalidMsgVal()); if (!credited) { if (poolKey.token0 == address(0)) { require(msg.value == amount0, InvalidMsgVal()); } else { require(msg.value == 0, InvalidMsgVal()); _safeTransferFrom(poolKey.token0, poolKey.id0, amount0); } } credited = _useTransientBalance(poolKey.token1, poolKey.id1, amount1); if (!credited) _safeTransferFrom(poolKey.token1, poolKey.id1, amount1); if (supply == 0) { // enforce a single, canonical poolId for any unordered pair: if (poolKey.token0 == address(0)) require(poolKey.id0 == 0, InvalidPoolTokens()); require( // 1) two different token contracts/ETH: order by address poolKey.token0 < poolKey.token1 // 2) same ERC6909 contract: two distinct, non‑zero IDs in ascending order || ( poolKey.token0 == poolKey.token1 && poolKey.id0 != 0 && poolKey.id1 != 0 && poolKey.id0 < poolKey.id1 ), InvalidPoolTokens() ); require(poolKey.swapFee <= MAX_FEE, InvalidSwapFee()); liquidity = sqrt(amount0 * amount1) - MINIMUM_LIQUIDITY; require(liquidity != 0, InsufficientLiquidityMinted()); _initMint(to, poolId, liquidity); unchecked { pool.supply = liquidity + MINIMUM_LIQUIDITY; } } else { liquidity = min(mulDiv(amount0, supply, reserve0), mulDiv(amount1, supply, reserve1)); require(liquidity != 0, InsufficientLiquidityMinted()); _mint(to, poolId, liquidity); pool.supply += liquidity; } _update(pool, poolId, amount0 + reserve0, amount1 + reserve1, reserve0, reserve1); if (feeOn) pool.kLast = uint256(pool.reserve0) * pool.reserve1; emit Mint(poolId, msg.sender, amount0, amount1); } function removeLiquidity( PoolKey calldata poolKey, uint256 liquidity, uint256 amount0Min, uint256 amount1Min, address to, uint256 deadline ) public lock returns (uint256 amount0, uint256 amount1) { require(deadline >= block.timestamp, Expired()); uint256 poolId = _getPoolId(poolKey); Pool storage pool = pools[poolId]; (uint112 reserve0, uint112 reserve1) = (pool.reserve0, pool.reserve1); bool feeOn = _mintFee(pool, poolId, reserve0, reserve1); amount0 = mulDiv(liquidity, reserve0, pool.supply); amount1 = mulDiv(liquidity, reserve1, pool.supply); require(amount0 >= amount0Min, InsufficientOutputAmount()); require(amount1 >= amount1Min, InsufficientOutputAmount()); _burn(poolId, liquidity); unchecked { pool.supply -= liquidity; } _safeTransfer(poolKey.token0, to, poolKey.id0, amount0); _safeTransfer(poolKey.token1, to, poolKey.id1, amount1); unchecked { _update(pool, poolId, reserve0 - amount0, reserve1 - amount1, reserve0, reserve1); } if (feeOn) pool.kLast = uint256(pool.reserve0) * pool.reserve1; // `reserve0` and `reserve1` are up-to-date emit Burn(poolId, msg.sender, amount0, amount1, to); } // ** FACTORY event URI(string uri, uint256 indexed coinId); function make(address maker, uint256 supply, string calldata uri) public returns (uint256 coinId) { coinId = uint256(keccak256(abi.encodePacked(this.make.selector, msg.sender, block.timestamp))); _initMint(maker, coinId, supply); emit URI(uri, coinId); } function makeLiquid( address maker, address liqTo, uint256 mkrAmt, uint256 liqAmt, uint256 swapFee, string calldata uri ) public payable returns (uint256 coinId, uint256 poolId, uint256 liquidity) { require(swapFee <= MAX_FEE, InvalidSwapFee()); require(liqAmt <= type(uint256).max - mkrAmt, Overflow()); coinId = uint256( keccak256(abi.encodePacked(this.makeLiquid.selector, msg.sender, block.timestamp)) ); if (mkrAmt != 0) _initMint(maker, coinId, mkrAmt); emit URI(uri, coinId); assembly ("memory-safe") { let m := mload(0x40) mstore(m, 0) mstore(add(m, 0x20), coinId) mstore(add(m, 0x40), 0) mstore(add(m, 0x60), address()) mstore(add(m, 0x80), swapFee) poolId := keccak256(m, 0xa0) } Pool storage pool = pools[poolId]; bool feeOn; assembly ("memory-safe") { feeOn := iszero(iszero(sload(0x20))) } liquidity = sqrt(msg.value * liqAmt) - MINIMUM_LIQUIDITY; require(liquidity != 0, InsufficientLiquidityMinted()); _initMint(liqTo, poolId, liquidity); unchecked { pool.supply = liquidity + MINIMUM_LIQUIDITY; } _update(pool, poolId, msg.value, liqAmt, 0, 0); if (feeOn) pool.kLast = msg.value * liqAmt; emit Mint(poolId, msg.sender, msg.value, liqAmt); } // ** TRANSIENT receive() external payable { assembly ("memory-safe") { let m := mload(0x40) mstore(0x00, caller()) mstore(0x20, 0) mstore(0x40, 0) let slot := keccak256(0x00, 0x60) tstore(slot, add(tload(slot), callvalue())) mstore(0x40, m) } } function deposit(address token, uint256 id, uint256 amount) public payable { require(msg.value == (token == address(0) ? amount : 0), InvalidMsgVal()); if (token != address(0)) _safeTransferFrom(token, id, amount); assembly ("memory-safe") { let m := mload(0x40) mstore(0x00, caller()) mstore(0x20, token) mstore(0x40, id) let slot := keccak256(0x00, 0x60) tstore(slot, add(tload(slot), amount)) mstore(0x40, m) } } function _getTransientBalance(address token, uint256 id) internal returns (uint256 bal) { assembly ("memory-safe") { let m := mload(0x40) mstore(0x00, caller()) mstore(0x20, token) mstore(0x40, id) let slot := keccak256(0x00, 0x60) bal := tload(slot) if bal { tstore(slot, 0) } mstore(0x40, m) } } function _useTransientBalance(address token, uint256 id, uint256 amount) internal returns (bool credited) { assembly ("memory-safe") { let m := mload(0x40) mstore(0x00, caller()) mstore(0x20, token) mstore(0x40, id) let slot := keccak256(0x00, 0x60) let bal := tload(slot) if iszero(lt(bal, amount)) { tstore(slot, sub(bal, amount)) credited := 1 } mstore(0x40, m) } } function recoverTransientBalance(address token, uint256 id, address to) public lock returns (uint256 amount) { assembly ("memory-safe") { let m := mload(0x40) mstore(0x00, caller()) mstore(0x20, token) mstore(0x40, id) let slot := keccak256(0x00, 0x60) amount := tload(slot) if amount { tstore(slot, 0) } mstore(0x40, m) } if (amount != 0) _safeTransfer(token, to, id, amount); } // ** GETTERS function _getPoolId(PoolKey calldata poolKey) internal pure returns (uint256 poolId) { assembly ("memory-safe") { let m := mload(0x40) calldatacopy(m, poolKey, 0xa0) poolId := keccak256(m, 0xa0) } } function _getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut, uint96 swapFee) internal pure returns (uint256 amountOut) { uint256 amountInWithFee = amountIn * (10000 - swapFee); uint256 numerator = amountInWithFee * reserveOut; uint256 denominator = (reserveIn * 10000) + amountInWithFee; return numerator / denominator; } function _getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut, uint96 swapFee) internal pure returns (uint256 amountIn) { uint256 numerator = reserveIn * amountOut * 10000; uint256 denominator = (reserveOut - amountOut) * (10000 - swapFee); return (numerator / denominator) + 1; } // ** PROTOCOL FEES error Unauthorized(); function setFeeTo(address feeTo) public payable { assembly ("memory-safe") { if iszero(eq(caller(), sload(0x00))) { mstore(0x00, 0x82b42900) // `Unauthorized()` revert(0x1c, 0x04) } sstore(0x20, feeTo) } } function setFeeToSetter(address feeToSetter) public payable { assembly ("memory-safe") { if iszero(eq(caller(), sload(0x00))) { mstore(0x00, 0x82b42900) // `Unauthorized()` revert(0x1c, 0x04) } sstore(0x00, feeToSetter) } } // ** BATCH function multicall(bytes[] calldata data) public returns (bytes[] memory results) { results = new bytes[](data.length); for (uint256 i; i != data.length; ++i) { (bool success, bytes memory result) = address(this).delegatecall(data[i]); if (!success) { assembly ("memory-safe") { revert(add(result, 0x20), mload(result)) } } results[i] = result; } } // ** COMPRESS // Solady (https://github.com/Vectorized/solady/blob/main/src/utils/LibZip) fallback() external payable { assembly ("memory-safe") { if iszero(calldatasize()) { return(calldatasize(), calldatasize()) } let o := 0 let f := not(3) for { let i := 0 } lt(i, calldatasize()) {} { let c := byte(0, xor(add(i, f), calldataload(i))) i := add(i, 1) if iszero(c) { let d := byte(0, xor(add(i, f), calldataload(i))) i := add(i, 1) mstore(o, not(0)) if iszero(gt(d, 0x7f)) { calldatacopy(o, calldatasize(), add(d, 1)) } o := add(o, add(and(d, 0x7f), 1)) continue } mstore8(o, c) o := add(o, 1) } let success := delegatecall(gas(), address(), 0x00, o, codesize(), 0x00) returndatacopy(0x00, 0x00, returndatasize()) if iszero(success) { revert(0x00, returndatasize()) } return(0x00, returndatasize()) } } } // minimal ZAMM call interface interface IZAMMCallee { function zammCall( uint256 poolId, address sender, uint256 amount0, uint256 amount1, bytes calldata data ) external; } // SPDX-License-Identifier: MIT pragma solidity ^0.8.29; /// @notice Highly optimized ERC6909 implementation for ZAMM. /// @author Modified from Solady (https://github.com/vectorized/solady/blob/main/src/tokens/ERC6909.sol) abstract contract ZERC6909 { uint256 constant TRANSFER_EVENT_SIGNATURE = 0x1b3d7edb2e9c0b0e7c525b20aaaef0f5940d2ed71663c7d39266ecafac728859; uint256 constant OPERATOR_SET_EVENT_SIGNATURE = 0xceb576d9f15e4e200fdb5096d64d5dfd667e16def20c1eefd14256d8e3faa267; uint256 constant APPROVAL_EVENT_SIGNATURE = 0xb3fd5071835887567a0671151121894ddccc2842f1d10bedad13e0d17cace9a7; uint256 constant ERC6909_MASTER_SLOT_SEED = 0xedcaa89a82293940; function balanceOf(address owner, uint256 id) public view returns (uint256 amount) { assembly ("memory-safe") { mstore(0x20, ERC6909_MASTER_SLOT_SEED) mstore(0x14, owner) mstore(0x00, id) amount := sload(keccak256(0x00, 0x40)) } } function allowance(address owner, address spender, uint256 id) public view returns (uint256 amount) { assembly ("memory-safe") { mstore(0x34, ERC6909_MASTER_SLOT_SEED) mstore(0x28, owner) mstore(0x14, spender) mstore(0x00, id) amount := sload(keccak256(0x00, 0x54)) mstore(0x34, 0) } } function isOperator(address owner, address spender) public view returns (bool status) { assembly ("memory-safe") { mstore(0x20, ERC6909_MASTER_SLOT_SEED) mstore(0x14, owner) mstore(0x00, spender) status := sload(keccak256(0x0c, 0x34)) } } function transfer(address to, uint256 id, uint256 amount) public returns (bool) { assembly ("memory-safe") { mstore(0x20, ERC6909_MASTER_SLOT_SEED) mstore(0x14, caller()) mstore(0x00, id) let fromBalanceSlot := keccak256(0x00, 0x40) let fromBalance := sload(fromBalanceSlot) if gt(amount, fromBalance) { mstore(0x00, 0xf4d678b8) revert(0x1c, 0x04) } sstore(fromBalanceSlot, sub(fromBalance, amount)) mstore(0x14, to) mstore(0x00, id) let toBalanceSlot := keccak256(0x00, 0x40) let toBalanceBefore := sload(toBalanceSlot) let toBalanceAfter := add(toBalanceBefore, amount) if lt(toBalanceAfter, toBalanceBefore) { mstore(0x00, 0x89560ca1) revert(0x1c, 0x04) } sstore(toBalanceSlot, toBalanceAfter) mstore(0x00, caller()) mstore(0x20, amount) log4(0x00, 0x40, TRANSFER_EVENT_SIGNATURE, caller(), shr(96, shl(96, to)), id) mstore(0x00, 1) return(0x00, 0x20) } } function transferFrom(address from, address to, uint256 id, uint256 amount) public returns (bool) { assembly ("memory-safe") { mstore(0x34, ERC6909_MASTER_SLOT_SEED) mstore(0x28, from) mstore(0x14, caller()) if iszero(sload(keccak256(0x20, 0x34))) { mstore(0x00, id) let allowanceSlot := keccak256(0x00, 0x54) let allowance_ := sload(allowanceSlot) if add(allowance_, 1) { if gt(amount, allowance_) { mstore(0x00, 0xdeda9030) revert(0x1c, 0x04) } sstore(allowanceSlot, sub(allowance_, amount)) } } mstore(0x14, id) let fromBalanceSlot := keccak256(0x14, 0x40) let fromBalance := sload(fromBalanceSlot) if gt(amount, fromBalance) { mstore(0x00, 0xf4d678b8) revert(0x1c, 0x04) } sstore(fromBalanceSlot, sub(fromBalance, amount)) mstore(0x28, to) mstore(0x14, id) let toBalanceSlot := keccak256(0x14, 0x40) let toBalanceBefore := sload(toBalanceSlot) let toBalanceAfter := add(toBalanceBefore, amount) if lt(toBalanceAfter, toBalanceBefore) { mstore(0x00, 0x89560ca1) revert(0x1c, 0x04) } sstore(toBalanceSlot, toBalanceAfter) mstore(0x00, caller()) mstore(0x20, amount) // forgefmt: disable-next-line log4(0x00, 0x40, TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, from)), shr(96, shl(96, to)), id) mstore(0x34, 0) mstore(0x00, 1) return(0x00, 0x20) } } function approve(address spender, uint256 id, uint256 amount) public returns (bool) { assembly ("memory-safe") { mstore(0x34, ERC6909_MASTER_SLOT_SEED) mstore(0x28, caller()) mstore(0x14, spender) mstore(0x00, id) sstore(keccak256(0x00, 0x54), amount) mstore(0x00, amount) log4(0x00, 0x20, APPROVAL_EVENT_SIGNATURE, caller(), shr(96, mload(0x20)), id) mstore(0x34, 0) mstore(0x00, 1) return(0x00, 0x20) } } function setOperator(address operator, bool approved) public returns (bool) { assembly ("memory-safe") { let approvedCleaned := iszero(iszero(approved)) mstore(0x20, ERC6909_MASTER_SLOT_SEED) mstore(0x14, caller()) mstore(0x00, operator) sstore(keccak256(0x0c, 0x34), approvedCleaned) mstore(0x20, approvedCleaned) log3(0x20, 0x20, OPERATOR_SET_EVENT_SIGNATURE, caller(), shr(96, mload(0x0c))) mstore(0x00, 1) return(0x00, 0x20) } } function supportsInterface(bytes4 interfaceId) public pure returns (bool result) { assembly ("memory-safe") { let s := shr(224, interfaceId) result := or(eq(s, 0x01ffc9a7), eq(s, 0x0f632fb3)) } } function _initMint(address to, uint256 id, uint256 amount) internal { assembly ("memory-safe") { mstore(0x20, ERC6909_MASTER_SLOT_SEED) mstore(0x14, to) mstore(0x00, id) sstore(keccak256(0x00, 0x40), amount) mstore(0x00, caller()) mstore(0x20, amount) log4(0x00, 0x40, TRANSFER_EVENT_SIGNATURE, 0, shr(96, shl(96, to)), id) } } function _mint(address to, uint256 id, uint256 amount) internal { assembly ("memory-safe") { mstore(0x20, ERC6909_MASTER_SLOT_SEED) mstore(0x14, to) mstore(0x00, id) let toBalanceSlot := keccak256(0x00, 0x40) let toBalanceBefore := sload(toBalanceSlot) let toBalanceAfter := add(toBalanceBefore, amount) if lt(toBalanceAfter, toBalanceBefore) { mstore(0x00, 0x89560ca1) revert(0x1c, 0x04) } sstore(toBalanceSlot, toBalanceAfter) mstore(0x00, caller()) mstore(0x20, amount) log4(0x00, 0x40, TRANSFER_EVENT_SIGNATURE, 0, shr(96, shl(96, to)), id) } } function _burn(uint256 id, uint256 amount) internal { assembly ("memory-safe") { mstore(0x20, ERC6909_MASTER_SLOT_SEED) mstore(0x14, caller()) mstore(0x00, id) let fromBalanceSlot := keccak256(0x00, 0x40) let fromBalance := sload(fromBalanceSlot) if gt(amount, fromBalance) { mstore(0x00, 0xf4d678b8) revert(0x1c, 0x04) } sstore(fromBalanceSlot, sub(fromBalance, amount)) mstore(0x00, caller()) mstore(0x20, amount) log4(0x00, 0x40, TRANSFER_EVENT_SIGNATURE, shr(96, shl(96, caller())), 0, id) } } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.29; // Modified from Solady (https://github.com/Vectorized/solady/blob/main/src/utils/FixedPointMathLib.sol) /// @dev Returns the minimum of `x` and `y`. function min(uint256 x, uint256 y) pure returns (uint256 z) { assembly ("memory-safe") { z := xor(x, mul(xor(x, y), lt(y, x))) } } /// @dev Returns `floor(x * y / d)`. /// Reverts if `x * y` overflows, or `d` is zero. function mulDiv(uint256 x, uint256 y, uint256 d) pure returns (uint256 z) { assembly ("memory-safe") { z := mul(x, y) if iszero(mul(or(iszero(x), eq(div(z, x), y)), d)) { mstore(0x00, 0xad251c27) revert(0x1c, 0x04) } z := div(z, d) } } /// @dev Returns the square root of `x`, rounded down. function sqrt(uint256 x) pure returns (uint256 z) { assembly ("memory-safe") { z := 181 let r := shl(7, lt(0xffffffffffffffffffffffffffffffffff, x)) r := or(r, shl(6, lt(0xffffffffffffffffff, shr(r, x)))) r := or(r, shl(5, lt(0xffffffffff, shr(r, x)))) r := or(r, shl(4, lt(0xffffff, shr(r, x)))) z := shl(shr(1, r), z) z := shr(18, mul(z, add(shr(r, x), 65536))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := shr(1, add(z, div(x, z))) z := sub(z, lt(div(x, z), z)) } } // Modified from Uniswap V2 (https://github.com/Uniswap/v2-core/blob/master/contracts/libraries/UQ112x112.sol) // Licensed GPL-3.0 /// @dev Encode a uint112 as a UQ112x112. function encode(uint112 y) pure returns (uint224 z) { unchecked { z = uint224(y) * 2 ** 112; } } /// @dev Divide a UQ112x112 by a uint112, returning a UQ112x112. function uqdiv(uint224 x, uint112 y) pure returns (uint224 z) { unchecked { z = x / uint224(y); } } // SPDX-License-Identifier: MIT pragma solidity ^0.8.29; // Modified from Solady (https://github.com/Vectorized/solady/blob/main/src/utils/SafeTransferLib.sol) /// @dev Sends `amount` (in wei) ETH to `to`. function safeTransferETH(address to, uint256 amount) { assembly ("memory-safe") { if iszero(call(gas(), to, amount, codesize(), 0x00, codesize(), 0x00)) { mstore(0x00, 0xb12d13eb) revert(0x1c, 0x04) } } } /// @dev Sends `amount` of ERC20 `token` from the current contract to `to`. /// Reverts upon failure. function safeTransfer(address token, address to, uint256 amount) { assembly ("memory-safe") { mstore(0x14, to) mstore(0x34, amount) mstore(0x00, 0xa9059cbb000000000000000000000000) let success := call(gas(), token, 0, 0x10, 0x44, 0x00, 0x20) if iszero(and(eq(mload(0x00), 1), success)) { if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { mstore(0x00, 0x90b8ec18) revert(0x1c, 0x04) } } mstore(0x34, 0) } } /// @dev Sends `amount` of ERC20 `token` from caller to `this`. /// Reverts upon failure. /// /// The caller account must have at least `amount` approved for /// the current contract to manage. function safeTransferFrom(address token, uint256 amount) { assembly ("memory-safe") { let m := mload(0x40) mstore(0x60, amount) mstore(0x40, address()) mstore(0x2c, shl(96, caller())) mstore(0x0c, 0x23b872dd000000000000000000000000) let success := call(gas(), token, 0, 0x1c, 0x64, 0x00, 0x20) if iszero(and(eq(mload(0x00), 1), success)) { if iszero(lt(or(iszero(extcodesize(token)), returndatasize()), success)) { mstore(0x00, 0x7939f424) revert(0x1c, 0x04) } } mstore(0x60, 0) mstore(0x40, m) } }