Files
uniswap-v2/contracts/UniswapV2.sol
Noah Zinsmeister 7fbf2244e9 add uniswapv2 interface
make bytecode in factory public

update tests
2019-10-25 16:37:17 -04:00

191 lines
6.8 KiB
Solidity

// TODO overflow counter, review, fee
pragma solidity 0.5.12;
import "./interfaces/IUniswapV2.sol";
import "./interfaces/IERC20.sol";
import "./libraries/Math.sol";
import "./libraries/SafeMath.sol";
import "./token/ERC20.sol";
contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) {
using Math for uint256;
using SafeMath for uint256;
event Swap(
address indexed input,
address indexed sender,
address indexed recipient,
uint256 amountInput,
uint256 amountOutput
);
event LiquidityMinted(
address indexed sender,
address indexed recipient,
uint256 amountToken0,
uint256 amountToken1
);
event LiquidityBurned(
address indexed sender,
address indexed recipient,
uint256 amountToken0,
uint256 amountToken1
);
struct TokenData {
uint128 reserve;
uint128 accumulator;
}
struct LastUpdate {
uint64 blockNumber;
uint64 blockTimestamp; // overflows about 280 billion years after the earth's sun explodes
}
bool public initialized;
bool private locked;
address public factory;
address public token0;
address public token1;
mapping (address => TokenData) private tokenData;
LastUpdate private lastUpdate;
modifier lock() {
require(!locked, "UniswapV2: LOCKED");
locked = true;
_;
locked = false;
}
constructor() public {
factory = msg.sender;
}
function initialize(address _token0, address _token1) public {
require(!initialized, "UniswapV2: ALREADY_INITIALIZED");
initialized = true;
token0 = _token0;
token1 = _token1;
}
function updateData(uint256 balanceToken0, uint256 balanceToken1) private {
require(balanceToken0 <= uint128(-1) && balanceToken1 <= uint128(-1), "UniswapV2: OVERFLOW");
require(block.number <= uint64(-1), "UniswapV2: BLOCK_NUMBER_TOO_HIGH");
uint64 blocksElapsed = uint64(block.number) - lastUpdate.blockNumber;
// get token data
TokenData storage tokenDataToken0 = tokenData[token0];
TokenData storage tokenDataToken1 = tokenData[token1];
// TODO does this have a gas impact because it unnecessarily triggers for the 2nd+ trades within a block?
// update accumulators
tokenDataToken0.accumulator += tokenDataToken0.reserve * blocksElapsed;
tokenDataToken1.accumulator += tokenDataToken1.reserve * blocksElapsed;
// update reserves
tokenDataToken0.reserve = uint128(balanceToken0);
tokenDataToken1.reserve = uint128(balanceToken1);
if (blocksElapsed > 0) {
require(block.timestamp <= uint64(-1), "UniswapV2: BLOCK_TIMESTAMP_TOO_HIGH");
lastUpdate.blockNumber = uint64(block.number);
lastUpdate.blockTimestamp = uint64(block.timestamp);
}
}
// TODO merge/sync/donate function? think about the difference between over/under cases
function getAmountOutput(
uint256 amountInput,
uint256 reserveInput,
uint256 reserveOutput
) public pure returns (uint256 amountOutput) {
require(reserveInput > 0 && reserveOutput > 0, "UniswapV2: INVALID_VALUE");
uint256 amountInputWithFee = amountInput.mul(997);
uint256 numerator = amountInputWithFee.mul(reserveOutput);
uint256 denominator = reserveInput.mul(1000).add(amountInputWithFee);
amountOutput = numerator.div(denominator);
}
function mintLiquidity(address recipient) public lock returns (uint256 liquidity) {
// get balances
uint256 balanceToken0 = IERC20(token0).balanceOf(address(this));
uint256 balanceToken1 = IERC20(token1).balanceOf(address(this));
// get reserves
uint256 reserveToken0 = uint256(tokenData[token0].reserve);
uint256 reserveToken1 = uint256(tokenData[token1].reserve);
// get amounts
uint256 amountToken0 = balanceToken0.sub(reserveToken0);
uint256 amountToken1 = balanceToken1.sub(reserveToken1);
if (totalSupply == 0) {
liquidity = amountToken0.mul(amountToken1).sqrt(); // TODO think through this (enforce min amount?)
} else {
// TODO think about "donating" the non-min token amount somehow
// TODO think about rounding here
liquidity = Math.min(
amountToken0.mul(totalSupply).div(reserveToken0),
amountToken1.mul(totalSupply).div(reserveToken1)
);
}
mint(recipient, liquidity); // TODO gas golf?
updateData(balanceToken0, balanceToken1);
emit LiquidityMinted(msg.sender, recipient, amountToken0, amountToken1);
}
function burnLiquidity(
uint256 amount,
address recipient
) public lock returns (uint256 amountToken0, uint256 amountToken1) {
require(amount > 0, "UniswapV2: ZERO_AMOUNT");
amountToken0 = amount.mul(tokenData[token0].reserve).div(totalSupply);
amountToken1 = amount.mul(tokenData[token1].reserve).div(totalSupply);
burnFrom(msg.sender, amount); // TODO gas golf?
require(IERC20(token0).transfer(recipient, amountToken0), "UniswapV2: TRANSFER_FAILED");
require(IERC20(token1).transfer(recipient, amountToken1), "UniswapV2: TRANSFER_FAILED");
// get balances
uint256 balanceToken0 = IERC20(token0).balanceOf(address(this));
uint256 balanceToken1 = IERC20(token1).balanceOf(address(this));
updateData(balanceToken0, balanceToken1);
emit LiquidityBurned(msg.sender, recipient, amountToken0, amountToken1);
}
function swap(address input, address recipient) public lock returns (uint256 amountOutput) {
require(input == token0 || input == token1, "UniswapV2: INVALID_INPUT");
address output = input == token0 ? token1 : token0;
// get balances
uint256 balanceInput = IERC20(input).balanceOf(address(this));
// get reserves
uint256 reserveInput = uint256(tokenData[input].reserve);
uint256 reserveOutput = uint256(tokenData[output].reserve);
// get input amount
uint256 amountInput = balanceInput.sub(reserveInput); // TODO think through edge cases here
require(amountInput > 0, "UniswapV2: ZERO_AMOUNT");
// calculate output amount and send to the recipient
amountOutput = getAmountOutput(amountInput, reserveInput, reserveOutput);
require(IERC20(output).transfer(recipient, amountOutput), "UniswapV2: TRANSFER_FAILED"); // TODO fallback here
// update data
uint256 balanceOutput = IERC20(output).balanceOf(address(this));
input == token0 ? updateData(balanceInput, balanceOutput) : updateData(balanceOutput, balanceInput);
emit Swap(input, msg.sender, recipient, amountInput, amountOutput);
}
}