add downcast helpers
add safe transfer for bad tokens reorder updateData order refactor tests to be more explicit
This commit is contained in:
@ -2,6 +2,7 @@ pragma solidity 0.5.12;
|
|||||||
|
|
||||||
import "./interfaces/IUniswapV2.sol";
|
import "./interfaces/IUniswapV2.sol";
|
||||||
import "./interfaces/IERC20.sol";
|
import "./interfaces/IERC20.sol";
|
||||||
|
import "./interfaces/IIncompatibleERC20.sol";
|
||||||
|
|
||||||
import "./libraries/Math.sol";
|
import "./libraries/Math.sol";
|
||||||
import "./libraries/SafeMath.sol";
|
import "./libraries/SafeMath.sol";
|
||||||
@ -71,28 +72,46 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) {
|
|||||||
initialize(chainId);
|
initialize(chainId);
|
||||||
}
|
}
|
||||||
|
|
||||||
function updateData(uint256 reserveToken0, uint256 reserveToken1) private {
|
// https://medium.com/coinmonks/missing-return-value-bug-at-least-130-tokens-affected-d67bf08521ca
|
||||||
require(reserveToken0 <= uint128(-1) && reserveToken1 <= uint128(-1), "UniswapV2: OVERFLOW");
|
function safeTransfer(address token, address to, uint value) private returns (bool result) {
|
||||||
|
IIncompatibleERC20(token).transfer(to, value);
|
||||||
|
|
||||||
require(block.number <= uint64(-1), "UniswapV2: BLOCK_NUMBER_TOO_HIGH");
|
assembly {
|
||||||
uint64 blocksElapsed = uint64(block.number) - lastUpdate.blockNumber;
|
switch returndatasize()
|
||||||
|
case 0 {
|
||||||
|
result := not(0)
|
||||||
|
}
|
||||||
|
case 32 {
|
||||||
|
returndatacopy(0, 0, 32)
|
||||||
|
result := mload(0)
|
||||||
|
}
|
||||||
|
default {
|
||||||
|
revert(0, 0)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function updateData(uint256 reserveToken0, uint256 reserveToken1) private {
|
||||||
|
uint64 blockNumber = (block.number).downcastTo64();
|
||||||
|
uint64 blocksElapsed = blockNumber - lastUpdate.blockNumber;
|
||||||
|
|
||||||
// get token data
|
// get token data
|
||||||
TokenData storage tokenDataToken0 = tokenData[token0];
|
TokenData storage tokenDataToken0 = tokenData[token0];
|
||||||
TokenData storage tokenDataToken1 = tokenData[token1];
|
TokenData storage tokenDataToken1 = tokenData[token1];
|
||||||
// TODO does this have a gas impact because it unnecessarily triggers for the 2nd+ trades within a block?
|
|
||||||
|
if (blocksElapsed > 0) {
|
||||||
|
// TODO do edge case math here
|
||||||
// update accumulators
|
// update accumulators
|
||||||
tokenDataToken0.accumulator += tokenDataToken0.reserve * blocksElapsed;
|
tokenDataToken0.accumulator += tokenDataToken0.reserve * blocksElapsed;
|
||||||
tokenDataToken1.accumulator += tokenDataToken1.reserve * blocksElapsed;
|
tokenDataToken1.accumulator += tokenDataToken1.reserve * blocksElapsed;
|
||||||
// update reserves
|
|
||||||
tokenDataToken0.reserve = uint128(reserveToken0);
|
|
||||||
tokenDataToken1.reserve = uint128(reserveToken1);
|
|
||||||
|
|
||||||
if (blocksElapsed > 0) {
|
// update last update
|
||||||
require(block.timestamp <= uint64(-1), "UniswapV2: BLOCK_TIMESTAMP_TOO_HIGH");
|
lastUpdate.blockNumber = blockNumber;
|
||||||
lastUpdate.blockNumber = uint64(block.number);
|
lastUpdate.blockTimestamp = (block.timestamp).downcastTo64();
|
||||||
lastUpdate.blockTimestamp = uint64(block.timestamp);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
tokenDataToken0.reserve = reserveToken0.downcastTo128();
|
||||||
|
tokenDataToken1.reserve = reserveToken1.downcastTo128();
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO merge/sync/donate function? think about the difference between over/under cases
|
// TODO merge/sync/donate function? think about the difference between over/under cases
|
||||||
@ -118,6 +137,7 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) {
|
|||||||
uint256 reserveToken0 = uint256(tokenData[token0].reserve);
|
uint256 reserveToken0 = uint256(tokenData[token0].reserve);
|
||||||
uint256 reserveToken1 = uint256(tokenData[token1].reserve);
|
uint256 reserveToken1 = uint256(tokenData[token1].reserve);
|
||||||
|
|
||||||
|
// TODO think about what happens if this fails
|
||||||
// get amounts
|
// get amounts
|
||||||
uint256 amountToken0 = balanceToken0.sub(reserveToken0);
|
uint256 amountToken0 = balanceToken0.sub(reserveToken0);
|
||||||
uint256 amountToken1 = balanceToken1.sub(reserveToken1);
|
uint256 amountToken1 = balanceToken1.sub(reserveToken1);
|
||||||
@ -150,8 +170,8 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) {
|
|||||||
amountToken1 = liquidity.mul(tokenData[token1].reserve).div(totalSupply);
|
amountToken1 = liquidity.mul(tokenData[token1].reserve).div(totalSupply);
|
||||||
|
|
||||||
burn(liquidity); // TODO gas golf?
|
burn(liquidity); // TODO gas golf?
|
||||||
require(IERC20(token0).transfer(recipient, amountToken0), "UniswapV2: TRANSFER_FAILED");
|
require(safeTransfer(token0, recipient, amountToken0), "UniswapV2: TRANSFER_FAILED");
|
||||||
require(IERC20(token1).transfer(recipient, amountToken1), "UniswapV2: TRANSFER_FAILED");
|
require(safeTransfer(token1, recipient, amountToken1), "UniswapV2: TRANSFER_FAILED");
|
||||||
|
|
||||||
// get balances
|
// get balances
|
||||||
uint256 balanceToken0 = IERC20(token0).balanceOf(address(this));
|
uint256 balanceToken0 = IERC20(token0).balanceOf(address(this));
|
||||||
@ -165,22 +185,24 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) {
|
|||||||
require(input == token0 || input == token1, "UniswapV2: INVALID_INPUT");
|
require(input == token0 || input == token1, "UniswapV2: INVALID_INPUT");
|
||||||
address output = input == token0 ? token1 : token0;
|
address output = input == token0 ? token1 : token0;
|
||||||
|
|
||||||
// get balances
|
// get input balance
|
||||||
uint256 balanceInput = IERC20(input).balanceOf(address(this));
|
uint256 balanceInput = IERC20(input).balanceOf(address(this));
|
||||||
|
|
||||||
// get reserves
|
// get reserves
|
||||||
uint256 reserveInput = uint256(tokenData[input].reserve);
|
uint256 reserveInput = uint256(tokenData[input].reserve);
|
||||||
uint256 reserveOutput = uint256(tokenData[output].reserve);
|
uint256 reserveOutput = uint256(tokenData[output].reserve);
|
||||||
|
|
||||||
|
// TODO think about what happens if this fails
|
||||||
// get input amount
|
// get input amount
|
||||||
uint256 amountInput = balanceInput.sub(reserveInput); // TODO think through edge cases here
|
uint256 amountInput = balanceInput.sub(reserveInput);
|
||||||
require(amountInput > 0, "UniswapV2: ZERO_AMOUNT");
|
require(amountInput > 0, "UniswapV2: ZERO_AMOUNT");
|
||||||
|
|
||||||
// calculate output amount and send to the recipient
|
// calculate output amount and send to the recipient
|
||||||
amountOutput = getAmountOutput(amountInput, reserveInput, reserveOutput);
|
amountOutput = getAmountOutput(amountInput, reserveInput, reserveOutput);
|
||||||
require(IERC20(output).transfer(recipient, amountOutput), "UniswapV2: TRANSFER_FAILED"); // TODO fallback here
|
require(safeTransfer(output, recipient, amountOutput), "UniswapV2: TRANSFER_FAILED");
|
||||||
|
|
||||||
// update data
|
// update data
|
||||||
|
// TODO re-fetch input balance here?
|
||||||
uint256 balanceOutput = IERC20(output).balanceOf(address(this));
|
uint256 balanceOutput = IERC20(output).balanceOf(address(this));
|
||||||
input == token0 ? updateData(balanceInput, balanceOutput) : updateData(balanceOutput, balanceInput);
|
input == token0 ? updateData(balanceInput, balanceOutput) : updateData(balanceOutput, balanceInput);
|
||||||
|
|
||||||
|
|||||||
5
contracts/interfaces/IIncompatibleERC20.sol
Normal file
5
contracts/interfaces/IIncompatibleERC20.sol
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
pragma solidity 0.5.12;
|
||||||
|
|
||||||
|
interface IIncompatibleERC20 {
|
||||||
|
function transfer(address to, uint256 value) external;
|
||||||
|
}
|
||||||
@ -26,4 +26,14 @@ library SafeMath {
|
|||||||
uint256 c = a / b;
|
uint256 c = a / b;
|
||||||
return c;
|
return c;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function downcastTo64(uint256 a) internal pure returns (uint64) {
|
||||||
|
require(a <= uint64(-1), "SafeMath: downcast overflow");
|
||||||
|
return uint64(a);
|
||||||
|
}
|
||||||
|
|
||||||
|
function downcastTo128(uint256 a) internal pure returns (uint128) {
|
||||||
|
require(a <= uint128(-1), "SafeMath: downcast overflow");
|
||||||
|
return uint128(a);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -36,9 +36,6 @@ describe('UniswapV2Factory', () => {
|
|||||||
factory = await deployContract(wallet, UniswapV2Factory, [bytecode, chainId], {
|
factory = await deployContract(wallet, UniswapV2Factory, [bytecode, chainId], {
|
||||||
gasLimit: (provider._web3Provider as any).options.gasLimit
|
gasLimit: (provider._web3Provider as any).options.gasLimit
|
||||||
})
|
})
|
||||||
|
|
||||||
expect(await factory.exchangeBytecode()).to.eq(bytecode)
|
|
||||||
expect(await factory.exchangeCount()).to.eq(0)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
async function createExchange(tokens: string[]) {
|
async function createExchange(tokens: string[]) {
|
||||||
@ -64,6 +61,12 @@ describe('UniswapV2Factory', () => {
|
|||||||
expect(await exchange.token1()).to.eq(dummyTokens[1])
|
expect(await exchange.token1()).to.eq(dummyTokens[1])
|
||||||
}
|
}
|
||||||
|
|
||||||
|
it('exchangeBytecode, chainId, exchangeCount', async () => {
|
||||||
|
expect(await factory.exchangeBytecode()).to.eq(bytecode)
|
||||||
|
expect(await factory.chainId()).to.eq(chainId)
|
||||||
|
expect(await factory.exchangeCount()).to.eq(0)
|
||||||
|
})
|
||||||
|
|
||||||
it('createExchange', async () => {
|
it('createExchange', async () => {
|
||||||
await createExchange(dummyTokens)
|
await createExchange(dummyTokens)
|
||||||
})
|
})
|
||||||
|
|||||||
Reference in New Issue
Block a user