Files
uniswap-v2/contracts/UniswapV2.sol
Noah Zinsmeister da6ba92d92 clean up names and implementation of oracle stuff
update UQ104.104 copy

make safetransfer much clearer
2019-11-26 13:42:55 -05:00

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
);
}
}