add uint128 where applicable
remove mapping in exchange add example oracle syntax changes
This commit is contained in:
@ -5,54 +5,44 @@ import "./interfaces/IERC20.sol";
|
||||
import "./interfaces/IIncompatibleERC20.sol";
|
||||
|
||||
import "./libraries/Math.sol";
|
||||
import "./libraries/SafeMath.sol";
|
||||
import "./libraries/SafeMath128.sol";
|
||||
import "./libraries/SafeMath256.sol";
|
||||
|
||||
import "./token/ERC20.sol";
|
||||
|
||||
contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) {
|
||||
using Math for uint256;
|
||||
using SafeMath for uint256;
|
||||
using SafeMath128 for uint128;
|
||||
using SafeMath256 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 liquidity,
|
||||
uint256 amountToken0,
|
||||
uint256 amountToken1
|
||||
address indexed sender, address indexed recipient, uint256 liquidity, uint128 amountToken0, uint128 amountToken1
|
||||
);
|
||||
event LiquidityBurned(
|
||||
address indexed sender,
|
||||
address indexed recipient,
|
||||
uint256 liquidity,
|
||||
uint256 amountToken0,
|
||||
uint256 amountToken1
|
||||
address indexed sender, address indexed recipient, uint256 liquidity, uint128 amountToken0, uint128 amountToken1
|
||||
);
|
||||
event Swap(
|
||||
address indexed sender, address indexed recipient, address input, uint128 amountInput, uint128 amountOutput
|
||||
);
|
||||
|
||||
struct TokenData {
|
||||
uint128 reserve;
|
||||
uint128 accumulator;
|
||||
uint128 token0;
|
||||
uint128 token1;
|
||||
}
|
||||
|
||||
struct LastUpdate {
|
||||
struct Time {
|
||||
uint64 blockNumber;
|
||||
uint64 blockTimestamp; // overflows about 280 billion years after the earth's sun explodes
|
||||
}
|
||||
|
||||
bool private locked;
|
||||
bool private locked; // reentrancy lock
|
||||
|
||||
address public factory;
|
||||
address public token0;
|
||||
address public token1;
|
||||
|
||||
mapping (address => TokenData) private tokenData;
|
||||
LastUpdate private lastUpdate;
|
||||
TokenData private reserves;
|
||||
TokenData private reservesCumulative;
|
||||
Time private lastUpdate;
|
||||
|
||||
modifier lock() {
|
||||
require(!locked, "UniswapV2: LOCKED");
|
||||
@ -65,7 +55,7 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) {
|
||||
factory = msg.sender;
|
||||
}
|
||||
|
||||
function initialize(address _token0, address _token1, uint256 chainId) public {
|
||||
function initialize(address _token0, address _token1, uint256 chainId) external {
|
||||
require(token0 == address(0) && token1 == address(0), "UniswapV2: ALREADY_INITIALIZED");
|
||||
token0 = _token0;
|
||||
token1 = _token1;
|
||||
@ -73,57 +63,47 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) {
|
||||
}
|
||||
|
||||
// https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca
|
||||
function safeTransfer(address token, address to, uint value) private returns (bool result) {
|
||||
IIncompatibleERC20(token).transfer(to, value);
|
||||
|
||||
function safeTransfer(address token, address to, uint128 value) private returns (bool result) {
|
||||
IIncompatibleERC20(token).transfer(to, uint256(value));
|
||||
assembly {
|
||||
switch returndatasize()
|
||||
case 0 {
|
||||
result := not(0)
|
||||
result := not(0) // for no-bool responses, treat as successful
|
||||
}
|
||||
case 32 {
|
||||
returndatacopy(0, 0, 32)
|
||||
result := mload(0)
|
||||
result := mload(0) // for (presumably) bool responses, return whatever the function returned
|
||||
}
|
||||
default {
|
||||
revert(0, 0)
|
||||
revert(0, 0) // for invalid responses, revert
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO merge/sync/donate function? think about the difference between over/under cases
|
||||
// TODO sync/merge/donate function? think about the difference between over/under cases
|
||||
|
||||
function getReserves() public view returns (uint128 reserveToken0, uint128 reserveToken1) {
|
||||
reserveToken0 = tokenData[token0].reserve;
|
||||
reserveToken1 = tokenData[token1].reserve;
|
||||
function getReserves() external view returns (uint128, uint128) {
|
||||
return (reserves.token0, reserves.token1);
|
||||
}
|
||||
|
||||
function getData() public view returns (
|
||||
uint128 accumulatorToken0,
|
||||
uint128 accumulatorToken1,
|
||||
uint64 blockNumber,
|
||||
uint64 blockTimestamp
|
||||
) {
|
||||
accumulatorToken0 = tokenData[token0].accumulator;
|
||||
accumulatorToken1 = tokenData[token1].accumulator;
|
||||
blockNumber = lastUpdate.blockNumber;
|
||||
blockTimestamp = lastUpdate.blockTimestamp;
|
||||
function getReservesCumulative() external view returns (uint128, uint128) {
|
||||
return (reservesCumulative.token0, reservesCumulative.token1);
|
||||
}
|
||||
|
||||
function updateData(uint256 reserveToken0, uint256 reserveToken1) private {
|
||||
function getLastUpdate() external view returns (uint64, uint64) {
|
||||
return (lastUpdate.blockNumber, lastUpdate.blockTimestamp);
|
||||
}
|
||||
|
||||
function updateReserves(TokenData memory reservesNext) private {
|
||||
uint64 blockNumber = block.number.downcastTo64();
|
||||
uint64 blocksElapsed = blockNumber - lastUpdate.blockNumber;
|
||||
|
||||
// get token data
|
||||
TokenData storage tokenDataToken0 = tokenData[token0];
|
||||
TokenData storage tokenDataToken1 = tokenData[token1];
|
||||
|
||||
if (blocksElapsed > 0) {
|
||||
// TODO do edge case math here
|
||||
// update accumulators if this isn't the first call to updateData
|
||||
// if this isn't the first-ever call to this function, update the accumulators
|
||||
if (lastUpdate.blockNumber != 0) {
|
||||
tokenDataToken0.accumulator += tokenDataToken0.reserve * blocksElapsed;
|
||||
tokenDataToken1.accumulator += tokenDataToken1.reserve * blocksElapsed;
|
||||
// TODO do edge case math here
|
||||
reservesCumulative.token0 += reserves.token0 * blocksElapsed;
|
||||
reservesCumulative.token1 += reserves.token1 * blocksElapsed;
|
||||
}
|
||||
|
||||
// update last update
|
||||
@ -131,100 +111,102 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) {
|
||||
lastUpdate.blockTimestamp = block.timestamp.downcastTo64();
|
||||
}
|
||||
|
||||
tokenDataToken0.reserve = reserveToken0.downcastTo128();
|
||||
tokenDataToken1.reserve = reserveToken1.downcastTo128();
|
||||
reserves.token0 = reservesNext.token0;
|
||||
reserves.token1 = reservesNext.token1;
|
||||
}
|
||||
|
||||
function getAmountOutput(
|
||||
uint256 amountInput,
|
||||
uint256 reserveInput,
|
||||
uint256 reserveOutput
|
||||
) public pure returns (uint256 amountOutput) {
|
||||
uint128 amountInput, uint128 reserveInput, uint128 reserveOutput
|
||||
) public pure returns (uint128 amountOutput) {
|
||||
require(amountInput > 0 && reserveInput > 0 && reserveOutput > 0, "UniswapV2: INVALID_VALUE");
|
||||
uint256 fee = 3; // TODO 30 bips for now, think through this later
|
||||
uint256 amountInputWithFee = amountInput.mul(1000 - fee);
|
||||
uint256 numerator = amountInputWithFee.mul(reserveOutput);
|
||||
uint256 denominator = reserveInput.mul(1000).add(amountInputWithFee);
|
||||
amountOutput = numerator.div(denominator);
|
||||
uint256 amountInputWithFee = uint256(amountInput).mul(1000 - 3); // 30 bips for now, TODO think through this later
|
||||
uint256 numerator = amountInputWithFee.mul(uint256(reserveOutput));
|
||||
uint256 denominator = uint256(reserveInput).mul(1000).add(amountInputWithFee);
|
||||
amountOutput = numerator.div(denominator).downcastTo128();
|
||||
}
|
||||
|
||||
function mintLiquidity(address recipient) public lock returns (uint256 liquidity) {
|
||||
function mintLiquidity(address recipient) external lock returns (uint256 liquidity) {
|
||||
// get balances
|
||||
uint256 balanceToken0 = IERC20(token0).balanceOf(address(this));
|
||||
uint256 balanceToken1 = IERC20(token1).balanceOf(address(this));
|
||||
TokenData memory balances = TokenData({
|
||||
token0: IERC20(token0).balanceOf(address(this)).downcastTo128(),
|
||||
token1: IERC20(token1).balanceOf(address(this)).downcastTo128()
|
||||
});
|
||||
|
||||
// get reserves
|
||||
uint256 reserveToken0 = uint256(tokenData[token0].reserve);
|
||||
uint256 reserveToken1 = uint256(tokenData[token1].reserve);
|
||||
|
||||
// TODO think about what happens if this fails
|
||||
// get amounts
|
||||
uint256 amountToken0 = balanceToken0.sub(reserveToken0);
|
||||
uint256 amountToken1 = balanceToken1.sub(reserveToken1);
|
||||
// get amounts sent to be added as liquidity
|
||||
TokenData memory amounts = TokenData({
|
||||
token0: balances.token0.sub(reserves.token0),
|
||||
token1: balances.token1.sub(reserves.token1)
|
||||
});
|
||||
|
||||
if (totalSupply == 0) {
|
||||
liquidity = amountToken0.mul(amountToken1).sqrt(); // TODO think through this (enforce min amount?)
|
||||
// TODO is this right? enforce min amount? enforce no remainder?
|
||||
liquidity = Math.sqrt(uint256(amounts.token0).mul(uint256(amounts.token1)));
|
||||
} else {
|
||||
// TODO think about "donating" the non-min token amount somehow
|
||||
// TODO think about rounding here
|
||||
// TODO is this right?
|
||||
// TODO "donate" or ignore the non-min token amount?
|
||||
// TODO does this round the way we want?
|
||||
liquidity = Math.min(
|
||||
amountToken0.mul(totalSupply).div(reserveToken0),
|
||||
amountToken1.mul(totalSupply).div(reserveToken1)
|
||||
uint256(amounts.token0).mul(totalSupply).div(uint256(reserves.token0)),
|
||||
uint256(amounts.token1).mul(totalSupply).div(uint256(reserves.token1))
|
||||
);
|
||||
}
|
||||
|
||||
mint(recipient, liquidity); // TODO gas golf?
|
||||
|
||||
updateData(balanceToken0, balanceToken1);
|
||||
|
||||
emit LiquidityMinted(msg.sender, recipient, liquidity, amountToken0, amountToken1);
|
||||
updateReserves(balances);
|
||||
emit LiquidityMinted(msg.sender, recipient, liquidity, amounts.token0, amounts.token1);
|
||||
}
|
||||
|
||||
function burnLiquidity(
|
||||
uint256 liquidity,
|
||||
address recipient
|
||||
) public lock returns (uint256 amountToken0, uint256 amountToken1) {
|
||||
uint256 liquidity, address recipient
|
||||
) external lock returns (uint128 amountToken0, uint128 amountToken1) {
|
||||
require(liquidity > 0, "UniswapV2: ZERO_AMOUNT");
|
||||
|
||||
amountToken0 = liquidity.mul(tokenData[token0].reserve).div(totalSupply);
|
||||
amountToken1 = liquidity.mul(tokenData[token1].reserve).div(totalSupply);
|
||||
// send tokens back
|
||||
// TODO is this right?
|
||||
TokenData memory amounts = TokenData({
|
||||
token0: liquidity.mul(uint256(reserves.token0)).div(totalSupply).downcastTo128(),
|
||||
token1: liquidity.mul(uint256(reserves.token1)).div(totalSupply).downcastTo128()
|
||||
});
|
||||
(amountToken0, amountToken1) = (amounts.token0, amounts.token1);
|
||||
require(safeTransfer(token0, recipient, amounts.token0), "UniswapV2: TRANSFER_FAILED");
|
||||
require(safeTransfer(token1, recipient, amounts.token1), "UniswapV2: TRANSFER_FAILED");
|
||||
|
||||
_burn(address(this), liquidity); // TODO gas golf?
|
||||
require(safeTransfer(token0, recipient, amountToken0), "UniswapV2: TRANSFER_FAILED");
|
||||
require(safeTransfer(token1, recipient, amountToken1), "UniswapV2: TRANSFER_FAILED");
|
||||
|
||||
// get balances
|
||||
uint256 balanceToken0 = IERC20(token0).balanceOf(address(this));
|
||||
uint256 balanceToken1 = IERC20(token1).balanceOf(address(this));
|
||||
updateData(balanceToken0, balanceToken1);
|
||||
|
||||
TokenData memory balances = TokenData({
|
||||
token0: IERC20(token0).balanceOf(address(this)).downcastTo128(),
|
||||
token1: IERC20(token1).balanceOf(address(this)).downcastTo128()
|
||||
});
|
||||
updateReserves(balances);
|
||||
emit LiquidityBurned(msg.sender, recipient, liquidity, 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;
|
||||
function swap(address input, address recipient) external lock returns (uint128 amountOutput) {
|
||||
uint128 inputBalance = IERC20(input).balanceOf(address(this)).downcastTo128();
|
||||
|
||||
// get input balance
|
||||
uint256 balanceInput = IERC20(input).balanceOf(address(this));
|
||||
uint128 amountInput;
|
||||
TokenData memory balances;
|
||||
if (input == token0) {
|
||||
amountInput = inputBalance.sub(reserves.token0);
|
||||
amountOutput = getAmountOutput(amountInput, reserves.token0, reserves.token1);
|
||||
require(safeTransfer(token1, recipient, amountOutput), "UniswapV2: TRANSFER_FAILED");
|
||||
balances = TokenData({
|
||||
token0: inputBalance,
|
||||
token1: IERC20(token1).balanceOf(address(this)).downcastTo128()
|
||||
});
|
||||
} else {
|
||||
require(input == token1, "UniswapV2: INVALID_INPUT");
|
||||
|
||||
// get reserves
|
||||
uint256 reserveInput = uint256(tokenData[input].reserve);
|
||||
uint256 reserveOutput = uint256(tokenData[output].reserve);
|
||||
amountInput = inputBalance.sub(reserves.token1);
|
||||
amountOutput = getAmountOutput(amountInput, reserves.token1, reserves.token0);
|
||||
require(safeTransfer(token0, recipient, amountOutput), "UniswapV2: TRANSFER_FAILED");
|
||||
balances = TokenData({
|
||||
token0: IERC20(token0).balanceOf(address(this)).downcastTo128(),
|
||||
token1: inputBalance
|
||||
});
|
||||
}
|
||||
|
||||
// TODO think about what happens if this fails
|
||||
// get input amount
|
||||
uint256 amountInput = balanceInput.sub(reserveInput);
|
||||
|
||||
// calculate output amount and send to the recipient
|
||||
amountOutput = getAmountOutput(amountInput, reserveInput, reserveOutput);
|
||||
require(safeTransfer(output, recipient, amountOutput), "UniswapV2: TRANSFER_FAILED");
|
||||
|
||||
// update data
|
||||
// TODO re-fetch input balance here?
|
||||
uint256 balanceOutput = IERC20(output).balanceOf(address(this));
|
||||
input == token0 ? updateData(balanceInput, balanceOutput) : updateData(balanceOutput, balanceInput);
|
||||
|
||||
emit Swap(input, msg.sender, recipient, amountInput, amountOutput);
|
||||
updateReserves(balances);
|
||||
emit Swap(msg.sender, recipient, input, amountInput, amountOutput);
|
||||
}
|
||||
}
|
||||
|
||||
@ -15,6 +15,7 @@ contract UniswapV2Factory is IUniswapV2Factory {
|
||||
bytes public exchangeBytecode;
|
||||
uint256 public chainId;
|
||||
uint256 public exchangeCount;
|
||||
|
||||
mapping (address => Pair) private exchangeToPair;
|
||||
mapping (address => mapping(address => address)) private token0ToToken1ToExchange;
|
||||
|
||||
@ -24,25 +25,15 @@ contract UniswapV2Factory is IUniswapV2Factory {
|
||||
chainId = _chainId;
|
||||
}
|
||||
|
||||
function orderTokens(address tokenA, address tokenB) private pure returns (Pair memory pair) {
|
||||
pair = tokenA < tokenB ? Pair({ token0: tokenA, token1: tokenB }) : Pair({ token0: tokenB, token1: tokenA });
|
||||
function getPair(address tokenA, address tokenB) private pure returns (Pair memory) {
|
||||
return tokenA < tokenB ? Pair({ token0: tokenA, token1: tokenB }) : Pair({ token0: tokenB, token1: tokenA });
|
||||
}
|
||||
|
||||
function getTokens(address exchange) public view returns (address token0, address token1) {
|
||||
Pair storage pair = exchangeToPair[exchange];
|
||||
(token0, token1) = (pair.token0, pair.token1);
|
||||
}
|
||||
function createExchange(address tokenA, address tokenB) external returns (address exchange) {
|
||||
require(tokenA != tokenB, "UniswapV2Factory: SAME_ADDRESS");
|
||||
require(tokenA != address(0) && tokenB != address(0), "UniswapV2Factory: ZERO_ADDRESS");
|
||||
|
||||
function getExchange(address tokenA, address tokenB) public view returns (address exchange) {
|
||||
Pair memory pair = orderTokens(tokenA, tokenB);
|
||||
exchange = token0ToToken1ToExchange[pair.token0][pair.token1];
|
||||
}
|
||||
|
||||
function createExchange(address tokenA, address tokenB) public returns (address exchange) {
|
||||
require(tokenA != tokenB, "UniswapV2Factory: SAME_TOKEN");
|
||||
require(tokenA != address(0) && tokenB != address(0), "UniswapV2Factory: ZERO_ADDRESS_TOKEN");
|
||||
|
||||
Pair memory pair = orderTokens(tokenA, tokenB);
|
||||
Pair memory pair = getPair(tokenA, tokenB);
|
||||
|
||||
require(token0ToToken1ToExchange[pair.token0][pair.token1] == address(0), "UniswapV2Factory: EXCHANGE_EXISTS");
|
||||
|
||||
@ -63,4 +54,14 @@ contract UniswapV2Factory is IUniswapV2Factory {
|
||||
|
||||
emit ExchangeCreated(pair.token0, pair.token1, exchange, exchangeCount++);
|
||||
}
|
||||
|
||||
function getTokens(address exchange) external view returns (address, address) {
|
||||
Pair storage pair = exchangeToPair[exchange];
|
||||
return (pair.token0, pair.token1);
|
||||
}
|
||||
|
||||
function getExchange(address tokenA, address tokenB) external view returns (address) {
|
||||
Pair memory pair = getPair(tokenA, tokenB);
|
||||
return token0ToToken1ToExchange[pair.token0][pair.token1];
|
||||
}
|
||||
}
|
||||
|
||||
@ -1,51 +1,30 @@
|
||||
pragma solidity 0.5.12;
|
||||
|
||||
interface IUniswapV2 {
|
||||
event Swap(
|
||||
address indexed input,
|
||||
address indexed sender,
|
||||
address indexed recipient,
|
||||
uint256 amountInput,
|
||||
uint256 amountOutput
|
||||
);
|
||||
event LiquidityMinted(
|
||||
address indexed sender,
|
||||
address indexed recipient,
|
||||
uint256 liquidity,
|
||||
uint256 amountToken0,
|
||||
uint256 amountToken1
|
||||
address indexed sender, address indexed recipient, uint256 liquidity, uint128 amountToken0, uint128 amountToken1
|
||||
);
|
||||
event LiquidityBurned(
|
||||
address indexed sender,
|
||||
address indexed recipient,
|
||||
uint256 liquidity,
|
||||
uint256 amountToken0,
|
||||
uint256 amountToken1
|
||||
address indexed sender, address indexed recipient, uint256 liquidity, uint128 amountToken0, uint128 amountToken1
|
||||
);
|
||||
event Swap(
|
||||
address indexed sender, address indexed recipient, address input, uint128 amountInput, uint128 amountOutput
|
||||
);
|
||||
|
||||
function factory() external view returns (address);
|
||||
function token0() external view returns (address);
|
||||
function token1() external view returns (address);
|
||||
|
||||
function getReserves() external view returns (uint128 reserveToken0, uint128 reserveToken1);
|
||||
function getData() external view returns (
|
||||
uint128 accumulatorToken0,
|
||||
uint128 accumulatorToken1,
|
||||
uint64 blockNumber,
|
||||
uint64 blockTimestamp
|
||||
);
|
||||
function getReserves() external view returns (uint128, uint128);
|
||||
function getReservesCumulative() external view returns (uint128, uint128);
|
||||
function getLastUpdate() external view returns (uint64, uint64);
|
||||
|
||||
function getAmountOutput(
|
||||
uint256 amountInput,
|
||||
uint256 reserveInput,
|
||||
uint256 reserveOutput
|
||||
) external pure returns (uint256 amountOutput);
|
||||
|
||||
function initialize(address _token0, address _token1, uint256 chainId) external;
|
||||
|
||||
uint128 amountInput, uint128 reserveInput, uint128 reserveOutput
|
||||
) external pure returns (uint128 amountOutput);
|
||||
function mintLiquidity(address recipient) external returns (uint256 liquidity);
|
||||
function burnLiquidity(
|
||||
uint256 liquidity,
|
||||
address recipient
|
||||
) external returns (uint256 amountToken0, uint256 amountToken1);
|
||||
function swap(address input, address recipient) external returns (uint256 amountOutput);
|
||||
uint256 liquidity, address recipient
|
||||
) external returns (uint128 amountToken0, uint128 amountToken1);
|
||||
function swap(address input, address recipient) external returns (uint128 amountOutput);
|
||||
}
|
||||
|
||||
@ -6,8 +6,9 @@ interface IUniswapV2Factory {
|
||||
function exchangeBytecode() external view returns (bytes memory);
|
||||
function chainId() external view returns (uint256);
|
||||
function exchangeCount() external view returns (uint256);
|
||||
function getTokens(address exchange) external view returns (address token0, address token1);
|
||||
function getExchange(address tokenA, address tokenB) external view returns (address exchange);
|
||||
|
||||
function createExchange(address tokenA, address tokenB) external returns (address exchange);
|
||||
|
||||
function getTokens(address exchange) external view returns (address, address);
|
||||
function getExchange(address tokenA, address tokenB) external view returns (address);
|
||||
}
|
||||
|
||||
29
contracts/libraries/SafeMath128.sol
Normal file
29
contracts/libraries/SafeMath128.sol
Normal file
@ -0,0 +1,29 @@
|
||||
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/2f9ae975c8bdc5c7f7fa26204896f6c717f07164/contracts/math/SafeMath.sol
|
||||
pragma solidity 0.5.12;
|
||||
|
||||
library SafeMath128 {
|
||||
function add(uint128 a, uint128 b) internal pure returns (uint128) {
|
||||
uint128 c = a + b;
|
||||
require(c >= a, "SafeMath: addition overflow");
|
||||
return c;
|
||||
}
|
||||
|
||||
function sub(uint128 a, uint128 b) internal pure returns (uint128) {
|
||||
require(b <= a, "SafeMath: subtraction overflow");
|
||||
uint128 c = a - b;
|
||||
return c;
|
||||
}
|
||||
|
||||
function mul(uint128 a, uint128 b) internal pure returns (uint128) {
|
||||
if (a == 0) return 0;
|
||||
uint128 c = a * b;
|
||||
require(c / a == b, "SafeMath: multiplication overflow");
|
||||
return c;
|
||||
}
|
||||
|
||||
function div(uint128 a, uint128 b) internal pure returns (uint128) {
|
||||
require(b > 0, "SafeMath: division by zero");
|
||||
uint128 c = a / b;
|
||||
return c;
|
||||
}
|
||||
}
|
||||
@ -1,7 +1,7 @@
|
||||
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/2f9ae975c8bdc5c7f7fa26204896f6c717f07164/contracts/math/SafeMath.sol
|
||||
pragma solidity 0.5.12;
|
||||
|
||||
library SafeMath {
|
||||
library SafeMath256 {
|
||||
function add(uint256 a, uint256 b) internal pure returns (uint256) {
|
||||
uint256 c = a + b;
|
||||
require(c >= a, "SafeMath: addition overflow");
|
||||
74
contracts/test/Oracle.sol
Normal file
74
contracts/test/Oracle.sol
Normal file
@ -0,0 +1,74 @@
|
||||
pragma solidity 0.5.12;
|
||||
|
||||
import "../interfaces/IUniswapV2.sol";
|
||||
|
||||
contract Oracle {
|
||||
struct TokenData {
|
||||
uint128 token0;
|
||||
uint128 token1;
|
||||
}
|
||||
|
||||
struct Time {
|
||||
uint64 blockNumber;
|
||||
uint64 blockTimestamp;
|
||||
}
|
||||
|
||||
address public exchange;
|
||||
uint128 constant period = 1 days;
|
||||
|
||||
TokenData private reservesCumulative;
|
||||
Time private lastUpdate;
|
||||
TokenData private currentPrice;
|
||||
|
||||
constructor(address _exchange) public {
|
||||
exchange = _exchange;
|
||||
}
|
||||
|
||||
function _updateCurrentPrice(TokenData memory averages, uint128 timestampDelta) private {
|
||||
TokenData memory nextPrice;
|
||||
if (timestampDelta >= period || (currentPrice.token0 == 0 && currentPrice.token1 == 0)) {
|
||||
nextPrice = averages;
|
||||
} else {
|
||||
nextPrice = TokenData({
|
||||
token0: (currentPrice.token0 * (period - timestampDelta) + averages.token0 * timestampDelta) / period,
|
||||
token1: (currentPrice.token1 * (period - timestampDelta) + averages.token1 * timestampDelta) / period
|
||||
});
|
||||
}
|
||||
currentPrice = nextPrice;
|
||||
}
|
||||
|
||||
function updateCurrentPrice() external {
|
||||
IUniswapV2 uniswapV2 = IUniswapV2(exchange);
|
||||
// TODO handle the case where time has passed (basically, always use the most up-to-date data)
|
||||
(uint128 reserveCumulativeToken0, uint128 reserveCumulativeToken1) = uniswapV2.getReservesCumulative();
|
||||
(uint64 blockNumber, uint64 blockTimestamp) = uniswapV2.getLastUpdate();
|
||||
|
||||
if (blockNumber > lastUpdate.blockNumber) {
|
||||
uint128 blocksElapsed = blockNumber - lastUpdate.blockNumber;
|
||||
|
||||
if (lastUpdate.blockNumber != 0) {
|
||||
TokenData memory deltas = TokenData({
|
||||
token0: reserveCumulativeToken0 - reservesCumulative.token0,
|
||||
token1: reserveCumulativeToken1 - reservesCumulative.token1
|
||||
});
|
||||
|
||||
TokenData memory averages = TokenData({
|
||||
token0: deltas.token0 / blocksElapsed,
|
||||
token1: deltas.token1 / blocksElapsed
|
||||
});
|
||||
|
||||
uint128 timeElapsed = blockTimestamp - lastUpdate.blockTimestamp;
|
||||
_updateCurrentPrice(averages, timeElapsed);
|
||||
}
|
||||
|
||||
reservesCumulative.token0 = reserveCumulativeToken0;
|
||||
reservesCumulative.token1 = reserveCumulativeToken1;
|
||||
lastUpdate.blockNumber = blockNumber;
|
||||
lastUpdate.blockTimestamp = blockTimestamp;
|
||||
}
|
||||
}
|
||||
|
||||
function getCurrentPrice() external view returns (uint128, uint128) {
|
||||
return (currentPrice.token0, currentPrice.token1);
|
||||
}
|
||||
}
|
||||
@ -4,10 +4,10 @@ pragma solidity 0.5.12;
|
||||
|
||||
import "../interfaces/IERC20.sol";
|
||||
|
||||
import "../libraries/SafeMath.sol";
|
||||
import "../libraries/SafeMath256.sol";
|
||||
|
||||
contract ERC20 is IERC20 {
|
||||
using SafeMath for uint256;
|
||||
using SafeMath256 for uint256;
|
||||
|
||||
// ERC-20 data
|
||||
string public name;
|
||||
|
||||
61
test/Oracle.ts
Normal file
61
test/Oracle.ts
Normal file
@ -0,0 +1,61 @@
|
||||
import path from 'path'
|
||||
import chai from 'chai'
|
||||
import { solidity, createMockProvider, getWallets, createFixtureLoader, deployContract } from 'ethereum-waffle'
|
||||
import { Contract } from 'ethers'
|
||||
import { BigNumber, bigNumberify } from 'ethers/utils'
|
||||
|
||||
import { expandTo18Decimals } from './shared/utilities'
|
||||
import { exchangeFixture, ExchangeFixture } from './shared/fixtures'
|
||||
|
||||
import Oracle from '../build/Oracle.json'
|
||||
|
||||
chai.use(solidity)
|
||||
const { expect } = chai
|
||||
|
||||
describe('Oracle', () => {
|
||||
const provider = createMockProvider(path.join(__dirname, '..', 'waffle.json'))
|
||||
const [wallet] = getWallets(provider)
|
||||
const loadFixture = createFixtureLoader(provider, [wallet])
|
||||
|
||||
let token0: Contract
|
||||
let token1: Contract
|
||||
let exchange: Contract
|
||||
let oracle: Contract
|
||||
beforeEach(async () => {
|
||||
const { token0: _token0, token1: _token1, exchange: _exchange } = (await loadFixture(
|
||||
exchangeFixture as any
|
||||
)) as ExchangeFixture
|
||||
token0 = _token0
|
||||
token1 = _token1
|
||||
exchange = _exchange
|
||||
oracle = await deployContract(wallet, Oracle, [exchange.address])
|
||||
})
|
||||
|
||||
async function addLiquidity(token0Amount: BigNumber, token1Amount: BigNumber) {
|
||||
await token0.transfer(exchange.address, token0Amount)
|
||||
await token1.transfer(exchange.address, token1Amount)
|
||||
await exchange.connect(wallet).mintLiquidity(wallet.address)
|
||||
}
|
||||
|
||||
async function swap(token: Contract, amount: BigNumber) {
|
||||
await token.transfer(exchange.address, amount)
|
||||
await exchange.connect(wallet).swap(token.address, wallet.address)
|
||||
}
|
||||
|
||||
it('exchange, getCurrentPrice', async () => {
|
||||
expect(await oracle.exchange()).to.eq(exchange.address)
|
||||
expect(await oracle.getCurrentPrice()).to.deep.eq([0, 0].map(n => bigNumberify(n)))
|
||||
})
|
||||
|
||||
it('updateCurrentPrice', async () => {
|
||||
const token0Amount = expandTo18Decimals(10)
|
||||
const token1Amount = expandTo18Decimals(5)
|
||||
|
||||
await addLiquidity(token0Amount, token1Amount)
|
||||
await oracle.connect(wallet).updateCurrentPrice()
|
||||
await swap(token0, bigNumberify(1))
|
||||
|
||||
await oracle.connect(wallet).updateCurrentPrice()
|
||||
expect(await oracle.getCurrentPrice()).to.deep.eq([token0Amount, token1Amount])
|
||||
})
|
||||
})
|
||||
@ -59,7 +59,7 @@ describe('UniswapV2', () => {
|
||||
'0996006981039903216'
|
||||
].map((n: string) => bigNumberify(n))
|
||||
|
||||
const outputs = await Promise.all(testCases.map(c => exchange.getAmountOutput(...c)))
|
||||
const outputs = await Promise.all(testCases.map(a => exchange.getAmountOutput(...a)))
|
||||
|
||||
expect(outputs).to.deep.eq(expectedOutputs)
|
||||
})
|
||||
@ -96,7 +96,7 @@ describe('UniswapV2', () => {
|
||||
await token0.transfer(exchange.address, swapAmount)
|
||||
await expect(exchange.connect(wallet).swap(token0.address, wallet.address))
|
||||
.to.emit(exchange, 'Swap')
|
||||
.withArgs(token0.address, wallet.address, wallet.address, swapAmount, expectedOutputAmount)
|
||||
.withArgs(wallet.address, wallet.address, token0.address, swapAmount, expectedOutputAmount)
|
||||
|
||||
expect(await token0.balanceOf(exchange.address)).to.eq(token0Amount.add(swapAmount))
|
||||
expect(await token1.balanceOf(exchange.address)).to.eq(token1Amount.sub(expectedOutputAmount))
|
||||
@ -128,36 +128,34 @@ describe('UniswapV2', () => {
|
||||
})
|
||||
|
||||
it('getReserves', async () => {
|
||||
expect(await exchange.getReserves()).to.deep.eq([0, 0].map(n => bigNumberify(n)))
|
||||
|
||||
const token0Amount = expandTo18Decimals(3)
|
||||
const token1Amount = expandTo18Decimals(3)
|
||||
|
||||
expect(await exchange.getReserves()).to.deep.eq([0, 0].map(n => bigNumberify(n)))
|
||||
await addLiquidity(token0Amount, token1Amount)
|
||||
|
||||
expect(await exchange.getReserves()).to.deep.eq([token0Amount, token1Amount])
|
||||
})
|
||||
|
||||
it('getData', async () => {
|
||||
it('getReservesCumulative, getLastUpdate', async () => {
|
||||
expect(await exchange.getReservesCumulative()).to.deep.eq([0, 0].map(n => bigNumberify(n)))
|
||||
expect(await exchange.getLastUpdate()).to.deep.eq([0, 0].map(n => bigNumberify(n)))
|
||||
|
||||
const token0Amount = expandTo18Decimals(3)
|
||||
const token1Amount = expandTo18Decimals(3)
|
||||
|
||||
const preData = await exchange.getData()
|
||||
expect(preData).to.deep.eq([0, 0, 0, 0].map(n => bigNumberify(n)))
|
||||
|
||||
await addLiquidity(token0Amount, token1Amount)
|
||||
|
||||
const data = await exchange.getData()
|
||||
expect(data).to.deep.eq([0, 0].map(n => bigNumberify(n)).concat(data.slice(2, 4)))
|
||||
const reservesCumulativePre = await exchange.getReservesCumulative()
|
||||
const lastUpdatePre = await exchange.getLastUpdate()
|
||||
expect(reservesCumulativePre).to.deep.eq([0, 0].map(n => bigNumberify(n)))
|
||||
|
||||
const dummySwapAmount = bigNumberify(1)
|
||||
await token0.transfer(exchange.address, dummySwapAmount)
|
||||
await exchange.connect(wallet).swap(token0.address, wallet.address)
|
||||
|
||||
const postData = await exchange.getData()
|
||||
expect(postData).to.deep.eq([
|
||||
token0Amount.mul(bigNumberify(2)),
|
||||
token1Amount.mul(bigNumberify(2)),
|
||||
data[2].add(bigNumberify(2)),
|
||||
postData[3]
|
||||
])
|
||||
const reservesCumulativePost = await exchange.getReservesCumulative()
|
||||
const lastUpdatePost = await exchange.getLastUpdate()
|
||||
expect(reservesCumulativePost).to.deep.eq([token0Amount.mul(bigNumberify(2)), token1Amount.mul(bigNumberify(2))])
|
||||
expect(lastUpdatePost).to.deep.eq([lastUpdatePre[0].add(bigNumberify(2)), lastUpdatePost[1]])
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user