diff --git a/README.md b/README.md index 69b7584..0f7c102 100644 --- a/README.md +++ b/README.md @@ -29,6 +29,7 @@ Anyone can create governance proposal for the addition of a new ERC20 instance b 1. `max number of new instances in one proposal` - the current version supports the addition of a maximum of 4 instances at once. 2. `proposal creation fee` - this fee is charged from creator of proposal during `createProposalApprove/createProposalPermit` factory method execution. It can be changed by governance. Default value is stored in `config.js`. +3. `TWAPSlotsMin` - minimum number of TWAP slots for Uniswap pool that is chosen during `createProposalApprove/createProposalPermit` factory method call. It can be changed by governance. Default value is stored in `config.js`. ## Warnings @@ -60,8 +61,8 @@ Check config.js for actual values. With `salt` = `0x0000000000000000000000000000000000000000000000000000000047941987` address must be: -1. `InstanceFactory` - `0x9A04e3F1091A69CB53D163abE7ad9bbc86C23823` -1. `InstanceFactoryWithRegistry` - `0xee994E045B9Ec5a37f3f85d34f9fD087A0c69236` +1. `InstanceFactory` - `0x09110e04d5AEF747bcf7A3585D8FFC892Ab9D1Cf` +2. `InstanceFactoryWithRegistry` - `0xC01D57d83E9Fe35E0Fb900F9D384EFcA679DF4bD` Check addresses with current config: diff --git a/config.js b/config.js index 5c9c57e..58fed34 100644 --- a/config.js +++ b/config.js @@ -15,4 +15,7 @@ module.exports = { creationFee: '200000000000000000000', // 200 TORN deployGasLimit: 7000000, owner: '0xBAE5aBfa98466Dbe68836763B087f2d189f4D28f', + WETH: '0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2', + UniswapV3Factory: '0x1F98431c8aD98523631AE4a59f267346ea31F984', + TWAPSlotsMin: 50, } diff --git a/contracts/AddInstanceProposal.sol b/contracts/AddInstanceProposal.sol index 876f4f0..96cf6de 100644 --- a/contracts/AddInstanceProposal.sol +++ b/contracts/AddInstanceProposal.sol @@ -37,7 +37,7 @@ contract AddInstanceProposal { token = _token; uniswapPoolSwappingFee = _uniswapPoolSwappingFee; - require(_denominations.length == _protocolFees.length, "denominations length != protocolFees length"); + require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length"); uint256 _numInstances = _denominations.length; require(_numInstances > 0 && _numInstances <= 4, "incorrect instances number"); numInstances = _numInstances; diff --git a/contracts/InstanceFactoryWithRegistry.sol b/contracts/InstanceFactoryWithRegistry.sol index 9046df7..c9e2b9e 100644 --- a/contracts/InstanceFactoryWithRegistry.sol +++ b/contracts/InstanceFactoryWithRegistry.sol @@ -8,6 +8,8 @@ import "./AddInstanceProposal.sol"; import "./InstanceFactory.sol"; import { IERC20 } from "@openzeppelin/contracts/token/ERC20/IERC20.sol"; import { IERC20Permit } from "@openzeppelin/contracts/drafts/IERC20Permit.sol"; +import { IUniswapV3Factory } from "@uniswap/v3-core/contracts/interfaces/IUniswapV3Factory.sol"; +import { IUniswapV3PoolState } from "@uniswap/v3-core/contracts/interfaces/pool/IUniswapV3PoolState.sol"; contract InstanceFactoryWithRegistry is InstanceFactory { using Address for address; @@ -15,9 +17,13 @@ contract InstanceFactoryWithRegistry is InstanceFactory { address public immutable governance; address public immutable torn; address public immutable instanceRegistry; + IUniswapV3Factory public immutable UniswapV3Factory; + address public immutable WETH; + uint16 public TWAPSlotsMin; uint256 public creationFee; - event NewCreationFeeSet(uint256 indexed newCreationFee); + event NewCreationFeeSet(uint256 newCreationFee); + event NewTWAPSlotsMinSet(uint256 newTWAPSlotsMin); event NewGovernanceProposalCreated(address indexed proposal); /** @@ -35,11 +41,17 @@ contract InstanceFactoryWithRegistry is InstanceFactory { address _governance, address _instanceRegistry, address _torn, + address _UniswapV3Factory, + address _WETH, + uint16 _TWAPSlotsMin, uint256 _creationFee ) InstanceFactory(_verifier, _hasher, _merkleTreeHeight, _governance) { governance = _governance; instanceRegistry = _instanceRegistry; torn = _torn; + UniswapV3Factory = IUniswapV3Factory(_UniswapV3Factory); + WETH = _WETH; + TWAPSlotsMin = _TWAPSlotsMin; creationFee = _creationFee; } @@ -103,10 +115,22 @@ contract InstanceFactoryWithRegistry is InstanceFactory { uint32[] memory _protocolFees ) internal returns (address) { require(_token.isContract(), "Token is not contract"); - require(_uniswapPoolSwappingFee > 0, "uniswapPoolSwappingFee is zero"); require(_denominations.length > 0, "Empty denominations"); require(_denominations.length == _protocolFees.length, "Incorrect denominations/fees length"); + // check Uniswap Pool + for (uint8 i = 0; i < _protocolFees.length; i++) { + if (_protocolFees[i] > 0) { + // pool exists + address poolAddr = UniswapV3Factory.getPool(_token, WETH, _uniswapPoolSwappingFee); + require(poolAddr != address(0), "Uniswap pool is not exist"); + // TWAP slots + (, , , , uint16 observationCardinalityNext, , ) = IUniswapV3PoolState(poolAddr).slot0(); + require(observationCardinalityNext >= TWAPSlotsMin, "Uniswap pool TWAP slots number is low"); + break; + } + } + address proposal = address( new AddInstanceProposal(address(this), instanceRegistry, _token, _uniswapPoolSwappingFee, _denominations, _protocolFees) ); @@ -119,4 +143,9 @@ contract InstanceFactoryWithRegistry is InstanceFactory { creationFee = _creationFee; emit NewCreationFeeSet(_creationFee); } + + function setTWAPSlotsMin(uint16 _TWAPSlotsMin) external onlyGovernance { + TWAPSlotsMin = _TWAPSlotsMin; + emit NewTWAPSlotsMinSet(_TWAPSlotsMin); + } } diff --git a/src/generateAddresses.js b/src/generateAddresses.js index 50ece99..c6a57c7 100644 --- a/src/generateAddresses.js +++ b/src/generateAddresses.js @@ -26,6 +26,9 @@ async function generate(config = defaultConfig) { config.governance, config.instanceRegistry, config.TORN, + config.UniswapV3Factory, + config.WETH, + config.TWAPSlotsMin, config.creationFee, ]) .slice(2) diff --git a/test/factory.with.registry.test.js b/test/factory.with.registry.test.js index 8927dc2..6fce033 100644 --- a/test/factory.with.registry.test.js +++ b/test/factory.with.registry.test.js @@ -101,6 +101,9 @@ describe('Instance Factory With Registry Tests', () => { expect(await instanceFactory.implementation()).to.exist expect(await instanceFactory.creationFee()).to.be.equal(config.creationFee) expect(await instanceFactory.torn()).to.be.equal(config.TORN) + expect(await instanceFactory.TWAPSlotsMin()).to.be.equal(config.TWAPSlotsMin) + expect(await instanceFactory.WETH()).to.be.equal(config.WETH) + expect(await instanceFactory.UniswapV3Factory()).to.be.equal(config.UniswapV3Factory) }) it('Governance should be able to set factory params', async function () { @@ -114,20 +117,24 @@ describe('Instance Factory With Registry Tests', () => { await instanceFactory.generateNewImplementation(addressZero, addressZero) await instanceFactory.setMerkleTreeHeight(1) await instanceFactory.setCreationFee(0) + await instanceFactory.setTWAPSlotsMin(0) expect(await instanceFactory.verifier()).to.be.equal(addressZero) expect(await instanceFactory.hasher()).to.be.equal(addressZero) expect(await instanceFactory.merkleTreeHeight()).to.be.equal(1) expect(await instanceFactory.creationFee()).to.be.equal(0) + expect(await instanceFactory.TWAPSlotsMin()).to.be.equal(0) await instanceFactory.generateNewImplementation(config.verifier, config.hasher) await instanceFactory.setMerkleTreeHeight(config.merkleTreeHeight) await instanceFactory.setCreationFee(config.creationFee) + await instanceFactory.setTWAPSlotsMin(config.TWAPSlotsMin) expect(await instanceFactory.verifier()).to.be.equal(config.verifier) expect(await instanceFactory.hasher()).to.be.equal(config.hasher) expect(await instanceFactory.merkleTreeHeight()).to.be.equal(config.merkleTreeHeight) expect(await instanceFactory.creationFee()).to.be.equal(config.creationFee) + expect(await instanceFactory.TWAPSlotsMin()).to.be.equal(config.TWAPSlotsMin) }) it('Should successfully deploy/propose/execute proposal - add instance', async function () { @@ -466,4 +473,24 @@ describe('Instance Factory With Registry Tests', () => { [BigNumber.from(0).sub(value), value], ) }) + + it('Should not deploy proposal with incorrect Uniswap pool', async function () { + let { sender, instanceFactory, tornWhale, tornToken } = await loadFixture(fixture) + + // deploy proposal ---------------------------------------------- + await tornToken.connect(tornWhale).transfer(sender.address, config.creationFee) + await tornToken.approve(instanceFactory.address, config.creationFee) + + await expect( + instanceFactory + .connect(sender) + .createProposalApprove(config.COMP, 4000, [ethers.utils.parseEther('100')], [30]), + ).to.be.revertedWith('Uniswap pool is not exist') + + await expect( + instanceFactory + .connect(sender) + .createProposalApprove(config.COMP, 10000, [ethers.utils.parseEther('100')], [30]), + ).to.be.revertedWith('Uniswap pool TWAP slots number is low') + }) })