// SPDX-License-Identifier: MIT pragma solidity 0.7.6; pragma abicoder v2; import { SafeMath } from "@openzeppelin/contracts/math/SafeMath.sol"; import { Address } from "@openzeppelin/contracts/utils/Address.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"; import { InstanceAdditionProposal } from "./InstanceAdditionProposal.sol"; /** * @notice Contract which creates instance addition proposals. * @dev In this version, no initializable is needed, and it was also a kind of bad initializable * in the first place, because the variables which were set, do not have to be pseudo-immutable. * * Also in this version, there is no "creationFee", this was a bad idea in the first place by * whoever implemented it, because anyone would just be able to fork this code in 1 hour as * I did, and redeploy a free version. */ contract InstanceProposalFactory { using SafeMath for uint256; using Address for address; address public immutable weth; address public immutable governance; address public immutable instanceFactory; address public immutable instanceRegistry; IUniswapV3Factory public immutable UniswapV3Factory; uint16 public minObservationCardinality; event NewMinimumObservationCardinalitySet(uint256 newMinObservationCardinality); event NewGovernanceProposalCreated(address indexed proposal); /** * @dev Throws if called by any account other than the Governance. */ modifier onlyGovernance() { require(governance == msg.sender, "IF: caller is not the Governance"); _; } /** * @notice Contract constructor. * @param _governance The TC governance address. * @param _instanceFactory The factory address with which created proposals will use to deploy an instance. * @param _instanceRegistry The registry address with which created proposals will use to register an instance. * @param _UniswapV3Factory The UNIV3 Factory address with which we check TWAP cardinality. * @param _wethAddress The address of the Wrapped Ether contract. * @param _minObservationCardinality The minimum TWAP cardinality for a pool which we will accept. */ constructor( address _governance, address _instanceFactory, address _instanceRegistry, address _UniswapV3Factory, address _wethAddress, uint16 _minObservationCardinality ) { governance = _governance; instanceFactory = _instanceFactory; instanceRegistry = _instanceRegistry; UniswapV3Factory = IUniswapV3Factory(_UniswapV3Factory); minObservationCardinality = _minObservationCardinality; weth = _wethAddress; } /** * @notice Create an instance addition proposal contract. * @dev Note that here, Uniswap is being used as an anti-shitcoin oracle. Also, specifically in this version the upper limit on the protocol fee is 3%, I do not consider it very beneficial for anyone to have it above this number. * The statement of the function: "I have some tokens for which there exist good uniswap pools, for which I would like to create [total number denominations] instances, of denominations which may be divided by a common exponent characteristic for each token's denomination group, and with a protocol fee which is reasonable (under 3%)." * @param _tokenAddresses ERC20 token addresses to add to the proposal. * @param _uniswapPoolSwappingFees Swapping fees for each token which are used to determine whether the observation cardinality of the Uniswap Pool is large enough. `3000` means 0.3% fee Uniswap pool. * @param _denomBase10DivExponents These are the exponents of the (10**exp) divisor which is used to assure that the denomination may be represented as a number below 2^40 - 1. * @param _denominations The denominations for each token. These will be stored as smallDenom = denomination / (10 ^ exponent) * @param _protocolFees List of protocol fees for each new instance. `100` means that instance withdrawal fee is 1% of denomination. * @param _totalNumberDenominations This is the total number of denominations, the sum of the lengths of subarrays of denominations. */ function createProposalContract( address[] calldata _tokenAddresses, uint24[] calldata _uniswapPoolSwappingFees, uint16[] calldata _denomBase10DivExponents, uint256[][] calldata _denominations, uint16[][] calldata _protocolFees, uint256 _totalNumberDenominations ) external returns (address) { InstanceAdditionProposal.InstanceAdditionData[] memory toAdd = new InstanceAdditionProposal.InstanceAdditionData[]( _totalNumberDenominations ); uint256 toAddSize = 0; require(0 < _tokenAddresses.length, "No tokens specified"); // 1️⃣ Each token must be ether, or a contract with a uniswap pool of a minimum cardinality. for (uint256 t = 0; t < _tokenAddresses.length; t++) { address token = _tokenAddresses[t]; // 2️⃣ The swapping fees must have an element at t. // We do not care if there is more swapping fees than tokens uint24 swappingFee = _uniswapPoolSwappingFees[t]; // 3️⃣ If not eth the token is a contract, the swapping fee must give a good pool, the cardinality must be good. if (token != address(0)) { require(token.isContract(), "Token must be a contract if not addr(0)"); address poolAddress = UniswapV3Factory.getPool(token, weth, swappingFee); require(poolAddress != address(0), "A Uniswap V3 pool does not exist for this token."); (, , , , uint16 observationCardinalityNext, , ) = IUniswapV3PoolState(poolAddress).slot0(); require(minObservationCardinality <= observationCardinalityNext, "Uniswap Pool observation cardinality is low."); } // 4️⃣ Denominations, div exponent and protocol fees must have an element at t(oken). The exponent must be good. uint256[] memory denominations = _denominations[t]; uint16 exponent = _denomBase10DivExponents[t]; require(0 < exponent, "Exponent is 0."); uint16[] memory protocolFees = _protocolFees[t]; // 5️⃣ The denominations element must never have 0 length. uint256 numDenoms = denominations.length; require(0 < numDenoms, "There is no denominations for some token"); // 6️⃣ All denominations divided by the exponent must not be 0, and there must be a reasonable protocol fee for each. for (uint256 d = 0; d < numDenoms; d++) { // 7️⃣ protocolFee must have an element at d and be appropriate. // We do not care if there is more fees than denominations uint16 protocolFee = protocolFees[d]; require(protocolFee <= 300, "Protocol fee is more than 3%!"); // 8️⃣ The denomination must not be 0 post division. uint40 smallDenomination = uint40(denominations[d].div(10 ** exponent)); require(0 < smallDenomination, "When divided, denom is 0"); // 9️⃣ If all of the above are fine, add the packed struct to the memory array. toAdd[toAddSize] = InstanceAdditionProposal.InstanceAdditionData({ tokenAddress: token, smallDenomination: smallDenomination, base10Exponent: exponent, uniPoolSwappingFee: swappingFee, protocolFee: protocolFee }); toAddSize++; } } // 🔟 The number of elements added must be _totalNumberDenominations. require(toAddSize == _totalNumberDenominations, "Wrong total number of denominations."); // 1️⃣1️⃣ Finish and return. address proposal = address(new InstanceAdditionProposal(instanceFactory, instanceRegistry, toAdd)); emit NewGovernanceProposalCreated(proposal); return proposal; } function setMinObservationCardinality(uint16 _newMinObservationCardinality) external onlyGovernance { minObservationCardinality = _newMinObservationCardinality; emit NewMinimumObservationCardinalitySet(_newMinObservationCardinality); } }