Skip to content

Commit

Permalink
🧹 Tidy
Browse files Browse the repository at this point in the history
  • Loading branch information
z0r0z committed Jan 23, 2024
1 parent 88054ee commit 1b0b8e1
Showing 1 changed file with 113 additions and 65 deletions.
178 changes: 113 additions & 65 deletions src/Helios.sol
Original file line number Diff line number Diff line change
@@ -1,23 +1,40 @@
// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity ^0.8.19;

import {ERC6909} from "./utils/ERC6909.sol";
import {Math2} from "./libraries/Math2.sol";
import {ERC6909} from "./utils/ERC6909.sol";
import {ReentrancyGuard} from "./utils/ReentrancyGuard.sol";
import {SafeTransferLib} from "./libraries/SafeTransferLib.sol";

/// @dev Simple XYK Exchange for ERC20s.
/// LP shares are tokenized using ERC6909.
/// @notice Simple xyk-style exchange for ERC20 tokens.
/// LP shares are tokenized using the ERC6909 interface.
/// @author Modified from Uniswap V2
/// (https://github.com/Uniswap/v2-core/blob/master/contracts/UniswapV2Pair.sol)
contract Helios is ERC6909, ReentrancyGuard {
/// ========================= LIBRARIES ========================= ///

/// @dev Did the maths.
using Math2 for uint224;

/// @dev Safety library for ERC20.
using SafeTransferLib for address;

uint256 internal constant MINIMUM_LIQUIDITY = 1000;
bytes4 internal constant SELECTOR = bytes4(keccak256(bytes("transfer(address,uint256)")));
/// ========================= CONSTANTS ========================= ///

/// @dev Minimum liquidity to start pool.
uint256 internal constant MIN_LIQ = 1000;

/// ========================== STORAGE ========================== ///

/// @dev Pool swapping data mapping.
mapping(uint256 => Pool) public pools;

/// @dev Pool cumulative price mapping.
mapping(uint256 => Price) public prices;

/// ========================== STRUCTS ========================== ///

/// @dev Pool data.
struct Pool {
address token0;
address token1;
Expand All @@ -26,68 +43,20 @@ contract Helios is ERC6909, ReentrancyGuard {
uint32 blockTimestampLast;
}

/// @dev Price data.
struct Price {
uint256 price0CumulativeLast;
uint256 price1CumulativeLast;
uint256 kLast;
}

constructor() payable {}

/// @dev Update reserves and, on the first call per block, price accumulators.
function _update(
uint256 id,
uint256 balance0,
uint256 balance1,
uint112 _reserve0,
uint112 _reserve1
) internal virtual {
Pool storage pool = pools[id];
Price storage price = prices[id];

require(balance0 <= type(uint112).max && balance1 <= type(uint112).max, "Helios: OVERFLOW");
uint32 blockTimestamp = uint32(block.timestamp % 2 ** 32);
unchecked {
uint32 timeElapsed = blockTimestamp - pool.blockTimestampLast; // Overflow is desired.
if (timeElapsed != 0 && _reserve0 != 0 && _reserve1 != 0) {
// * Never overflows, and + overflow is desired.
price.price0CumulativeLast +=
uint256(Math2.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
price.price1CumulativeLast +=
uint256(Math2.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
}
}
pool.reserve0 = uint112(balance0);
pool.reserve1 = uint112(balance1);
pool.blockTimestampLast = blockTimestamp;
}
/// ======================== CONSTRUCTOR ======================== ///

function _feeTo() public view virtual returns (address) {}
/// @dev Constructs
/// this implementation.
constructor() payable {}

/// @dev If fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k).
function _mintFee(uint256 id, uint112 _reserve0, uint112 _reserve1)
internal
virtual
returns (bool feeOn)
{
Price storage price = prices[id];
address feeTo = _feeTo();
feeOn = feeTo != address(0);
if (feeOn) {
if (price.kLast != 0) {
uint256 rootK = Math2.sqrt(uint256(_reserve0) * (_reserve1));
uint256 rootKLast = Math2.sqrt(price.kLast);
if (rootK > rootKLast) {
uint256 numerator = totalSupply[id] * (rootK - rootKLast);
uint256 denominator = rootK * 5 + rootKLast;
uint256 liquidity = numerator / denominator;
if (liquidity != 0) _mint(feeTo, id, liquidity);
}
}
} else if (price.kLast != 0) {
price.kLast = 0;
}
}
/// ======================== MINT & BURN ======================== ///

/// @dev This low-level function should be called from a contract which performs important safety checks.
function mint(uint256 id, address to) public payable nonReentrant returns (uint256 liquidity) {
Expand All @@ -103,18 +72,18 @@ contract Helios is ERC6909, ReentrancyGuard {
uint256 amount1 = balance1 - _reserve1;

bool feeOn = _mintFee(id, _reserve0, _reserve1);
uint256 _totalSupply = totalSupply[id]; // Gas savings, must be defined here since totalSupply can update in _mintFee.
uint256 _totalSupply = totalSupply[id]; // Gas savings, must be defined here since totalSupply can update in `_mintFee`.
if (_totalSupply == 0) {
liquidity = Math2.sqrt((amount0 * amount1) - (MINIMUM_LIQUIDITY));
_mint(address(0), id, MINIMUM_LIQUIDITY); // Permanently lock the first MINIMUM_LIQUIDITY tokens.
liquidity = Math2.sqrt((amount0 * amount1) - MIN_LIQ);
_mint(address(0), id, MIN_LIQ); // Permanently lock the first `MIN_LIQ` tokens.
} else {
liquidity =
Math2.min(amount0 * _totalSupply / _reserve0, (amount1 * _totalSupply) / _reserve1);
}
require(liquidity != 0, "Helios: INSUFFICIENT_LIQUIDITY_MINTED");
_mint(to, id, liquidity);
_update(id, balance0, balance1, _reserve0, _reserve1);
if (feeOn) prices[id].kLast = uint256(pool.reserve0) * (pool.reserve1); // Reserve0 and reserve1 are up-to-date.
if (feeOn) prices[id].kLast = uint256(pool.reserve0) * (pool.reserve1); // `reserve0` and `reserve1` are up-to-date.
}

/// @dev This low-level function should be called from a contract which performs important safety checks.
Expand All @@ -132,7 +101,7 @@ contract Helios is ERC6909, ReentrancyGuard {
uint256 liquidity;

bool feeOn = _mintFee(id, pool.reserve0, pool.reserve1);
uint256 _totalSupply = totalSupply[id]; // Gas savings, must be defined here since totalSupply can update in _mintFee.
uint256 _totalSupply = totalSupply[id]; // Gas savings, must be defined here since totalSupply can update in `_mintFee`.
amount0 = liquidity * balance0 / _totalSupply; // Using balances ensures pro-rata distribution.
amount1 = liquidity * balance1 / _totalSupply; // Using balances ensures pro-rata distribution.
require(amount0 != 0 && amount1 != 0, "Helios: INSUFFICIENT_LIQUIDITY_BURNED");
Expand All @@ -142,9 +111,11 @@ contract Helios is ERC6909, ReentrancyGuard {
balance0 = pool.token0.balanceOf(address(this));
balance1 = pool.token1.balanceOf(address(this));
_update(id, balance0, balance1, pool.reserve0, pool.reserve1);
if (feeOn) prices[id].kLast = uint256(pool.reserve0) * (pool.reserve1); // Reserve0 and reserve1 are up-to-date.
if (feeOn) prices[id].kLast = uint256(pool.reserve0) * (pool.reserve1); // `reserve0` and `reserve1` are up-to-date.
}

/// ======================== SWAP ======================== ///

/// @dev This low-level function should be called from a contract which performs important safety checks.
function swap(
uint256 id,
Expand Down Expand Up @@ -205,6 +176,83 @@ contract Helios is ERC6909, ReentrancyGuard {
pool.reserve1
);
}

/// ======================== INTERNAL ======================== ///

function _feeTo() public view virtual returns (address) {}

/// @dev Update reserves and, on the first call per block, price accumulators.
function _update(
uint256 id,
uint256 balance0,
uint256 balance1,
uint112 _reserve0,
uint112 _reserve1
) internal virtual {
Pool storage pool = pools[id];
Price storage price = prices[id];

require(balance0 <= type(uint112).max && balance1 <= type(uint112).max, "Helios: OVERFLOW");
uint32 blockTimestamp = uint32(block.timestamp % 2 ** 32);
unchecked {
uint32 timeElapsed = blockTimestamp - pool.blockTimestampLast; // Overflow is desired.
if (timeElapsed != 0 && _reserve0 != 0 && _reserve1 != 0) {
// * Never overflows, and + overflow is desired.
price.price0CumulativeLast +=
uint256(Math2.encode(_reserve1).uqdiv(_reserve0)) * timeElapsed;
price.price1CumulativeLast +=
uint256(Math2.encode(_reserve0).uqdiv(_reserve1)) * timeElapsed;
}
}
pool.reserve0 = uint112(balance0);
pool.reserve1 = uint112(balance1);
pool.blockTimestampLast = blockTimestamp;
}

/// @dev If fee is on, mint liquidity equivalent to 1/6th of the growth in sqrt(k).
function _mintFee(uint256 id, uint112 _reserve0, uint112 _reserve1)
internal
virtual
returns (bool feeOn)
{
Price storage price = prices[id];
address feeTo = _feeTo();
feeOn = feeTo != address(0);
if (feeOn) {
if (price.kLast != 0) {
uint256 rootK = Math2.sqrt(uint256(_reserve0) * (_reserve1));
uint256 rootKLast = Math2.sqrt(price.kLast);
if (rootK > rootKLast) {
uint256 numerator = totalSupply[id] * (rootK - rootKLast);
uint256 denominator = rootK * 5 + rootKLast;
uint256 liquidity = numerator / denominator;
if (liquidity != 0) _mint(feeTo, id, liquidity);
}
}
} else if (price.kLast != 0) {
price.kLast = 0;
}
}

/// =================== EXTERNAL TOKEN HELPERS =================== ///

/// @dev Returns the amount of ERC20 `token` owned by `account`.
/// Returns zero if the `token` does not exist.
function _balanceOf(address token, address account) internal view returns (uint256 amount) {
/// @solidity memory-safe-assembly
assembly {
mstore(0x14, account) // Store the `account` argument.
mstore(0x00, 0x70a08231000000000000000000000000) // `balanceOf(address)`.
amount :=
mul(
mload(0x20),
and( // The arguments of `and` are evaluated from right to left.
gt(returndatasize(), 0x1f), // At least 32 bytes returned.
staticcall(gas(), token, 0x10, 0x24, 0x20, 0x20)
)
)
}
}
}

/// @dev Simple external call interface for swaps.
Expand Down

0 comments on commit 1b0b8e1

Please sign in to comment.