add uint128 where applicable

remove mapping in exchange

add example oracle

syntax changes
This commit is contained in:
Noah Zinsmeister
2019-10-30 18:56:12 -04:00
parent 976ac977e9
commit 2c5a1afd39
10 changed files with 320 additions and 195 deletions

View File

@ -5,54 +5,44 @@ import "./interfaces/IERC20.sol";
import "./interfaces/IIncompatibleERC20.sol"; import "./interfaces/IIncompatibleERC20.sol";
import "./libraries/Math.sol"; import "./libraries/Math.sol";
import "./libraries/SafeMath.sol"; import "./libraries/SafeMath128.sol";
import "./libraries/SafeMath256.sol";
import "./token/ERC20.sol"; import "./token/ERC20.sol";
contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) { contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) {
using Math for uint256; using SafeMath128 for uint128;
using SafeMath for uint256; using SafeMath256 for uint256;
event Swap(
address indexed input,
address indexed sender,
address indexed recipient,
uint256 amountInput,
uint256 amountOutput
);
event LiquidityMinted( event LiquidityMinted(
address indexed sender, address indexed sender, address indexed recipient, uint256 liquidity, uint128 amountToken0, uint128 amountToken1
address indexed recipient,
uint256 liquidity,
uint256 amountToken0,
uint256 amountToken1
); );
event LiquidityBurned( event LiquidityBurned(
address indexed sender, address indexed sender, address indexed recipient, uint256 liquidity, uint128 amountToken0, uint128 amountToken1
address indexed recipient, );
uint256 liquidity, event Swap(
uint256 amountToken0, address indexed sender, address indexed recipient, address input, uint128 amountInput, uint128 amountOutput
uint256 amountToken1
); );
struct TokenData { struct TokenData {
uint128 reserve; uint128 token0;
uint128 accumulator; uint128 token1;
} }
struct LastUpdate { struct Time {
uint64 blockNumber; uint64 blockNumber;
uint64 blockTimestamp; // overflows about 280 billion years after the earth's sun explodes 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 factory;
address public token0; address public token0;
address public token1; address public token1;
mapping (address => TokenData) private tokenData; TokenData private reserves;
LastUpdate private lastUpdate; TokenData private reservesCumulative;
Time private lastUpdate;
modifier lock() { modifier lock() {
require(!locked, "UniswapV2: LOCKED"); require(!locked, "UniswapV2: LOCKED");
@ -65,7 +55,7 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) {
factory = msg.sender; 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"); require(token0 == address(0) && token1 == address(0), "UniswapV2: ALREADY_INITIALIZED");
token0 = _token0; token0 = _token0;
token1 = _token1; 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 // 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) { function safeTransfer(address token, address to, uint128 value) private returns (bool result) {
IIncompatibleERC20(token).transfer(to, value); IIncompatibleERC20(token).transfer(to, uint256(value));
assembly { assembly {
switch returndatasize() switch returndatasize()
case 0 { case 0 {
result := not(0) result := not(0) // for no-bool responses, treat as successful
} }
case 32 { case 32 {
returndatacopy(0, 0, 32) returndatacopy(0, 0, 32)
result := mload(0) result := mload(0) // for (presumably) bool responses, return whatever the function returned
} }
default { 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) { function getReserves() external view returns (uint128, uint128) {
reserveToken0 = tokenData[token0].reserve; return (reserves.token0, reserves.token1);
reserveToken1 = tokenData[token1].reserve;
} }
function getData() public view returns ( function getReservesCumulative() external view returns (uint128, uint128) {
uint128 accumulatorToken0, return (reservesCumulative.token0, reservesCumulative.token1);
uint128 accumulatorToken1,
uint64 blockNumber,
uint64 blockTimestamp
) {
accumulatorToken0 = tokenData[token0].accumulator;
accumulatorToken1 = tokenData[token1].accumulator;
blockNumber = lastUpdate.blockNumber;
blockTimestamp = lastUpdate.blockTimestamp;
} }
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 blockNumber = block.number.downcastTo64();
uint64 blocksElapsed = blockNumber - lastUpdate.blockNumber; uint64 blocksElapsed = blockNumber - lastUpdate.blockNumber;
// get token data
TokenData storage tokenDataToken0 = tokenData[token0];
TokenData storage tokenDataToken1 = tokenData[token1];
if (blocksElapsed > 0) { if (blocksElapsed > 0) {
// TODO do edge case math here // if this isn't the first-ever call to this function, update the accumulators
// update accumulators if this isn't the first call to updateData
if (lastUpdate.blockNumber != 0) { if (lastUpdate.blockNumber != 0) {
tokenDataToken0.accumulator += tokenDataToken0.reserve * blocksElapsed; // TODO do edge case math here
tokenDataToken1.accumulator += tokenDataToken1.reserve * blocksElapsed; reservesCumulative.token0 += reserves.token0 * blocksElapsed;
reservesCumulative.token1 += reserves.token1 * blocksElapsed;
} }
// update last update // update last update
@ -131,100 +111,102 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) {
lastUpdate.blockTimestamp = block.timestamp.downcastTo64(); lastUpdate.blockTimestamp = block.timestamp.downcastTo64();
} }
tokenDataToken0.reserve = reserveToken0.downcastTo128(); reserves.token0 = reservesNext.token0;
tokenDataToken1.reserve = reserveToken1.downcastTo128(); reserves.token1 = reservesNext.token1;
} }
function getAmountOutput( function getAmountOutput(
uint256 amountInput, uint128 amountInput, uint128 reserveInput, uint128 reserveOutput
uint256 reserveInput, ) public pure returns (uint128 amountOutput) {
uint256 reserveOutput
) public pure returns (uint256 amountOutput) {
require(amountInput > 0 && reserveInput > 0 && reserveOutput > 0, "UniswapV2: INVALID_VALUE"); require(amountInput > 0 && reserveInput > 0 && reserveOutput > 0, "UniswapV2: INVALID_VALUE");
uint256 fee = 3; // TODO 30 bips for now, think through this later uint256 amountInputWithFee = uint256(amountInput).mul(1000 - 3); // 30 bips for now, TODO think through this later
uint256 amountInputWithFee = amountInput.mul(1000 - fee); uint256 numerator = amountInputWithFee.mul(uint256(reserveOutput));
uint256 numerator = amountInputWithFee.mul(reserveOutput); uint256 denominator = uint256(reserveInput).mul(1000).add(amountInputWithFee);
uint256 denominator = reserveInput.mul(1000).add(amountInputWithFee); amountOutput = numerator.div(denominator).downcastTo128();
amountOutput = numerator.div(denominator);
} }
function mintLiquidity(address recipient) public lock returns (uint256 liquidity) { function mintLiquidity(address recipient) external lock returns (uint256 liquidity) {
// get balances // get balances
uint256 balanceToken0 = IERC20(token0).balanceOf(address(this)); TokenData memory balances = TokenData({
uint256 balanceToken1 = IERC20(token1).balanceOf(address(this)); token0: IERC20(token0).balanceOf(address(this)).downcastTo128(),
token1: IERC20(token1).balanceOf(address(this)).downcastTo128()
});
// get reserves // get amounts sent to be added as liquidity
uint256 reserveToken0 = uint256(tokenData[token0].reserve); TokenData memory amounts = TokenData({
uint256 reserveToken1 = uint256(tokenData[token1].reserve); token0: balances.token0.sub(reserves.token0),
token1: balances.token1.sub(reserves.token1)
// TODO think about what happens if this fails });
// get amounts
uint256 amountToken0 = balanceToken0.sub(reserveToken0);
uint256 amountToken1 = balanceToken1.sub(reserveToken1);
if (totalSupply == 0) { 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 { } else {
// TODO think about "donating" the non-min token amount somehow // TODO is this right?
// TODO think about rounding here // TODO "donate" or ignore the non-min token amount?
// TODO does this round the way we want?
liquidity = Math.min( liquidity = Math.min(
amountToken0.mul(totalSupply).div(reserveToken0), uint256(amounts.token0).mul(totalSupply).div(uint256(reserves.token0)),
amountToken1.mul(totalSupply).div(reserveToken1) uint256(amounts.token1).mul(totalSupply).div(uint256(reserves.token1))
); );
} }
mint(recipient, liquidity); // TODO gas golf? mint(recipient, liquidity); // TODO gas golf?
updateReserves(balances);
updateData(balanceToken0, balanceToken1); emit LiquidityMinted(msg.sender, recipient, liquidity, amounts.token0, amounts.token1);
emit LiquidityMinted(msg.sender, recipient, liquidity, amountToken0, amountToken1);
} }
function burnLiquidity( function burnLiquidity(
uint256 liquidity, uint256 liquidity, address recipient
address recipient ) external lock returns (uint128 amountToken0, uint128 amountToken1) {
) public lock returns (uint256 amountToken0, uint256 amountToken1) {
require(liquidity > 0, "UniswapV2: ZERO_AMOUNT"); require(liquidity > 0, "UniswapV2: ZERO_AMOUNT");
amountToken0 = liquidity.mul(tokenData[token0].reserve).div(totalSupply); // send tokens back
amountToken1 = liquidity.mul(tokenData[token1].reserve).div(totalSupply); // 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? _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); emit LiquidityBurned(msg.sender, recipient, liquidity, amountToken0, amountToken1);
} }
function swap(address input, address recipient) public lock returns (uint256 amountOutput) { function swap(address input, address recipient) external lock returns (uint128 amountOutput) {
require(input == token0 || input == token1, "UniswapV2: INVALID_INPUT"); uint128 inputBalance = IERC20(input).balanceOf(address(this)).downcastTo128();
address output = input == token0 ? token1 : token0;
// get input balance uint128 amountInput;
uint256 balanceInput = IERC20(input).balanceOf(address(this)); 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 amountInput = inputBalance.sub(reserves.token1);
uint256 reserveInput = uint256(tokenData[input].reserve); amountOutput = getAmountOutput(amountInput, reserves.token1, reserves.token0);
uint256 reserveOutput = uint256(tokenData[output].reserve); 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 updateReserves(balances);
// get input amount emit Swap(msg.sender, recipient, input, amountInput, amountOutput);
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);
} }
} }

View File

@ -15,6 +15,7 @@ contract UniswapV2Factory is IUniswapV2Factory {
bytes public exchangeBytecode; bytes public exchangeBytecode;
uint256 public chainId; uint256 public chainId;
uint256 public exchangeCount; uint256 public exchangeCount;
mapping (address => Pair) private exchangeToPair; mapping (address => Pair) private exchangeToPair;
mapping (address => mapping(address => address)) private token0ToToken1ToExchange; mapping (address => mapping(address => address)) private token0ToToken1ToExchange;
@ -24,25 +25,15 @@ contract UniswapV2Factory is IUniswapV2Factory {
chainId = _chainId; chainId = _chainId;
} }
function orderTokens(address tokenA, address tokenB) private pure returns (Pair memory pair) { function getPair(address tokenA, address tokenB) private pure returns (Pair memory) {
pair = tokenA < tokenB ? Pair({ token0: tokenA, token1: tokenB }) : Pair({ token0: tokenB, token1: tokenA }); return tokenA < tokenB ? Pair({ token0: tokenA, token1: tokenB }) : Pair({ token0: tokenB, token1: tokenA });
} }
function getTokens(address exchange) public view returns (address token0, address token1) { function createExchange(address tokenA, address tokenB) external returns (address exchange) {
Pair storage pair = exchangeToPair[exchange]; require(tokenA != tokenB, "UniswapV2Factory: SAME_ADDRESS");
(token0, token1) = (pair.token0, pair.token1); require(tokenA != address(0) && tokenB != address(0), "UniswapV2Factory: ZERO_ADDRESS");
}
function getExchange(address tokenA, address tokenB) public view returns (address exchange) { Pair memory pair = getPair(tokenA, tokenB);
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);
require(token0ToToken1ToExchange[pair.token0][pair.token1] == address(0), "UniswapV2Factory: EXCHANGE_EXISTS"); 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++); 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];
}
} }

