251 lines
9.6 KiB
Solidity
251 lines
9.6 KiB
Solidity
pragma solidity 0.5.12;
|
|
|
|
import "./interfaces/IUniswapV2.sol";
|
|
|
|
import "./libraries/Math.sol";
|
|
import "./libraries/SafeMath128.sol";
|
|
import "./libraries/UQ104x104.sol";
|
|
|
|
import "./token/ERC20.sol";
|
|
import "./token/SafeTransfer.sol";
|
|
|
|
contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0), SafeTransfer {
|
|
using SafeMath128 for uint128;
|
|
using SafeMath256 for uint256;
|
|
using UQ104x104 for uint240;
|
|
|
|
struct TokenData {
|
|
uint128 token0;
|
|
uint128 token1;
|
|
}
|
|
|
|
struct OracleData {
|
|
uint240 priceAccumulated;
|
|
uint16 blockNumberHalf;
|
|
}
|
|
|
|
address public factory;
|
|
address public token0;
|
|
address public token1;
|
|
|
|
TokenData private reserves;
|
|
|
|
OracleData private oracleDataToken0;
|
|
OracleData private oracleDataToken1;
|
|
|
|
bool private notEntered = true;
|
|
modifier lock() {
|
|
require(notEntered, "UniswapV2: LOCKED");
|
|
notEntered = false;
|
|
_;
|
|
notEntered = true;
|
|
}
|
|
|
|
event LiquidityMinted(
|
|
address indexed sender,
|
|
address indexed recipient,
|
|
uint128 amountToken0,
|
|
uint128 amountToken1,
|
|
uint128 reserveToken0,
|
|
uint128 reserveToken1,
|
|
uint256 liquidity
|
|
);
|
|
event LiquidityBurned(
|
|
address indexed sender,
|
|
address indexed recipient,
|
|
uint128 amountToken0,
|
|
uint128 amountToken1,
|
|
uint128 reserveToken0,
|
|
uint128 reserveToken1,
|
|
uint256 liquidity
|
|
);
|
|
event Swap(
|
|
address indexed sender,
|
|
address indexed recipient,
|
|
uint128 amountToken0,
|
|
uint128 amountToken1,
|
|
uint128 reserveToken0,
|
|
uint128 reserveToken1,
|
|
address input
|
|
);
|
|
|
|
|
|
constructor() public {
|
|
factory = msg.sender;
|
|
}
|
|
|
|
function initialize(address _token0, address _token1) external {
|
|
require(token0 == address(0) && token1 == address(0), 'UniswapV2: ALREADY_INITIALIZED');
|
|
token0 = _token0;
|
|
token1 = _token1;
|
|
}
|
|
|
|
function getReserves() external view returns (uint128, uint128) {
|
|
return (reserves.token0, reserves.token1);
|
|
}
|
|
|
|
function readOraclePricesAccumulated() external view returns (uint240, uint240) {
|
|
return (oracleDataToken0.priceAccumulated, oracleDataToken1.priceAccumulated);
|
|
}
|
|
|
|
function readOracleBlockNumber() public view returns (uint32) {
|
|
return (uint32(oracleDataToken0.blockNumberHalf) << 16) + oracleDataToken1.blockNumberHalf;
|
|
}
|
|
|
|
function consultOracle() external view returns (uint240, uint240) {
|
|
uint32 blockNumberLast = readOracleBlockNumber();
|
|
|
|
require(reserves.token0 != 0 && reserves.token1 != 0, "UniswapV2: NO_LIQUIDITY");
|
|
|
|
// replicate the logic in update
|
|
if (block.number > blockNumberLast) {
|
|
uint240 priceToken0 = UQ104x104.encode(reserves.token0).qdiv(reserves.token1);
|
|
uint240 priceToken1 = UQ104x104.encode(reserves.token1).qdiv(reserves.token0);
|
|
|
|
uint32 blocksElapsed = block.number.downcast32() - blockNumberLast;
|
|
return (
|
|
oracleDataToken0.priceAccumulated + priceToken0 * blocksElapsed,
|
|
oracleDataToken1.priceAccumulated + priceToken1 * blocksElapsed
|
|
);
|
|
} else {
|
|
return (
|
|
oracleDataToken0.priceAccumulated,
|
|
oracleDataToken1.priceAccumulated
|
|
);
|
|
}
|
|
}
|
|
|
|
|
|
function getAmountOutput(uint128 amountInput, uint128 reserveInput, uint128 reserveOutput)
|
|
public pure returns (uint128 amountOutput)
|
|
{
|
|
require(amountInput > 0 && reserveInput > 0 && reserveOutput > 0, "UniswapV2: INVALID_VALUE");
|
|
uint256 amountInputWithFee = uint256(amountInput).mul(997);
|
|
uint256 numerator = amountInputWithFee.mul(reserveOutput);
|
|
uint256 denominator = uint256(reserveInput).mul(1000).add(amountInputWithFee);
|
|
amountOutput = (numerator / denominator).downcast128();
|
|
}
|
|
|
|
function update(TokenData memory balances) private {
|
|
uint32 blockNumberLast = readOracleBlockNumber();
|
|
|
|
// if any blocks have gone by since the last time this function was called, we have to update
|
|
if (block.number > blockNumberLast) {
|
|
// we have to ensure that neither reserves are 0, else our price division fails
|
|
if (reserves.token0 != 0 && reserves.token1 != 0) {
|
|
// get the prices according to the reserves as of the last official interaction with the contract
|
|
uint240 priceToken0 = UQ104x104.encode(reserves.token0).qdiv(reserves.token1);
|
|
uint240 priceToken1 = UQ104x104.encode(reserves.token1).qdiv(reserves.token0);
|
|
|
|
// multiply these prices by the number of elapsed blocks and add to the accumulators
|
|
uint32 blocksElapsed = block.number.downcast32() - blockNumberLast;
|
|
oracleDataToken0.priceAccumulated += priceToken0 * blocksElapsed;
|
|
oracleDataToken1.priceAccumulated += priceToken1 * blocksElapsed;
|
|
}
|
|
|
|
// update the last block number
|
|
oracleDataToken0.blockNumberHalf = uint16(block.number >> 16);
|
|
oracleDataToken1.blockNumberHalf = uint16(block.number);
|
|
}
|
|
|
|
// update reserves
|
|
reserves = balances;
|
|
}
|
|
|
|
function mintLiquidity(address recipient) external lock returns (uint256 liquidity) {
|
|
TokenData memory balances = TokenData({
|
|
token0: IERC20(token0).balanceOf(address(this)).downcast128(),
|
|
token1: IERC20(token1).balanceOf(address(this)).downcast128()
|
|
});
|
|
TokenData memory amounts = TokenData({
|
|
token0: balances.token0.sub(reserves.token0),
|
|
token1: balances.token1.sub(reserves.token1)
|
|
});
|
|
|
|
if (totalSupply == 0) {
|
|
liquidity = Math.sqrt(uint256(amounts.token0).mul(amounts.token1));
|
|
} else {
|
|
liquidity = Math.min(
|
|
uint256(amounts.token0).mul(totalSupply) / reserves.token0,
|
|
uint256(amounts.token1).mul(totalSupply) / reserves.token1
|
|
);
|
|
}
|
|
|
|
if (liquidity > 0) mint(recipient, liquidity);
|
|
update(balances);
|
|
emit LiquidityMinted(
|
|
msg.sender, recipient, amounts.token0, amounts.token1, balances.token0, balances.token1, liquidity
|
|
);
|
|
}
|
|
|
|
function burnLiquidity(address recipient) external lock returns (uint128 amountToken0, uint128 amountToken1) {
|
|
uint256 liquidity = balanceOf[address(this)];
|
|
TokenData memory amounts = TokenData({
|
|
token0: amountToken0 = (liquidity.mul(reserves.token0) / totalSupply).downcast128(),
|
|
token1: amountToken1 = (liquidity.mul(reserves.token1) / totalSupply).downcast128()
|
|
});
|
|
if (amounts.token0 > 0) safeTransfer(token0, recipient, amounts.token0);
|
|
if (amounts.token1 > 0) safeTransfer(token1, recipient, amounts.token1);
|
|
if (liquidity > 0) _burn(address(this), liquidity);
|
|
|
|
TokenData memory balances = TokenData({
|
|
token0: IERC20(token0).balanceOf(address(this)).downcast128(),
|
|
token1: IERC20(token1).balanceOf(address(this)).downcast128()
|
|
});
|
|
update(balances);
|
|
emit LiquidityBurned(
|
|
msg.sender, recipient, amounts.token0, amounts.token1, balances.token0, balances.token1, liquidity
|
|
);
|
|
}
|
|
|
|
function rageQuit(address output, address recipient) external lock returns (uint128 amountOutput) {
|
|
uint256 liquidity = balanceOf[address(this)];
|
|
TokenData memory amounts;
|
|
|
|
if (output == token0) {
|
|
amounts.token0 = amountOutput = (liquidity.mul(reserves.token0) / totalSupply).downcast128();
|
|
safeTransfer(token0, recipient, amounts.token0);
|
|
} else {
|
|
require(output == token1, "UniswapV2: INVALID_OUTPUT");
|
|
amounts.token1 = amountOutput = (liquidity.mul(reserves.token1) / totalSupply).downcast128();
|
|
safeTransfer(token1, recipient, amounts.token1);
|
|
}
|
|
|
|
if (liquidity > 0) _burn(address(this), liquidity);
|
|
|
|
TokenData memory balances = TokenData({
|
|
token0: IERC20(token0).balanceOf(address(this)).downcast128(),
|
|
token1: IERC20(token1).balanceOf(address(this)).downcast128()
|
|
});
|
|
update(balances);
|
|
emit LiquidityBurned(
|
|
msg.sender, recipient, amounts.token0, amounts.token1, balances.token0, balances.token1, liquidity
|
|
);
|
|
}
|
|
|
|
function swap(address input, address recipient) external lock returns (uint128 amountOutput) {
|
|
TokenData memory balances;
|
|
TokenData memory amounts;
|
|
|
|
if (input == token0) {
|
|
balances.token0 = IERC20(input).balanceOf(address(this)).downcast128();
|
|
amounts.token0 = balances.token0.sub(reserves.token0);
|
|
amounts.token1 = amountOutput = getAmountOutput(amounts.token0, reserves.token0, reserves.token1);
|
|
safeTransfer(token1, recipient, amounts.token1);
|
|
balances.token1 = IERC20(token1).balanceOf(address(this)).downcast128();
|
|
} else {
|
|
require(input == token1, "UniswapV2: INVALID_INPUT");
|
|
balances.token1 = IERC20(input).balanceOf(address(this)).downcast128();
|
|
amounts.token1 = balances.token1.sub(reserves.token1);
|
|
amounts.token0 = amountOutput = getAmountOutput(amounts.token1, reserves.token1, reserves.token0);
|
|
safeTransfer(token0, recipient, amounts.token0);
|
|
balances.token0 = IERC20(token0).balanceOf(address(this)).downcast128();
|
|
}
|
|
|
|
update(balances);
|
|
emit Swap(
|
|
msg.sender, recipient, amounts.token0, amounts.token1, balances.token0, balances.token1, input
|
|
);
|
|
}
|
|
}
|