diff --git a/contracts/UniswapV2Factory.sol b/contracts/UniswapV2Factory.sol index 5e892e9..cb7ebe1 100644 --- a/contracts/UniswapV2Factory.sol +++ b/contracts/UniswapV2Factory.sol @@ -12,8 +12,8 @@ contract UniswapV2Factory is IUniswapV2Factory { address token1; } - uint256 public chainId; bytes public exchangeBytecode; + uint256 public chainId; uint256 public exchangeCount; mapping (address => Pair) private exchangeToPair; mapping (address => mapping(address => address)) private token0ToToken1ToExchange; diff --git a/contracts/interfaces/IERC20.sol b/contracts/interfaces/IERC20.sol index 8c50035..d47fc8b 100644 --- a/contracts/interfaces/IERC20.sol +++ b/contracts/interfaces/IERC20.sol @@ -11,9 +11,8 @@ interface IERC20 { function balanceOf(address owner) external view returns (uint256); function allowance(address owner, address spender) external view returns (uint256); + function chainId() external returns (uint256); function nonceFor(address owner) external view returns (uint256); - function DOMAIN_SEPARATOR() external view returns (bytes32); - function APPROVE_TYPEHASH() external pure returns (bytes32); function transfer(address to, uint256 value) external returns (bool); function transferFrom(address from, address to, uint256 value) external returns (bool); diff --git a/contracts/interfaces/IUniswapV2.sol b/contracts/interfaces/IUniswapV2.sol index ba2fd97..31f579a 100644 --- a/contracts/interfaces/IUniswapV2.sol +++ b/contracts/interfaces/IUniswapV2.sol @@ -23,7 +23,6 @@ interface IUniswapV2 { uint256 amountToken1 ); - function initialized() external view returns (bool); function factory() external view returns (address); function token0() external view returns (address); function token1() external view returns (address); @@ -34,7 +33,7 @@ interface IUniswapV2 { uint256 reserveOutput ) external pure returns (uint256 amountOutput); - function initialize(address _token0, address _token1) external; + function initialize(address _token0, address _token1, uint256 chainId) external; function mintLiquidity(address recipient) external returns (uint256 liquidity); function burnLiquidity( diff --git a/contracts/interfaces/IUniswapV2Factory.sol b/contracts/interfaces/IUniswapV2Factory.sol index a6e46df..e1c769b 100644 --- a/contracts/interfaces/IUniswapV2Factory.sol +++ b/contracts/interfaces/IUniswapV2Factory.sol @@ -4,6 +4,7 @@ interface IUniswapV2Factory { event ExchangeCreated(address indexed token0, address indexed token1, address exchange, uint256 exchangeCount); function exchangeBytecode() external view returns (bytes memory); + function chainId() external view returns (uint256); function exchangeCount() external view returns (uint256); function getTokens(address exchange) external view returns (address token0, address token1); function getExchange(address tokenA, address tokenB) external view returns (address exchange); diff --git a/contracts/token/ERC20.sol b/contracts/token/ERC20.sol index 2cc4f03..a198a2a 100644 --- a/contracts/token/ERC20.sol +++ b/contracts/token/ERC20.sol @@ -16,12 +16,9 @@ contract ERC20 is IERC20 { mapping (address => uint256) public balanceOf; mapping (address => mapping (address => uint256)) public allowance; - // EIP-712 + // EIP-191 + uint256 public chainId; mapping (address => uint) public nonceFor; - bytes32 public DOMAIN_SEPARATOR; - bytes32 public APPROVE_TYPEHASH = keccak256( - "Approve(address owner,address spender,uint256 value,uint256 nonce,uint256 expiration)" - ); event Transfer(address indexed from, address indexed to, uint256 value); event Approval(address indexed owner, address indexed spender, uint256 value); @@ -33,15 +30,9 @@ contract ERC20 is IERC20 { mint(msg.sender, _totalSupply); } - function initialize(uint256 chainId) internal { - require(DOMAIN_SEPARATOR == bytes32(0), "ERC20: ALREADY_INITIALIZED"); - DOMAIN_SEPARATOR = keccak256(abi.encode( - keccak256("EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)"), - keccak256(bytes(name)), - keccak256(bytes("1")), - chainId, - address(this) - )); + function initialize(uint256 _chainId) internal { + require(chainId == 0, "ERC20: ALREADY_INITIALIZED"); + chainId = _chainId; } function mint(address to, uint256 value) internal { @@ -106,20 +97,20 @@ contract ERC20 is IERC20 { bytes32 r, bytes32 s ) external { - require(DOMAIN_SEPARATOR != bytes32(0), "ERC20: UNINITIALIZED"); + require(chainId != 0, "ERC20: UNINITIALIZED"); require(nonce == nonceFor[owner]++, "ERC20: INVALID_NONCE"); require(expiration > block.timestamp, "ERC20: EXPIRED_SIGNATURE"); bytes32 digest = keccak256(abi.encodePacked( - byte(0x19), - byte(0x01), - DOMAIN_SEPARATOR, - keccak256(abi.encode( - APPROVE_TYPEHASH, owner, spender, value, nonce, expiration + hex'19', + hex'00', + address(this), + keccak256(abi.encodePacked( + owner, spender, value, nonce, expiration, chainId )) - )); + )); require(owner == ecrecover(digest, v, r, s), "ERC20: INVALID_SIGNATURE"); // TODO add ECDSA checks? https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/cryptography/ECDSA.sol - _approve(msg.sender, spender, value); + _approve(owner, spender, value); } } diff --git a/package.json b/package.json index 13a59ac..cb1b13c 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "node": "^10" }, "dependencies": { + "ethereumjs-util": "^6.1.0", "solc": "0.5.12" }, "devDependencies": { diff --git a/test/ERC20.ts b/test/ERC20.ts index 46ea581..0afe553 100644 --- a/test/ERC20.ts +++ b/test/ERC20.ts @@ -3,15 +3,8 @@ import chai from 'chai' import { solidity, createMockProvider, getWallets, deployContract } from 'ethereum-waffle' import { Contract } from 'ethers' import { AddressZero, MaxUint256 } from 'ethers/constants' -import { - BigNumber, - bigNumberify, - defaultAbiCoder, - toUtf8Bytes, - keccak256, - splitSignature, - solidityPack -} from 'ethers/utils' +import { BigNumber, bigNumberify, keccak256, solidityPack, hexlify } from 'ethers/utils' +import { ecsign } from 'ethereumjs-util' import ERC20 from '../build/GenericERC20.json' @@ -31,7 +24,7 @@ const testAmount = decimalize(10) describe('ERC20', () => { const provider = createMockProvider(path.join(__dirname, '..', 'waffle.json')) - const [wallet, walletTo] = getWallets(provider) + const [wallet, other] = getWallets(provider) let token: Contract beforeEach(async () => { @@ -41,17 +34,17 @@ describe('ERC20', () => { it('name, symbol, decimals, totalSupply', async () => { expect(await token.name()).to.eq(name) expect(await token.symbol()).to.eq(symbol) - expect(await token.decimals()).to.eq(18) + expect(await token.decimals()).to.eq(decimals) expect(await token.totalSupply()).to.eq(totalSupply) }) it('transfer', async () => { - await expect(token.transfer(walletTo.address, testAmount)) + await expect(token.transfer(other.address, testAmount)) .to.emit(token, 'Transfer') - .withArgs(wallet.address, walletTo.address, testAmount) + .withArgs(wallet.address, other.address, testAmount) expect(await token.balanceOf(wallet.address)).to.eq(totalSupply.sub(testAmount)) - expect(await token.balanceOf(walletTo.address)).to.eq(testAmount) + expect(await token.balanceOf(other.address)).to.eq(testAmount) }) it('burn', async () => { @@ -64,106 +57,73 @@ describe('ERC20', () => { }) it('approve', async () => { - await expect(token.approve(walletTo.address, testAmount)) + await expect(token.approve(other.address, testAmount)) .to.emit(token, 'Approval') - .withArgs(wallet.address, walletTo.address, testAmount) + .withArgs(wallet.address, other.address, testAmount) + + expect(await token.allowance(wallet.address, other.address)).to.eq(testAmount) }) it('approveMeta', async () => { const nonce = await token.nonceFor(wallet.address) const expiration = MaxUint256 - - const domainSeparator = 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')), - chainId, - token.address - ] - ) - ) - const approveTypehash = keccak256( - toUtf8Bytes('Approve(address owner,address spender,uint256 value,uint256 nonce,uint256 expiration)') - ) const digest = keccak256( solidityPack( - ['bytes1', 'bytes1', 'bytes32', 'bytes32'], + ['bytes1', 'bytes1', 'address', 'bytes32'], [ '0x19', - '0x01', - domainSeparator, + '0x00', + token.address, keccak256( - defaultAbiCoder.encode( - ['bytes32', 'address', 'address', 'uint256', 'uint256', 'uint256'], - [approveTypehash, wallet.address, walletTo.address, testAmount, nonce, expiration] + solidityPack( + ['address', 'address', 'uint256', 'uint256', 'uint256', 'uint256'], + [wallet.address, other.address, testAmount, nonce, expiration, chainId] ) ) ] ) ) - const { v, r, s } = splitSignature(await wallet.signMessage(digest)) + const { v, r, s } = ecsign(Buffer.from(digest.slice(2), 'hex'), Buffer.from(wallet.privateKey.slice(2), 'hex')) - // const sig = ethUtil.ecsign(signHash(), privateKey); - - // console.log(wallet.privateKey) - // console.log(digest) - // console.log(v, r, s) - - await expect(token.approveMeta(wallet.address, walletTo.address, testAmount, nonce, expiration, v, r, s)) + await expect( + token + .connect(other) + .approveMeta(wallet.address, other.address, testAmount, nonce, expiration, v, hexlify(r), hexlify(s)) + ) .to.emit(token, 'Approval') - .withArgs(wallet.address, walletTo.address, testAmount) + .withArgs(wallet.address, other.address, testAmount) + + expect(await token.allowance(wallet.address, other.address)).to.eq(testAmount) }) - // it('transferFrom', async () => { - // await expect(token.approve(walletTo.address, testAmount)) - // .to.emit(token, 'Approval') - // .withArgs(wallet.address, walletTo.address, testAmount) + it('transferFrom', async () => { + await token.approve(other.address, testAmount) - // await expect(token.connect(walletTo).transferFrom(wallet.address, walletTo.address, testAmount)) - // .to.emit(token, 'Transfer') - // .withArgs(wallet.address, walletTo.address, testAmount) + await expect(token.connect(other).transferFrom(wallet.address, other.address, testAmount)) + .to.emit(token, 'Transfer') + .withArgs(wallet.address, other.address, testAmount) - // expect(await token.balanceOf(wallet.address)).to.eq(totalSupply.sub(testAmount)) - // expect(await token.balanceOf(walletTo.address)).to.eq(testAmount) - // }) + expect(await token.allowance(wallet.address, other.address)).to.eq(0) + expect(await token.balanceOf(wallet.address)).to.eq(totalSupply.sub(testAmount)) + expect(await token.balanceOf(other.address)).to.eq(testAmount) + }) - // it('burnFrom', async () => { - // await expect(token.approve(walletTo.address, testAmount)) - // .to.emit(token, 'Approval') - // .withArgs(wallet.address, walletTo.address, testAmount) + it('burnFrom', async () => { + await token.approve(other.address, testAmount) - // await expect(token.connect(walletTo).transferFrom(wallet.address, walletTo.address, testAmount)) - // .to.emit(token, 'Transfer') - // .withArgs(wallet.address, walletTo.address, testAmount) + await expect(token.connect(other).burnFrom(wallet.address, testAmount)) + .to.emit(token, 'Transfer') + .withArgs(wallet.address, AddressZero, testAmount) - // expect(await token.balanceOf(wallet.address)).to.eq(totalSupply.sub(testAmount)) - // expect(await token.balanceOf(walletTo.address)).to.eq(testAmount) - // }) + expect(await token.allowance(wallet.address, other.address)).to.eq(0) + expect(await token.balanceOf(wallet.address)).to.eq(totalSupply.sub(testAmount)) + expect(await token.totalSupply()).to.eq(totalSupply.sub(testAmount)) + expect(await token.balanceOf(other.address)).to.eq(0) + }) - // it('approveMeta', async () => { - // await expect(token.approve(walletTo.address, testAmount)) - // .to.emit(token, 'Approval') - // .withArgs(wallet.address, walletTo.address, testAmount) - - // await expect(token.connect(walletTo).transferFrom(wallet.address, walletTo.address, testAmount)) - // .to.emit(token, 'Transfer') - // .withArgs(wallet.address, walletTo.address, testAmount) - - // expect(await token.balanceOf(wallet.address)).to.eq(totalSupply.sub(testAmount)) - // expect(await token.balanceOf(walletTo.address)).to.eq(testAmount) - // }) - - // it('transfer:fail', async () => { - // await expect(token.transfer(walletTo.address, totalSupply.add(1))).to.be.revertedWith( - // 'SafeMath: subtraction overflow' - // ) - - // await expect(token.connect(walletTo).transfer(walletTo.address, 1)).to.be.revertedWith( - // 'SafeMath: subtraction overflow' - // ) - // }) + it('transfer:fail', async () => { + await expect(token.transfer(other.address, totalSupply.add(1))).to.be.revertedWith('SafeMath: subtraction overflow') + await expect(token.connect(other).transfer(other.address, 1)).to.be.revertedWith('SafeMath: subtraction overflow') + }) }) diff --git a/test/UniswapV2.ts.temp b/test/UniswapV2.ts similarity index 88% rename from test/UniswapV2.ts.temp rename to test/UniswapV2.ts index c13da42..3ed42f0 100644 --- a/test/UniswapV2.ts.temp +++ b/test/UniswapV2.ts @@ -4,17 +4,19 @@ import { solidity, createMockProvider, getWallets, deployContract } from 'ethere import { Contract } from 'ethers' import { BigNumber, bigNumberify } from 'ethers/utils' -import ERC20 from '../build/ERC20.json' +import ERC20 from '../build/GenericERC20.json' import UniswapV2 from '../build/UniswapV2.json' import UniswapV2Factory from '../build/UniswapV2Factory.json' chai.use(solidity) const { expect } = chai +const chainId = 1 + const decimalize = (n: number): BigNumber => bigNumberify(n).mul(bigNumberify(10).pow(18)) -const token0Details = ['Token 0', 'T0', 18, decimalize(100)] -const token1Details = ['Token 1', 'T1', 18, decimalize(100)] +const token0Details = ['Token 0', 'T0', 18, decimalize(100), chainId] +const token1Details = ['Token 1', 'T1', 18, decimalize(100), chainId] describe('UniswapV2', () => { const provider = createMockProvider(path.join(__dirname, '..', 'waffle.json')) @@ -29,7 +31,7 @@ describe('UniswapV2', () => { token1 = await deployContract(wallet, ERC20, token1Details) const bytecode = `0x${UniswapV2.evm.bytecode.object}` - factory = await deployContract(wallet, UniswapV2Factory, [bytecode], { + factory = await deployContract(wallet, UniswapV2Factory, [bytecode, chainId], { gasLimit: (provider._web3Provider as any).options.gasLimit }) diff --git a/test/UniswapV2Factory.ts.temp b/test/UniswapV2Factory.ts similarity index 94% rename from test/UniswapV2Factory.ts.temp rename to test/UniswapV2Factory.ts index 3bb8a97..c9128ad 100644 --- a/test/UniswapV2Factory.ts.temp +++ b/test/UniswapV2Factory.ts @@ -10,6 +10,8 @@ import UniswapV2Factory from '../build/UniswapV2Factory.json' chai.use(solidity) const { expect } = chai +const chainId = 1 + const dummyTokens = ['0x1000000000000000000000000000000000000000', '0x2000000000000000000000000000000000000000'] const getExpectedAddress = (factoryAddress: string, bytecode: string): string => getAddress( @@ -17,7 +19,7 @@ const getExpectedAddress = (factoryAddress: string, bytecode: string): string => [ '0xff', factoryAddress.slice(2), - keccak256(solidityPack(['address', 'address'], dummyTokens)).slice(2), + keccak256(solidityPack(['address', 'address', 'uint256'], [...dummyTokens, chainId])).slice(2), keccak256(bytecode).slice(2) ].join('') ).slice(-40)}` @@ -31,7 +33,7 @@ describe('UniswapV2Factory', () => { beforeEach(async () => { bytecode = `0x${UniswapV2.evm.bytecode.object}` - factory = await deployContract(wallet, UniswapV2Factory, [bytecode], { + factory = await deployContract(wallet, UniswapV2Factory, [bytecode, chainId], { gasLimit: (provider._web3Provider as any).options.gasLimit }) @@ -57,7 +59,6 @@ describe('UniswapV2Factory', () => { expect(await factory.getExchange(...tokens.slice().reverse())).to.eq(expectedAddress) const exchange = new Contract(expectedAddress, UniswapV2.abi as any, provider) - expect(await exchange.initialized()).to.eq(true) expect(await exchange.factory()).to.eq(factory.address) expect(await exchange.token0()).to.eq(dummyTokens[0]) expect(await exchange.token1()).to.eq(dummyTokens[1]) diff --git a/yarn.lock b/yarn.lock index 5438f78..db02e73 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2339,7 +2339,7 @@ ethereumjs-tx@1.3.7, ethereumjs-tx@^1.1.1, ethereumjs-tx@^1.2.0, ethereumjs-tx@^ ethereum-common "^0.0.18" ethereumjs-util "^5.0.0" -ethereumjs-util@6.1.0, ethereumjs-util@^6.0.0: +ethereumjs-util@6.1.0, ethereumjs-util@^6.0.0, ethereumjs-util@^6.1.0: version "6.1.0" resolved "https://registry.yarnpkg.com/ethereumjs-util/-/ethereumjs-util-6.1.0.tgz#e9c51e5549e8ebd757a339cc00f5380507e799c8" integrity sha512-URESKMFbDeJxnAxPppnk2fN6Y3BIatn9fwn76Lm8bQlt+s52TpG8dN9M66MLPuRAiAOIqL3dfwqWJf0sd0fL0Q==