add more tests

This commit is contained in:
Noah Zinsmeister
2019-12-13 14:39:09 -05:00
parent 4c77c630fb
commit f263dab9b5
7 changed files with 201 additions and 98 deletions

View File

@ -29,4 +29,4 @@ yarn test
- [dapp-bin math](https://github.com/ethereum/dapp-bin/pull/50)
- [OpenZeppelin ECDSA](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/81b1e4810761b088922dbd19a0642873ea581176/contracts/cryptography/ECDSA.sol)
- [OpenZeppelin SafeERC20](https://github.com/OpenZeppelin/openzeppelin-contracts/blob/81b1e4810761b088922dbd19a0642873ea581176/contracts/token/ERC20/SafeERC20.sol)
- [DAI token](https://github.com/makerdao/dss/blob/17be7db1c663d8069308c6b78fa5c5f9d71134a3/src/dai.sol)
- [DAI](https://github.com/makerdao/dss/blob/17be7db1c663d8069308c6b78fa5c5f9d71134a3/src/dai.sol)

View File

@ -1,10 +1,10 @@
pragma solidity 0.5.14;
import "./interfaces/IUniswapV2.sol";
import "./interfaces/IUniswapV2Factory.sol";
import "./ERC20.sol";
import "./libraries/UQ112x112.sol";
import "./libraries/Math.sol";
import "./interfaces/IUniswapV2Factory.sol";
contract UniswapV2 is IUniswapV2, ERC20("Uniswap V2", "UNI-V2", 18, 0) {
using SafeMath for uint;

View File

@ -3,7 +3,7 @@ import chai from 'chai'
import { solidity, createMockProvider, getWallets, deployContract } from 'ethereum-waffle'
import { Contract } from 'ethers'
import { AddressZero, MaxUint256 } from 'ethers/constants'
import { bigNumberify, hexlify } from 'ethers/utils'
import { bigNumberify, hexlify, keccak256, defaultAbiCoder, toUtf8Bytes } from 'ethers/utils'
import { ecsign } from 'ethereumjs-util'
import { expandTo18Decimals, getApprovalDigest } from './shared/utilities'
@ -35,18 +35,38 @@ describe('ERC20', () => {
])
})
it('name, symbol, decimals, totalSupply', async () => {
expect(await token.name()).to.eq(TOKEN_DETAILS.name)
it('name, symbol, decimals, totalSupply, balanceOf, DOMAIN_SEPARATOR, APPROVE_TYPEHASH', async () => {
const name = await token.name()
expect(name).to.eq(TOKEN_DETAILS.name)
expect(await token.symbol()).to.eq(TOKEN_DETAILS.symbol)
expect(await token.decimals()).to.eq(TOKEN_DETAILS.decimals)
expect(await token.totalSupply()).to.eq(TOKEN_DETAILS.totalSupply)
expect(await token.balanceOf(wallet.address)).to.eq(TOKEN_DETAILS.totalSupply)
expect(await token.DOMAIN_SEPARATOR()).to.eq(
keccak256(
defaultAbiCoder.encode(
['bytes32', 'bytes32', 'bytes32', 'uint256', 'address'],
[
keccak256(
toUtf8Bytes('EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)')
),
keccak256(toUtf8Bytes(name)),
keccak256(toUtf8Bytes('1')),
1,
token.address
]
)
)
)
expect(await token.APPROVE_TYPEHASH()).to.eq(
keccak256(toUtf8Bytes('Approve(address owner,address spender,uint256 value,uint256 nonce,uint256 expiration)'))
)
})
it('transfer', async () => {
await expect(token.transfer(other.address, TEST_AMOUNT))
.to.emit(token, 'Transfer')
.withArgs(wallet.address, other.address, TEST_AMOUNT)
expect(await token.balanceOf(wallet.address)).to.eq(TOKEN_DETAILS.totalSupply.sub(TEST_AMOUNT))
expect(await token.balanceOf(other.address)).to.eq(TEST_AMOUNT)
})
@ -55,7 +75,6 @@ describe('ERC20', () => {
await expect(token.burn(TEST_AMOUNT))
.to.emit(token, 'Transfer')
.withArgs(wallet.address, AddressZero, TEST_AMOUNT)
expect(await token.balanceOf(wallet.address)).to.eq(TOKEN_DETAILS.totalSupply.sub(TEST_AMOUNT))
expect(await token.totalSupply()).to.eq(TOKEN_DETAILS.totalSupply.sub(TEST_AMOUNT))
})
@ -64,10 +83,47 @@ describe('ERC20', () => {
await expect(token.approve(other.address, TEST_AMOUNT))
.to.emit(token, 'Approval')
.withArgs(wallet.address, other.address, TEST_AMOUNT)
expect(await token.allowance(wallet.address, other.address)).to.eq(TEST_AMOUNT)
})
it('transferFrom', async () => {
await token.approve(other.address, TEST_AMOUNT)
await expect(token.connect(other).transferFrom(wallet.address, other.address, TEST_AMOUNT))
.to.emit(token, 'Transfer')
.withArgs(wallet.address, other.address, TEST_AMOUNT)
expect(await token.allowance(wallet.address, other.address)).to.eq(0)
expect(await token.balanceOf(wallet.address)).to.eq(TOKEN_DETAILS.totalSupply.sub(TEST_AMOUNT))
expect(await token.balanceOf(other.address)).to.eq(TEST_AMOUNT)
})
it('transferFrom:max', async () => {
await token.approve(other.address, MaxUint256)
await expect(token.connect(other).transferFrom(wallet.address, other.address, TEST_AMOUNT))
.to.emit(token, 'Transfer')
.withArgs(wallet.address, other.address, TEST_AMOUNT)
expect(await token.allowance(wallet.address, other.address)).to.eq(MaxUint256)
expect(await token.balanceOf(wallet.address)).to.eq(TOKEN_DETAILS.totalSupply.sub(TEST_AMOUNT))
expect(await token.balanceOf(other.address)).to.eq(TEST_AMOUNT)
})
it('burnFrom', async () => {
await token.approve(other.address, TEST_AMOUNT)
await expect(token.connect(other).burnFrom(wallet.address, TEST_AMOUNT))
.to.emit(token, 'Transfer')
.withArgs(wallet.address, AddressZero, TEST_AMOUNT)
expect(await token.allowance(wallet.address, other.address)).to.eq(0)
expect(await token.balanceOf(wallet.address)).to.eq(TOKEN_DETAILS.totalSupply.sub(TEST_AMOUNT))
expect(await token.totalSupply()).to.eq(TOKEN_DETAILS.totalSupply.sub(TEST_AMOUNT))
expect(await token.balanceOf(other.address)).to.eq(0)
})
it('transfer:fail', async () => {
await expect(token.transfer(other.address, TOKEN_DETAILS.totalSupply.add(1))).to.be.revertedWith(
'ds-math-sub-underflow'
)
await expect(token.connect(other).transfer(wallet.address, 1)).to.be.revertedWith('ds-math-sub-underflow')
})
it('approveMeta', async () => {
const nonce = await token.nonces(wallet.address)
const expiration = MaxUint256
@ -85,38 +141,7 @@ describe('ERC20', () => {
)
.to.emit(token, 'Approval')
.withArgs(wallet.address, other.address, TEST_AMOUNT)
expect(await token.nonces(wallet.address)).to.eq(bigNumberify(1))
expect(await token.allowance(wallet.address, other.address)).to.eq(TEST_AMOUNT)
})
it('transferFrom', async () => {
await token.approve(other.address, TEST_AMOUNT)
await expect(token.connect(other).transferFrom(wallet.address, other.address, TEST_AMOUNT))
.to.emit(token, 'Transfer')
.withArgs(wallet.address, other.address, TEST_AMOUNT)
expect(await token.allowance(wallet.address, other.address)).to.eq(0)
expect(await token.balanceOf(wallet.address)).to.eq(TOKEN_DETAILS.totalSupply.sub(TEST_AMOUNT))
expect(await token.balanceOf(other.address)).to.eq(TEST_AMOUNT)
})
it('burnFrom', async () => {
await token.approve(other.address, TEST_AMOUNT)
await expect(token.connect(other).burnFrom(wallet.address, TEST_AMOUNT))
.to.emit(token, 'Transfer')
.withArgs(wallet.address, AddressZero, TEST_AMOUNT)
expect(await token.allowance(wallet.address, other.address)).to.eq(0)
expect(await token.balanceOf(wallet.address)).to.eq(TOKEN_DETAILS.totalSupply.sub(TEST_AMOUNT))
expect(await token.totalSupply()).to.eq(TOKEN_DETAILS.totalSupply.sub(TEST_AMOUNT))
expect(await token.balanceOf(other.address)).to.eq(0)
})
it('transfer:fail', async () => {
await expect(token.transfer(other.address, TOKEN_DETAILS.totalSupply.add(1))).to.be.revertedWith(
'ds-math-sub-underflow'
)
await expect(token.connect(other).transfer(wallet.address, 1)).to.be.revertedWith('ds-math-sub-underflow')
})
})

View File

@ -61,22 +61,22 @@ describe('UniswapV2', () => {
it('mintLiquidity', async () => {
const token0Amount = expandTo18Decimals(1)
const token1Amount = expandTo18Decimals(4)
const expectedLiquidity = expandTo18Decimals(2)
expect(await exchange.reserve0()).to.eq(bigNumberify(0))
expect(await exchange.reserve1()).to.eq(bigNumberify(0))
await token0.transfer(exchange.address, token0Amount)
await token1.transfer(exchange.address, token1Amount)
const expectedLiquidity = expandTo18Decimals(2)
await expect(exchange.connect(wallet).mintLiquidity(wallet.address))
.to.emit(exchange, 'LiquidityMinted')
.withArgs(wallet.address, token0Amount, token1Amount)
.to.emit(exchange, 'Transfer')
.withArgs(AddressZero, wallet.address, expectedLiquidity)
.to.emit(exchange, 'ReservesUpdated')
.withArgs(token0Amount, token1Amount)
.to.emit(exchange, 'LiquidityMinted')
.withArgs(wallet.address, token0Amount, token1Amount)
expect(await exchange.totalSupply()).to.eq(expectedLiquidity)
expect(await exchange.balanceOf(wallet.address)).to.eq(expectedLiquidity)
expect(await token0.balanceOf(exchange.address)).to.eq(token0Amount)
expect(await token1.balanceOf(exchange.address)).to.eq(token1Amount)
expect(await exchange.reserve0()).to.eq(token0Amount)
expect(await exchange.reserve1()).to.eq(token1Amount)
})
@ -87,21 +87,7 @@ describe('UniswapV2', () => {
await exchange.connect(wallet).mintLiquidity(wallet.address)
}
it('swap:gas', async () => {
const token0Amount = expandTo18Decimals(5)
const token1Amount = expandTo18Decimals(10)
await addLiquidity(token0Amount, token1Amount)
const swapAmount = expandTo18Decimals(1)
await token0.transfer(exchange.address, swapAmount)
await exchange.connect(wallet).swap0(wallet.address)
await token0.transfer(exchange.address, swapAmount)
const gasCost = await exchange.estimate.swap0(wallet.address)
console.log(`Gas cost of swap: ${gasCost}`)
})
it('swap', async () => {
it('swap0', async () => {
const token0Amount = expandTo18Decimals(5)
const token1Amount = expandTo18Decimals(10)
await addLiquidity(token0Amount, token1Amount)
@ -110,41 +96,110 @@ describe('UniswapV2', () => {
const expectedOutputAmount = bigNumberify('1662497915624478906')
await token0.transfer(exchange.address, swapAmount)
await expect(exchange.connect(wallet).swap0(wallet.address))
.to.emit(exchange, 'Swap')
.withArgs(wallet.address, wallet.address, token0.address, swapAmount, expectedOutputAmount)
.to.emit(exchange, 'ReservesUpdated')
.withArgs(token0Amount.add(swapAmount), token1Amount.sub(expectedOutputAmount))
.to.emit(exchange, 'Swap')
.withArgs(wallet.address, wallet.address, token0.address, swapAmount, expectedOutputAmount)
expect(await exchange.reserve0()).to.eq(token0Amount.add(swapAmount))
expect(await exchange.reserve1()).to.eq(token1Amount.sub(expectedOutputAmount))
expect(await token0.balanceOf(exchange.address)).to.eq(token0Amount.add(swapAmount))
expect(await token1.balanceOf(exchange.address)).to.eq(token1Amount.sub(expectedOutputAmount))
const totalSupplyToken0 = await token0.totalSupply()
const totalSupplyToken1 = await token1.totalSupply()
expect(await token0.balanceOf(wallet.address)).to.eq(totalSupplyToken0.sub(token0Amount).sub(swapAmount))
expect(await token1.balanceOf(wallet.address)).to.eq(totalSupplyToken1.sub(token1Amount).add(expectedOutputAmount))
})
it('swap1', async () => {
const token0Amount = expandTo18Decimals(5)
const token1Amount = expandTo18Decimals(10)
await addLiquidity(token0Amount, token1Amount)
const swapAmount = expandTo18Decimals(1)
const expectedOutputAmount = bigNumberify('453305446940074565')
await token1.transfer(exchange.address, swapAmount)
await expect(exchange.connect(wallet).swap1(wallet.address))
.to.emit(exchange, 'ReservesUpdated')
.withArgs(token0Amount.sub(expectedOutputAmount), token1Amount.add(swapAmount))
.to.emit(exchange, 'Swap')
.withArgs(wallet.address, wallet.address, token1.address, expectedOutputAmount, swapAmount)
expect(await exchange.reserve0()).to.eq(token0Amount.sub(expectedOutputAmount))
expect(await exchange.reserve1()).to.eq(token1Amount.add(swapAmount))
expect(await token0.balanceOf(exchange.address)).to.eq(token0Amount.sub(expectedOutputAmount))
expect(await token1.balanceOf(exchange.address)).to.eq(token1Amount.add(swapAmount))
const totalSupplyToken0 = await token0.totalSupply()
const totalSupplyToken1 = await token1.totalSupply()
expect(await token0.balanceOf(wallet.address)).to.eq(totalSupplyToken0.sub(token0Amount).add(expectedOutputAmount))
expect(await token1.balanceOf(wallet.address)).to.eq(totalSupplyToken1.sub(token1Amount).sub(swapAmount))
})
it('swap:gas', async () => {
const token0Amount = expandTo18Decimals(5)
const token1Amount = expandTo18Decimals(10)
await addLiquidity(token0Amount, token1Amount)
// ensure that setting price{0,1}CumulativeLast for the first time doesn't affect our gas math
await exchange.connect(wallet).sync()
const swapAmount = expandTo18Decimals(1)
await token0.transfer(exchange.address, swapAmount)
const gasCost = await exchange.estimate.swap0(wallet.address)
console.log(`Gas required for swap: ${gasCost}`)
})
it('burnLiquidity', async () => {
const token0Amount = expandTo18Decimals(3)
const token1Amount = expandTo18Decimals(3)
await addLiquidity(token0Amount, token1Amount)
const liquidity = expandTo18Decimals(3)
await exchange.connect(wallet).transfer(exchange.address, liquidity)
const expectedLiquidity = expandTo18Decimals(3)
await exchange.connect(wallet).transfer(exchange.address, expectedLiquidity)
// this test is bugged, it catches the token{0,1} transfers before the lp transfers
await expect(exchange.connect(wallet).burnLiquidity(wallet.address))
// .to.emit(exchange, 'Transfer')
// .withArgs(exchange.address, AddressZero, expectedLiquidity)
.to.emit(exchange, 'LiquidityBurned')
.withArgs(wallet.address, wallet.address, token0Amount, token1Amount)
.to.emit(exchange, 'ReservesUpdated')
.withArgs(bigNumberify(0), bigNumberify(0))
.withArgs(0, 0)
expect(await exchange.balanceOf(wallet.address)).to.eq(0)
expect(await exchange.totalSupply()).to.eq(0)
expect(await token0.balanceOf(exchange.address)).to.eq(0)
expect(await token1.balanceOf(exchange.address)).to.eq(0)
const totalSupplyToken0 = await token0.totalSupply()
const totalSupplyToken1 = await token1.totalSupply()
expect(await token0.balanceOf(wallet.address)).to.eq(totalSupplyToken0)
expect(await token1.balanceOf(wallet.address)).to.eq(totalSupplyToken1)
})
it('price{0,1}CumulativeLast', async () => {
const token0Amount = expandTo18Decimals(3)
const token1Amount = expandTo18Decimals(3)
await addLiquidity(token0Amount, token1Amount)
const blockNumber = await exchange.blockNumberLast()
expect(await exchange.price0CumulativeLast()).to.eq(0)
expect(await exchange.price1CumulativeLast()).to.eq(0)
await exchange.connect(wallet).sync()
expect(await exchange.price0CumulativeLast()).to.eq(bigNumberify(2).pow(112))
expect(await exchange.price1CumulativeLast()).to.eq(bigNumberify(2).pow(112))
expect(await exchange.blockNumberLast()).to.eq(blockNumber + 1)
await exchange.connect(wallet).sync()
expect(await exchange.price0CumulativeLast()).to.eq(
bigNumberify(2)
.pow(112)
.mul(2)
)
expect(await exchange.price1CumulativeLast()).to.eq(
bigNumberify(2)
.pow(112)
.mul(2)
)
expect(await exchange.blockNumberLast()).to.eq(blockNumber + 2)
})
})

View File

@ -19,25 +19,37 @@ const TEST_ADDRESSES = {
describe('UniswapV2Factory', () => {
const provider = createMockProvider(path.join(__dirname, '..', 'waffle.json'))
const [wallet] = getWallets(provider)
const [wallet, other] = getWallets(provider)
const loadFixture = createFixtureLoader(provider, [wallet])
let bytecode: string
let factory: Contract
beforeEach(async () => {
const { bytecode: _bytecode, factory: _factory } = (await loadFixture(factoryFixture as any)) as FactoryFixture
const { bytecode: _bytecode, factory: _factory }: FactoryFixture = await loadFixture(factoryFixture as any)
bytecode = _bytecode
factory = _factory
})
it('exchangeBytecode', async () => {
it('exchangeBytecode, feeAddress, feeOn, exchangesCount', async () => {
expect(await factory.exchangeBytecode()).to.eq(bytecode)
expect(await factory.feeAddress()).to.eq(wallet.address)
expect(await factory.feeOn()).to.eq(false)
expect(await factory.exchangesCount()).to.eq(0)
})
it('sortTokens', async () => {
expect(await factory.sortTokens(TEST_ADDRESSES.token0, TEST_ADDRESSES.token1)).to.deep.eq([
TEST_ADDRESSES.token0,
TEST_ADDRESSES.token1
])
expect(await factory.sortTokens(TEST_ADDRESSES.token1, TEST_ADDRESSES.token0)).to.deep.eq([
TEST_ADDRESSES.token0,
TEST_ADDRESSES.token1
])
})
async function createExchange(tokens: string[]) {
const create2Address = getCreate2Address(factory.address, TEST_ADDRESSES.token0, TEST_ADDRESSES.token1, bytecode)
await expect(factory.createExchange(...tokens))
.to.emit(factory, 'ExchangeCreated')
.withArgs(TEST_ADDRESSES.token0, TEST_ADDRESSES.token1, create2Address, bigNumberify(1))
@ -45,7 +57,6 @@ describe('UniswapV2Factory', () => {
await expect(factory.createExchange(...tokens.slice().reverse())).to.be.revertedWith(
'UniswapV2Factory: EXCHANGE_EXISTS'
)
expect(await factory.getExchange(...tokens)).to.eq(create2Address)
expect(await factory.getExchange(...tokens.slice().reverse())).to.eq(create2Address)
expect(await factory.getTokens(create2Address)).to.deep.eq([TEST_ADDRESSES.token0, TEST_ADDRESSES.token1])
@ -56,6 +67,7 @@ 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 () => {
@ -65,4 +77,17 @@ describe('UniswapV2Factory', () => {
it('createExchange:reverse', async () => {
await createExchange([TEST_ADDRESSES.token1, TEST_ADDRESSES.token0])
})
it('setFeeAddress', async () => {
await expect(factory.connect(other).setFeeAddress(other.address)).to.be.revertedWith('UniswapV2Factory: FORBIDDEN')
await factory.setFeeAddress(other.address)
expect(await factory.feeAddress()).to.eq(other.address)
await expect(factory.setFeeAddress(wallet.address)).to.be.revertedWith('UniswapV2Factory: FORBIDDEN')
})
it('turnFeeOn', async () => {
await expect(factory.connect(other).turnFeeOn()).to.be.revertedWith('UniswapV2Factory: FORBIDDEN')
await factory.turnFeeOn()
expect(await factory.feeOn()).to.eq(true)
})
})

View File

@ -15,10 +15,9 @@ export interface FactoryFixture {
export async function factoryFixture(provider: providers.Web3Provider, [wallet]: Wallet[]): Promise<FactoryFixture> {
const bytecode = `0x${UniswapV2.evm.bytecode.object}`
const factory = await deployContract(wallet, UniswapV2Factory, [bytecode, AddressZero], {
const factory = await deployContract(wallet, UniswapV2Factory, [bytecode, wallet.address], {
gasLimit: (provider._web3Provider as any).options.gasLimit
})
return { bytecode, factory }
}

View File

@ -9,6 +9,10 @@ import {
solidityPack
} from 'ethers/utils'
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)')
)
@ -29,8 +33,22 @@ const GET_DOMAIN_SEPARATOR = async (token: Contract) => {
)
}
export function expandTo18Decimals(n: number): BigNumber {
return bigNumberify(n).mul(bigNumberify(10).pow(18))
export function getCreate2Address(
factoryAddress: string,
token0Address: string,
token1Address: string,
bytecode: string
): string {
const create2Inputs = [
'0xff',
factoryAddress,
keccak256(solidityPack(['address', 'address'], [token0Address, token1Address])),
keccak256(bytecode)
]
const sanitizedInputs = `0x${create2Inputs.map(i => i.slice(2)).join('')}`
return getAddress(`0x${keccak256(sanitizedInputs).slice(-40)}`)
}
interface Approve {
@ -38,7 +56,6 @@ interface Approve {
spender: string
value: BigNumber
}
export async function getApprovalDigest(
token: Contract,
approve: Approve,
@ -64,24 +81,6 @@ export async function getApprovalDigest(
)
}
export function getCreate2Address(
factoryAddress: string,
token0Address: string,
token1Address: string,
bytecode: string
): string {
const create2Inputs = [
'0xff',
factoryAddress,
keccak256(solidityPack(['address', 'address'], [token0Address, token1Address])),
keccak256(bytecode)
]
const sanitizedInputs = `0x${create2Inputs.map(i => i.slice(2)).join('')}`
return getAddress(`0x${keccak256(sanitizedInputs).slice(-40)}`)
}
async function mineBlock(provider: providers.Web3Provider, timestamp?: number): Promise<void> {
await new Promise((resolve, reject) => {
;(provider._web3Provider.sendAsync as any)(