diff --git a/contracts/ERC20.sol b/contracts/ERC20.sol index 27ce46d..a0010a1 100644 --- a/contracts/ERC20.sol +++ b/contracts/ERC20.sol @@ -14,8 +14,8 @@ contract ERC20 is IERC20 { mapping (address => mapping (address => uint)) public allowance; bytes32 public DOMAIN_SEPARATOR; - // keccak256("Approve(address owner,address spender,uint256 value,uint256 nonce,uint256 expiration)"); - bytes32 public constant APPROVE_TYPEHASH = 0x25a0822e8c2ed7ff64a57c55df37ff176282195b9e0c9bb770ed24a300c89762; + // keccak256("Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 expiration)"); + bytes32 public constant PERMIT_TYPEHASH = 0xf0a99559fef847d211c4182aa5791e1529af3ce414597e8210f570d662791c01; mapping (address => uint) public nonces; event Transfer(address indexed from, address indexed to, uint value); @@ -91,7 +91,7 @@ contract ERC20 is IERC20 { _burn(from, value); } - function approveMeta( + function permit( address owner, address spender, uint value, uint nonce, uint expiration, uint8 v, bytes32 r, bytes32 s ) external @@ -103,7 +103,7 @@ contract ERC20 is IERC20 { bytes32 digest = keccak256(abi.encodePacked( "\x19\x01", DOMAIN_SEPARATOR, - keccak256(abi.encode(APPROVE_TYPEHASH, owner, spender, value, nonce, expiration)) + keccak256(abi.encode(PERMIT_TYPEHASH, owner, spender, value, nonce, expiration)) )); address recoveredAddress = ecrecover(digest, v, r, s); require(recoveredAddress != address(0) && recoveredAddress == owner, "ERC20: INVALID_SIGNATURE"); diff --git a/contracts/UniswapV2.sol b/contracts/UniswapV2.sol index 4f03970..e5298c5 100644 --- a/contracts/UniswapV2.sol +++ b/contracts/UniswapV2.sol @@ -13,7 +13,6 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) { address public factory; address public token0; address public token1; - address public feeAddress; uint112 public reserve0; uint112 public reserve1; @@ -22,14 +21,13 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) { uint public price1CumulativeLast; uint private invariantLast; - bool private notEntered = true; - bool private feeOn; event ReservesUpdated(uint112 reserve0, uint112 reserve1); event LiquidityMinted(address indexed sender, uint amount0, uint amount1); event LiquidityBurned(address indexed sender, address indexed recipient, uint amount0, uint amount1); event Swap(address indexed sender, address indexed recipient, address indexed input, uint amount0, uint amount1); + bool private notEntered = true; modifier lock() { require(notEntered, "UniswapV2: LOCKED"); notEntered = false; @@ -42,11 +40,10 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) { blockNumberLast = uint32(block.number % 2**32); } - function initialize(address _token0, address _token1, address _feeAddress) external { + function initialize(address _token0, address _token1) external { require(msg.sender == factory && token0 == address(0) && token1 == address(0), "UniswapV2: FORBIDDEN"); token0 = _token0; token1 = _token1; - feeAddress = _feeAddress; } function safeTransfer(address token, address to, uint value) private { @@ -68,6 +65,7 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) { // increment price accumulators if necessary, and update reserves function update(uint balance0, uint balance1) private { + require(balance0 <= uint112(-1) && balance1 <= uint112(-1), "UniswapV2: EXCESS_BALANCES"); uint32 blockNumber = uint32(block.number % 2**32); uint32 blocksElapsed = blockNumber - blockNumberLast; // overflow is desired if (blocksElapsed > 0 && reserve0 != 0 && reserve1 != 0) { @@ -75,36 +73,33 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) { price0CumulativeLast += uint256(UQ112x112.encode(reserve0).qdiv(reserve1)) * blocksElapsed; price1CumulativeLast += uint256(UQ112x112.encode(reserve1).qdiv(reserve0)) * blocksElapsed; } - reserve0 = Math.clamp112(balance0); - reserve1 = Math.clamp112(balance1); + reserve0 = uint112(balance0); + reserve1 = uint112(balance1); blockNumberLast = blockNumber; emit ReservesUpdated(reserve0, reserve1); } // mint liquidity equivalent to 20% of accumulated fees function mintFeeLiquidity() private { - if (feeOn) { + if (invariantLast != 0) { uint invariant = Math.sqrt(uint(reserve0).mul(reserve1)); if (invariant > invariantLast) { uint numerator = totalSupply.mul(invariant.sub(invariantLast)); uint denominator = uint256(4).mul(invariant).add(invariantLast); uint liquidity = numerator / denominator; - if (liquidity > 0) _mint(feeAddress, liquidity); + if (liquidity > 0) _mint(IUniswapV2Factory(factory).feeAddress(), liquidity); } - } else if (IUniswapV2Factory(factory).feeOn()) { - feeOn = true; - invariantLast = Math.sqrt(uint(reserve0).mul(reserve1)); } } function mintLiquidity(address recipient) external lock returns (uint liquidity) { uint balance0 = IERC20(token0).balanceOf(address(this)); uint balance1 = IERC20(token1).balanceOf(address(this)); - require(balance0 <= uint112(-1) && balance1 <= uint112(-1), "UniswapV2: EXCESS_BALANCES"); uint amount0 = balance0.sub(reserve0); uint amount1 = balance1.sub(reserve1); - mintFeeLiquidity(); + bool feeOn = IUniswapV2Factory(factory).feeOn(); + if (feeOn) mintFeeLiquidity(); liquidity = totalSupply == 0 ? Math.sqrt(amount0.mul(amount1)) : Math.min(amount0.mul(totalSupply) / reserve0, amount1.mul(totalSupply) / reserve1); @@ -118,13 +113,11 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) { function burnLiquidity(address recipient) external lock returns (uint amount0, uint amount1) { uint liquidity = balanceOf[address(this)]; - uint balance0 = IERC20(token0).balanceOf(address(this)); - uint balance1 = IERC20(token1).balanceOf(address(this)); - require(balance0 >= reserve0 && balance1 >= reserve1, "UniswapV2: INSUFFICIENT_BALANCES"); - mintFeeLiquidity(); - amount0 = liquidity.mul(balance0) / totalSupply; // intentionally using balances not reserves - amount1 = liquidity.mul(balance1) / totalSupply; // intentionally using balances not reserves + bool feeOn = IUniswapV2Factory(factory).feeOn(); + if (feeOn) mintFeeLiquidity(); + amount0 = liquidity.mul(reserve0) / totalSupply; + amount1 = liquidity.mul(reserve1) / totalSupply; require(amount0 > 0 && amount1 > 0, "UniswapV2: INSUFFICIENT_AMOUNTS"); safeTransfer(token0, recipient, amount0); safeTransfer(token1, recipient, amount1); @@ -161,13 +154,23 @@ contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) { emit Swap(msg.sender, recipient, token1, amount0, amount1); } + // recover from the case where the contract is force sent tokens so that its balance exceeds uint112(-1) + function skim(address recipient) external lock { + uint balance0 = IERC20(token0).balanceOf(address(this)); + uint balance1 = IERC20(token1).balanceOf(address(this)); + if (balance0 > uint112(-1)) safeTransfer(token0, recipient, balance0.sub(uint112(-1))); + if (balance1 > uint112(-1)) safeTransfer(token1, recipient, balance1.sub(uint112(-1))); + } + // almost certainly never needs to be called, but can be helpful for oracles and possibly some weird tokens function sync() external lock { update(IERC20(token0).balanceOf(address(this)), IERC20(token1).balanceOf(address(this))); } - function sweep() external lock { - mintFeeLiquidity(); + // mint fees without having to wait for {mint,burn}Liquidity + function sort() external lock { + bool feeOn = IUniswapV2Factory(factory).feeOn(); + if (feeOn) mintFeeLiquidity(); if (feeOn) invariantLast = Math.sqrt(uint(reserve0).mul(reserve1)); } } diff --git a/contracts/UniswapV2Factory.sol b/contracts/UniswapV2Factory.sol index 13c0725..3459f65 100644 --- a/contracts/UniswapV2Factory.sol +++ b/contracts/UniswapV2Factory.sol @@ -49,7 +49,7 @@ contract UniswapV2Factory is IUniswapV2Factory { assembly { // solium-disable-line security/no-inline-assembly exchange := create2(0, add(exchangeBytecodeMemory, 32), mload(exchangeBytecodeMemory), salt) } - UniswapV2(exchange).initialize(token0, token1, feeAddress); + UniswapV2(exchange).initialize(token0, token1); _getExchange[token0][token1] = exchange; _getTokens[exchange] = [token0, token1]; diff --git a/contracts/interfaces/IERC20.sol b/contracts/interfaces/IERC20.sol index 9017ba0..ce52ffc 100644 --- a/contracts/interfaces/IERC20.sol +++ b/contracts/interfaces/IERC20.sol @@ -12,7 +12,7 @@ interface IERC20 { function allowance(address owner, address spender) external view returns (uint); function DOMAIN_SEPARATOR() external view returns (bytes32); - function APPROVE_TYPEHASH() external pure returns (bytes32); + function PERMIT_TYPEHASH() external pure returns (bytes32); function nonces(address owner) external view returns (uint); function transfer(address to, uint value) external returns (bool); @@ -20,7 +20,7 @@ interface IERC20 { function approve(address spender, uint value) external returns (bool); function transferFrom(address from, address to, uint value) external returns (bool); function burnFrom(address from, uint value) external; - function approveMeta( + function permit( address owner, address spender, uint value, uint nonce, uint expiration, uint8 v, bytes32 r, bytes32 s ) external; diff --git a/contracts/interfaces/IUniswapV2.sol b/contracts/interfaces/IUniswapV2.sol index f480f9c..e38f1b3 100644 --- a/contracts/interfaces/IUniswapV2.sol +++ b/contracts/interfaces/IUniswapV2.sol @@ -23,6 +23,7 @@ interface IUniswapV2 { function swap0(address recipient) external returns (uint amount1); function swap1(address recipient) external returns (uint amount0); + function skim(address recipient) external; function sync() external; - function sweep() external; + function sort() external; } diff --git a/contracts/libraries/Math.sol b/contracts/libraries/Math.sol index fea46bb..434100d 100644 --- a/contracts/libraries/Math.sol +++ b/contracts/libraries/Math.sol @@ -5,10 +5,6 @@ library Math { z = x <= y ? x : y; } - function clamp112(uint y) internal pure returns (uint112 z) { - z = y <= uint112(-1) ? uint112(y) : uint112(-1); - } - function sqrt(uint y) internal pure returns (uint z) { if (y > 3) { uint x = (y + 1) / 2; diff --git a/test/ERC20.spec.ts b/test/ERC20.spec.ts index e58bc04..b746551 100644 --- a/test/ERC20.spec.ts +++ b/test/ERC20.spec.ts @@ -35,7 +35,7 @@ describe('ERC20', () => { ]) }) - it('name, symbol, decimals, totalSupply, balanceOf, DOMAIN_SEPARATOR, APPROVE_TYPEHASH', async () => { + it('name, symbol, decimals, totalSupply, balanceOf, DOMAIN_SEPARATOR, PERMIT_TYPEHASH', async () => { const name = await token.name() expect(name).to.eq(TOKEN_DETAILS.name) expect(await token.symbol()).to.eq(TOKEN_DETAILS.symbol) @@ -58,8 +58,8 @@ describe('ERC20', () => { ) ) ) - expect(await token.APPROVE_TYPEHASH()).to.eq( - keccak256(toUtf8Bytes('Approve(address owner,address spender,uint256 value,uint256 nonce,uint256 expiration)')) + expect(await token.PERMIT_TYPEHASH()).to.eq( + keccak256(toUtf8Bytes('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 expiration)')) ) }) @@ -124,7 +124,7 @@ describe('ERC20', () => { await expect(token.connect(other).transfer(wallet.address, 1)).to.be.revertedWith('ds-math-sub-underflow') }) - it('approveMeta', async () => { + it('permit', async () => { const nonce = await token.nonces(wallet.address) const expiration = MaxUint256 const digest = await getApprovalDigest( @@ -136,9 +136,7 @@ describe('ERC20', () => { const { v, r, s } = ecsign(Buffer.from(digest.slice(2), 'hex'), Buffer.from(wallet.privateKey.slice(2), 'hex')) - await expect( - token.approveMeta(wallet.address, other.address, TEST_AMOUNT, nonce, expiration, v, hexlify(r), hexlify(s)) - ) + await expect(token.permit(wallet.address, other.address, TEST_AMOUNT, nonce, expiration, v, hexlify(r), hexlify(s))) .to.emit(token, 'Approval') .withArgs(wallet.address, other.address, TEST_AMOUNT) expect(await token.nonces(wallet.address)).to.eq(bigNumberify(1)) diff --git a/test/UniswapV2Factory.spec.ts b/test/UniswapV2Factory.spec.ts index 2f23e19..6056909 100644 --- a/test/UniswapV2Factory.spec.ts +++ b/test/UniswapV2Factory.spec.ts @@ -67,7 +67,6 @@ describe('UniswapV2Factory', () => { expect(await exchange.factory()).to.eq(factory.address) expect(await exchange.token0()).to.eq(TEST_ADDRESSES.token0) expect(await exchange.token1()).to.eq(TEST_ADDRESSES.token1) - expect(await exchange.feeAddress()).to.eq(wallet.address) } it('createExchange', async () => { diff --git a/test/shared/utilities.ts b/test/shared/utilities.ts index b9928e1..1f54aea 100644 --- a/test/shared/utilities.ts +++ b/test/shared/utilities.ts @@ -13,8 +13,8 @@ export function expandTo18Decimals(n: number): BigNumber { return bigNumberify(n).mul(bigNumberify(10).pow(18)) } -const APPROVE_TYPEHASH = keccak256( - toUtf8Bytes('Approve(address owner,address spender,uint256 value,uint256 nonce,uint256 expiration)') +const PERMIT_TYPEHASH = keccak256( + toUtf8Bytes('Permit(address owner,address spender,uint256 value,uint256 nonce,uint256 expiration)') ) const GET_DOMAIN_SEPARATOR = async (token: Contract) => { @@ -73,7 +73,7 @@ export async function getApprovalDigest( keccak256( defaultAbiCoder.encode( ['bytes32', 'address', 'address', 'uint256', 'uint256', 'uint256'], - [APPROVE_TYPEHASH, approve.owner, approve.spender, approve.value, nonce, expiration] + [PERMIT_TYPEHASH, approve.owner, approve.spender, approve.value, nonce, expiration] ) ) ]