View File

@ -1,51 +1,30 @@
pragma solidity 0.5.12; pragma solidity 0.5.12;
interface IUniswapV2 { interface IUniswapV2 {
event Swap(
address indexed input,
address indexed sender,
address indexed recipient,
uint256 amountInput,
uint256 amountOutput
);
event LiquidityMinted( event LiquidityMinted(
address indexed sender, address indexed sender, address indexed recipient, uint256 liquidity, uint128 amountToken0, uint128 amountToken1
address indexed recipient,
uint256 liquidity,
uint256 amountToken0,
uint256 amountToken1
); );
event LiquidityBurned( event LiquidityBurned(
address indexed sender, address indexed sender, address indexed recipient, uint256 liquidity, uint128 amountToken0, uint128 amountToken1
address indexed recipient, );
uint256 liquidity, event Swap(
uint256 amountToken0, address indexed sender, address indexed recipient, address input, uint128 amountInput, uint128 amountOutput
uint256 amountToken1
); );
function factory() external view returns (address); function factory() external view returns (address);
function token0() external view returns (address); function token0() external view returns (address);
function token1() external view returns (address); function token1() external view returns (address);
function getReserves() external view returns (uint128 reserveToken0, uint128 reserveToken1); function getReserves() external view returns (uint128, uint128);
function getData() external view returns ( function getReservesCumulative() external view returns (uint128, uint128);
uint128 accumulatorToken0, function getLastUpdate() external view returns (uint64, uint64);
uint128 accumulatorToken1,
uint64 blockNumber,
uint64 blockTimestamp
);
function getAmountOutput( function getAmountOutput(
uint256 amountInput, uint128 amountInput, uint128 reserveInput, uint128 reserveOutput
uint256 reserveInput, ) external pure returns (uint128 amountOutput);
uint256 reserveOutput
) external pure returns (uint256 amountOutput);
function initialize(address _token0, address _token1, uint256 chainId) external;
function mintLiquidity(address recipient) external returns (uint256 liquidity); function mintLiquidity(address recipient) external returns (uint256 liquidity);
function burnLiquidity( function burnLiquidity(
uint256 liquidity, uint256 liquidity, address recipient
address recipient ) external returns (uint128 amountToken0, uint128 amountToken1);
) external returns (uint256 amountToken0, uint256 amountToken1); function swap(address input, address recipient) external returns (uint128 amountOutput);
function swap(address input, address recipient) external returns (uint256 amountOutput);
} }

