initial exchange re-write

add initialization function to exchanges

add mint function in erc20
This commit is contained in:
Noah Zinsmeister
2019-10-25 15:06:06 -04:00
parent d48b5f5a44
commit e5b8db9df1
4 changed files with 165 additions and 147 deletions

View File

@ -1,178 +1,186 @@
// TODO overflow counter, review, fee
pragma solidity 0.5.12;
import "./interfaces/IERC20.sol";
import "./libraries/Math.sol";
import "./libraries/SafeMath.sol";
import "./interfaces/IERC20.sol";
import "./token/ERC20.sol";
contract UniswapV2 is ERC20("Uniswap V2", "UNI-V2", 18, 0) {
using Math for uint256;
using SafeMath for uint256;
event Swap(address inputToken, address buyer, address recipient, uint256 amountSold, uint256 amountBought);
event AddLiquidity(address indexed provider, uint256 amountTokenA, uint256 amountTokenB);
event RemoveLiquidity(address indexed provider, uint256 amountTokenA, uint256 amountTokenB);
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; // cached reserve for this token
uint128 accumulator; // accumulated TWAP value (TODO)
uint128 reserve;
uint128 accumulator;
}
// TODO: add overflow counter?
struct LastUpdate {
uint128 time;
uint128 blockNumber;
uint64 blockNumber;
uint64 blockTimestamp; // overflows about 280 billion years after the earth's sun explodes
}
// ERC20 Data
address public tokenA; // ERC20 token traded on this contract
address public tokenB; // ERC20 token traded on this contract
address public factory; // factory that created this contract
bool public initialized;
bool private locked;
address public factory;
address public token0;
address public token1;
mapping (address => TokenData) public dataForToken; // cached information about the token
mapping (address => TokenData) private tokenData;
LastUpdate private lastUpdate;
LastUpdate public lastUpdate; // information about the last time the reserves were updated
bool private reentrancyLock = false;
modifier nonReentrant() {
require(!reentrancyLock, "Uniswap: REENTRANCY_FORBIDDEN");
reentrancyLock = true;
modifier lock() {
require(!locked, "UniswapV2: LOCKED");
locked = true;
_;
reentrancyLock = false;
locked = false;
}
constructor() public {
factory = msg.sender;
// tokenA = _tokenA;
// tokenB = _tokenB;
}
function () external {}
function initialize(address _token0, address _token1) public {
require(!initialized, "UniswapV2: ALREADY_INITIALIZED");
initialized = true;
// TODO public?
function getInputPrice(uint256 inputAmount, uint256 inputReserve, uint256 outputReserve) internal pure returns (uint256) {
require(inputReserve > 0 && outputReserve > 0, "Uniswap: INVALID_VALUE");
uint256 inputAmountWithFee = inputAmount.mul(997);
uint256 numerator = inputAmountWithFee.mul(outputReserve);
uint256 denominator = inputReserve.mul(1000).add(inputAmountWithFee);
return numerator / denominator;
token0 = _token0;
token1 = _token1;
}
function updateData(
address firstToken,
address secondToken,
TokenData memory oldFirstTokenData,
TokenData memory oldSecondTokenData,
uint128 newFirstTokenReserve,
uint128 newSecondTokenReserve
) internal returns (uint128, uint128) {
uint128 diff = uint128(block.number) - lastUpdate.blockNumber;
dataForToken[firstToken] = TokenData({
reserve: newFirstTokenReserve,
accumulator: diff * oldFirstTokenData.reserve + oldFirstTokenData.accumulator
});
dataForToken[secondToken] = TokenData({
reserve: newSecondTokenReserve,
accumulator: diff * oldSecondTokenData.reserve + oldSecondTokenData.accumulator
});
if (diff != 0) {
lastUpdate = LastUpdate({
blockNumber: uint128(block.number),
time: uint128(block.timestamp)
});
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: consider switching to output token
function swap(address inputToken, address recipient) public nonReentrant returns (uint256) {
address outputToken;
if (inputToken == tokenA) {
outputToken = tokenB;
} else {
require(inputToken == tokenB, "Uniswap: INVALID_TOKEN");
outputToken = tokenA;
}
TokenData memory inputTokenData = dataForToken[inputToken];
TokenData memory outputTokenData = dataForToken[outputToken];
uint256 newInputReserve = IERC20(inputToken).balanceOf(address(this));
uint256 oldInputReserve = uint256(inputTokenData.reserve);
uint256 currentOutputReserve = IERC20(outputToken).balanceOf(address(this));
uint256 amountSold = newInputReserve - oldInputReserve;
uint256 amountBought = getInputPrice(amountSold, oldInputReserve, currentOutputReserve);
require(IERC20(outputToken).transfer(recipient, amountBought), "Uniswap: TRANSFER_FAILED");
uint256 newOutputReserve = currentOutputReserve - amountBought;
updateData(inputToken, outputToken, inputTokenData, outputTokenData, uint128(newInputReserve), uint128(newOutputReserve));
emit Swap(inputToken, msg.sender, recipient, amountSold, amountBought);
return amountBought;
}
function addLiquidity(address recipient) public nonReentrant returns (uint256) {
uint256 _totalSupply = totalSupply;
address _tokenA = tokenA;
address _tokenB = tokenB;
TokenData memory tokenAData = dataForToken[_tokenA];
TokenData memory tokenBData = dataForToken[_tokenB];
uint256 newReserveA = IERC20(_tokenA).balanceOf(address(this));
uint256 newReserveB = IERC20(_tokenB).balanceOf(address(this));
uint256 amountA = newReserveA - tokenAData.reserve;
uint256 amountB = newReserveB - tokenBData.reserve;
uint256 liquidityMinted;
if (_totalSupply > 0) {
// TODO think about "donating" the non-min token amount by not updating stored balances
// TODO think about rounding here
liquidityMinted = Math.min(amountA.mul(_totalSupply).div(tokenAData.reserve), amountB.mul(_totalSupply).div(tokenBData.reserve));
} else {
// TODO think through this (enforce min amount?)
liquidityMinted = amountA.mul(amountB).sqrt();
}
balanceOf[recipient] = balanceOf[recipient].add(liquidityMinted);
totalSupply = _totalSupply.add(liquidityMinted);
updateData(_tokenA, _tokenB, tokenAData, tokenBData, uint128(newReserveA), uint128(newReserveB));
emit AddLiquidity(msg.sender, amountA, amountB);
emit Transfer(address(0), msg.sender, liquidityMinted);
return liquidityMinted;
}
function removeLiquidity(uint256 amount, address recipient) public nonReentrant returns (uint256, uint256) {
address _tokenA = tokenA;
address _tokenB = tokenB;
TokenData memory tokenAData = dataForToken[_tokenA];
TokenData memory tokenBData = dataForToken[_tokenB];
uint256 reserveA = IERC20(_tokenA).balanceOf(address(this));
uint256 reserveB = IERC20(_tokenB).balanceOf(address(this));
uint256 _totalSupply = totalSupply;
uint256 amountA = amount.mul(reserveA) / _totalSupply;
uint256 amountB = amount.mul(reserveB) / _totalSupply;
balanceOf[msg.sender] = balanceOf[msg.sender].sub(amount);
totalSupply = _totalSupply.sub(amount);
require(IERC20(_tokenA).transfer(recipient, amountA), "Uniswap: TRANSFER_A_FAILED");
require(IERC20(_tokenB).transfer(recipient, amountB), "Uniswap: TRANSFER_B_FAILED");
updateData(_tokenA, _tokenB, tokenAData, tokenBData, uint128(reserveA - amountA), uint128(reserveB - amountB));
emit RemoveLiquidity(recipient, amountA, amountB);
emit Transfer(msg.sender, address(0), amount);
return (amountA, amountB);
}
// 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);
}
}

View File

@ -3,6 +3,8 @@ pragma solidity 0.5.12;
import "./interfaces/IUniswapV2Factory.sol";
import "./UniswapV2.sol";
contract UniswapV2Factory is IUniswapV2Factory {
event ExchangeCreated(address indexed token0, address indexed token1, address exchange, uint256 exchangeCount);
@ -22,7 +24,7 @@ contract UniswapV2Factory is IUniswapV2Factory {
}
function orderTokens(address tokenA, address tokenB) private pure returns (Pair memory pair) {
pair = tokenA < tokenB ? Pair(tokenA, tokenB) : Pair(tokenB, tokenA);
pair = tokenA < tokenB ? Pair({ token0: tokenA, token1: tokenB }) : Pair({ token0: tokenB, token1: tokenA });
}
function getTokens(address exchange) public view returns (address token0, address token1) {
@ -54,6 +56,7 @@ contract UniswapV2Factory is IUniswapV2Factory {
salt
)
}
UniswapV2(exchange).initialize(pair.token0, pair.token1);
exchangeToPair[exchange] = pair;
token0ToToken1ToExchange[pair.token0][pair.token1] = exchange;

View File

@ -3,6 +3,7 @@ pragma solidity 0.5.12;
import "./libraries/Math.sol";
import "./libraries/SafeMath.sol";
import "./UniswapV2Factory.sol";
import "./UniswapV2.sol";

View File

@ -26,13 +26,19 @@ contract ERC20 is IERC20 {
balanceOf[msg.sender] = totalSupply;
}
function _transfer(address from, address to, uint256 value) internal {
function mint(address to, uint256 value) internal {
totalSupply = totalSupply.add(value);
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(address(0), to, value);
}
function _transfer(address from, address to, uint256 value) private {
balanceOf[from] = balanceOf[from].sub(value);
balanceOf[to] = balanceOf[to].add(value);
emit Transfer(from, to, value);
}
function _burn(address from, uint256 value) internal {
function _burn(address from, uint256 value) private {
balanceOf[from] = balanceOf[from].sub(value);
totalSupply = totalSupply.sub(value);
emit Transfer(from, address(0), value);
@ -55,7 +61,7 @@ contract ERC20 is IERC20 {
_burn(msg.sender, value);
}
function burnFrom(address from, uint256 value) external {
function burnFrom(address from, uint256 value) public {
if (allowance[from][msg.sender] != uint256(-1)) {
allowance[from][msg.sender] = allowance[from][msg.sender].sub(value);
}