approveMeta -> permit
call factory for fee logic fail on balances > max112 add skim use reserves in burnLiquidity
This commit is contained in:
@ -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");
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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;
|
||||
|
||||
@ -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))
|
||||
|
||||
@ -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 () => {
|
||||
|
||||
@ -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]
|
||||
)
|
||||
)
|
||||
]
|
||||
|
||||
Reference in New Issue
Block a user