View File

@ -6,8 +6,9 @@ interface IUniswapV2Factory {
function exchangeBytecode() external view returns (bytes memory); function exchangeBytecode() external view returns (bytes memory);
function chainId() external view returns (uint256); function chainId() external view returns (uint256);
function exchangeCount() 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 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);
} }

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

View File

@ -1,7 +1,7 @@
// https://github.com/OpenZeppelin/openzeppelin-contracts/blob/2f9ae975c8bdc5c7f7fa26204896f6c717f07164/contracts/math/SafeMath.sol // https://github.com/OpenZeppelin/openzeppelin-contracts/blob/2f9ae975c8bdc5c7f7fa26204896f6c717f07164/contracts/math/SafeMath.sol
pragma solidity 0.5.12; pragma solidity 0.5.12;
library SafeMath { library SafeMath256 {
function add(uint256 a, uint256 b) internal pure returns (uint256) { function add(uint256 a, uint256 b) internal pure returns (uint256) {
uint256 c = a + b; uint256 c = a + b;
require(c >= a, "SafeMath: addition overflow"); require(c >= a, "SafeMath: addition overflow");

74
contracts/test/Oracle.sol Normal file
View 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);
}
}

View File

@ -4,10 +4,10 @@ pragma solidity 0.5.12;
import "../interfaces/IERC20.sol"; import "../interfaces/IERC20.sol";
import "../libraries/SafeMath.sol"; import "../libraries/SafeMath256.sol";
contract ERC20 is IERC20 { contract ERC20 is IERC20 {
using SafeMath for uint256; using SafeMath256 for uint256;
// ERC-20 data // ERC-20 data
string public name; string public name;

61
test/Oracle.ts Normal file
View 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])
})
})

