Aere Network public source. Everything here can be checked against the live chain (chain id 2800, https://rpc.aere.network). Scope note, stated up front rather than buried: consensus on chain 2800 is classical secp256k1 ECDSA QBFT. The post-quantum work in this repository is at the signature, precompile, account and transport layers. Nothing here makes the consensus post-quantum, and no document in it should be read as claiming so.
333 lines
16 KiB
Solidity
333 lines
16 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.19;
|
|
|
|
/**
|
|
* @title AereSwapRouter
|
|
* @notice Periphery router for AereSwap constant-product AMM (factory + pairs
|
|
* already deployed). ABI-compatible with the canonical x*y=k router
|
|
* interface so existing wallets, aggregators, and SDKs work against
|
|
* AERE without code changes.
|
|
* Adds:
|
|
* - addLiquidity (with deadline + slippage min-amounts)
|
|
* - addLiquidityAERE (native AERE auto-wraps to WAERE)
|
|
* - removeLiquidity / removeLiquidityAERE
|
|
* - swapExactTokensForTokens
|
|
* - swapExactAEREForTokens / swapExactTokensForAERE (auto-wrap/unwrap)
|
|
* - quote / getAmountOut / getAmountIn / getAmountsOut / getAmountsIn helpers
|
|
*
|
|
* Designed to plug into the already-deployed AereSwapFactory
|
|
* at 0xf0a8df7BDc25721892475B21271e52D77B0e84DC.
|
|
*/
|
|
|
|
interface IERC20 {
|
|
function transfer(address to, uint256 amount) external returns (bool);
|
|
function transferFrom(address from, address to, uint256 amount) external returns (bool);
|
|
function balanceOf(address account) external view returns (uint256);
|
|
function approve(address spender, uint256 amount) external returns (bool);
|
|
}
|
|
|
|
interface IWAERE {
|
|
function deposit() external payable;
|
|
function withdraw(uint256) external;
|
|
function transfer(address to, uint256 amount) external returns (bool);
|
|
function balanceOf(address) external view returns (uint256);
|
|
}
|
|
|
|
interface IAereSwapFactory {
|
|
function getPair(address tokenA, address tokenB) external view returns (address pair);
|
|
function createPair(address tokenA, address tokenB) external returns (address pair);
|
|
function WAERE() external view returns (address);
|
|
}
|
|
|
|
interface IAereSwapPair {
|
|
function token0() external view returns (address);
|
|
function token1() external view returns (address);
|
|
function getReserves() external view returns (uint112 reserve0, uint112 reserve1, uint32 ts);
|
|
function mint(address to) external returns (uint256 liquidity);
|
|
function burn(address to) external returns (uint256 amount0, uint256 amount1);
|
|
function swap(uint256 amount0Out, uint256 amount1Out, address to) external;
|
|
function totalSupply() external view returns (uint256);
|
|
function balanceOf(address) external view returns (uint256);
|
|
function transfer(address to, uint256 amount) external returns (bool);
|
|
function transferFrom(address from, address to, uint256 amount) external returns (bool);
|
|
}
|
|
|
|
contract AereSwapRouter {
|
|
address public immutable factory;
|
|
address public immutable WAERE;
|
|
|
|
modifier ensure(uint256 deadline) {
|
|
require(block.timestamp <= deadline, "AereRouter: EXPIRED");
|
|
_;
|
|
}
|
|
|
|
constructor(address _factory, address _WAERE) {
|
|
factory = _factory;
|
|
WAERE = _WAERE;
|
|
}
|
|
|
|
// Allow this contract to receive native AERE from WAERE.withdraw().
|
|
receive() external payable {
|
|
require(msg.sender == WAERE, "AereRouter: only WAERE");
|
|
}
|
|
|
|
// ═════════════════════════════════════════════════════════════════════
|
|
// LIQUIDITY
|
|
// ═════════════════════════════════════════════════════════════════════
|
|
|
|
function _addLiquidity(
|
|
address tokenA,
|
|
address tokenB,
|
|
uint256 amountADesired,
|
|
uint256 amountBDesired,
|
|
uint256 amountAMin,
|
|
uint256 amountBMin
|
|
) internal returns (uint256 amountA, uint256 amountB) {
|
|
if (IAereSwapFactory(factory).getPair(tokenA, tokenB) == address(0)) {
|
|
IAereSwapFactory(factory).createPair(tokenA, tokenB);
|
|
}
|
|
(uint256 rA, uint256 rB) = getReserves(factory, tokenA, tokenB);
|
|
if (rA == 0 && rB == 0) {
|
|
(amountA, amountB) = (amountADesired, amountBDesired);
|
|
} else {
|
|
uint256 amountBOptimal = quote(amountADesired, rA, rB);
|
|
if (amountBOptimal <= amountBDesired) {
|
|
require(amountBOptimal >= amountBMin, "AereRouter: INSUFFICIENT_B_AMOUNT");
|
|
(amountA, amountB) = (amountADesired, amountBOptimal);
|
|
} else {
|
|
uint256 amountAOptimal = quote(amountBDesired, rB, rA);
|
|
assert(amountAOptimal <= amountADesired);
|
|
require(amountAOptimal >= amountAMin, "AereRouter: INSUFFICIENT_A_AMOUNT");
|
|
(amountA, amountB) = (amountAOptimal, amountBDesired);
|
|
}
|
|
}
|
|
}
|
|
|
|
function addLiquidity(
|
|
address tokenA,
|
|
address tokenB,
|
|
uint256 amountADesired,
|
|
uint256 amountBDesired,
|
|
uint256 amountAMin,
|
|
uint256 amountBMin,
|
|
address to,
|
|
uint256 deadline
|
|
) external ensure(deadline) returns (uint256 amountA, uint256 amountB, uint256 liquidity) {
|
|
(amountA, amountB) = _addLiquidity(tokenA, tokenB, amountADesired, amountBDesired, amountAMin, amountBMin);
|
|
address pair = pairFor(factory, tokenA, tokenB);
|
|
_safeTransferFrom(tokenA, msg.sender, pair, amountA);
|
|
_safeTransferFrom(tokenB, msg.sender, pair, amountB);
|
|
liquidity = IAereSwapPair(pair).mint(to);
|
|
}
|
|
|
|
function addLiquidityAERE(
|
|
address token,
|
|
uint256 amountTokenDesired,
|
|
uint256 amountTokenMin,
|
|
uint256 amountAEREMin,
|
|
address to,
|
|
uint256 deadline
|
|
)
|
|
external
|
|
payable
|
|
ensure(deadline)
|
|
returns (uint256 amountToken, uint256 amountAERE, uint256 liquidity)
|
|
{
|
|
(amountToken, amountAERE) = _addLiquidity(
|
|
token, WAERE, amountTokenDesired, msg.value, amountTokenMin, amountAEREMin
|
|
);
|
|
address pair = pairFor(factory, token, WAERE);
|
|
_safeTransferFrom(token, msg.sender, pair, amountToken);
|
|
IWAERE(WAERE).deposit{ value: amountAERE }();
|
|
require(IWAERE(WAERE).transfer(pair, amountAERE), "AereRouter: WAERE_TRANSFER_FAIL");
|
|
liquidity = IAereSwapPair(pair).mint(to);
|
|
// refund any dust
|
|
if (msg.value > amountAERE) _safeTransferAERE(msg.sender, msg.value - amountAERE);
|
|
}
|
|
|
|
function removeLiquidity(
|
|
address tokenA,
|
|
address tokenB,
|
|
uint256 liquidity,
|
|
uint256 amountAMin,
|
|
uint256 amountBMin,
|
|
address to,
|
|
uint256 deadline
|
|
) public ensure(deadline) returns (uint256 amountA, uint256 amountB) {
|
|
address pair = pairFor(factory, tokenA, tokenB);
|
|
require(IAereSwapPair(pair).transferFrom(msg.sender, pair, liquidity), "AereRouter: LP_TRANSFER_FAIL");
|
|
(uint256 amount0, uint256 amount1) = IAereSwapPair(pair).burn(to);
|
|
(address t0, ) = sortTokens(tokenA, tokenB);
|
|
(amountA, amountB) = tokenA == t0 ? (amount0, amount1) : (amount1, amount0);
|
|
require(amountA >= amountAMin, "AereRouter: INSUFFICIENT_A_AMOUNT");
|
|
require(amountB >= amountBMin, "AereRouter: INSUFFICIENT_B_AMOUNT");
|
|
}
|
|
|
|
function removeLiquidityAERE(
|
|
address token,
|
|
uint256 liquidity,
|
|
uint256 amountTokenMin,
|
|
uint256 amountAEREMin,
|
|
address to,
|
|
uint256 deadline
|
|
) external ensure(deadline) returns (uint256 amountToken, uint256 amountAERE) {
|
|
(amountToken, amountAERE) = removeLiquidity(
|
|
token, WAERE, liquidity, amountTokenMin, amountAEREMin, address(this), deadline
|
|
);
|
|
_safeTransfer(token, to, amountToken);
|
|
IWAERE(WAERE).withdraw(amountAERE);
|
|
_safeTransferAERE(to, amountAERE);
|
|
}
|
|
|
|
// ═════════════════════════════════════════════════════════════════════
|
|
// SWAPS
|
|
// ═════════════════════════════════════════════════════════════════════
|
|
|
|
function _swap(uint256[] memory amounts, address[] memory path, address _to) internal {
|
|
for (uint256 i = 0; i < path.length - 1; i++) {
|
|
(address input, address output) = (path[i], path[i + 1]);
|
|
(address t0, ) = sortTokens(input, output);
|
|
uint256 amountOut = amounts[i + 1];
|
|
(uint256 amount0Out, uint256 amount1Out) =
|
|
input == t0 ? (uint256(0), amountOut) : (amountOut, uint256(0));
|
|
address to = i < path.length - 2 ? pairFor(factory, output, path[i + 2]) : _to;
|
|
IAereSwapPair(pairFor(factory, input, output)).swap(amount0Out, amount1Out, to);
|
|
}
|
|
}
|
|
|
|
function swapExactTokensForTokens(
|
|
uint256 amountIn,
|
|
uint256 amountOutMin,
|
|
address[] calldata path,
|
|
address to,
|
|
uint256 deadline
|
|
) external ensure(deadline) returns (uint256[] memory amounts) {
|
|
amounts = getAmountsOut(factory, amountIn, path);
|
|
require(amounts[amounts.length - 1] >= amountOutMin, "AereRouter: INSUFFICIENT_OUTPUT_AMOUNT");
|
|
_safeTransferFrom(path[0], msg.sender, pairFor(factory, path[0], path[1]), amounts[0]);
|
|
_swap(amounts, path, to);
|
|
}
|
|
|
|
function swapExactAEREForTokens(
|
|
uint256 amountOutMin,
|
|
address[] calldata path,
|
|
address to,
|
|
uint256 deadline
|
|
) external payable ensure(deadline) returns (uint256[] memory amounts) {
|
|
require(path[0] == WAERE, "AereRouter: INVALID_PATH");
|
|
amounts = getAmountsOut(factory, msg.value, path);
|
|
require(amounts[amounts.length - 1] >= amountOutMin, "AereRouter: INSUFFICIENT_OUTPUT_AMOUNT");
|
|
IWAERE(WAERE).deposit{ value: amounts[0] }();
|
|
require(IWAERE(WAERE).transfer(pairFor(factory, path[0], path[1]), amounts[0]), "AereRouter: WAERE_FAIL");
|
|
_swap(amounts, path, to);
|
|
}
|
|
|
|
function swapExactTokensForAERE(
|
|
uint256 amountIn,
|
|
uint256 amountOutMin,
|
|
address[] calldata path,
|
|
address to,
|
|
uint256 deadline
|
|
) external ensure(deadline) returns (uint256[] memory amounts) {
|
|
require(path[path.length - 1] == WAERE, "AereRouter: INVALID_PATH");
|
|
amounts = getAmountsOut(factory, amountIn, path);
|
|
require(amounts[amounts.length - 1] >= amountOutMin, "AereRouter: INSUFFICIENT_OUTPUT_AMOUNT");
|
|
_safeTransferFrom(path[0], msg.sender, pairFor(factory, path[0], path[1]), amounts[0]);
|
|
_swap(amounts, path, address(this));
|
|
IWAERE(WAERE).withdraw(amounts[amounts.length - 1]);
|
|
_safeTransferAERE(to, amounts[amounts.length - 1]);
|
|
}
|
|
|
|
// ═════════════════════════════════════════════════════════════════════
|
|
// LIBRARY (constant-product AMM math, inlined)
|
|
// ═════════════════════════════════════════════════════════════════════
|
|
|
|
function sortTokens(address tokenA, address tokenB) public pure returns (address token0, address token1) {
|
|
require(tokenA != tokenB, "AereRouter: IDENTICAL_ADDRESSES");
|
|
(token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
|
|
require(token0 != address(0), "AereRouter: ZERO_ADDRESS");
|
|
}
|
|
|
|
function pairFor(address _factory, address tokenA, address tokenB) public view returns (address pair) {
|
|
pair = IAereSwapFactory(_factory).getPair(tokenA, tokenB);
|
|
require(pair != address(0), "AereRouter: PAIR_NOT_EXISTS");
|
|
}
|
|
|
|
function getReserves(address _factory, address tokenA, address tokenB)
|
|
public view returns (uint256 reserveA, uint256 reserveB)
|
|
{
|
|
(address t0, ) = sortTokens(tokenA, tokenB);
|
|
(uint112 r0, uint112 r1, ) = IAereSwapPair(pairFor(_factory, tokenA, tokenB)).getReserves();
|
|
(reserveA, reserveB) = tokenA == t0 ? (uint256(r0), uint256(r1)) : (uint256(r1), uint256(r0));
|
|
}
|
|
|
|
function quote(uint256 amountA, uint256 reserveA, uint256 reserveB) public pure returns (uint256 amountB) {
|
|
require(amountA > 0, "AereRouter: INSUFFICIENT_AMOUNT");
|
|
require(reserveA > 0 && reserveB > 0, "AereRouter: INSUFFICIENT_LIQUIDITY");
|
|
amountB = (amountA * reserveB) / reserveA;
|
|
}
|
|
|
|
// 0.3% fee — matches AereSwapPair.swap() math (1000-3 = 997 numerator).
|
|
function getAmountOut(uint256 amountIn, uint256 reserveIn, uint256 reserveOut) public pure returns (uint256 amountOut) {
|
|
require(amountIn > 0, "AereRouter: INSUFFICIENT_INPUT");
|
|
require(reserveIn > 0 && reserveOut > 0, "AereRouter: INSUFFICIENT_LIQUIDITY");
|
|
uint256 amountInWithFee = amountIn * 997;
|
|
uint256 numerator = amountInWithFee * reserveOut;
|
|
uint256 denominator = reserveIn * 1000 + amountInWithFee;
|
|
amountOut = numerator / denominator;
|
|
}
|
|
|
|
function getAmountIn(uint256 amountOut, uint256 reserveIn, uint256 reserveOut) public pure returns (uint256 amountIn) {
|
|
require(amountOut > 0, "AereRouter: INSUFFICIENT_OUTPUT");
|
|
require(reserveIn > 0 && reserveOut > 0, "AereRouter: INSUFFICIENT_LIQUIDITY");
|
|
uint256 numerator = reserveIn * amountOut * 1000;
|
|
uint256 denominator = (reserveOut - amountOut) * 997;
|
|
amountIn = (numerator / denominator) + 1;
|
|
}
|
|
|
|
function getAmountsOut(address _factory, uint256 amountIn, address[] memory path)
|
|
public view returns (uint256[] memory amounts)
|
|
{
|
|
require(path.length >= 2, "AereRouter: INVALID_PATH");
|
|
amounts = new uint256[](path.length);
|
|
amounts[0] = amountIn;
|
|
for (uint256 i = 0; i < path.length - 1; i++) {
|
|
(uint256 rI, uint256 rO) = getReserves(_factory, path[i], path[i + 1]);
|
|
amounts[i + 1] = getAmountOut(amounts[i], rI, rO);
|
|
}
|
|
}
|
|
|
|
function getAmountsIn(address _factory, uint256 amountOut, address[] memory path)
|
|
public view returns (uint256[] memory amounts)
|
|
{
|
|
require(path.length >= 2, "AereRouter: INVALID_PATH");
|
|
amounts = new uint256[](path.length);
|
|
amounts[amounts.length - 1] = amountOut;
|
|
for (uint256 i = path.length - 1; i > 0; i--) {
|
|
(uint256 rI, uint256 rO) = getReserves(_factory, path[i - 1], path[i]);
|
|
amounts[i - 1] = getAmountIn(amounts[i], rI, rO);
|
|
}
|
|
}
|
|
|
|
// ═════════════════════════════════════════════════════════════════════
|
|
// SAFE TRANSFERS
|
|
// ═════════════════════════════════════════════════════════════════════
|
|
|
|
function _safeTransfer(address token, address to, uint256 value) private {
|
|
(bool ok, bytes memory data) =
|
|
token.call(abi.encodeWithSelector(IERC20.transfer.selector, to, value));
|
|
require(ok && (data.length == 0 || abi.decode(data, (bool))), "AereRouter: TRANSFER_FAILED");
|
|
}
|
|
|
|
function _safeTransferFrom(address token, address from, address to, uint256 value) private {
|
|
(bool ok, bytes memory data) =
|
|
token.call(abi.encodeWithSelector(IERC20.transferFrom.selector, from, to, value));
|
|
require(ok && (data.length == 0 || abi.decode(data, (bool))), "AereRouter: TRANSFER_FROM_FAILED");
|
|
}
|
|
|
|
function _safeTransferAERE(address to, uint256 value) private {
|
|
(bool ok, ) = to.call{ value: value }("");
|
|
require(ok, "AereRouter: AERE_TRANSFER_FAILED");
|
|
}
|
|
}
|