- CCIP/trustless bridge contracts, GRU tokens, DEX/PMM tests, reserve vault. - Token-aggregation service routes, planner, chain config, relay env templates. - Config snapshots and multi-chain deployment markdown updates. - gitignore services/btc-intake/dist/ (tsc output); do not track dist. Run forge build && forge test before deploy (large solc graph). Made-with: Cursor
597 lines
22 KiB
Solidity
597 lines
22 KiB
Solidity
// SPDX-License-Identifier: MIT
|
|
pragma solidity ^0.8.19;
|
|
|
|
import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
|
|
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
|
|
import "../interfaces/IAggregationRouter.sol";
|
|
import "../interfaces/IBalancerVault.sol";
|
|
import "../interfaces/ICurvePool.sol";
|
|
import "../interfaces/ISwapRouter.sol";
|
|
|
|
library Chain138PilotVenueMath {
|
|
function sortTokens(address tokenA, address tokenB) internal pure returns (address token0, address token1) {
|
|
require(tokenA != address(0) && tokenB != address(0) && tokenA != tokenB, "Chain138PilotVenueMath: invalid tokens");
|
|
(token0, token1) = tokenA < tokenB ? (tokenA, tokenB) : (tokenB, tokenA);
|
|
}
|
|
|
|
function pairKey(address tokenA, address tokenB, uint24 fee) internal pure returns (bytes32) {
|
|
(address token0, address token1) = sortTokens(tokenA, tokenB);
|
|
return keccak256(abi.encodePacked(token0, token1, fee));
|
|
}
|
|
|
|
function pairKey(address tokenA, address tokenB) internal pure returns (bytes32) {
|
|
(address token0, address token1) = sortTokens(tokenA, tokenB);
|
|
return keccak256(abi.encodePacked(token0, token1));
|
|
}
|
|
|
|
function constantProductQuote(
|
|
uint256 reserveIn,
|
|
uint256 reserveOut,
|
|
uint256 amountIn,
|
|
uint256 feeBps
|
|
) internal pure returns (uint256 amountOut) {
|
|
if (reserveIn == 0 || reserveOut == 0 || amountIn == 0 || feeBps >= 10_000) {
|
|
return 0;
|
|
}
|
|
uint256 amountInWithFee = amountIn * (10_000 - feeBps);
|
|
return (reserveOut * amountInWithFee) / (reserveIn * 10_000 + amountInWithFee);
|
|
}
|
|
}
|
|
|
|
contract Chain138PilotOwned {
|
|
address public owner;
|
|
|
|
event OwnershipTransferred(address indexed previousOwner, address indexed newOwner);
|
|
|
|
modifier onlyOwner() {
|
|
require(msg.sender == owner, "Chain138PilotOwned: not owner");
|
|
_;
|
|
}
|
|
|
|
constructor() {
|
|
owner = msg.sender;
|
|
emit OwnershipTransferred(address(0), msg.sender);
|
|
}
|
|
|
|
function transferOwnership(address newOwner) external onlyOwner {
|
|
require(newOwner != address(0), "Chain138PilotOwned: zero owner");
|
|
emit OwnershipTransferred(owner, newOwner);
|
|
owner = newOwner;
|
|
}
|
|
}
|
|
|
|
contract Chain138PilotUniswapV3Router is Chain138PilotOwned, ISwapRouter {
|
|
using SafeERC20 for IERC20;
|
|
|
|
struct Pool {
|
|
address token0;
|
|
address token1;
|
|
uint24 fee;
|
|
uint256 reserve0;
|
|
uint256 reserve1;
|
|
bool exists;
|
|
}
|
|
|
|
mapping(bytes32 => Pool) private pools;
|
|
|
|
event PairSeeded(bytes32 indexed poolKey, address indexed token0, address indexed token1, uint24 fee, uint256 amount0, uint256 amount1);
|
|
event PairFunded(bytes32 indexed poolKey, uint256 amount0, uint256 amount1);
|
|
event Swapped(bytes32 indexed poolKey, address indexed tokenIn, address indexed tokenOut, uint256 amountIn, uint256 amountOut);
|
|
|
|
function seedPair(
|
|
address tokenA,
|
|
address tokenB,
|
|
uint24 fee,
|
|
uint256 amountA,
|
|
uint256 amountB
|
|
) external onlyOwner returns (bytes32 poolKey) {
|
|
require(amountA > 0 && amountB > 0, "Chain138PilotUniswapV3Router: zero seed");
|
|
poolKey = Chain138PilotVenueMath.pairKey(tokenA, tokenB, fee);
|
|
Pool storage pool = pools[poolKey];
|
|
(address token0, address token1) = Chain138PilotVenueMath.sortTokens(tokenA, tokenB);
|
|
|
|
if (!pool.exists) {
|
|
pool.token0 = token0;
|
|
pool.token1 = token1;
|
|
pool.fee = fee;
|
|
pool.exists = true;
|
|
emit PairSeeded(
|
|
poolKey,
|
|
token0,
|
|
token1,
|
|
fee,
|
|
tokenA == token0 ? amountA : amountB,
|
|
tokenA == token0 ? amountB : amountA
|
|
);
|
|
}
|
|
|
|
_fundPool(poolKey, tokenA, tokenB, amountA, amountB);
|
|
}
|
|
|
|
function fundPair(
|
|
address tokenA,
|
|
address tokenB,
|
|
uint24 fee,
|
|
uint256 amountA,
|
|
uint256 amountB
|
|
) external onlyOwner {
|
|
bytes32 poolKey = Chain138PilotVenueMath.pairKey(tokenA, tokenB, fee);
|
|
require(pools[poolKey].exists, "Chain138PilotUniswapV3Router: pair missing");
|
|
_fundPool(poolKey, tokenA, tokenB, amountA, amountB);
|
|
}
|
|
|
|
function getPairReserves(
|
|
address tokenA,
|
|
address tokenB,
|
|
uint24 fee
|
|
) external view returns (uint256 reserveIn, uint256 reserveOut, bool exists) {
|
|
bytes32 poolKey = Chain138PilotVenueMath.pairKey(tokenA, tokenB, fee);
|
|
Pool storage pool = pools[poolKey];
|
|
if (!pool.exists) {
|
|
return (0, 0, false);
|
|
}
|
|
if (tokenA == pool.token0) {
|
|
return (pool.reserve0, pool.reserve1, true);
|
|
}
|
|
return (pool.reserve1, pool.reserve0, true);
|
|
}
|
|
|
|
function quoteExactInputSingle(
|
|
address tokenIn,
|
|
address tokenOut,
|
|
uint24 fee,
|
|
uint256 amountIn,
|
|
uint160
|
|
) external view returns (uint256 amountOut) {
|
|
(uint256 reserveIn, uint256 reserveOut, bool exists) = this.getPairReserves(tokenIn, tokenOut, fee);
|
|
require(exists, "Chain138PilotUniswapV3Router: pair missing");
|
|
return Chain138PilotVenueMath.constantProductQuote(reserveIn, reserveOut, amountIn, fee / 100);
|
|
}
|
|
|
|
function quoteExactInput(bytes calldata path, uint256 amountIn) external view returns (uint256 amountOut) {
|
|
(address tokenIn, address tokenOut, uint24 fee) = _decodeSingleHopPath(path);
|
|
return this.quoteExactInputSingle(tokenIn, tokenOut, fee, amountIn, 0);
|
|
}
|
|
|
|
function exactInputSingle(
|
|
ExactInputSingleParams calldata params
|
|
) external payable override returns (uint256 amountOut) {
|
|
amountOut = _swap(
|
|
params.tokenIn,
|
|
params.tokenOut,
|
|
params.fee,
|
|
params.amountIn,
|
|
params.amountOutMinimum,
|
|
params.recipient
|
|
);
|
|
}
|
|
|
|
function exactInput(
|
|
ExactInputParams calldata params
|
|
) external payable override returns (uint256 amountOut) {
|
|
(address tokenIn, address tokenOut, uint24 fee) = _decodeSingleHopPath(params.path);
|
|
amountOut = _swap(
|
|
tokenIn,
|
|
tokenOut,
|
|
fee,
|
|
params.amountIn,
|
|
params.amountOutMinimum,
|
|
params.recipient
|
|
);
|
|
}
|
|
|
|
function _fundPool(
|
|
bytes32 poolKey,
|
|
address tokenA,
|
|
address tokenB,
|
|
uint256 amountA,
|
|
uint256 amountB
|
|
) internal {
|
|
Pool storage pool = pools[poolKey];
|
|
IERC20(tokenA).safeTransferFrom(msg.sender, address(this), amountA);
|
|
IERC20(tokenB).safeTransferFrom(msg.sender, address(this), amountB);
|
|
|
|
if (tokenA == pool.token0) {
|
|
pool.reserve0 += amountA;
|
|
pool.reserve1 += amountB;
|
|
emit PairFunded(poolKey, amountA, amountB);
|
|
} else {
|
|
pool.reserve0 += amountB;
|
|
pool.reserve1 += amountA;
|
|
emit PairFunded(poolKey, amountB, amountA);
|
|
}
|
|
}
|
|
|
|
function _swap(
|
|
address tokenIn,
|
|
address tokenOut,
|
|
uint24 fee,
|
|
uint256 amountIn,
|
|
uint256 minAmountOut,
|
|
address recipient
|
|
) internal returns (uint256 amountOut) {
|
|
bytes32 poolKey = Chain138PilotVenueMath.pairKey(tokenIn, tokenOut, fee);
|
|
Pool storage pool = pools[poolKey];
|
|
require(pool.exists, "Chain138PilotUniswapV3Router: pair missing");
|
|
|
|
bool inputIsToken0 = tokenIn == pool.token0;
|
|
uint256 reserveIn = inputIsToken0 ? pool.reserve0 : pool.reserve1;
|
|
uint256 reserveOut = inputIsToken0 ? pool.reserve1 : pool.reserve0;
|
|
|
|
IERC20(tokenIn).safeTransferFrom(msg.sender, address(this), amountIn);
|
|
amountOut = Chain138PilotVenueMath.constantProductQuote(reserveIn, reserveOut, amountIn, fee / 100);
|
|
require(amountOut >= minAmountOut, "Chain138PilotUniswapV3Router: insufficient output");
|
|
|
|
if (inputIsToken0) {
|
|
pool.reserve0 += amountIn;
|
|
pool.reserve1 -= amountOut;
|
|
} else {
|
|
pool.reserve1 += amountIn;
|
|
pool.reserve0 -= amountOut;
|
|
}
|
|
|
|
IERC20(tokenOut).safeTransfer(recipient, amountOut);
|
|
emit Swapped(poolKey, tokenIn, tokenOut, amountIn, amountOut);
|
|
}
|
|
|
|
function _decodeSingleHopPath(bytes memory path) internal pure returns (address tokenIn, address tokenOut, uint24 fee) {
|
|
require(path.length == 43, "Chain138PilotUniswapV3Router: single hop only");
|
|
assembly {
|
|
tokenIn := shr(96, mload(add(path, 32)))
|
|
fee := shr(232, mload(add(path, 52)))
|
|
tokenOut := shr(96, mload(add(path, 55)))
|
|
}
|
|
}
|
|
}
|
|
|
|
contract Chain138PilotBalancerVault is Chain138PilotOwned, IBalancerVault {
|
|
using SafeERC20 for IERC20;
|
|
|
|
struct Pool {
|
|
address token0;
|
|
address token1;
|
|
uint256 reserve0;
|
|
uint256 reserve1;
|
|
uint256 feeBps;
|
|
bool exists;
|
|
}
|
|
|
|
mapping(bytes32 => Pool) private pools;
|
|
|
|
event PoolSeeded(bytes32 indexed poolId, address indexed token0, address indexed token1, uint256 amount0, uint256 amount1, uint256 feeBps);
|
|
event PoolFunded(bytes32 indexed poolId, uint256 amount0, uint256 amount1);
|
|
event Swapped(bytes32 indexed poolId, address indexed tokenIn, address indexed tokenOut, uint256 amountIn, uint256 amountOut);
|
|
|
|
function seedPool(
|
|
bytes32 poolId,
|
|
address token0,
|
|
address token1,
|
|
uint256 amount0,
|
|
uint256 amount1,
|
|
uint256 feeBps
|
|
) external onlyOwner {
|
|
require(poolId != bytes32(0), "Chain138PilotBalancerVault: zero poolId");
|
|
require(amount0 > 0 && amount1 > 0, "Chain138PilotBalancerVault: zero seed");
|
|
Pool storage pool = pools[poolId];
|
|
if (!pool.exists) {
|
|
pool.token0 = token0;
|
|
pool.token1 = token1;
|
|
pool.feeBps = feeBps;
|
|
pool.exists = true;
|
|
emit PoolSeeded(poolId, token0, token1, amount0, amount1, feeBps);
|
|
}
|
|
_fund(poolId, amount0, amount1);
|
|
}
|
|
|
|
function fundPool(bytes32 poolId, uint256 amount0, uint256 amount1) external onlyOwner {
|
|
require(pools[poolId].exists, "Chain138PilotBalancerVault: pool missing");
|
|
_fund(poolId, amount0, amount1);
|
|
}
|
|
|
|
function swap(
|
|
SingleSwap memory singleSwap,
|
|
FundManagement memory funds,
|
|
uint256 limit,
|
|
uint256 deadline
|
|
) external payable override returns (uint256 amountCalculated) {
|
|
require(deadline >= block.timestamp, "Chain138PilotBalancerVault: expired");
|
|
Pool storage pool = pools[singleSwap.poolId];
|
|
require(pool.exists, "Chain138PilotBalancerVault: pool missing");
|
|
require(singleSwap.kind == SwapKind.GIVEN_IN, "Chain138PilotBalancerVault: GIVEN_IN only");
|
|
|
|
bool inputIsToken0;
|
|
if (singleSwap.assetIn == pool.token0 && singleSwap.assetOut == pool.token1) {
|
|
inputIsToken0 = true;
|
|
} else if (singleSwap.assetIn == pool.token1 && singleSwap.assetOut == pool.token0) {
|
|
inputIsToken0 = false;
|
|
} else {
|
|
revert("Chain138PilotBalancerVault: bad assets");
|
|
}
|
|
|
|
uint256 reserveIn = inputIsToken0 ? pool.reserve0 : pool.reserve1;
|
|
uint256 reserveOut = inputIsToken0 ? pool.reserve1 : pool.reserve0;
|
|
IERC20(singleSwap.assetIn).safeTransferFrom(funds.sender, address(this), singleSwap.amount);
|
|
amountCalculated = Chain138PilotVenueMath.constantProductQuote(reserveIn, reserveOut, singleSwap.amount, pool.feeBps);
|
|
require(amountCalculated >= limit, "Chain138PilotBalancerVault: below limit");
|
|
|
|
if (inputIsToken0) {
|
|
pool.reserve0 += singleSwap.amount;
|
|
pool.reserve1 -= amountCalculated;
|
|
} else {
|
|
pool.reserve1 += singleSwap.amount;
|
|
pool.reserve0 -= amountCalculated;
|
|
}
|
|
|
|
IERC20(singleSwap.assetOut).safeTransfer(funds.recipient, amountCalculated);
|
|
emit Swapped(singleSwap.poolId, singleSwap.assetIn, singleSwap.assetOut, singleSwap.amount, amountCalculated);
|
|
}
|
|
|
|
function getPool(bytes32 poolId) external view override returns (address poolAddress, uint8 specialization) {
|
|
require(pools[poolId].exists, "Chain138PilotBalancerVault: pool missing");
|
|
return (address(this), 0);
|
|
}
|
|
|
|
function queryBatchSwap(
|
|
SwapKind kind,
|
|
SingleSwap[] memory swaps,
|
|
address[] memory
|
|
) external view override returns (int256[] memory assetDeltas) {
|
|
require(kind == SwapKind.GIVEN_IN, "Chain138PilotBalancerVault: GIVEN_IN only");
|
|
assetDeltas = new int256[](swaps.length > 0 ? 2 : 0);
|
|
if (swaps.length == 0) {
|
|
return assetDeltas;
|
|
}
|
|
Pool storage pool = pools[swaps[0].poolId];
|
|
bool inputIsToken0 = swaps[0].assetIn == pool.token0;
|
|
uint256 reserveIn = inputIsToken0 ? pool.reserve0 : pool.reserve1;
|
|
uint256 reserveOut = inputIsToken0 ? pool.reserve1 : pool.reserve0;
|
|
uint256 amountOut = Chain138PilotVenueMath.constantProductQuote(reserveIn, reserveOut, swaps[0].amount, pool.feeBps);
|
|
assetDeltas[0] = int256(swaps[0].amount);
|
|
assetDeltas[1] = -int256(amountOut);
|
|
}
|
|
|
|
function getPoolTokens(
|
|
bytes32 poolId
|
|
) external view override returns (address[] memory tokens, uint256[] memory balances, uint256 lastChangeBlock) {
|
|
Pool storage pool = pools[poolId];
|
|
require(pool.exists, "Chain138PilotBalancerVault: pool missing");
|
|
tokens = new address[](2);
|
|
balances = new uint256[](2);
|
|
tokens[0] = pool.token0;
|
|
tokens[1] = pool.token1;
|
|
balances[0] = pool.reserve0;
|
|
balances[1] = pool.reserve1;
|
|
return (tokens, balances, block.number);
|
|
}
|
|
|
|
function _fund(bytes32 poolId, uint256 amount0, uint256 amount1) internal {
|
|
Pool storage pool = pools[poolId];
|
|
IERC20(pool.token0).safeTransferFrom(msg.sender, address(this), amount0);
|
|
IERC20(pool.token1).safeTransferFrom(msg.sender, address(this), amount1);
|
|
pool.reserve0 += amount0;
|
|
pool.reserve1 += amount1;
|
|
emit PoolFunded(poolId, amount0, amount1);
|
|
}
|
|
}
|
|
|
|
contract Chain138PilotCurve3Pool is Chain138PilotOwned, ICurvePool {
|
|
using SafeERC20 for IERC20;
|
|
|
|
address[3] private _coins;
|
|
uint256[3] private _reserves;
|
|
uint256 public immutable feeBps;
|
|
|
|
event Funded(uint256 amount0, uint256 amount1, uint256 amount2);
|
|
event Exchanged(int128 indexed i, int128 indexed j, uint256 dx, uint256 dy);
|
|
|
|
constructor(address token0, address token1, address token2, uint256 feeBps_) {
|
|
require(token0 != address(0) && token1 != address(0), "Chain138PilotCurve3Pool: invalid tokens");
|
|
_coins[0] = token0;
|
|
_coins[1] = token1;
|
|
_coins[2] = token2;
|
|
feeBps = feeBps_;
|
|
}
|
|
|
|
function fund(uint256 amount0, uint256 amount1, uint256 amount2) external onlyOwner {
|
|
if (amount0 > 0) IERC20(_coins[0]).safeTransferFrom(msg.sender, address(this), amount0);
|
|
if (amount1 > 0) IERC20(_coins[1]).safeTransferFrom(msg.sender, address(this), amount1);
|
|
if (_coins[2] != address(0) && amount2 > 0) IERC20(_coins[2]).safeTransferFrom(msg.sender, address(this), amount2);
|
|
_reserves[0] += amount0;
|
|
_reserves[1] += amount1;
|
|
_reserves[2] += amount2;
|
|
emit Funded(amount0, amount1, amount2);
|
|
}
|
|
|
|
function reserves(uint256 index) external view returns (uint256) {
|
|
return _reserves[index];
|
|
}
|
|
|
|
function exchange(
|
|
int128 i,
|
|
int128 j,
|
|
uint256 dx,
|
|
uint256 min_dy
|
|
) external payable override returns (uint256 dy) {
|
|
dy = _exchange(i, j, dx, min_dy, false);
|
|
}
|
|
|
|
function exchange_underlying(
|
|
int128 i,
|
|
int128 j,
|
|
uint256 dx,
|
|
uint256 min_dy
|
|
) external payable override returns (uint256 dy) {
|
|
dy = _exchange(i, j, dx, min_dy, true);
|
|
}
|
|
|
|
function get_dy(
|
|
int128 i,
|
|
int128 j,
|
|
uint256 dx
|
|
) public view override returns (uint256 dy) {
|
|
uint256 ui = uint256(uint128(i));
|
|
uint256 uj = uint256(uint128(j));
|
|
require(ui < 3 && uj < 3 && ui != uj, "Chain138PilotCurve3Pool: bad indexes");
|
|
require(_coins[ui] != address(0) && _coins[uj] != address(0), "Chain138PilotCurve3Pool: token missing");
|
|
|
|
uint256 reserveIn = _reserves[ui];
|
|
uint256 reserveOut = _reserves[uj];
|
|
require(reserveIn > 0 && reserveOut > 0, "Chain138PilotCurve3Pool: empty reserves");
|
|
|
|
uint256 grossOut = (dx * reserveOut) / reserveIn;
|
|
dy = (grossOut * (10_000 - feeBps)) / 10_000;
|
|
if (dy > reserveOut) {
|
|
dy = reserveOut;
|
|
}
|
|
}
|
|
|
|
function coins(uint256 i) external view override returns (address) {
|
|
return _coins[i];
|
|
}
|
|
|
|
function _exchange(
|
|
int128 i,
|
|
int128 j,
|
|
uint256 dx,
|
|
uint256 min_dy,
|
|
bool
|
|
) internal returns (uint256 dy) {
|
|
uint256 ui = uint256(uint128(i));
|
|
uint256 uj = uint256(uint128(j));
|
|
dy = get_dy(i, j, dx);
|
|
require(dy >= min_dy, "Chain138PilotCurve3Pool: insufficient output");
|
|
|
|
IERC20(_coins[ui]).safeTransferFrom(msg.sender, address(this), dx);
|
|
_reserves[ui] += dx;
|
|
_reserves[uj] -= dy;
|
|
IERC20(_coins[uj]).safeTransfer(msg.sender, dy);
|
|
emit Exchanged(i, j, dx, dy);
|
|
}
|
|
}
|
|
|
|
contract Chain138PilotOneInchAggregationRouter is Chain138PilotOwned, IAggregationRouter {
|
|
using SafeERC20 for IERC20;
|
|
|
|
struct Route {
|
|
address token0;
|
|
address token1;
|
|
uint256 reserve0;
|
|
uint256 reserve1;
|
|
uint256 feeBps;
|
|
bool exists;
|
|
}
|
|
|
|
mapping(bytes32 => Route) private routes;
|
|
|
|
event RouteSeeded(address indexed token0, address indexed token1, uint256 amount0, uint256 amount1, uint256 feeBps);
|
|
event RouteFunded(address indexed token0, address indexed token1, uint256 amount0, uint256 amount1);
|
|
event Swapped(address indexed tokenIn, address indexed tokenOut, uint256 amountIn, uint256 amountOut, address indexed recipient);
|
|
|
|
function seedRoute(
|
|
address tokenA,
|
|
address tokenB,
|
|
uint256 amountA,
|
|
uint256 amountB,
|
|
uint256 feeBps
|
|
) external onlyOwner {
|
|
require(amountA > 0 && amountB > 0, "Chain138PilotOneInchAggregationRouter: zero seed");
|
|
bytes32 key = Chain138PilotVenueMath.pairKey(tokenA, tokenB);
|
|
Route storage route = routes[key];
|
|
(address token0, address token1) = Chain138PilotVenueMath.sortTokens(tokenA, tokenB);
|
|
if (!route.exists) {
|
|
route.token0 = token0;
|
|
route.token1 = token1;
|
|
route.feeBps = feeBps;
|
|
route.exists = true;
|
|
emit RouteSeeded(
|
|
token0,
|
|
token1,
|
|
tokenA == token0 ? amountA : amountB,
|
|
tokenA == token0 ? amountB : amountA,
|
|
feeBps
|
|
);
|
|
}
|
|
_fundRoute(key, tokenA, tokenB, amountA, amountB);
|
|
}
|
|
|
|
function fundRoute(address tokenA, address tokenB, uint256 amountA, uint256 amountB) external onlyOwner {
|
|
bytes32 key = Chain138PilotVenueMath.pairKey(tokenA, tokenB);
|
|
require(routes[key].exists, "Chain138PilotOneInchAggregationRouter: route missing");
|
|
_fundRoute(key, tokenA, tokenB, amountA, amountB);
|
|
}
|
|
|
|
function getRouteReserves(
|
|
address tokenA,
|
|
address tokenB
|
|
) external view returns (uint256 reserveIn, uint256 reserveOut, bool exists) {
|
|
bytes32 key = Chain138PilotVenueMath.pairKey(tokenA, tokenB);
|
|
Route storage route = routes[key];
|
|
if (!route.exists) {
|
|
return (0, 0, false);
|
|
}
|
|
if (tokenA == route.token0) {
|
|
return (route.reserve0, route.reserve1, true);
|
|
}
|
|
return (route.reserve1, route.reserve0, true);
|
|
}
|
|
|
|
function quote(address tokenIn, address tokenOut, uint256 amountIn) external view returns (uint256 amountOut) {
|
|
(uint256 reserveIn, uint256 reserveOut, bool exists) = this.getRouteReserves(tokenIn, tokenOut);
|
|
require(exists, "Chain138PilotOneInchAggregationRouter: route missing");
|
|
return Chain138PilotVenueMath.constantProductQuote(reserveIn, reserveOut, amountIn, routes[Chain138PilotVenueMath.pairKey(tokenIn, tokenOut)].feeBps);
|
|
}
|
|
|
|
function swap(
|
|
address,
|
|
SwapDescription calldata desc,
|
|
bytes calldata,
|
|
bytes calldata
|
|
) external payable override returns (uint256 returnAmount, uint256 spentAmount) {
|
|
bytes32 key = Chain138PilotVenueMath.pairKey(desc.srcToken, desc.dstToken);
|
|
Route storage route = routes[key];
|
|
require(route.exists, "Chain138PilotOneInchAggregationRouter: route missing");
|
|
|
|
bool inputIsToken0 = desc.srcToken == route.token0;
|
|
uint256 reserveIn = inputIsToken0 ? route.reserve0 : route.reserve1;
|
|
uint256 reserveOut = inputIsToken0 ? route.reserve1 : route.reserve0;
|
|
|
|
IERC20(desc.srcToken).safeTransferFrom(msg.sender, address(this), desc.amount);
|
|
returnAmount = Chain138PilotVenueMath.constantProductQuote(reserveIn, reserveOut, desc.amount, route.feeBps);
|
|
require(returnAmount >= desc.minReturnAmount, "Chain138PilotOneInchAggregationRouter: insufficient output");
|
|
|
|
if (inputIsToken0) {
|
|
route.reserve0 += desc.amount;
|
|
route.reserve1 -= returnAmount;
|
|
} else {
|
|
route.reserve1 += desc.amount;
|
|
route.reserve0 -= returnAmount;
|
|
}
|
|
|
|
IERC20(desc.dstToken).safeTransfer(desc.dstReceiver, returnAmount);
|
|
emit Swapped(desc.srcToken, desc.dstToken, desc.amount, returnAmount, desc.dstReceiver);
|
|
return (returnAmount, desc.amount);
|
|
}
|
|
|
|
function _fundRoute(
|
|
bytes32 key,
|
|
address tokenA,
|
|
address tokenB,
|
|
uint256 amountA,
|
|
uint256 amountB
|
|
) internal {
|
|
Route storage route = routes[key];
|
|
IERC20(tokenA).safeTransferFrom(msg.sender, address(this), amountA);
|
|
IERC20(tokenB).safeTransferFrom(msg.sender, address(this), amountB);
|
|
|
|
if (tokenA == route.token0) {
|
|
route.reserve0 += amountA;
|
|
route.reserve1 += amountB;
|
|
emit RouteFunded(route.token0, route.token1, amountA, amountB);
|
|
} else {
|
|
route.reserve0 += amountB;
|
|
route.reserve1 += amountA;
|
|
emit RouteFunded(route.token0, route.token1, amountB, amountA);
|
|
}
|
|
}
|
|
}
|