View File

@ -59,7 +59,7 @@ describe('UniswapV2', () => {
'0996006981039903216' '0996006981039903216'
].map((n: string) => bigNumberify(n)) ].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) expect(outputs).to.deep.eq(expectedOutputs)
}) })
@ -96,7 +96,7 @@ describe('UniswapV2', () => {
await token0.transfer(exchange.address, swapAmount) await token0.transfer(exchange.address, swapAmount)
await expect(exchange.connect(wallet).swap(token0.address, wallet.address)) await expect(exchange.connect(wallet).swap(token0.address, wallet.address))
.to.emit(exchange, 'Swap') .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 token0.balanceOf(exchange.address)).to.eq(token0Amount.add(swapAmount))
expect(await token1.balanceOf(exchange.address)).to.eq(token1Amount.sub(expectedOutputAmount)) expect(await token1.balanceOf(exchange.address)).to.eq(token1Amount.sub(expectedOutputAmount))
@ -128,36 +128,34 @@ describe('UniswapV2', () => {
}) })
it('getReserves', async () => { it('getReserves', async () => {
expect(await exchange.getReserves()).to.deep.eq([0, 0].map(n => bigNumberify(n)))
const token0Amount = expandTo18Decimals(3) const token0Amount = expandTo18Decimals(3)
const token1Amount = expandTo18Decimals(3) const token1Amount = expandTo18Decimals(3)
expect(await exchange.getReserves()).to.deep.eq([0, 0].map(n => bigNumberify(n)))
await addLiquidity(token0Amount, token1Amount) await addLiquidity(token0Amount, token1Amount)
expect(await exchange.getReserves()).to.deep.eq([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 token0Amount = expandTo18Decimals(3)
const token1Amount = 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) await addLiquidity(token0Amount, token1Amount)
const data = await exchange.getData() const reservesCumulativePre = await exchange.getReservesCumulative()
expect(data).to.deep.eq([0, 0].map(n => bigNumberify(n)).concat(data.slice(2, 4))) const lastUpdatePre = await exchange.getLastUpdate()
expect(reservesCumulativePre).to.deep.eq([0, 0].map(n => bigNumberify(n)))
const dummySwapAmount = bigNumberify(1) const dummySwapAmount = bigNumberify(1)
await token0.transfer(exchange.address, dummySwapAmount) await token0.transfer(exchange.address, dummySwapAmount)
await exchange.connect(wallet).swap(token0.address, wallet.address) await exchange.connect(wallet).swap(token0.address, wallet.address)
const postData = await exchange.getData() const reservesCumulativePost = await exchange.getReservesCumulative()
expect(postData).to.deep.eq([ const lastUpdatePost = await exchange.getLastUpdate()
token0Amount.mul(bigNumberify(2)), expect(reservesCumulativePost).to.deep.eq([token0Amount.mul(bigNumberify(2)), token1Amount.mul(bigNumberify(2))])
token1Amount.mul(bigNumberify(2)), expect(lastUpdatePost).to.deep.eq([lastUpdatePre[0].add(bigNumberify(2)), lastUpdatePost[1]])
data[2].add(bigNumberify(2)),
postData[3]
])
}) })
}) })