approveMeta -> permit

call factory for fee logic

fail on balances > max112

add skim

use reserves in burnLiquidity
This commit is contained in:
Noah Zinsmeister
2019-12-13 17:25:04 -05:00
parent 467e15b931
commit 5b15d59773
9 changed files with 42 additions and 45 deletions

View File

@ -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");

View File

@ -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));
}
}

View File

@ -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];

View File

@ -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;

View File

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

View File

@ -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;

View File

@ -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))

View File

@ -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 () => {

View File

@ -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]
)
)
]